1use core::panic;
2use std::{
3 collections::{BinaryHeap, VecDeque, hash_map::Entry},
4 future::Future,
5};
6
7use anyhow::{Context, Result, bail};
8use auto_hash_map::AutoSet;
9use bincode::{Decode, Encode};
10use petgraph::{
11 Direction,
12 graph::{DiGraph, EdgeIndex, NodeIndex},
13 visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
14};
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::{Deserialize, Serialize};
17use tracing::{Instrument, Level, Span};
18use turbo_rcstr::RcStr;
19use turbo_tasks::{
20 CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt,
21 ValueToString, Vc,
22 debug::ValueDebugFormat,
23 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
24 trace::TraceRawVcs,
25};
26use turbo_tasks_fs::FileSystemPath;
27
28use crate::{
29 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
30 issue::{ImportTrace, ImportTracer, ImportTraces, Issue},
31 module::Module,
32 module_graph::{
33 async_module_info::{AsyncModulesInfo, compute_async_module_info},
34 binding_usage_info::BindingUsageInfo,
35 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
36 merged_modules::{MergedModuleInfo, compute_merged_modules},
37 module_batches::{ModuleBatchesGraph, compute_module_batches},
38 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
39 traced_di_graph::TracedDiGraph,
40 },
41 reference::{ModuleReference, primary_chunkable_referenced_modules},
42 resolve::BindingUsage,
43};
44
45pub mod async_module_info;
46pub mod binding_usage_info;
47pub mod chunk_group_info;
48pub mod merged_modules;
49pub mod module_batch;
50pub(crate) mod module_batches;
51mod side_effect_module_info;
52pub(crate) mod style_groups;
53mod traced_di_graph;
54
55pub use self::module_batches::BatchingConfig;
56
57#[derive(
58 Debug,
59 Copy,
60 Clone,
61 Eq,
62 PartialOrd,
63 Ord,
64 Hash,
65 PartialEq,
66 Serialize,
67 Deserialize,
68 TraceRawVcs,
69 Encode,
70 Decode,
71)]
72pub struct GraphNodeIndex {
73 #[turbo_tasks(trace_ignore)]
74 graph_idx: u32,
75 #[turbo_tasks(trace_ignore)]
76 #[bincode(with_serde)]
77 node_idx: NodeIndex,
78}
79impl GraphNodeIndex {
80 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
81 Self {
82 graph_idx,
83 node_idx,
84 }
85 }
86}
87
88unsafe impl NonLocalValue for GraphNodeIndex {}
89
90#[derive(
91 Debug,
92 Copy,
93 Clone,
94 Eq,
95 PartialOrd,
96 Ord,
97 Hash,
98 PartialEq,
99 TraceRawVcs,
100 NonLocalValue,
101 Encode,
102 Decode,
103)]
104pub struct GraphEdgeIndex {
105 graph_idx: u32,
106 #[turbo_tasks(trace_ignore)]
107 #[bincode(with_serde)]
108 edge_idx: EdgeIndex,
109}
110
111impl GraphEdgeIndex {
112 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
113 Self {
114 graph_idx,
115 edge_idx,
116 }
117 }
118}
119
120#[turbo_tasks::value]
121#[derive(Clone, Debug)]
122pub struct VisitedModules {
123 #[bincode(with = "turbo_bincode::indexmap")]
124 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
125 next_graph_idx: u32,
126}
127
128#[turbo_tasks::value_impl]
129impl VisitedModules {
130 #[turbo_tasks::function]
131 pub fn empty() -> Vc<Self> {
132 Self {
133 modules: Default::default(),
134 next_graph_idx: 0,
135 }
136 .cell()
137 }
138
139 #[turbo_tasks::function]
140 pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
141 Ok(Self {
142 modules: graph
143 .await?
144 .enumerate_nodes()
145 .flat_map(|(node_idx, module)| match module {
146 SingleModuleGraphNode::Module(module) => Some((
147 *module,
148 GraphNodeIndex {
149 graph_idx: 0,
150 node_idx,
151 },
152 )),
153 SingleModuleGraphNode::VisitedModule { .. } => None,
154 })
155 .collect(),
156 next_graph_idx: 1,
157 }
158 .cell())
159 }
160
161 #[turbo_tasks::function]
162 pub fn with_incremented_index(&self) -> Result<Vc<Self>> {
163 Ok(Self {
164 modules: self.modules.clone(),
165 next_graph_idx: self.next_graph_idx + 1,
166 }
167 .cell())
168 }
169
170 #[turbo_tasks::function]
171 pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
172 let graph = graph.await?;
173 let iter = self
174 .modules
175 .iter()
176 .map(|(module, idx)| (*module, *idx))
177 .chain(
178 graph
179 .enumerate_nodes()
180 .flat_map(|(node_idx, module)| match module {
181 SingleModuleGraphNode::Module(module) => Some((
182 *module,
183 GraphNodeIndex {
184 graph_idx: self.next_graph_idx,
185 node_idx,
186 },
187 )),
188 SingleModuleGraphNode::VisitedModule { .. } => None,
189 }),
190 );
191
192 let mut map = FxIndexMap::with_capacity_and_hasher(
193 self.modules.len() + graph.number_of_modules,
194 Default::default(),
195 );
196 for (k, v) in iter {
197 map.entry(k).or_insert(v);
198 }
199 map.shrink_to_fit();
200
201 Ok(Self {
202 modules: map,
203 next_graph_idx: self.next_graph_idx + 1,
204 }
205 .cell())
206 }
207}
208
209pub type GraphEntriesT = Vec<ChunkGroupEntry>;
210
211#[turbo_tasks::value(transparent)]
212pub struct GraphEntries(GraphEntriesT);
213
214#[turbo_tasks::value_impl]
215impl GraphEntries {
216 #[turbo_tasks::function]
217 pub fn empty() -> Vc<Self> {
218 Vc::cell(Vec::new())
219 }
220}
221
222#[turbo_tasks::value(cell = "new", eq = "manual")]
223#[derive(Clone, Default)]
224pub struct SingleModuleGraph {
225 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
226
227 pub number_of_modules: usize,
229
230 #[turbo_tasks(trace_ignore)]
237 #[bincode(with_serde)]
238 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
239
240 #[turbo_tasks(trace_ignore)]
241 pub entries: GraphEntriesT,
242}
243
244#[derive(
245 Debug,
246 Clone,
247 Hash,
248 TraceRawVcs,
249 Serialize,
250 Deserialize,
251 Eq,
252 PartialEq,
253 ValueDebugFormat,
254 NonLocalValue,
255)]
256pub struct RefData {
257 pub chunking_type: ChunkingType,
258 pub binding_usage: BindingUsage,
259 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
260}
261
262impl SingleModuleGraph {
263 async fn new_inner(
267 entries: &GraphEntriesT,
268 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
269 include_traced: bool,
270 include_binding_usage: bool,
271 ) -> Result<Vc<Self>> {
272 let emit_spans = tracing::enabled!(Level::INFO);
273 let root_nodes = entries
274 .iter()
275 .flat_map(|e| e.entries())
276 .map(|e| SingleModuleGraphBuilderNode::new_module(emit_spans, e))
277 .try_join()
278 .await?;
279
280 let children_nodes_iter = AdjacencyMap::new()
281 .visit(
282 root_nodes,
283 SingleModuleGraphBuilder {
284 visited_modules,
285 emit_spans,
286 include_traced,
287 include_binding_usage,
288 },
289 )
290 .await
291 .completed()?;
292 let node_count = children_nodes_iter.len();
293
294 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
295 node_count,
296 node_count * 4,
299 );
300
301 let mut number_of_modules = 0;
302 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
303 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
304 {
305 let _span = tracing::info_span!("build module graph").entered();
306 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
307 let (module, graph_node, count) = match current {
308 SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
309 (module, SingleModuleGraphNode::Module(module), 1)
310 }
311 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
312 module,
313 SingleModuleGraphNode::VisitedModule { idx, module },
314 0,
315 ),
316 };
317
318 let current_idx = if let Some(current_idx) = modules.get(&module) {
320 *current_idx
321 } else {
322 let idx = graph.add_node(graph_node);
323 number_of_modules += count;
324 modules.insert(module, idx);
325 idx
326 };
327 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
329 parent
330 {
331 let parent_idx = *modules.get(&module).unwrap();
332 graph.add_edge(parent_idx, current_idx, ref_data);
333 }
334 }
335 }
336
337 graph.shrink_to_fit();
338
339 #[cfg(debug_assertions)]
340 {
341 use once_cell::sync::Lazy;
342 static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
343 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
344 Some(v) => v != "1" && v != "true",
345 None => true,
346 }
347 });
348 if *CHECK_FOR_DUPLICATE_MODULES {
349 let mut duplicates = Vec::new();
350 let mut set = FxHashSet::default();
351 for &module in modules.keys() {
352 let ident = module.ident().to_string().await?;
353 if !set.insert(ident.clone()) {
354 duplicates.push(ident)
355 }
356 }
357 if !duplicates.is_empty() {
358 panic!("Duplicate module idents in graph: {duplicates:#?}");
359 }
360 }
361 }
362
363 let graph = SingleModuleGraph {
364 graph: TracedDiGraph::new(graph),
365 number_of_modules,
366 modules,
367 entries: entries.clone(),
368 }
369 .cell();
370
371 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
372 ModuleGraphImportTracer::new(graph).to_resolved().await?,
373 ));
374 Ok(graph)
375 }
376
377 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
379 self.graph.node_weights().filter_map(|n| match n {
380 SingleModuleGraphNode::Module(node) => Some(*node),
381 SingleModuleGraphNode::VisitedModule { .. } => None,
382 })
383 }
384
385 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
387 if let Some(index) = self.modules.get(&module) {
388 self.graph
389 .edges_directed(*index, Direction::Incoming)
390 .next()
391 .is_none()
392 } else {
393 false
394 }
395 }
396
397 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
399 self.entries.iter().flat_map(|e| e.entries())
400 }
401
402 pub fn enumerate_nodes(
404 &self,
405 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
406 self.graph.node_references()
407 }
408
409 fn traverse_cycles<'l>(
410 &'l self,
411 edge_filter: impl Fn(&'l RefData) -> bool,
412 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
413 graph_idx: u32,
414 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
415 ) -> Result<()> {
416 #[derive(Clone)]
423 struct NodeState {
424 index: u32,
425 lowlink: u32,
426 on_stack: bool,
427 has_self_loop: bool,
428 }
429 enum VisitStep {
430 UnvisitedNode(NodeIndex),
431 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
432 AfterVisit(NodeIndex),
433 }
434 let mut node_states = vec![None; self.graph.node_bound()];
435 let mut stack = Vec::new();
436 let mut visit_stack = Vec::new();
437 let mut index = 0;
438 let mut scc = Vec::new();
439 for initial_index in self.graph.node_indices() {
440 if node_states[initial_index.index()].is_some() {
442 continue;
443 }
444 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
445 while let Some(step) = visit_stack.pop() {
446 match step {
447 VisitStep::UnvisitedNode(node) => {
448 node_states[node.index()] = Some(NodeState {
449 index,
450 lowlink: index,
451 on_stack: true,
452 has_self_loop: false,
453 });
454 index += 1;
455 stack.push(node);
456 visit_stack.push(VisitStep::AfterVisit(node));
457 let mut neighbors = self.graph.neighbors(node).detach();
458 while let Some((edge, succ)) = neighbors.next(&self.graph) {
459 if binding_usage.as_ref().is_some_and(|binding_usage| {
460 binding_usage
461 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
462 }) {
463 continue;
464 }
465
466 let edge_weight = self.graph.edge_weight(edge).unwrap();
467 if !edge_filter(edge_weight) {
468 continue;
469 }
470 let node_state = &node_states[succ.index()];
471 if let Some(node_state) = node_state {
472 if node_state.on_stack {
473 let index = node_state.index;
474 let parent_state = node_states[node.index()].as_mut().unwrap();
475 parent_state.lowlink = parent_state.lowlink.min(index);
476 if succ == node {
477 parent_state.has_self_loop = true;
478 }
479 }
480 } else {
481 visit_stack.push(VisitStep::EdgeAfterVisit {
482 parent: node,
483 child: succ,
484 });
485 visit_stack.push(VisitStep::UnvisitedNode(succ));
486 }
487 }
488 }
489 VisitStep::EdgeAfterVisit { parent, child } => {
490 let child_state = node_states[child.index()].as_ref().unwrap();
491 let lowlink = child_state.lowlink;
492
493 let parent_state = node_states[parent.index()].as_mut().unwrap();
494 parent_state.lowlink = parent_state.lowlink.min(lowlink);
495 }
496 VisitStep::AfterVisit(node) => {
497 let node_state = node_states[node.index()].as_ref().unwrap();
498 let node_has_self_loop = node_state.has_self_loop;
499 if node_state.lowlink == node_state.index {
500 loop {
501 let poppped = stack.pop().unwrap();
502 let popped_state = node_states[poppped.index()].as_mut().unwrap();
503 popped_state.on_stack = false;
504 if let SingleModuleGraphNode::Module(module) =
505 self.graph.node_weight(poppped).unwrap()
506 {
507 scc.push(module);
508 }
509 if poppped == node {
510 break;
511 }
512 }
513 if scc.len() > 1 || node_has_self_loop {
514 visit_cycle(&scc)?;
515 }
516 scc.clear();
517 }
518 }
519 }
520 }
521 }
522 Ok(())
523 }
524
525 pub async fn compute_import_traces_for_issues(
533 &self,
534 issues: &AutoSet<ResolvedVc<Box<dyn Issue>>>,
535 ) -> Result<FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>>> {
536 let issue_paths = issues
537 .iter()
538 .map(|issue| issue.file_path().owned())
539 .try_join()
540 .await?;
541 let mut file_path_to_traces: FxHashMap<FileSystemPath, Vec<ImportTrace>> =
542 FxHashMap::with_capacity_and_hasher(issue_paths.len(), Default::default());
543 for issue in &issue_paths {
545 file_path_to_traces.entry(issue.clone()).or_default();
546 }
547
548 {
549 let modules =
550 self.modules
551 .iter()
552 .map(|(module, &index)| async move {
553 Ok((module.ident().path().owned().await?, index))
554 })
555 .try_join()
556 .await?;
557 let reversed_graph = Reversed(&self.graph.0);
559 for (path, module_idx) in modules {
560 if let Entry::Occupied(mut entry) = file_path_to_traces.entry(path) {
561 let Some((_, path)) = petgraph::algo::astar(
563 &reversed_graph,
564 module_idx,
565 |n| reversed_graph.neighbors(n).next().is_none(),
566 |e| match e.weight().chunking_type {
568 ChunkingType::Parallel { .. } => 0,
570 _ => 1,
571 },
572 |_| 0,
586 ) else {
587 unreachable!("there must be a path to a root");
588 };
589 let path = path
593 .into_iter()
594 .map(async |n| {
595 Ok(self
596 .graph
597 .node_weight(n)
598 .unwrap()
599 .module()
600 .ident()
601 .await?
602 .clone())
603 })
604 .try_join()
605 .await?;
606 entry.get_mut().push(path);
607 }
608 }
609 }
610 let mut issue_to_traces: FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>> =
611 FxHashMap::with_capacity_and_hasher(issues.len(), Default::default());
612 for (path, issue) in issue_paths.iter().zip(issues) {
616 if let Some(traces) = file_path_to_traces.get(path) {
617 issue_to_traces.insert(*issue, traces.clone());
618 }
619 }
620 Ok(issue_to_traces)
621 }
622}
623
624#[turbo_tasks::value]
625struct ModuleGraphImportTracer {
626 graph: ResolvedVc<SingleModuleGraph>,
627}
628
629#[turbo_tasks::value(shared)]
630struct PathToModulesMap {
631 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
632}
633
634#[turbo_tasks::value_impl]
635impl ModuleGraphImportTracer {
636 #[turbo_tasks::function]
637 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
638 Self::cell(Self { graph })
639 }
640
641 #[turbo_tasks::function]
643 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
644 let path_and_modules = self
645 .graph
646 .await?
647 .modules
648 .iter()
649 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
650 .try_join()
651 .await?;
652 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
653 FxHashMap::default();
654 for (path, module) in path_and_modules {
655 map.entry(path).or_default().push(module)
656 }
657 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
658 }
659}
660
661#[turbo_tasks::value_impl]
662impl ImportTracer for ModuleGraphImportTracer {
663 #[turbo_tasks::function]
664 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
665 let path_to_modules = self.path_to_modules().await?;
666 let Some(modules) = path_to_modules.map.get(&path) else {
667 return Ok(Vc::default()); };
670 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
671 let graph = &*self.await?.graph.await?;
672
673 let reversed_graph = Reversed(&graph.graph.0);
674 return Ok(ImportTraces::cell(ImportTraces(
675 modules
676 .iter()
677 .map(|m| async move {
678 let Some(&module_idx) = graph.modules.get(m) else {
679 bail!("inconsistent read?")
682 };
683 let Some((_, path)) = petgraph::algo::astar(
685 &reversed_graph,
686 module_idx,
687 |n| reversed_graph.neighbors(n).next().is_none(),
688 |e| match e.weight().chunking_type {
690 ChunkingType::Parallel { .. } => 0,
692 _ => 1,
693 },
694 |_| 0,
708 ) else {
709 unreachable!("there must be a path to a root");
710 };
711
712 let path = path
716 .into_iter()
717 .map(async |n| {
718 graph
719 .graph
720 .node_weight(n)
721 .unwrap() .module()
723 .ident()
724 .await
725 })
726 .try_join()
727 .await?;
728 Ok(path)
729 })
730 .try_join()
731 .await?,
732 )));
733 }
734}
735
736#[turbo_tasks::value(shared)]
737#[derive(Clone, Default)]
738pub struct ModuleGraph {
739 pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
740
741 pub binding_usage: Option<ResolvedVc<BindingUsageInfo>>,
742}
743
744#[turbo_tasks::value_impl]
745impl ModuleGraph {
746 #[turbo_tasks::function]
747 pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
748 Self {
749 graphs,
750 binding_usage: None,
751 }
752 .cell()
753 }
754
755 #[turbo_tasks::function]
756 pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
757 Self {
758 graphs: vec![graph],
759 binding_usage: None,
760 }
761 .cell()
762 }
763
764 #[turbo_tasks::function]
765 pub fn from_entry_module(
766 module: ResolvedVc<Box<dyn Module>>,
767 include_traced: bool,
768 include_binding_usage: bool,
769 ) -> Vc<Self> {
770 Self::from_single_graph(SingleModuleGraph::new_with_entries(
771 Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
772 include_traced,
773 include_binding_usage,
774 ))
775 }
776
777 #[turbo_tasks::function]
778 pub fn from_modules(
779 modules: Vc<GraphEntries>,
780 include_traced: bool,
781 include_binding_usage: bool,
782 ) -> Vc<Self> {
783 Self::from_single_graph(SingleModuleGraph::new_with_entries(
784 modules,
785 include_traced,
786 include_binding_usage,
787 ))
788 }
789
790 #[turbo_tasks::function]
791 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
792 compute_chunk_group_info(&self.read_graphs().await?).await
793 }
794
795 #[turbo_tasks::function]
796 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
797 compute_merged_modules(self).await
798 }
799
800 #[turbo_tasks::function]
801 pub async fn module_batches(
802 self: Vc<Self>,
803 config: Vc<BatchingConfig>,
804 ) -> Result<Vc<ModuleBatchesGraph>> {
805 compute_module_batches(self, &*config.await?).await
806 }
807
808 #[turbo_tasks::function]
809 pub async fn style_groups(
810 self: Vc<Self>,
811 chunking_context: Vc<Box<dyn ChunkingContext>>,
812 config: StyleGroupsConfig,
813 ) -> Result<Vc<StyleGroups>> {
814 compute_style_groups(self, chunking_context, &config).await
815 }
816
817 #[turbo_tasks::function]
818 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
819 async move {
822 let result_op = compute_async_module_info(self.to_resolved().await?);
823 let result_vc = result_op.resolve_strongly_consistent().await?;
824 result_op.drop_collectibles::<Box<dyn Issue>>();
825 anyhow::Ok(*result_vc)
826 }
827 .instrument(tracing::info_span!("compute async module info"))
828 .await
829 }
830
831 #[turbo_tasks::function]
832 pub async fn referenced_async_modules(
833 self: Vc<Self>,
834 module: ResolvedVc<Box<dyn Module>>,
835 ) -> Result<Vc<AsyncModuleInfo>> {
836 let graph_ref = self.read_graphs().await?;
837 let async_modules_info = self.async_module_info().await?;
838
839 let entry = graph_ref.get_entry(module)?;
840 let referenced_modules = graph_ref
841 .iter_graphs_neighbors_rev(entry, Direction::Outgoing)
842 .filter(|(edge_idx, _)| {
843 let ty = graph_ref.get_edge(*edge_idx).unwrap();
844 ty.chunking_type.is_inherit_async()
845 })
846 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
847 .collect::<Result<Vec<_>>>()?
848 .into_iter()
849 .rev()
850 .filter(|m| async_modules_info.contains(m))
851 .map(|m| *m)
852 .collect();
853
854 Ok(AsyncModuleInfo::new(referenced_modules))
855 }
856
857 #[turbo_tasks::function]
863 pub async fn without_unused_references(
864 self: ResolvedVc<Self>,
865 binding_usage: ResolvedVc<BindingUsageInfo>,
866 ) -> Result<Vc<Self>> {
867 Ok(Self {
868 graphs: self.await?.graphs.clone(),
869 binding_usage: Some(binding_usage),
870 }
871 .cell())
872 }
873}
874
875impl ModuleGraph {
876 pub async fn read_graphs(self: Vc<ModuleGraph>) -> Result<ModuleGraphRef> {
878 let this = self.await?;
879 Ok(ModuleGraphRef {
880 graphs: this.graphs.iter().try_join().await?,
881 skip_visited_module_children: false,
882 graph_idx_override: None,
883 binding_usage: if let Some(binding_usage) = this.binding_usage {
884 Some(binding_usage.await?)
885 } else {
886 None
887 },
888 })
889 }
890
891 pub fn iter_graphs(
893 self: &ModuleGraph,
894 ) -> impl Iterator<Item = SingleModuleGraphWithBindingUsage> {
895 self.graphs
896 .iter()
897 .enumerate()
898 .map(|(graph_idx, graph)| SingleModuleGraphWithBindingUsage {
899 graph: *graph,
900 graph_idx: graph_idx as u32,
901 binding_usage: self.binding_usage,
902 })
903 }
904}
905
906#[derive(
907 Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, NonLocalValue, Encode, Decode,
908)]
909pub struct SingleModuleGraphWithBindingUsage {
910 pub graph: ResolvedVc<SingleModuleGraph>,
911 pub graph_idx: u32,
912 pub binding_usage: Option<ResolvedVc<BindingUsageInfo>>,
913}
914
915impl SingleModuleGraphWithBindingUsage {
916 pub async fn read(self: &SingleModuleGraphWithBindingUsage) -> Result<ModuleGraphRef> {
917 Ok(ModuleGraphRef {
918 graphs: vec![self.graph.await?],
919 skip_visited_module_children: true,
920 graph_idx_override: Some(self.graph_idx),
921 binding_usage: if let Some(binding_usage) = &self.binding_usage {
922 Some(binding_usage.await?)
923 } else {
924 None
925 },
926 })
927 }
928}
929
930pub struct ModuleGraphRef {
933 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
934 skip_visited_module_children: bool,
937
938 pub graph_idx_override: Option<u32>,
939
940 pub binding_usage: Option<ReadRef<BindingUsageInfo>>,
941}
942
943impl ModuleGraphRef {
944 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
945 if self.graph_idx_override.is_some() {
946 debug_assert_eq!(self.graphs.len(), 1,);
947 }
948
949 let Some(idx) = self
950 .graphs
951 .iter()
952 .enumerate()
953 .find_map(|(graph_idx, graph)| {
954 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
955 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
956 node_idx: *node_idx,
957 })
958 })
959 else {
960 bail!("Couldn't find entry module {entry:?} in module graph");
961 };
962 Ok(idx)
963 }
964
965 pub fn entries(&self) -> impl Iterator<Item = ChunkGroupEntry> + '_ {
966 self.graphs.iter().flat_map(|g| g.entries.iter().cloned())
967 }
968
969 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
970 if self.graph_idx_override.is_some() {
971 self.graphs.first().unwrap()
972 } else {
973 &self.graphs[graph_idx as usize]
974 }
975 }
976
977 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
978 let graph = self.get_graph(node.graph_idx);
979 graph
980 .graph
981 .node_weight(node.node_idx)
982 .context("Expected graph node")
983 }
984
985 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
986 let graph = self.get_graph(edge.graph_idx);
987 graph
988 .graph
989 .edge_weight(edge.edge_idx)
990 .context("Expected graph node")
991 }
992
993 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
994 if self.skip_visited_module_children && direction == Direction::Outgoing {
995 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
996 } else {
997 true
998 }
999 }
1000
1001 pub fn enumerate_nodes(
1002 &self,
1003 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
1004 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
1005 }
1006
1007 fn iter_graphs_neighbors_rev<'a>(
1009 &'a self,
1010 node: GraphNodeIndex,
1011 direction: Direction,
1012 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1013 let graph = &*self.get_graph(node.graph_idx).graph;
1014
1015 if cfg!(debug_assertions) && direction == Direction::Outgoing {
1016 let node_weight = graph.node_weight(node.node_idx).unwrap();
1017 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1018 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1019 }
1020 }
1021
1022 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1023 std::iter::from_fn(move || {
1024 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1025 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1026 if self
1027 .binding_usage
1028 .as_ref()
1029 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
1030 {
1031 continue;
1033 }
1034
1035 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1036 }
1037 None
1038 })
1039 }
1040
1041 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1044 Ok(self
1045 .graphs
1046 .iter()
1047 .flat_map(|g| g.iter_nodes())
1048 .map(async |n| Ok((n, n.ident().to_string().await?)))
1049 .try_join()
1050 .await?
1051 .into_iter()
1052 .collect::<FxHashMap<_, _>>())
1053 }
1054
1055 pub fn traverse_nodes_dfs<S>(
1064 &self,
1065 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1066 state: &mut S,
1067 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1068 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1069 ) -> Result<()> {
1070 let entries = entries.into_iter().collect::<Vec<_>>();
1071
1072 enum Pass {
1073 Visit,
1074 ExpandAndVisit,
1075 }
1076 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1077 for entry in entries.into_iter().rev() {
1078 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1079 }
1080 let mut expanded = FxHashSet::default();
1081 while let Some((pass, current)) = stack.pop() {
1082 let current_node = self.get_node(current)?;
1083 match pass {
1084 Pass::Visit => {
1085 visit_postorder(current_node.module(), state)?;
1086 }
1087 Pass::ExpandAndVisit => {
1088 if !expanded.insert(current) {
1089 continue;
1090 }
1091 let action = visit_preorder(current_node.module(), state)?;
1092 if action == GraphTraversalAction::Exclude {
1093 continue;
1094 }
1095 stack.push((Pass::Visit, current));
1096 if action == GraphTraversalAction::Continue
1097 && self.should_visit_node(current_node, Direction::Outgoing)
1098 {
1099 let current = current_node
1100 .target_idx(Direction::Outgoing)
1101 .unwrap_or(current);
1102 stack.extend(
1103 self.iter_graphs_neighbors_rev(current, Direction::Outgoing)
1104 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1105 );
1106 }
1107 }
1108 }
1109 }
1110
1111 Ok(())
1112 }
1113
1114 pub fn traverse_edges_bfs(
1125 &self,
1126 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1127 mut visitor: impl FnMut(
1128 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1129 ResolvedVc<Box<dyn Module>>,
1130 ) -> Result<GraphTraversalAction>,
1131 ) -> Result<()> {
1132 let mut queue = VecDeque::from(
1133 entries
1134 .into_iter()
1135 .map(|e| self.get_entry(e))
1136 .collect::<Result<Vec<_>>>()?,
1137 );
1138 let mut visited = FxHashSet::default();
1139 for entry_node in &queue {
1140 visitor(None, self.get_node(*entry_node)?.module())?;
1141 }
1142 while let Some(node) = queue.pop_front() {
1143 if visited.insert(node) {
1144 let node_weight = self.get_node(node)?;
1145 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1146 let succ_weight = self.get_node(succ)?;
1147 let action = visitor(
1148 Some((node_weight.module(), self.get_edge(edge)?)),
1149 succ_weight.module(),
1150 )?;
1151 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1152 continue;
1153 }
1154 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1155 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1156 queue.push_back(succ);
1157 }
1158 }
1159 }
1160 }
1161
1162 Ok(())
1163 }
1164
1165 pub fn traverse_edges_unordered(
1174 &self,
1175 mut visitor: impl FnMut(
1176 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1177 ResolvedVc<Box<dyn Module>>,
1178 ) -> Result<()>,
1179 ) -> Result<()> {
1180 let entries = self.graphs.iter().flat_map(|g| g.entry_modules());
1181
1182 self.traverse_edges_dfs(
1185 entries,
1186 &mut (),
1187 |parent, target, _| {
1188 visitor(parent, target)?;
1189 Ok(GraphTraversalAction::Continue)
1190 },
1191 |_, _, _| Ok(()),
1192 )
1193 }
1194
1195 pub fn traverse_edges_dfs<S>(
1214 &self,
1215 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1216 state: &mut S,
1217 visit_preorder: impl FnMut(
1218 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1219 ResolvedVc<Box<dyn Module>>,
1220 &mut S,
1221 ) -> Result<GraphTraversalAction>,
1222 visit_postorder: impl FnMut(
1223 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1224 ResolvedVc<Box<dyn Module>>,
1225 &mut S,
1226 ) -> Result<()>,
1227 ) -> Result<()> {
1228 self.traverse_edges_dfs_impl::<S>(
1229 entries,
1230 state,
1231 visit_preorder,
1232 visit_postorder,
1233 Direction::Outgoing,
1234 )
1235 }
1236
1237 pub fn traverse_edges_reverse_dfs<S>(
1254 &self,
1255 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1256 state: &mut S,
1257 visit_preorder: impl FnMut(
1258 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1259 ResolvedVc<Box<dyn Module>>,
1260 &mut S,
1261 ) -> Result<GraphTraversalAction>,
1262 visit_postorder: impl FnMut(
1263 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1264 ResolvedVc<Box<dyn Module>>,
1265 &mut S,
1266 ) -> Result<()>,
1267 ) -> Result<()> {
1268 self.traverse_edges_dfs_impl::<S>(
1269 entries,
1270 state,
1271 visit_preorder,
1272 visit_postorder,
1273 Direction::Incoming,
1274 )
1275 }
1276
1277 fn traverse_edges_dfs_impl<S>(
1278 &self,
1279 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1280 state: &mut S,
1281 mut visit_preorder: impl FnMut(
1282 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1283 ResolvedVc<Box<dyn Module>>,
1284 &mut S,
1285 ) -> Result<GraphTraversalAction>,
1286 mut visit_postorder: impl FnMut(
1287 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1288 ResolvedVc<Box<dyn Module>>,
1289 &mut S,
1290 ) -> Result<()>,
1291 direction: Direction,
1292 ) -> Result<()> {
1293 if direction == Direction::Incoming {
1294 debug_assert!(
1295 self.skip_visited_module_children,
1296 "Can only trace reverse edges in a single layer graph. We do not model cross \
1297 graph reverse edges"
1298 );
1299 }
1300 let entries = entries.into_iter().collect::<Vec<_>>();
1301
1302 enum Pass {
1303 Visit,
1304 ExpandAndVisit,
1305 }
1306 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1308 Pass,
1309 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1310 GraphNodeIndex,
1311 )> = Vec::with_capacity(entries.len());
1312 for entry in entries.into_iter().rev() {
1313 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1314 }
1315 let mut expanded = FxHashSet::default();
1316 while let Some((pass, parent, current)) = stack.pop() {
1317 let parent_arg = match parent {
1318 Some((parent_node, parent_edge)) => Some((
1319 self.get_node(parent_node)?.module(),
1320 self.get_edge(parent_edge)?,
1321 )),
1322 None => None,
1323 };
1324 let current_node = self.get_node(current)?;
1325 match pass {
1326 Pass::Visit => {
1327 visit_postorder(parent_arg, current_node.module(), state)?;
1328 }
1329 Pass::ExpandAndVisit => {
1330 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1331 if action == GraphTraversalAction::Exclude {
1332 continue;
1333 }
1334 stack.push((Pass::Visit, parent, current));
1335 if action == GraphTraversalAction::Continue
1336 && expanded.insert(current)
1337 && self.should_visit_node(current_node, direction)
1338 {
1339 let current = current_node.target_idx(direction).unwrap_or(current);
1340 stack.extend(self.iter_graphs_neighbors_rev(current, direction).map(
1341 |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1342 ));
1343 }
1344 }
1345 }
1346 }
1347
1348 Ok(())
1349 }
1350
1351 pub fn traverse_cycles(
1355 &self,
1356 edge_filter: impl Fn(&RefData) -> bool,
1357 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1358 ) -> Result<()> {
1359 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1360 graph.traverse_cycles(
1361 &edge_filter,
1362 &mut visit_cycle,
1363 graph_idx as u32,
1364 &self.binding_usage,
1365 )?;
1366 }
1367 Ok(())
1368 }
1369
1370 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1392 &self,
1393 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1394 state: &mut S,
1395 mut visit: impl FnMut(
1396 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1397 ResolvedVc<Box<dyn Module>>,
1398 &mut S,
1399 ) -> Result<GraphTraversalAction>,
1400 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1401 ) -> Result<usize> {
1402 if self.skip_visited_module_children {
1403 panic!(
1404 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1405 );
1406 }
1407
1408 let mut visit_order = 0usize;
1409 let mut order = || {
1410 let order = visit_order;
1411 visit_order += 1;
1412 order
1413 };
1414 #[derive(PartialEq, Eq)]
1415 struct NodeWithPriority<T: Ord> {
1416 node: GraphNodeIndex,
1417 priority: T,
1418 visit_order: usize,
1419 }
1420 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1421 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1422 Some(self.cmp(other))
1423 }
1424 }
1425 impl<T: Ord> Ord for NodeWithPriority<T> {
1426 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1427 self.priority
1430 .cmp(&other.priority)
1431 .then(self.visit_order.cmp(&other.visit_order))
1434 }
1435 }
1436
1437 let mut queue_set = FxHashSet::default();
1438 let mut queue = BinaryHeap::from_iter(
1439 entries
1440 .into_iter()
1441 .map(|(m, priority)| {
1442 Ok(NodeWithPriority {
1443 node: self.get_entry(m)?,
1444 priority,
1445 visit_order: order(),
1446 })
1447 })
1448 .collect::<Result<Vec<_>>>()?,
1449 );
1450
1451 for entry_node in &queue {
1452 visit(None, self.get_node(entry_node.node)?.module(), state)?;
1453 }
1454
1455 let mut visit_count = 0usize;
1456 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1457 queue_set.remove(&node);
1458 let node_weight = self.get_node(node)?;
1459 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1460
1461 visit_count += 1;
1462
1463 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1464 let succ_weight = self.get_node(succ)?;
1465
1466 let action = visit(
1467 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1468 succ_weight.module(),
1469 state,
1470 )?;
1471
1472 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1473 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1474 queue.push(NodeWithPriority {
1475 node: succ,
1476 priority: priority(succ_weight.module(), state)?,
1477 visit_order: order(),
1478 });
1479 }
1480 }
1481 }
1482
1483 Ok(visit_count)
1484 }
1485}
1486
1487#[turbo_tasks::value_impl]
1488impl SingleModuleGraph {
1489 #[turbo_tasks::function]
1490 pub async fn new_with_entries(
1491 entries: Vc<GraphEntries>,
1492 include_traced: bool,
1493 include_binding_usage: bool,
1494 ) -> Result<Vc<Self>> {
1495 SingleModuleGraph::new_inner(
1496 &*entries.await?,
1497 &Default::default(),
1498 include_traced,
1499 include_binding_usage,
1500 )
1501 .await
1502 }
1503
1504 #[turbo_tasks::function]
1505 pub async fn new_with_entries_visited(
1506 entries: Vc<GraphEntries>,
1507 visited_modules: Vc<VisitedModules>,
1508 include_traced: bool,
1509 include_binding_usage: bool,
1510 ) -> Result<Vc<Self>> {
1511 SingleModuleGraph::new_inner(
1512 &*entries.await?,
1513 &visited_modules.await?.modules,
1514 include_traced,
1515 include_binding_usage,
1516 )
1517 .await
1518 }
1519
1520 #[turbo_tasks::function]
1521 pub async fn new_with_entries_visited_intern(
1522 entries: GraphEntriesT,
1524 visited_modules: Vc<VisitedModules>,
1525 include_traced: bool,
1526 include_binding_usage: bool,
1527 ) -> Result<Vc<Self>> {
1528 SingleModuleGraph::new_inner(
1529 &entries,
1530 &visited_modules.await?.modules,
1531 include_traced,
1532 include_binding_usage,
1533 )
1534 .await
1535 }
1536}
1537
1538#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1539pub enum SingleModuleGraphNode {
1540 Module(ResolvedVc<Box<dyn Module>>),
1541 VisitedModule {
1543 idx: GraphNodeIndex,
1544 module: ResolvedVc<Box<dyn Module>>,
1545 },
1546}
1547
1548impl SingleModuleGraphNode {
1549 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1550 match self {
1551 SingleModuleGraphNode::Module(module) => *module,
1552 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1553 }
1554 }
1555 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1556 match self {
1557 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1558 Direction::Outgoing => Some(*idx),
1559 Direction::Incoming => None,
1560 },
1561 SingleModuleGraphNode::Module(_) => None,
1562 }
1563 }
1564}
1565
1566#[derive(PartialEq, Eq, Debug)]
1567pub enum GraphTraversalAction {
1568 Continue,
1570 Skip,
1572 Exclude,
1574}
1575
1576#[derive(Clone, Hash, PartialEq, Eq)]
1579enum SingleModuleGraphBuilderNode {
1580 Module {
1582 module: ResolvedVc<Box<dyn Module>>,
1583 ident: Option<ReadRef<RcStr>>,
1585 },
1586 VisitedModule {
1588 module: ResolvedVc<Box<dyn Module>>,
1589 idx: GraphNodeIndex,
1590 },
1591}
1592
1593impl SingleModuleGraphBuilderNode {
1594 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1595 Ok(Self::Module {
1596 module,
1597 ident: if emit_spans {
1598 Some(module.ident_string().untracked().await?)
1600 } else {
1601 None
1602 },
1603 })
1604 }
1605 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1606 Self::VisitedModule { module, idx }
1607 }
1608}
1609
1610struct SingleModuleGraphBuilder<'a> {
1611 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1612
1613 emit_spans: bool,
1614
1615 include_traced: bool,
1617
1618 include_binding_usage: bool,
1620}
1621impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1622 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1623 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1624
1625 fn visit(
1626 &mut self,
1627 node: &SingleModuleGraphBuilderNode,
1628 edge: Option<&RefData>,
1629 ) -> VisitControlFlow {
1630 if let Some(edge) = edge
1631 && matches!(edge.chunking_type, ChunkingType::Traced)
1632 {
1633 return VisitControlFlow::Skip;
1635 }
1636 match node {
1637 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1638 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1640 }
1641 }
1642
1643 fn edges(
1644 &mut self,
1645 node: &SingleModuleGraphBuilderNode,
1648 ) -> Self::EdgesFuture {
1649 let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1651 unreachable!()
1653 };
1654 let visited_modules = self.visited_modules;
1655 let emit_spans = self.emit_spans;
1656 let include_traced = self.include_traced;
1657 let include_binding_usage = self.include_binding_usage;
1658 async move {
1659 let refs_cell = primary_chunkable_referenced_modules(
1660 *module,
1661 include_traced,
1662 include_binding_usage,
1663 );
1664 let refs = match refs_cell.await {
1665 Ok(refs) => refs,
1666 Err(e) => {
1667 return Err(e.context(module.ident().to_string().await?));
1668 }
1669 };
1670
1671 refs.iter()
1672 .flat_map(|(reference, resolved)| {
1673 resolved.modules.iter().map(|m| {
1674 (
1675 *reference,
1676 resolved.chunking_type.clone(),
1677 resolved.binding_usage.clone(),
1678 *m,
1679 )
1680 })
1681 })
1682 .map(async |(reference, ty, binding_usage, target)| {
1683 let to = if let Some(idx) = visited_modules.get(&target) {
1684 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1685 } else {
1686 SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1687 };
1688 Ok((
1689 to,
1690 RefData {
1691 chunking_type: ty,
1692 binding_usage,
1693 reference,
1694 },
1695 ))
1696 })
1697 .try_join()
1698 .await
1699 }
1700 }
1701
1702 fn span(
1703 &mut self,
1704 node: &SingleModuleGraphBuilderNode,
1705 edge: Option<&RefData>,
1706 ) -> tracing::Span {
1707 if !self.emit_spans {
1708 return Span::none();
1709 }
1710
1711 let mut span = match node {
1712 SingleModuleGraphBuilderNode::Module {
1713 ident: Some(ident), ..
1714 } => {
1715 tracing::info_span!("module", name = display(ident))
1716 }
1717 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1718 tracing::info_span!("visited module")
1719 }
1720 _ => unreachable!(),
1721 };
1722
1723 if let Some(edge) = edge {
1724 match &edge.chunking_type {
1725 ChunkingType::Parallel {
1726 inherit_async: _,
1727 hoisted: _,
1728 } => {}
1729 ChunkingType::Traced => {
1730 let _span = span.entered();
1731 span = tracing::info_span!("traced reference");
1732 }
1733 ChunkingType::Async => {
1734 let _span = span.entered();
1735 span = tracing::info_span!("async reference");
1736 }
1737 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1738 let _span = span.entered();
1739 span = tracing::info_span!(
1740 "isolated reference",
1741 ty = debug(&ty),
1742 merge_tag = debug(&merge_tag)
1743 );
1744 }
1745 ChunkingType::Shared {
1746 inherit_async: _,
1747 merge_tag,
1748 } => {
1749 let _span = span.entered();
1750 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1751 }
1752 };
1753 }
1754
1755 span
1756 }
1757}
1758
1759#[cfg(test)]
1760pub mod tests {
1761 use anyhow::Result;
1762 use rustc_hash::FxHashMap;
1763 use turbo_rcstr::{RcStr, rcstr};
1764 use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
1765 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1766 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1767
1768 use crate::{
1769 asset::{Asset, AssetContent},
1770 ident::AssetIdent,
1771 module::{Module, ModuleSideEffects},
1772 module_graph::{
1773 GraphEntries, GraphTraversalAction, ModuleGraph, ModuleGraphRef, SingleModuleGraph,
1774 VisitedModules, chunk_group_info::ChunkGroupEntry,
1775 },
1776 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1777 resolve::ExportUsage,
1778 };
1779
1780 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1781 async fn test_traverse_dfs_from_entries_diamond() {
1782 run_graph_test(
1783 vec![rcstr!("a.js")],
1784 {
1785 let mut deps = FxHashMap::default();
1786 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1788 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1789 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1790 deps
1791 },
1792 |graph, entry_modules, module_to_name| {
1793 let mut preorder_visits = Vec::new();
1794 let mut postorder_visits = Vec::new();
1795
1796 graph.traverse_edges_dfs(
1797 entry_modules,
1798 &mut (),
1799 |parent, target, _| {
1800 preorder_visits.push((
1801 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1802 module_to_name.get(&target).unwrap().clone(),
1803 ));
1804 Ok(GraphTraversalAction::Continue)
1805 },
1806 |parent, target, _| {
1807 postorder_visits.push((
1808 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1809 module_to_name.get(&target).unwrap().clone(),
1810 ));
1811 Ok(())
1812 },
1813 )?;
1814 assert_eq!(
1815 vec![
1816 (None, rcstr!("a.js")),
1817 (Some(rcstr!("a.js")), rcstr!("b.js")),
1818 (Some(rcstr!("b.js")), rcstr!("d.js")),
1819 (Some(rcstr!("a.js")), rcstr!("c.js")),
1820 (Some(rcstr!("c.js")), rcstr!("d.js"))
1821 ],
1822 preorder_visits
1823 );
1824 assert_eq!(
1825 vec![
1826 (Some(rcstr!("b.js")), rcstr!("d.js")),
1827 (Some(rcstr!("a.js")), rcstr!("b.js")),
1828 (Some(rcstr!("c.js")), rcstr!("d.js")),
1829 (Some(rcstr!("a.js")), rcstr!("c.js")),
1830 (None, rcstr!("a.js"))
1831 ],
1832 postorder_visits
1833 );
1834 Ok(())
1835 },
1836 )
1837 .await;
1838 }
1839
1840 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1841 async fn test_traverse_dfs_from_entries_cycle() {
1842 run_graph_test(
1843 vec![rcstr!("a.js")],
1844 {
1845 let mut deps = FxHashMap::default();
1846 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1848 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1849 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1850 deps
1851 },
1852 |graph, entry_modules, module_to_name| {
1853 let mut preorder_visits = Vec::new();
1854 let mut postorder_visits = Vec::new();
1855
1856 graph.traverse_edges_dfs(
1857 entry_modules,
1858 &mut (),
1859 |parent, target, _| {
1860 preorder_visits.push((
1861 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1862 module_to_name.get(&target).unwrap().clone(),
1863 ));
1864 Ok(GraphTraversalAction::Continue)
1865 },
1866 |parent, target, _| {
1867 postorder_visits.push((
1868 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1869 module_to_name.get(&target).unwrap().clone(),
1870 ));
1871 Ok(())
1872 },
1873 )?;
1874 assert_eq!(
1875 vec![
1876 (None, rcstr!("a.js")),
1877 (Some(rcstr!("a.js")), rcstr!("b.js")),
1878 (Some(rcstr!("b.js")), rcstr!("c.js")),
1879 (Some(rcstr!("c.js")), rcstr!("a.js")),
1880 ],
1881 preorder_visits
1882 );
1883 assert_eq!(
1884 vec![
1885 (Some(rcstr!("c.js")), rcstr!("a.js")),
1886 (Some(rcstr!("b.js")), rcstr!("c.js")),
1887 (Some(rcstr!("a.js")), rcstr!("b.js")),
1888 (None, rcstr!("a.js"))
1889 ],
1890 postorder_visits
1891 );
1892 Ok(())
1893 },
1894 )
1895 .await;
1896 }
1897
1898 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1899 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1900 run_graph_test(
1901 vec![rcstr!("a.js")],
1902 {
1903 let mut deps = FxHashMap::default();
1904 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1906 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1907 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1908 deps
1909 },
1910 |graph, entry_modules, module_to_name| {
1911 let mut visits = Vec::new();
1912 let mut count = 0;
1913
1914 graph.traverse_edges_fixed_point_with_priority(
1915 entry_modules.into_iter().map(|m| (m, 0)),
1916 &mut (),
1917 |parent, target, _| {
1918 visits.push((
1919 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1920 module_to_name.get(&target).unwrap().clone(),
1921 ));
1922 count += 1;
1923
1924 Ok(if count < 6 {
1926 GraphTraversalAction::Continue
1927 } else {
1928 GraphTraversalAction::Skip
1929 })
1930 },
1931 |_, _| Ok(0),
1932 )?;
1933 assert_eq!(
1934 vec![
1935 (None, rcstr!("a.js")),
1936 (Some(rcstr!("a.js")), rcstr!("b.js")),
1937 (Some(rcstr!("b.js")), rcstr!("c.js")),
1938 (Some(rcstr!("c.js")), rcstr!("a.js")),
1939 (Some(rcstr!("a.js")), rcstr!("b.js")),
1941 (Some(rcstr!("b.js")), rcstr!("c.js")),
1942 ],
1943 visits
1944 );
1945
1946 Ok(())
1947 },
1948 )
1949 .await;
1950 }
1951
1952 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1953 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1954 run_graph_test(
1955 vec![rcstr!("a.js")],
1956 {
1957 let mut deps = FxHashMap::default();
1958 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1963 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1964 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1965 deps
1966 },
1967 |graph, entry_modules, module_to_name| {
1968 let mut visits = Vec::new();
1969 let mut count = 0;
1970
1971 graph.traverse_edges_fixed_point_with_priority(
1972 entry_modules.into_iter().map(|m| (m, 0)),
1973 &mut (),
1974 |parent, target, _| {
1975 visits.push((
1976 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1977 module_to_name.get(&target).unwrap().clone(),
1978 ));
1979 count += 1;
1980
1981 Ok(if count < 6 {
1983 GraphTraversalAction::Continue
1984 } else {
1985 GraphTraversalAction::Skip
1986 })
1987 },
1988 |_, _| Ok(0),
1989 )?;
1990
1991 assert_eq!(
1992 vec![
1993 (None, rcstr!("a.js")),
1994 (Some(rcstr!("a.js")), rcstr!("c.js")),
1995 (Some(rcstr!("a.js")), rcstr!("b.js")),
1996 (Some(rcstr!("b.js")), rcstr!("e.js")),
1997 (Some(rcstr!("b.js")), rcstr!("d.js")),
1998 (Some(rcstr!("c.js")), rcstr!("f.js")),
1999 (Some(rcstr!("c.js")), rcstr!("e.js")),
2000 ],
2001 visits
2002 );
2003
2004 Ok(())
2005 },
2006 )
2007 .await;
2008 }
2009
2010 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2011 async fn test_traverse_cycles() {
2012 run_graph_test(
2013 vec![rcstr!("a.js")],
2014 {
2015 let mut deps = FxHashMap::default();
2016 deps.insert(
2023 rcstr!("a.js"),
2024 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2025 );
2026 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2027 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2028 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2029 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2030 deps
2031 },
2032 |graph, _, module_to_name| {
2033 let mut cycles = vec![];
2034
2035 graph.traverse_cycles(
2036 |_| true,
2037 |cycle| {
2038 cycles.push(
2039 cycle
2040 .iter()
2041 .map(|n| module_to_name.get(*n).unwrap().clone())
2042 .collect::<Vec<_>>(),
2043 );
2044 Ok(())
2045 },
2046 )?;
2047
2048 assert_eq!(
2049 cycles,
2050 vec![
2051 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2052 vec![rcstr!("s.js")]
2053 ],
2054 );
2055
2056 Ok(())
2057 },
2058 )
2059 .await;
2060 }
2061
2062 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2063 async fn test_reverse_edges_through_layered_graph() {
2064 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2065 BackendOptions::default(),
2066 noop_backing_storage(),
2067 ));
2068 tt.run_once(async move {
2069 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2070 let root = fs.root().await?;
2071
2072 let graph = {
2075 let mut deps = FxHashMap::default();
2076
2077 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2078 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2079 deps
2080 };
2081 let repo = TestRepo {
2082 repo: graph
2083 .iter()
2084 .map(|(k, v)| {
2085 (
2086 root.join(k).unwrap(),
2087 v.iter().map(|f| root.join(f).unwrap()).collect(),
2088 )
2089 })
2090 .collect(),
2091 }
2092 .cell();
2093 let make_module = |name| {
2094 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2095 .to_resolved()
2096 };
2097 let a_module = make_module("a.js").await?;
2098 let b_module = make_module("b.js").await?;
2099
2100 let parent_graph = SingleModuleGraph::new_with_entries(
2101 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(vec![b_module])])),
2102 false,
2103 false,
2104 );
2105
2106 let module_graph = ModuleGraph::from_graphs(vec![
2107 parent_graph,
2108 SingleModuleGraph::new_with_entries_visited(
2109 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(vec![a_module])])),
2110 VisitedModules::from_graph(parent_graph),
2111 false,
2112 false,
2113 ),
2114 ])
2115 .await?;
2116 let child_graph = module_graph.iter_graphs().nth(1).unwrap().read().await?;
2117 {
2119 let mut visited_forward = Vec::new();
2120 child_graph.traverse_edges_dfs(
2121 vec![a_module],
2122 &mut (),
2123 |_parent, child, _state_| {
2124 visited_forward.push(child);
2125 Ok(GraphTraversalAction::Continue)
2126 },
2127 |_, _, _| Ok(()),
2128 )?;
2129
2130 assert_eq!(
2131 visited_forward
2132 .iter()
2133 .map(|m| m.ident().to_string().owned())
2134 .try_join()
2135 .await?,
2136 vec![
2137 rcstr!("[test]/a.js"),
2138 rcstr!("[test]/b.js"),
2139 rcstr!("[test]/d.js")
2140 ]
2141 );
2142 }
2143
2144 {
2146 use turbo_tasks::TryFlatJoinIterExt;
2147 let d_module = child_graph
2148 .enumerate_nodes()
2149 .map(|(_index, module)| async move {
2150 Ok(match module {
2151 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2152 if module.ident().to_string().owned().await.unwrap()
2153 == "[test]/d.js"
2154 {
2155 Some(*module)
2156 } else {
2157 None
2158 }
2159 }
2160 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2161 ..
2162 } => None,
2163 })
2164 })
2165 .try_flat_join()
2166 .await?
2167 .into_iter()
2168 .next()
2169 .unwrap();
2170 let mut visited_reverse = Vec::new();
2171 child_graph.traverse_edges_reverse_dfs(
2172 vec![d_module],
2173 &mut (),
2174 |_parent, child, _state_| {
2175 visited_reverse.push(child);
2176 Ok(GraphTraversalAction::Continue)
2177 },
2178 |_, _, _| Ok(()),
2179 )?;
2180 assert_eq!(
2181 visited_reverse
2182 .iter()
2183 .map(|m| m.ident().to_string().owned())
2184 .try_join()
2185 .await?,
2186 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2187 );
2188 }
2189 {
2192 let mut visited_reverse = Vec::new();
2193 child_graph.traverse_edges_reverse_dfs(
2194 vec![b_module],
2195 &mut (),
2196 |_parent, child, _state_| {
2197 visited_reverse.push(child);
2198 Ok(GraphTraversalAction::Continue)
2199 },
2200 |_, _, _| Ok(()),
2201 )?;
2202 assert_eq!(
2203 visited_reverse
2204 .iter()
2205 .map(|m| m.ident().to_string().owned())
2206 .try_join()
2207 .await?,
2208 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2209 );
2210 }
2211
2212 Ok(())
2213 })
2214 .await
2215 .unwrap();
2216 }
2217
2218 #[turbo_tasks::value(shared)]
2219 struct TestRepo {
2220 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2221 }
2222 #[turbo_tasks::value]
2223 struct MockModule {
2224 path: FileSystemPath,
2225 repo: ResolvedVc<TestRepo>,
2226 }
2227 #[turbo_tasks::value_impl]
2228 impl MockModule {
2229 #[turbo_tasks::function]
2230 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2231 Self { path, repo }.cell()
2232 }
2233 }
2234
2235 #[turbo_tasks::value_impl]
2236 impl Asset for MockModule {
2237 #[turbo_tasks::function]
2238 fn content(&self) -> Vc<AssetContent> {
2239 panic!("MockModule::content shouldn't be called")
2240 }
2241 }
2242
2243 #[turbo_tasks::value_impl]
2244 impl Module for MockModule {
2245 #[turbo_tasks::function]
2246 fn ident(&self) -> Vc<AssetIdent> {
2247 AssetIdent::from_path(self.path.clone())
2248 }
2249
2250 #[turbo_tasks::function]
2251 fn source(&self) -> Vc<crate::source::OptionSource> {
2252 Vc::cell(None)
2253 }
2254
2255 #[turbo_tasks::function]
2256 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2257 let repo = self.repo.await?;
2258 let references = match repo.repo.get(&self.path) {
2259 Some(deps) => {
2260 deps.iter()
2261 .map(|p| {
2262 Vc::upcast::<Box<dyn ModuleReference>>(
2263 SingleChunkableModuleReference::new(
2264 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2265 rcstr!("normal-dep"),
2266 ExportUsage::all(),
2267 ),
2268 )
2269 .to_resolved()
2270 })
2271 .try_join()
2272 .await?
2273 }
2274 None => vec![],
2275 };
2276
2277 Ok(Vc::cell(references))
2278 }
2279 #[turbo_tasks::function]
2280 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2281 ModuleSideEffects::SideEffectful.cell()
2282 }
2283 }
2284
2285 async fn run_graph_test(
2299 entries: Vec<RcStr>,
2300 graph: FxHashMap<RcStr, Vec<RcStr>>,
2301 test_fn: impl FnOnce(
2302 ModuleGraphRef,
2303 Vec<ResolvedVc<Box<dyn Module>>>,
2304 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2305 ) -> Result<()>
2306 + Send
2307 + 'static,
2308 ) {
2309 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2310 BackendOptions::default(),
2311 noop_backing_storage(),
2312 ));
2313 tt.run_once(async move {
2314 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2315 let root = fs.root().await?;
2316
2317 let repo = TestRepo {
2318 repo: graph
2319 .iter()
2320 .map(|(k, v)| {
2321 (
2322 root.join(k).unwrap(),
2323 v.iter().map(|f| root.join(f).unwrap()).collect(),
2324 )
2325 })
2326 .collect(),
2327 }
2328 .cell();
2329 let entry_modules = entries
2330 .iter()
2331 .map(|e| {
2332 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2333 .to_resolved()
2334 })
2335 .try_join()
2336 .await?;
2337 let graph = SingleModuleGraph::new_with_entries(
2338 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2339 entry_modules.clone(),
2340 )])),
2341 false,
2342 false,
2343 );
2344
2345 let module_to_name = graph
2349 .await?
2350 .modules
2351 .keys()
2352 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2353 .try_join()
2354 .await?
2355 .into_iter()
2356 .collect();
2357 test_fn(
2358 ModuleGraph::from_single_graph(graph).read_graphs().await?,
2359 entry_modules,
2360 module_to_name,
2361 )
2362 })
2363 .await
2364 .unwrap();
2365 }
2366}