Skip to main content

turbopack_core/module_graph/
mod.rs

1use std::{
2    collections::{BinaryHeap, VecDeque},
3    future::Future,
4    ops::Deref,
5};
6
7use anyhow::{Context, Result, bail};
8use bincode::{Decode, Encode};
9use petgraph::{
10    Direction,
11    graph::{DiGraph, EdgeIndex, NodeIndex},
12    visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
13};
14use rustc_hash::{FxHashMap, FxHashSet};
15use serde::{Deserialize, Serialize};
16use tracing::{Instrument, Level, Span};
17use turbo_rcstr::RcStr;
18use turbo_tasks::{
19    CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc,
20    TryJoinIterExt, ValueToString, Vc,
21    debug::ValueDebugFormat,
22    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
23    trace::TraceRawVcs,
24};
25use turbo_tasks_fs::FileSystemPath;
26
27use crate::{
28    chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
29    issue::{ImportTracer, ImportTraces, Issue},
30    module::Module,
31    module_graph::{
32        async_module_info::{AsyncModulesInfo, compute_async_module_info},
33        binding_usage_info::BindingUsageInfo,
34        chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
35        merged_modules::{MergedModuleInfo, compute_merged_modules},
36        module_batches::{ModuleBatchesGraph, compute_module_batches},
37        style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
38        traced_di_graph::TracedDiGraph,
39    },
40    reference::{ModuleReference, primary_chunkable_referenced_modules},
41    resolve::BindingUsage,
42};
43
44pub mod async_module_info;
45pub mod binding_usage_info;
46pub mod chunk_group_info;
47pub mod merged_modules;
48pub mod module_batch;
49pub(crate) mod module_batches;
50mod side_effect_module_info;
51pub(crate) mod style_groups;
52mod traced_di_graph;
53
54pub use self::module_batches::BatchingConfig;
55
56#[derive(
57    Debug,
58    Copy,
59    Clone,
60    Eq,
61    PartialOrd,
62    Ord,
63    Hash,
64    PartialEq,
65    Serialize,
66    Deserialize,
67    TraceRawVcs,
68    Encode,
69    Decode,
70)]
71pub struct GraphNodeIndex {
72    #[turbo_tasks(trace_ignore)]
73    graph_idx: u32,
74    #[turbo_tasks(trace_ignore)]
75    #[bincode(with_serde)]
76    node_idx: NodeIndex,
77}
78impl GraphNodeIndex {
79    fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
80        Self {
81            graph_idx,
82            node_idx,
83        }
84    }
85}
86
87unsafe impl NonLocalValue for GraphNodeIndex {}
88
89#[derive(
90    Debug,
91    Copy,
92    Clone,
93    Eq,
94    PartialOrd,
95    Ord,
96    Hash,
97    PartialEq,
98    TraceRawVcs,
99    NonLocalValue,
100    Encode,
101    Decode,
102)]
103pub struct GraphEdgeIndex {
104    graph_idx: u32,
105    #[turbo_tasks(trace_ignore)]
106    #[bincode(with_serde)]
107    edge_idx: EdgeIndex,
108}
109
110impl GraphEdgeIndex {
111    fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
112        Self {
113            graph_idx,
114            edge_idx,
115        }
116    }
117}
118
119#[turbo_tasks::value]
120#[derive(Clone, Debug)]
121pub struct VisitedModules {
122    #[bincode(with = "turbo_bincode::indexmap")]
123    pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
124    next_graph_idx: u32,
125}
126
127#[turbo_tasks::value_impl]
128impl VisitedModules {
129    #[turbo_tasks::function(operation)]
130    pub fn empty() -> Vc<Self> {
131        Self {
132            modules: Default::default(),
133            next_graph_idx: 0,
134        }
135        .cell()
136    }
137
138    #[turbo_tasks::function(operation)]
139    pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
140        Ok(Self {
141            modules: graph
142                .connect()
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(operation)]
162    pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
163        let this = this.connect().await?;
164        Ok(Self {
165            modules: this.modules.clone(),
166            next_graph_idx: this.next_graph_idx + 1,
167        }
168        .cell())
169    }
170
171    #[turbo_tasks::function(operation)]
172    pub async fn concatenate(
173        this: OperationVc<Self>,
174        graph: OperationVc<SingleModuleGraph>,
175    ) -> Result<Vc<Self>> {
176        let graph = graph.connect().await?;
177        let this = this.connect().await?;
178        let iter = this
179            .modules
180            .iter()
181            .map(|(module, idx)| (*module, *idx))
182            .chain(
183                graph
184                    .enumerate_nodes()
185                    .flat_map(|(node_idx, module)| match module {
186                        SingleModuleGraphNode::Module(module) => Some((
187                            *module,
188                            GraphNodeIndex {
189                                graph_idx: this.next_graph_idx,
190                                node_idx,
191                            },
192                        )),
193                        SingleModuleGraphNode::VisitedModule { .. } => None,
194                    }),
195            );
196
197        let mut map = FxIndexMap::with_capacity_and_hasher(
198            this.modules.len() + graph.number_of_modules,
199            Default::default(),
200        );
201        for (k, v) in iter {
202            map.entry(k).or_insert(v);
203        }
204        map.shrink_to_fit();
205
206        Ok(Self {
207            modules: map,
208            next_graph_idx: this.next_graph_idx + 1,
209        }
210        .cell())
211    }
212}
213
214pub type GraphEntriesT = Vec<ChunkGroupEntry>;
215
216#[turbo_tasks::value(transparent)]
217pub struct GraphEntries(GraphEntriesT);
218
219#[turbo_tasks::value_impl]
220impl GraphEntries {
221    #[turbo_tasks::function]
222    pub fn empty() -> Vc<Self> {
223        Vc::cell(Vec::new())
224    }
225}
226
227#[turbo_tasks::value(cell = "new", eq = "manual")]
228#[derive(Clone, Default)]
229pub struct SingleModuleGraph {
230    pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
231
232    /// The number of modules in the graph (excluding VisitedModule nodes)
233    pub number_of_modules: usize,
234
235    // NodeIndex isn't necessarily stable (because of swap_remove), but we never remove nodes.
236    //
237    // HashMaps have nondeterministic order, but this map is only used for lookups (in
238    // `get_module`) and not iteration.
239    //
240    // This contains Vcs, but they are already contained in the graph, so no need to trace this.
241    #[turbo_tasks(trace_ignore)]
242    #[bincode(with_serde)]
243    modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
244
245    #[turbo_tasks(trace_ignore)]
246    pub entries: GraphEntriesT,
247}
248
249#[derive(
250    Debug,
251    Clone,
252    Hash,
253    TraceRawVcs,
254    Serialize,
255    Deserialize,
256    Eq,
257    PartialEq,
258    ValueDebugFormat,
259    NonLocalValue,
260)]
261pub struct RefData {
262    pub chunking_type: ChunkingType,
263    pub binding_usage: BindingUsage,
264    pub reference: ResolvedVc<Box<dyn ModuleReference>>,
265}
266
267impl SingleModuleGraph {
268    /// Walks the graph starting from the given entries and collects all reachable nodes, skipping
269    /// nodes listed in `visited_modules`
270    /// The resulting graph's outgoing edges are in reverse order.
271    async fn new_inner(
272        entries: Vec<ChunkGroupEntry>,
273        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
274        include_traced: bool,
275        include_binding_usage: bool,
276    ) -> Result<Vc<Self>> {
277        let emit_spans = tracing::enabled!(Level::INFO);
278        let root_nodes = entries
279            .iter()
280            .flat_map(|e| e.entries())
281            .map(|e| SingleModuleGraphBuilderNode::new_module(emit_spans, e))
282            .try_join()
283            .await?;
284
285        let children_nodes_iter = AdjacencyMap::new()
286            .visit(
287                root_nodes,
288                SingleModuleGraphBuilder {
289                    visited_modules,
290                    emit_spans,
291                    include_traced,
292                    include_binding_usage,
293                },
294            )
295            .await
296            .completed()?;
297        let node_count = children_nodes_iter.len();
298
299        let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
300            node_count,
301            // From real world measurements each module has about 3-4 children
302            // If it has more this would cause an additional allocation, but that's fine
303            node_count * 4,
304        );
305
306        let mut number_of_modules = 0;
307        let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
308            FxHashMap::with_capacity_and_hasher(node_count, Default::default());
309        {
310            let _span = tracing::info_span!("build module graph").entered();
311            for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
312                let (module, graph_node, count) = match current {
313                    SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
314                        (module, SingleModuleGraphNode::Module(module), 1)
315                    }
316                    SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
317                        module,
318                        SingleModuleGraphNode::VisitedModule { idx, module },
319                        0,
320                    ),
321                };
322
323                // Find the current node, if it was already added
324                let current_idx = if let Some(current_idx) = modules.get(&module) {
325                    *current_idx
326                } else {
327                    let idx = graph.add_node(graph_node);
328                    number_of_modules += count;
329                    modules.insert(module, idx);
330                    idx
331                };
332                // Add the edge
333                if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
334                    parent
335                {
336                    let parent_idx = *modules.get(&module).unwrap();
337                    graph.add_edge(parent_idx, current_idx, ref_data);
338                }
339            }
340        }
341
342        graph.shrink_to_fit();
343
344        #[cfg(debug_assertions)]
345        {
346            use once_cell::sync::Lazy;
347            static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
348                match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
349                    Some(v) => v != "1" && v != "true",
350                    None => true,
351                }
352            });
353            if *CHECK_FOR_DUPLICATE_MODULES {
354                let mut duplicates = FxHashSet::default();
355                let mut set = FxHashSet::default();
356                for &module in modules.keys() {
357                    let ident = module.ident().to_string().await?;
358                    if !set.insert(ident.clone()) {
359                        duplicates.insert(ident);
360                    }
361                }
362                if !duplicates.is_empty() {
363                    use turbo_tasks::TryFlatJoinIterExt;
364
365                    let duplicates_clone = duplicates.clone();
366                    let duplicate_modules = modules
367                        .iter()
368                        .map(async |(&m, &idx)| {
369                            let id = m.ident().to_string().await?;
370                            if duplicates_clone.contains(&id) {
371                                // 3 is arbitrary but it is enough to reveal a little bit
372                                // of detail.
373                                let debug = m.value_debug_format(3).try_to_string().await?;
374
375                                // Collect reverse dependencies (parents) to help
376                                // diagnose how this module entered the graph.
377                                let parent_modules: Vec<_> = graph
378                                    .edges_directed(idx, petgraph::Direction::Incoming)
379                                    .filter_map(|edge| match graph.node_weight(edge.source()) {
380                                        Some(SingleModuleGraphNode::Module(m)) => Some(*m),
381                                        Some(SingleModuleGraphNode::VisitedModule {
382                                            module,
383                                            ..
384                                        }) => Some(*module),
385                                        None => None,
386                                    })
387                                    .collect();
388                                let parents: Vec<String> = parent_modules
389                                    .iter()
390                                    .map(async |p| {
391                                        let ident = p.ident().to_string().await?;
392                                        Ok((*ident).to_string())
393                                    })
394                                    .try_join()
395                                    .await?;
396
397                                Ok(Some((id, debug, parents)))
398                            } else {
399                                Ok(None)
400                            }
401                        })
402                        .try_flat_join()
403                        .await?;
404                    // group by ident
405                    let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
406                    for (key, debug, parents) in duplicate_modules {
407                        map.entry(key).or_default().push((debug, parents));
408                    }
409                    panic!("Duplicate module idents in graph: {map:#?}",);
410                }
411            }
412        }
413
414        let graph = SingleModuleGraph {
415            graph: TracedDiGraph::new(graph),
416            number_of_modules,
417            modules,
418            entries: entries.clone(),
419        }
420        .cell();
421
422        turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
423            ModuleGraphImportTracer::new(graph).to_resolved().await?,
424        ));
425        Ok(graph)
426    }
427
428    /// Iterate over all nodes in the graph
429    pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
430        self.graph.node_weights().filter_map(|n| match n {
431            SingleModuleGraphNode::Module(node) => Some(*node),
432            SingleModuleGraphNode::VisitedModule { .. } => None,
433        })
434    }
435
436    /// Returns true if the given module is in this graph and is an entry module
437    pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
438        if let Some(index) = self.modules.get(&module) {
439            self.graph
440                .edges_directed(*index, Direction::Incoming)
441                .next()
442                .is_none()
443        } else {
444            false
445        }
446    }
447
448    /// Iterate over graph entry points
449    pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
450        self.entries.iter().flat_map(|e| e.entries())
451    }
452
453    /// Enumerate all nodes in the graph
454    pub fn enumerate_nodes(
455        &self,
456    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
457        self.graph.node_references()
458    }
459
460    fn traverse_cycles<'l>(
461        &'l self,
462        edge_filter: impl Fn(&'l RefData) -> bool,
463        mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
464        graph_idx: u32,
465        binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
466    ) -> Result<()> {
467        // See https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm, but
468        // implemented iteratively instead of recursively.
469        //
470        // Compared to the standard Tarjan's, this also treated self-references (via
471        // `has_self_loop`) as SCCs.
472
473        #[derive(Clone)]
474        struct NodeState {
475            index: u32,
476            lowlink: u32,
477            on_stack: bool,
478            has_self_loop: bool,
479        }
480        enum VisitStep {
481            UnvisitedNode(NodeIndex),
482            EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
483            AfterVisit(NodeIndex),
484        }
485        let mut node_states = vec![None; self.graph.node_bound()];
486        let mut stack = Vec::new();
487        let mut visit_stack = Vec::new();
488        let mut index = 0;
489        let mut scc = Vec::new();
490        for initial_index in self.graph.node_indices() {
491            // Skip over already visited nodes
492            if node_states[initial_index.index()].is_some() {
493                continue;
494            }
495            visit_stack.push(VisitStep::UnvisitedNode(initial_index));
496            while let Some(step) = visit_stack.pop() {
497                match step {
498                    VisitStep::UnvisitedNode(node) => {
499                        node_states[node.index()] = Some(NodeState {
500                            index,
501                            lowlink: index,
502                            on_stack: true,
503                            has_self_loop: false,
504                        });
505                        index += 1;
506                        stack.push(node);
507                        visit_stack.push(VisitStep::AfterVisit(node));
508                        let mut neighbors = self.graph.neighbors(node).detach();
509                        while let Some((edge, succ)) = neighbors.next(&self.graph) {
510                            if binding_usage.as_ref().is_some_and(|binding_usage| {
511                                binding_usage
512                                    .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
513                            }) {
514                                continue;
515                            }
516
517                            let edge_weight = self.graph.edge_weight(edge).unwrap();
518                            if !edge_filter(edge_weight) {
519                                continue;
520                            }
521                            let node_state = &node_states[succ.index()];
522                            if let Some(node_state) = node_state {
523                                if node_state.on_stack {
524                                    let index = node_state.index;
525                                    let parent_state = node_states[node.index()].as_mut().unwrap();
526                                    parent_state.lowlink = parent_state.lowlink.min(index);
527                                    if succ == node {
528                                        parent_state.has_self_loop = true;
529                                    }
530                                }
531                            } else {
532                                visit_stack.push(VisitStep::EdgeAfterVisit {
533                                    parent: node,
534                                    child: succ,
535                                });
536                                visit_stack.push(VisitStep::UnvisitedNode(succ));
537                            }
538                        }
539                    }
540                    VisitStep::EdgeAfterVisit { parent, child } => {
541                        let child_state = node_states[child.index()].as_ref().unwrap();
542                        let lowlink = child_state.lowlink;
543
544                        let parent_state = node_states[parent.index()].as_mut().unwrap();
545                        parent_state.lowlink = parent_state.lowlink.min(lowlink);
546                    }
547                    VisitStep::AfterVisit(node) => {
548                        let node_state = node_states[node.index()].as_ref().unwrap();
549                        let node_has_self_loop = node_state.has_self_loop;
550                        if node_state.lowlink == node_state.index {
551                            loop {
552                                let poppped = stack.pop().unwrap();
553                                let popped_state = node_states[poppped.index()].as_mut().unwrap();
554                                popped_state.on_stack = false;
555                                if let SingleModuleGraphNode::Module(module) =
556                                    self.graph.node_weight(poppped).unwrap()
557                                {
558                                    scc.push(module);
559                                }
560                                if poppped == node {
561                                    break;
562                                }
563                            }
564                            if scc.len() > 1 || node_has_self_loop {
565                                visit_cycle(&scc)?;
566                            }
567                            scc.clear();
568                        }
569                    }
570                }
571            }
572        }
573        Ok(())
574    }
575}
576
577#[turbo_tasks::value]
578struct ModuleGraphImportTracer {
579    graph: ResolvedVc<SingleModuleGraph>,
580}
581
582#[turbo_tasks::value(shared)]
583struct PathToModulesMap {
584    map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
585}
586
587#[turbo_tasks::value_impl]
588impl ModuleGraphImportTracer {
589    #[turbo_tasks::function]
590    fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
591        Self::cell(Self { graph })
592    }
593
594    // Compute this mapping on demand since it might not always be needed.
595    #[turbo_tasks::function]
596    async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
597        let path_and_modules = self
598            .graph
599            .await?
600            .modules
601            .iter()
602            .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
603            .try_join()
604            .await?;
605        let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
606            FxHashMap::default();
607        for (path, module) in path_and_modules {
608            map.entry(path).or_default().push(module)
609        }
610        Ok(PathToModulesMap::cell(PathToModulesMap { map }))
611    }
612}
613
614#[turbo_tasks::value_impl]
615impl ImportTracer for ModuleGraphImportTracer {
616    #[turbo_tasks::function]
617    async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
618        let path_to_modules = self.path_to_modules().await?;
619        let Some(modules) = path_to_modules.map.get(&path) else {
620            return Ok(Vc::default()); // This isn't unusual, the file just might not be in this
621            // graph.
622        };
623        debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
624        let graph = &*self.await?.graph.await?;
625
626        let reversed_graph = Reversed(&graph.graph.0);
627        return Ok(ImportTraces::cell(ImportTraces(
628            modules
629                .iter()
630                .map(|m| async move {
631                    let Some(&module_idx) = graph.modules.get(m) else {
632                        // The only way this could really happen is if `path_to_modules` is computed
633                        // from a different graph than graph`.  Just error out.
634                        bail!("inconsistent read?")
635                    };
636                    // compute the path from this index to a root of the graph.
637                    let Some((_, path)) = petgraph::algo::astar(
638                        &reversed_graph,
639                        module_idx,
640                        |n| reversed_graph.neighbors(n).next().is_none(),
641                        // Edge weights
642                        |e| match e.weight().chunking_type {
643                            // Prefer following normal imports/requires when we can
644                            ChunkingType::Parallel { .. } => 0,
645                            _ => 1,
646                        },
647                        // `astar` can be accelerated with a distance estimation heuristic, as long
648                        // as our estimate is never > the actual distance.
649                        // However we don't have a mechanism, so just
650                        // estimate 0 which essentially makes this behave like
651                        // dijktra's shortest path algorithm.  `petgraph` has an implementation of
652                        // dijkstra's but it doesn't report  paths, just distances.
653                        // NOTE: dijkstra's with integer weights can be accelerated with incredibly
654                        // efficient priority queue structures (basically with only 0 and 1 as
655                        // weights you can use a `VecDeque`!).  However,
656                        // this is unlikely to be a performance concern.
657                        // Furthermore, if computing paths _does_ become a performance concern, the
658                        // solution would be a hand written implementation of dijkstras so we can
659                        // hoist redundant work out of this loop.
660                        |_| 0,
661                    ) else {
662                        unreachable!("there must be a path to a root");
663                    };
664
665                    // Represent the path as a sequence of AssetIdents
666                    // TODO: consider hinting at various transitions (e.g. was this an
667                    // import/require/dynamic-import?)
668                    let path = path
669                        .into_iter()
670                        .map(async |n| {
671                            graph
672                                .graph
673                                .node_weight(n)
674                                .unwrap() // This is safe since `astar`` only returns indices from the graph
675                                .module()
676                                .ident()
677                                .await
678                        })
679                        .try_join()
680                        .await?;
681                    Ok(path)
682                })
683                .try_join()
684                .await?,
685        )));
686    }
687}
688
689/// The ReadRef version of ModuleGraphBase. This is better for eventual consistency, as the graphs
690/// aren't awaited multiple times within the same task.
691#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
692pub struct ModuleGraph {
693    input_graphs: Vec<OperationVc<SingleModuleGraph>>,
694    input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
695
696    snapshot: ModuleGraphSnapshot,
697}
698
699#[turbo_tasks::value_impl]
700impl ModuleGraph {
701    #[turbo_tasks::function(operation)]
702    pub async fn from_single_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
703        let graph = Self::create(vec![graph], None)
704            .read_strongly_consistent()
705            .await?;
706        Ok(ReadRef::cell(graph))
707    }
708
709    #[turbo_tasks::function(operation)]
710    pub async fn from_graphs(graphs: Vec<OperationVc<SingleModuleGraph>>) -> Result<Vc<Self>> {
711        let graph = Self::create(graphs, None)
712            .read_strongly_consistent()
713            .await?;
714        Ok(ReadRef::cell(graph))
715    }
716
717    /// Analyze the module graph and remove unused references (by determining the used exports and
718    /// removing unused imports).
719    ///
720    /// In particular, this removes ModuleReference-s that list only unused exports in the
721    /// `import_usage()`
722    #[turbo_tasks::function(operation)]
723    pub async fn from_single_graph_without_unused_references(
724        graph: OperationVc<SingleModuleGraph>,
725        binding_usage: OperationVc<BindingUsageInfo>,
726    ) -> Result<Vc<Self>> {
727        let graph = Self::create(vec![graph], Some(binding_usage))
728            .read_strongly_consistent()
729            .await?;
730        Ok(ReadRef::cell(graph))
731    }
732
733    /// Analyze the module graph and remove unused references (by determining the used exports and
734    /// removing unused imports).
735    ///
736    /// In particular, this removes ModuleReference-s that list only unused exports in the
737    /// `import_usage()`
738    #[turbo_tasks::function(operation)]
739    pub async fn from_graphs_without_unused_references(
740        graphs: Vec<OperationVc<SingleModuleGraph>>,
741        binding_usage: OperationVc<BindingUsageInfo>,
742    ) -> Result<Vc<Self>> {
743        let graph = Self::create(graphs, Some(binding_usage))
744            .read_strongly_consistent()
745            .await?;
746        Ok(ReadRef::cell(graph))
747    }
748
749    #[turbo_tasks::function(operation)]
750    async fn create(
751        graphs: Vec<OperationVc<SingleModuleGraph>>,
752        binding_usage: Option<OperationVc<BindingUsageInfo>>,
753    ) -> Result<Vc<ModuleGraph>> {
754        Ok(ModuleGraph {
755            input_graphs: graphs.clone(),
756            input_binding_usage: binding_usage,
757            snapshot: ModuleGraphSnapshot {
758                graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
759                skip_visited_module_children: false,
760                graph_idx_override: None,
761                binding_usage: if let Some(binding_usage) = binding_usage {
762                    Some(binding_usage.connect().await?)
763                } else {
764                    None
765                },
766            },
767        }
768        .cell())
769    }
770
771    #[turbo_tasks::function]
772    pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
773        compute_chunk_group_info(&*self.await?).await
774    }
775
776    #[turbo_tasks::function]
777    pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
778        compute_merged_modules(self).await
779    }
780
781    #[turbo_tasks::function]
782    pub async fn module_batches(
783        self: Vc<Self>,
784        config: Vc<BatchingConfig>,
785    ) -> Result<Vc<ModuleBatchesGraph>> {
786        compute_module_batches(self, &*config.await?).await
787    }
788
789    #[turbo_tasks::function]
790    pub async fn style_groups(
791        self: Vc<Self>,
792        chunking_context: Vc<Box<dyn ChunkingContext>>,
793        config: StyleGroupsConfig,
794    ) -> Result<Vc<StyleGroups>> {
795        compute_style_groups(self, chunking_context, &config).await
796    }
797
798    #[turbo_tasks::function]
799    pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
800        // `compute_async_module_info` calls `module.is_self_async()`, so we need to again ignore
801        // all issues such that they aren't emitted multiple times.
802        async move {
803            let result_op = compute_async_module_info(self.to_resolved().await?);
804            let result_vc = result_op.resolve_strongly_consistent().await?;
805            result_op.drop_collectibles::<Box<dyn Issue>>();
806            anyhow::Ok(*result_vc)
807        }
808        .instrument(tracing::info_span!("compute async module info"))
809        .await
810    }
811
812    #[turbo_tasks::function]
813    pub async fn referenced_async_modules(
814        self: Vc<Self>,
815        module: ResolvedVc<Box<dyn Module>>,
816    ) -> Result<Vc<AsyncModuleInfo>> {
817        let graph_ref = self.await?;
818        let async_modules_info = self.async_module_info().await?;
819
820        let entry = graph_ref.get_entry(module)?;
821        let referenced_modules = graph_ref
822            .iter_graphs_neighbors_rev(entry, Direction::Outgoing)
823            .filter(|(edge_idx, _)| {
824                let ty = graph_ref.get_edge(*edge_idx).unwrap();
825                ty.chunking_type.is_inherit_async()
826            })
827            .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
828            .collect::<Result<Vec<_>>>()?
829            .into_iter()
830            .rev()
831            .filter(|m| async_modules_info.contains(m))
832            .map(|m| *m)
833            .collect();
834
835        Ok(AsyncModuleInfo::new(referenced_modules))
836    }
837
838    /// Returns the underlying graphs as a list, to be used for individual graph traversals.
839    #[turbo_tasks::function]
840    pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
841        Vc::cell(
842            self.input_graphs
843                .iter()
844                .enumerate()
845                .map(|(graph_idx, graph)| {
846                    ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
847                })
848                .collect(),
849        )
850    }
851}
852
853impl Deref for ModuleGraph {
854    type Target = ModuleGraphSnapshot;
855
856    fn deref(&self) -> &Self::Target {
857        &self.snapshot
858    }
859}
860
861#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
862pub struct ModuleGraphLayer {
863    snapshot: ModuleGraphSnapshot,
864}
865
866#[turbo_tasks::value_impl]
867impl ModuleGraphLayer {
868    #[turbo_tasks::function(operation)]
869    async fn new(
870        graph: OperationVc<SingleModuleGraph>,
871        graph_idx: u32,
872        binding_usage: Option<OperationVc<BindingUsageInfo>>,
873    ) -> Result<Vc<Self>> {
874        Ok(Self {
875            snapshot: ModuleGraphSnapshot {
876                graphs: vec![graph.connect().await?],
877                skip_visited_module_children: true,
878                graph_idx_override: Some(graph_idx),
879                binding_usage: if let Some(binding_usage) = binding_usage {
880                    Some(binding_usage.connect().await?)
881                } else {
882                    None
883                },
884            },
885        }
886        .cell())
887    }
888}
889
890impl Deref for ModuleGraphLayer {
891    type Target = ModuleGraphSnapshot;
892
893    fn deref(&self) -> &Self::Target {
894        &self.snapshot
895    }
896}
897
898#[turbo_tasks::value(transparent)]
899pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
900
901#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
902pub struct ModuleGraphSnapshot {
903    pub graphs: Vec<ReadRef<SingleModuleGraph>>,
904    // Whether to simply ignore SingleModuleGraphNode::VisitedModule during traversals. For single
905    // module graph usecases, this is what you want. For the whole graph, there should be an error.
906    skip_visited_module_children: bool,
907
908    pub graph_idx_override: Option<u32>,
909
910    pub binding_usage: Option<ReadRef<BindingUsageInfo>>,
911}
912
913impl ModuleGraphSnapshot {
914    fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
915        if self.graph_idx_override.is_some() {
916            debug_assert_eq!(self.graphs.len(), 1,);
917        }
918
919        let Some(idx) = self
920            .graphs
921            .iter()
922            .enumerate()
923            .find_map(|(graph_idx, graph)| {
924                graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
925                    graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
926                    node_idx: *node_idx,
927                })
928            })
929        else {
930            bail!("Couldn't find entry module {entry:?} in module graph");
931        };
932        Ok(idx)
933    }
934
935    pub fn entries(&self) -> impl Iterator<Item = ChunkGroupEntry> + '_ {
936        self.graphs.iter().flat_map(|g| g.entries.iter().cloned())
937    }
938
939    fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
940        if self.graph_idx_override.is_some() {
941            self.graphs.first().unwrap()
942        } else {
943            &self.graphs[graph_idx as usize]
944        }
945    }
946
947    fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
948        let graph = self.get_graph(node.graph_idx);
949        graph
950            .graph
951            .node_weight(node.node_idx)
952            .context("Expected graph node")
953    }
954
955    fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
956        let graph = self.get_graph(edge.graph_idx);
957        graph
958            .graph
959            .edge_weight(edge.edge_idx)
960            .context("Expected graph node")
961    }
962
963    fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
964        if self.skip_visited_module_children && direction == Direction::Outgoing {
965            !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
966        } else {
967            true
968        }
969    }
970
971    pub fn enumerate_nodes(
972        &self,
973    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
974        self.graphs.iter().flat_map(|g| g.enumerate_nodes())
975    }
976
977    pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
978        self.graphs.iter().flat_map(|g| g.iter_nodes())
979    }
980
981    /// Iterate the edges of a node REVERSED!
982    fn iter_graphs_neighbors_rev<'a>(
983        &'a self,
984        node: GraphNodeIndex,
985        direction: Direction,
986    ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
987        let graph = &*self.get_graph(node.graph_idx).graph;
988
989        if cfg!(debug_assertions) && direction == Direction::Outgoing {
990            let node_weight = graph.node_weight(node.node_idx).unwrap();
991            if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
992                panic!("iter_graphs_neighbors_rev called on VisitedModule node");
993            }
994        }
995
996        let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
997        std::iter::from_fn(move || {
998            while let Some((edge_idx, succ_idx)) = walker.next(graph) {
999                let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1000                if self
1001                    .binding_usage
1002                    .as_ref()
1003                    .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
1004                {
1005                    // Don't just return None here, that would end the iterator
1006                    continue;
1007                }
1008
1009                return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1010            }
1011            None
1012        })
1013    }
1014
1015    /// Returns a map of all modules in the graphs to their identifiers.
1016    /// This is primarily useful for debugging.
1017    pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1018        Ok(self
1019            .graphs
1020            .iter()
1021            .flat_map(|g| g.iter_nodes())
1022            .map(async |n| Ok((n, n.ident().to_string().await?)))
1023            .try_join()
1024            .await?
1025            .into_iter()
1026            .collect::<FxHashMap<_, _>>())
1027    }
1028
1029    /// Traverses all reachable nodes exactly once and calls the visitor.
1030    ///
1031    /// * `entries` - The entry modules to start the traversal from
1032    /// * `state` mutable state to be shared across the visitors
1033    /// * `visit_preorder` - Called before visiting the children of a node.
1034    ///    - Receives the module and the `state`
1035    ///    - Can return [GraphTraversalAction]s to control the traversal
1036    /// * `visit_postorder` - Called after visiting children of a node.
1037    pub fn traverse_nodes_dfs<S>(
1038        &self,
1039        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1040        state: &mut S,
1041        visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1042        mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1043    ) -> Result<()> {
1044        let entries = entries.into_iter().collect::<Vec<_>>();
1045
1046        enum Pass {
1047            Visit,
1048            ExpandAndVisit,
1049        }
1050        let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1051        for entry in entries.into_iter().rev() {
1052            stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1053        }
1054        let mut expanded = FxHashSet::default();
1055        while let Some((pass, current)) = stack.pop() {
1056            let current_node = self.get_node(current)?;
1057            match pass {
1058                Pass::Visit => {
1059                    visit_postorder(current_node.module(), state)?;
1060                }
1061                Pass::ExpandAndVisit => {
1062                    if !expanded.insert(current) {
1063                        continue;
1064                    }
1065                    let action = visit_preorder(current_node.module(), state)?;
1066                    if action == GraphTraversalAction::Exclude {
1067                        continue;
1068                    }
1069                    stack.push((Pass::Visit, current));
1070                    if action == GraphTraversalAction::Continue
1071                        && self.should_visit_node(current_node, Direction::Outgoing)
1072                    {
1073                        let current = current_node
1074                            .target_idx(Direction::Outgoing)
1075                            .unwrap_or(current);
1076                        stack.extend(
1077                            self.iter_graphs_neighbors_rev(current, Direction::Outgoing)
1078                                .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1079                        );
1080                    }
1081                }
1082            }
1083        }
1084
1085        Ok(())
1086    }
1087
1088    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
1089    /// target.
1090    ///
1091    /// This means that target nodes can be revisited (once per incoming edge).
1092    ///
1093    /// * `entry` - The entry module to start the traversal from
1094    /// * `visitor` - Called before visiting the children of a node.
1095    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1096    ///      &SingleModuleGraphNode, state &S
1097    ///    - Can return [GraphTraversalAction]s to control the traversal
1098    pub fn traverse_edges_bfs(
1099        &self,
1100        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1101        mut visitor: impl FnMut(
1102            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1103            ResolvedVc<Box<dyn Module>>,
1104        ) -> Result<GraphTraversalAction>,
1105    ) -> Result<()> {
1106        let mut queue = VecDeque::from(
1107            entries
1108                .into_iter()
1109                .map(|e| self.get_entry(e))
1110                .collect::<Result<Vec<_>>>()?,
1111        );
1112        let mut visited = FxHashSet::default();
1113        for entry_node in &queue {
1114            visitor(None, self.get_node(*entry_node)?.module())?;
1115        }
1116        while let Some(node) = queue.pop_front() {
1117            if visited.insert(node) {
1118                let node_weight = self.get_node(node)?;
1119                for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1120                    let succ_weight = self.get_node(succ)?;
1121                    let action = visitor(
1122                        Some((node_weight.module(), self.get_edge(edge)?)),
1123                        succ_weight.module(),
1124                    )?;
1125                    if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1126                        continue;
1127                    }
1128                    let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1129                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1130                        queue.push_back(succ);
1131                    }
1132                }
1133            }
1134        }
1135
1136        Ok(())
1137    }
1138
1139    /// Traverses all edges exactly once (in an unspecified order) and calls the visitor with the
1140    /// edge source and target.
1141    ///
1142    /// This means that target nodes can be revisited (once per incoming edge).
1143    ///
1144    /// * `visitor` - Called before visiting the children of a node.
1145    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1146    ///      &SingleModuleGraphNode
1147    pub fn traverse_edges_unordered(
1148        &self,
1149        mut visitor: impl FnMut(
1150            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1151            ResolvedVc<Box<dyn Module>>,
1152        ) -> Result<()>,
1153    ) -> Result<()> {
1154        let entries = self.graphs.iter().flat_map(|g| g.entry_modules());
1155
1156        // Despite the name we need to do a DFS to respect 'reachability' if an edge was trimmed we
1157        // should not follow it, and this is a reasonable way to do that.
1158        self.traverse_edges_dfs(
1159            entries,
1160            &mut (),
1161            |parent, target, _| {
1162                visitor(parent, target)?;
1163                Ok(GraphTraversalAction::Continue)
1164            },
1165            |_, _, _| Ok(()),
1166        )
1167    }
1168
1169    /// Traverses all reachable edges in dfs order. The preorder visitor can be used to
1170    /// forward state down the graph, and to skip subgraphs
1171    ///
1172    /// Use this to collect modules in evaluation order.
1173    ///
1174    /// Target nodes can be revisited (once per incoming edge) in the preorder_visitor, in the post
1175    /// order visitor they are visited exactly once with the first edge they were discovered with.
1176    /// Edges are traversed in normal order, so should correspond to reference order.
1177    ///
1178    /// * `entries` - The entry modules to start the traversal from
1179    /// * `state` - The state to be passed to the visitors
1180    /// * `visit_preorder` - Called before visiting the children of a node.
1181    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1182    ///      &SingleModuleGraphNode, state &S
1183    ///    - Can return [GraphTraversalAction]s to control the traversal
1184    /// * `visit_postorder` - Called after visiting the children of a node. Return
1185    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1186    ///      &SingleModuleGraphNode, state &S
1187    pub fn traverse_edges_dfs<S>(
1188        &self,
1189        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1190        state: &mut S,
1191        visit_preorder: impl FnMut(
1192            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1193            ResolvedVc<Box<dyn Module>>,
1194            &mut S,
1195        ) -> Result<GraphTraversalAction>,
1196        visit_postorder: impl FnMut(
1197            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1198            ResolvedVc<Box<dyn Module>>,
1199            &mut S,
1200        ) -> Result<()>,
1201    ) -> Result<()> {
1202        self.traverse_edges_dfs_impl::<S>(
1203            entries,
1204            state,
1205            visit_preorder,
1206            visit_postorder,
1207            Direction::Outgoing,
1208        )
1209    }
1210
1211    /// Traverses all reachable edges in dfs order over the reversed graph. The preorder visitor can
1212    /// be used to forward state up the graph, and to skip subgraphs
1213    ///
1214    /// Target nodes can be revisited (once per incoming edge) in the preorder_visitor, in the post
1215    /// order visitor they are visited exactly once with the first edge they were discovered with.
1216    /// Edges are traversed in normal order, so should correspond to reference order.
1217    ///
1218    /// * `entries` - The entry modules to start the traversal from
1219    /// * `state` - The state to be passed to the visitors
1220    /// * `visit_preorder` - Called before visiting the children of a node.
1221    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1222    ///      &SingleModuleGraphNode, state &S
1223    ///    - Can return [GraphTraversalAction]s to control the traversal
1224    /// * `visit_postorder` - Called after visiting the parents of a node. Return
1225    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1226    ///      &SingleModuleGraphNode, state &S
1227    pub fn traverse_edges_reverse_dfs<S>(
1228        &self,
1229        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1230        state: &mut S,
1231        visit_preorder: impl FnMut(
1232            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1233            ResolvedVc<Box<dyn Module>>,
1234            &mut S,
1235        ) -> Result<GraphTraversalAction>,
1236        visit_postorder: impl FnMut(
1237            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1238            ResolvedVc<Box<dyn Module>>,
1239            &mut S,
1240        ) -> Result<()>,
1241    ) -> Result<()> {
1242        self.traverse_edges_dfs_impl::<S>(
1243            entries,
1244            state,
1245            visit_preorder,
1246            visit_postorder,
1247            Direction::Incoming,
1248        )
1249    }
1250
1251    fn traverse_edges_dfs_impl<S>(
1252        &self,
1253        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1254        state: &mut S,
1255        mut visit_preorder: impl FnMut(
1256            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1257            ResolvedVc<Box<dyn Module>>,
1258            &mut S,
1259        ) -> Result<GraphTraversalAction>,
1260        mut visit_postorder: impl FnMut(
1261            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1262            ResolvedVc<Box<dyn Module>>,
1263            &mut S,
1264        ) -> Result<()>,
1265        direction: Direction,
1266    ) -> Result<()> {
1267        if direction == Direction::Incoming {
1268            debug_assert!(
1269                self.skip_visited_module_children,
1270                "Can only trace reverse edges in a single layer graph. We do not model cross \
1271                 graph reverse edges"
1272            );
1273        }
1274        let entries = entries.into_iter().collect::<Vec<_>>();
1275
1276        enum Pass {
1277            Visit,
1278            ExpandAndVisit,
1279        }
1280        #[allow(clippy::type_complexity)] // This is a temporary internal structure
1281        let mut stack: Vec<(
1282            Pass,
1283            Option<(GraphNodeIndex, GraphEdgeIndex)>,
1284            GraphNodeIndex,
1285        )> = Vec::with_capacity(entries.len());
1286        for entry in entries.into_iter().rev() {
1287            stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1288        }
1289        let mut expanded = FxHashSet::default();
1290        while let Some((pass, parent, current)) = stack.pop() {
1291            let parent_arg = match parent {
1292                Some((parent_node, parent_edge)) => Some((
1293                    self.get_node(parent_node)?.module(),
1294                    self.get_edge(parent_edge)?,
1295                )),
1296                None => None,
1297            };
1298            let current_node = self.get_node(current)?;
1299            match pass {
1300                Pass::Visit => {
1301                    visit_postorder(parent_arg, current_node.module(), state)?;
1302                }
1303                Pass::ExpandAndVisit => {
1304                    let action = visit_preorder(parent_arg, current_node.module(), state)?;
1305                    if action == GraphTraversalAction::Exclude {
1306                        continue;
1307                    }
1308                    stack.push((Pass::Visit, parent, current));
1309                    if action == GraphTraversalAction::Continue
1310                        && expanded.insert(current)
1311                        && self.should_visit_node(current_node, direction)
1312                    {
1313                        let current = current_node.target_idx(direction).unwrap_or(current);
1314                        stack.extend(self.iter_graphs_neighbors_rev(current, direction).map(
1315                            |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1316                        ));
1317                    }
1318                }
1319            }
1320        }
1321
1322        Ok(())
1323    }
1324
1325    /// Traverse all cycles in the graph (where the edge filter returns true for the whole cycle)
1326    /// and call the visitor with the nodes in the cycle.
1327    /// Notably, module self-references are also treated as cycles.
1328    pub fn traverse_cycles(
1329        &self,
1330        edge_filter: impl Fn(&RefData) -> bool,
1331        mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1332    ) -> Result<()> {
1333        for (graph_idx, graph) in self.graphs.iter().enumerate() {
1334            graph.traverse_cycles(
1335                &edge_filter,
1336                &mut visit_cycle,
1337                graph_idx as u32,
1338                &self.binding_usage,
1339            )?;
1340        }
1341        Ok(())
1342    }
1343
1344    /// Traverses all reachable nodes and also continue revisiting them as long the visitor returns
1345    /// GraphTraversalAction::Continue. The visitor is responsible for the runtime complexity and
1346    /// eventual termination of the traversal. This corresponds to computing a fixed point state for
1347    /// the graph.
1348    ///
1349    /// Nodes are (re)visited according to the returned priority of the node, prioritizing high
1350    /// values. This priority is intended to be used a heuristic to reduce the number of
1351    /// retraversals.
1352    ///
1353    /// * `entries` - The entry modules to start the traversal from
1354    /// * `state` - The state to be passed to the callbacks
1355    /// * `visit` - Called for a specific edge
1356    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1357    ///      &SingleModuleGraphNode, state &S
1358    ///    - Return [GraphTraversalAction]s to control the traversal
1359    /// * `priority` - Called for before visiting the children of a node to determine its priority.
1360    ///    - Receives: target &SingleModuleGraphNode, state &S
1361    ///    - Return a priority value for the node
1362    ///
1363    /// Returns the number of node visits (i.e. higher than the node count if there are
1364    /// retraversals).
1365    pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1366        &self,
1367        entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1368        state: &mut S,
1369        mut visit: impl FnMut(
1370            Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1371            ResolvedVc<Box<dyn Module>>,
1372            &mut S,
1373        ) -> Result<GraphTraversalAction>,
1374        priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1375    ) -> Result<usize> {
1376        if self.skip_visited_module_children {
1377            panic!(
1378                "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1379            );
1380        }
1381
1382        let mut visit_order = 0usize;
1383        let mut order = || {
1384            let order = visit_order;
1385            visit_order += 1;
1386            order
1387        };
1388        #[derive(PartialEq, Eq)]
1389        struct NodeWithPriority<T: Ord> {
1390            node: GraphNodeIndex,
1391            priority: T,
1392            visit_order: usize,
1393        }
1394        impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1395            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1396                Some(self.cmp(other))
1397            }
1398        }
1399        impl<T: Ord> Ord for NodeWithPriority<T> {
1400            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1401                // BinaryHeap prioritizes high values
1402
1403                self.priority
1404                    .cmp(&other.priority)
1405                    // Use visit_order, so when there are ties we prioritize earlier discovered
1406                    // nodes, reverting to a BFS in the the case where all priorities are equal
1407                    .then(self.visit_order.cmp(&other.visit_order))
1408            }
1409        }
1410
1411        let mut queue_set = FxHashSet::default();
1412        let mut queue = BinaryHeap::from_iter(
1413            entries
1414                .into_iter()
1415                .map(|(m, priority)| {
1416                    Ok(NodeWithPriority {
1417                        node: self.get_entry(m)?,
1418                        priority,
1419                        visit_order: order(),
1420                    })
1421                })
1422                .collect::<Result<Vec<_>>>()?,
1423        );
1424
1425        for entry_node in &queue {
1426            visit(None, self.get_node(entry_node.node)?.module(), state)?;
1427        }
1428
1429        let mut visit_count = 0usize;
1430        while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1431            queue_set.remove(&node);
1432            let node_weight = self.get_node(node)?;
1433            let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1434
1435            visit_count += 1;
1436
1437            for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1438                let succ_weight = self.get_node(succ)?;
1439
1440                let action = visit(
1441                    Some((node_weight.module(), self.get_edge(edge)?, edge)),
1442                    succ_weight.module(),
1443                    state,
1444                )?;
1445
1446                let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1447                if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1448                    queue.push(NodeWithPriority {
1449                        node: succ,
1450                        priority: priority(succ_weight.module(), state)?,
1451                        visit_order: order(),
1452                    });
1453                }
1454            }
1455        }
1456
1457        Ok(visit_count)
1458    }
1459}
1460
1461#[turbo_tasks::value_impl]
1462impl SingleModuleGraph {
1463    #[turbo_tasks::function(operation)]
1464    pub async fn new_with_entry(
1465        entry: ChunkGroupEntry,
1466        include_traced: bool,
1467        include_binding_usage: bool,
1468    ) -> Result<Vc<Self>> {
1469        SingleModuleGraph::new_inner(
1470            vec![entry],
1471            &Default::default(),
1472            include_traced,
1473            include_binding_usage,
1474        )
1475        .await
1476    }
1477
1478    #[turbo_tasks::function(operation)]
1479    pub async fn new_with_entries(
1480        entries: ResolvedVc<GraphEntries>,
1481        include_traced: bool,
1482        include_binding_usage: bool,
1483    ) -> Result<Vc<Self>> {
1484        SingleModuleGraph::new_inner(
1485            entries.owned().await?,
1486            &Default::default(),
1487            include_traced,
1488            include_binding_usage,
1489        )
1490        .await
1491    }
1492
1493    #[turbo_tasks::function(operation)]
1494    pub async fn new_with_entries_visited(
1495        entries: ResolvedVc<GraphEntries>,
1496        visited_modules: OperationVc<VisitedModules>,
1497        include_traced: bool,
1498        include_binding_usage: bool,
1499    ) -> Result<Vc<Self>> {
1500        SingleModuleGraph::new_inner(
1501            entries.owned().await?,
1502            &visited_modules.connect().await?.modules,
1503            include_traced,
1504            include_binding_usage,
1505        )
1506        .await
1507    }
1508
1509    #[turbo_tasks::function(operation)]
1510    pub async fn new_with_entries_visited_intern(
1511        // This must not be a Vc<Vec<_>> to ensure layout segment optimization hits the cache
1512        entries: GraphEntriesT,
1513        visited_modules: OperationVc<VisitedModules>,
1514        include_traced: bool,
1515        include_binding_usage: bool,
1516    ) -> Result<Vc<Self>> {
1517        SingleModuleGraph::new_inner(
1518            entries,
1519            &visited_modules.connect().await?.modules,
1520            include_traced,
1521            include_binding_usage,
1522        )
1523        .await
1524    }
1525}
1526
1527#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1528pub enum SingleModuleGraphNode {
1529    Module(ResolvedVc<Box<dyn Module>>),
1530    // Models a module that is referenced but has already been visited by an earlier graph.
1531    VisitedModule {
1532        idx: GraphNodeIndex,
1533        module: ResolvedVc<Box<dyn Module>>,
1534    },
1535}
1536
1537impl SingleModuleGraphNode {
1538    pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1539        match self {
1540            SingleModuleGraphNode::Module(module) => *module,
1541            SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1542        }
1543    }
1544    pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1545        match self {
1546            SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1547                Direction::Outgoing => Some(*idx),
1548                Direction::Incoming => None,
1549            },
1550            SingleModuleGraphNode::Module(_) => None,
1551        }
1552    }
1553}
1554
1555#[derive(PartialEq, Eq, Debug)]
1556pub enum GraphTraversalAction {
1557    /// Continue visiting children
1558    Continue,
1559    /// Skip the immediate children, but visit the node in postorder
1560    Skip,
1561    /// Skip the immediate children and the node in postorder
1562    Exclude,
1563}
1564
1565// These nodes are created while walking the Turbopack modules references, and are used to then
1566// afterwards build the SingleModuleGraph.
1567#[derive(Clone, Hash, PartialEq, Eq)]
1568enum SingleModuleGraphBuilderNode {
1569    /// A regular module
1570    Module {
1571        module: ResolvedVc<Box<dyn Module>>,
1572        // module.ident().to_string(), eagerly computed for tracing
1573        ident: Option<ReadRef<RcStr>>,
1574    },
1575    /// A reference to a module that is already listed in visited_modules
1576    VisitedModule {
1577        module: ResolvedVc<Box<dyn Module>>,
1578        idx: GraphNodeIndex,
1579    },
1580}
1581
1582impl SingleModuleGraphBuilderNode {
1583    async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1584        Ok(Self::Module {
1585            module,
1586            ident: if emit_spans {
1587                // INVALIDATION: we don't need to invalidate when the span name changes
1588                Some(module.ident_string().untracked().await?)
1589            } else {
1590                None
1591            },
1592        })
1593    }
1594    fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1595        Self::VisitedModule { module, idx }
1596    }
1597}
1598
1599struct SingleModuleGraphBuilder<'a> {
1600    visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1601
1602    emit_spans: bool,
1603
1604    /// Whether to walk ChunkingType::Traced references
1605    include_traced: bool,
1606
1607    /// Whether to read ModuleReference::binding_usage()
1608    include_binding_usage: bool,
1609}
1610impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1611    type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1612    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1613
1614    fn visit(
1615        &mut self,
1616        node: &SingleModuleGraphBuilderNode,
1617        edge: Option<&RefData>,
1618    ) -> VisitControlFlow {
1619        if let Some(edge) = edge
1620            && matches!(edge.chunking_type, ChunkingType::Traced)
1621        {
1622            // The graph behind traced references is not part of the module graph traversal
1623            return VisitControlFlow::Skip;
1624        }
1625        match node {
1626            SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1627            // Module was already visited previously
1628            SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1629        }
1630    }
1631
1632    fn edges(
1633        &mut self,
1634        // The `skip_duplicates_with_key()` above ensures only a single `edges()` call per module
1635        // (and not per `(module, export)` pair), so the export must not be read here!
1636        node: &SingleModuleGraphBuilderNode,
1637    ) -> Self::EdgesFuture {
1638        // Destructure beforehand to not have to clone the whole node when entering the async block
1639        let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1640            // These are always skipped in `visit()`
1641            unreachable!()
1642        };
1643        let visited_modules = self.visited_modules;
1644        let emit_spans = self.emit_spans;
1645        let include_traced = self.include_traced;
1646        let include_binding_usage = self.include_binding_usage;
1647        async move {
1648            let refs_cell = primary_chunkable_referenced_modules(
1649                *module,
1650                include_traced,
1651                include_binding_usage,
1652            );
1653            let refs = match refs_cell.await {
1654                Ok(refs) => refs,
1655                Err(e) => {
1656                    return Err(e.context(module.ident().to_string().await?));
1657                }
1658            };
1659
1660            refs.iter()
1661                .flat_map(|(reference, resolved)| {
1662                    resolved.modules.iter().map(|m| {
1663                        (
1664                            *reference,
1665                            resolved.chunking_type.clone(),
1666                            resolved.binding_usage.clone(),
1667                            *m,
1668                        )
1669                    })
1670                })
1671                .map(async |(reference, ty, binding_usage, target)| {
1672                    let to = if let Some(idx) = visited_modules.get(&target) {
1673                        SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1674                    } else {
1675                        SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1676                    };
1677                    Ok((
1678                        to,
1679                        RefData {
1680                            chunking_type: ty,
1681                            binding_usage,
1682                            reference,
1683                        },
1684                    ))
1685                })
1686                .try_join()
1687                .await
1688        }
1689    }
1690
1691    fn span(
1692        &mut self,
1693        node: &SingleModuleGraphBuilderNode,
1694        edge: Option<&RefData>,
1695    ) -> tracing::Span {
1696        if !self.emit_spans {
1697            return Span::none();
1698        }
1699
1700        let mut span = match node {
1701            SingleModuleGraphBuilderNode::Module {
1702                ident: Some(ident), ..
1703            } => {
1704                tracing::info_span!("module", name = display(ident))
1705            }
1706            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1707                tracing::info_span!("visited module")
1708            }
1709            _ => unreachable!(),
1710        };
1711
1712        if let Some(edge) = edge {
1713            match &edge.chunking_type {
1714                ChunkingType::Parallel {
1715                    inherit_async: _,
1716                    hoisted: _,
1717                } => {}
1718                ChunkingType::Traced => {
1719                    let _span = span.entered();
1720                    span = tracing::info_span!("traced reference");
1721                }
1722                ChunkingType::Async => {
1723                    let _span = span.entered();
1724                    span = tracing::info_span!("async reference");
1725                }
1726                ChunkingType::Isolated { _ty: ty, merge_tag } => {
1727                    let _span = span.entered();
1728                    span = tracing::info_span!(
1729                        "isolated reference",
1730                        ty = debug(&ty),
1731                        merge_tag = debug(&merge_tag)
1732                    );
1733                }
1734                ChunkingType::Shared {
1735                    inherit_async: _,
1736                    merge_tag,
1737                } => {
1738                    let _span = span.entered();
1739                    span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1740                }
1741            };
1742        }
1743
1744        span
1745    }
1746}
1747
1748#[cfg(test)]
1749pub mod tests {
1750    use anyhow::Result;
1751    use rustc_hash::FxHashMap;
1752    use turbo_rcstr::{RcStr, rcstr};
1753    use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1754    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1755    use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1756
1757    use super::*;
1758    use crate::{
1759        asset::{Asset, AssetContent},
1760        ident::AssetIdent,
1761        module::{Module, ModuleSideEffects},
1762        reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1763        resolve::ExportUsage,
1764    };
1765
1766    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1767    async fn test_traverse_dfs_from_entries_diamond() {
1768        run_graph_test(
1769            vec![rcstr!("a.js")],
1770            {
1771                let mut deps = FxHashMap::default();
1772                // A classic diamond dependency on d
1773                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1774                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1775                deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1776                deps
1777            },
1778            |graph, entry_modules, module_to_name| {
1779                let mut preorder_visits = Vec::new();
1780                let mut postorder_visits = Vec::new();
1781
1782                graph.traverse_edges_dfs(
1783                    entry_modules,
1784                    &mut (),
1785                    |parent, target, _| {
1786                        preorder_visits.push((
1787                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1788                            module_to_name.get(&target).unwrap().clone(),
1789                        ));
1790                        Ok(GraphTraversalAction::Continue)
1791                    },
1792                    |parent, target, _| {
1793                        postorder_visits.push((
1794                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1795                            module_to_name.get(&target).unwrap().clone(),
1796                        ));
1797                        Ok(())
1798                    },
1799                )?;
1800                assert_eq!(
1801                    vec![
1802                        (None, rcstr!("a.js")),
1803                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1804                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1805                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1806                        (Some(rcstr!("c.js")), rcstr!("d.js"))
1807                    ],
1808                    preorder_visits
1809                );
1810                assert_eq!(
1811                    vec![
1812                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1813                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1814                        (Some(rcstr!("c.js")), rcstr!("d.js")),
1815                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1816                        (None, rcstr!("a.js"))
1817                    ],
1818                    postorder_visits
1819                );
1820                Ok(())
1821            },
1822        )
1823        .await;
1824    }
1825
1826    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1827    async fn test_traverse_dfs_from_entries_cycle() {
1828        run_graph_test(
1829            vec![rcstr!("a.js")],
1830            {
1831                let mut deps = FxHashMap::default();
1832                // A cycle of length 3
1833                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1834                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1835                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1836                deps
1837            },
1838            |graph, entry_modules, module_to_name| {
1839                let mut preorder_visits = Vec::new();
1840                let mut postorder_visits = Vec::new();
1841
1842                graph.traverse_edges_dfs(
1843                    entry_modules,
1844                    &mut (),
1845                    |parent, target, _| {
1846                        preorder_visits.push((
1847                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1848                            module_to_name.get(&target).unwrap().clone(),
1849                        ));
1850                        Ok(GraphTraversalAction::Continue)
1851                    },
1852                    |parent, target, _| {
1853                        postorder_visits.push((
1854                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1855                            module_to_name.get(&target).unwrap().clone(),
1856                        ));
1857                        Ok(())
1858                    },
1859                )?;
1860                assert_eq!(
1861                    vec![
1862                        (None, rcstr!("a.js")),
1863                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1864                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1865                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1866                    ],
1867                    preorder_visits
1868                );
1869                assert_eq!(
1870                    vec![
1871                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1872                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1873                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1874                        (None, rcstr!("a.js"))
1875                    ],
1876                    postorder_visits
1877                );
1878                Ok(())
1879            },
1880        )
1881        .await;
1882    }
1883
1884    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1885    async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1886        run_graph_test(
1887            vec![rcstr!("a.js")],
1888            {
1889                let mut deps = FxHashMap::default();
1890                // A cycle of length 3
1891                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1892                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1893                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1894                deps
1895            },
1896            |graph, entry_modules, module_to_name| {
1897                let mut visits = Vec::new();
1898                let mut count = 0;
1899
1900                graph.traverse_edges_fixed_point_with_priority(
1901                    entry_modules.into_iter().map(|m| (m, 0)),
1902                    &mut (),
1903                    |parent, target, _| {
1904                        visits.push((
1905                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1906                            module_to_name.get(&target).unwrap().clone(),
1907                        ));
1908                        count += 1;
1909
1910                        // We are a cycle so we need to break the loop eventually
1911                        Ok(if count < 6 {
1912                            GraphTraversalAction::Continue
1913                        } else {
1914                            GraphTraversalAction::Skip
1915                        })
1916                    },
1917                    |_, _| Ok(0),
1918                )?;
1919                assert_eq!(
1920                    vec![
1921                        (None, rcstr!("a.js")),
1922                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1923                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1924                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1925                        // we start following the cycle again
1926                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1927                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1928                    ],
1929                    visits
1930                );
1931
1932                Ok(())
1933            },
1934        )
1935        .await;
1936    }
1937
1938    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1939    async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1940        run_graph_test(
1941            vec![rcstr!("a.js")],
1942            {
1943                let mut deps = FxHashMap::default();
1944                // a simple triangle
1945                //        a
1946                //      b   c
1947                //   d    e    f
1948                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1949                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1950                deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1951                deps
1952            },
1953            |graph, entry_modules, module_to_name| {
1954                let mut visits = Vec::new();
1955                let mut count = 0;
1956
1957                graph.traverse_edges_fixed_point_with_priority(
1958                    entry_modules.into_iter().map(|m| (m, 0)),
1959                    &mut (),
1960                    |parent, target, _| {
1961                        visits.push((
1962                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1963                            module_to_name.get(&target).unwrap().clone(),
1964                        ));
1965                        count += 1;
1966
1967                        // We are a cycle so we need to break the loop eventually
1968                        Ok(if count < 6 {
1969                            GraphTraversalAction::Continue
1970                        } else {
1971                            GraphTraversalAction::Skip
1972                        })
1973                    },
1974                    |_, _| Ok(0),
1975                )?;
1976
1977                assert_eq!(
1978                    vec![
1979                        (None, rcstr!("a.js")),
1980                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1981                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1982                        (Some(rcstr!("b.js")), rcstr!("e.js")),
1983                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1984                        (Some(rcstr!("c.js")), rcstr!("f.js")),
1985                        (Some(rcstr!("c.js")), rcstr!("e.js")),
1986                    ],
1987                    visits
1988                );
1989
1990                Ok(())
1991            },
1992        )
1993        .await;
1994    }
1995
1996    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1997    async fn test_traverse_cycles() {
1998        run_graph_test(
1999            vec![rcstr!("a.js")],
2000            {
2001                let mut deps = FxHashMap::default();
2002                // The cycles are: (i, j, k), and (s) which a self-import
2003                //          a
2004                //      /   |    \
2005                //     /i   s-\   x
2006                //     |j   \-/
2007                //     \k
2008                deps.insert(
2009                    rcstr!("a.js"),
2010                    vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2011                );
2012                deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2013                deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2014                deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2015                deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2016                deps
2017            },
2018            |graph, _, module_to_name| {
2019                let mut cycles = vec![];
2020
2021                graph.traverse_cycles(
2022                    |_| true,
2023                    |cycle| {
2024                        cycles.push(
2025                            cycle
2026                                .iter()
2027                                .map(|n| module_to_name.get(*n).unwrap().clone())
2028                                .collect::<Vec<_>>(),
2029                        );
2030                        Ok(())
2031                    },
2032                )?;
2033
2034                assert_eq!(
2035                    cycles,
2036                    vec![
2037                        vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2038                        vec![rcstr!("s.js")]
2039                    ],
2040                );
2041
2042                Ok(())
2043            },
2044        )
2045        .await;
2046    }
2047
2048    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2049    async fn test_reverse_edges_through_layered_graph() {
2050        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2051            BackendOptions::default(),
2052            noop_backing_storage(),
2053        ));
2054        tt.run_once(async move {
2055            #[turbo_tasks::value]
2056            struct ReverseTraversalResults {
2057                forward: Vec<RcStr>,
2058                reverse_from_d: Vec<RcStr>,
2059                reverse_from_b: Vec<RcStr>,
2060            }
2061
2062            #[turbo_tasks::function(operation)]
2063            async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2064                let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2065                let root = fs.root().await?;
2066
2067                // a simple linear graph a -> b ->c
2068                // but b->c is in a parent graph and a is in the child
2069                let graph = {
2070                    let mut deps = FxHashMap::default();
2071
2072                    deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2073                    deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2074                    deps
2075                };
2076                let repo = TestRepo {
2077                    repo: graph
2078                        .iter()
2079                        .map(|(k, v)| {
2080                            (
2081                                root.join(k).unwrap(),
2082                                v.iter().map(|f| root.join(f).unwrap()).collect(),
2083                            )
2084                        })
2085                        .collect(),
2086                }
2087                .cell();
2088                let make_module = |name| {
2089                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2090                        .to_resolved()
2091                };
2092                let a_module = make_module("a.js").await?;
2093                let b_module = make_module("b.js").await?;
2094
2095                let parent_graph = SingleModuleGraph::new_with_entries(
2096                    ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2097                    false,
2098                    false,
2099                );
2100
2101                let module_graph = ModuleGraph::from_graphs(vec![
2102                    parent_graph,
2103                    SingleModuleGraph::new_with_entries_visited(
2104                        ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2105                        VisitedModules::from_graph(parent_graph),
2106                        false,
2107                        false,
2108                    ),
2109                ])
2110                .connect();
2111                let child_graph = module_graph
2112                    .iter_graphs()
2113                    .await?
2114                    .get(1)
2115                    .unwrap()
2116                    .connect()
2117                    .await?;
2118
2119                // test traversing forward from a in the child graph
2120                let mut visited_forward = Vec::new();
2121                child_graph.traverse_edges_dfs(
2122                    vec![a_module],
2123                    &mut (),
2124                    |_parent, child, _state_| {
2125                        visited_forward.push(child);
2126                        Ok(GraphTraversalAction::Continue)
2127                    },
2128                    |_, _, _| Ok(()),
2129                )?;
2130                let forward = visited_forward
2131                    .iter()
2132                    .map(|m| m.ident().to_string().owned())
2133                    .try_join()
2134                    .await?;
2135
2136                // test traversing backwards from 'd' which is only in the child graph
2137                let d_module = child_graph
2138                    .enumerate_nodes()
2139                    .map(|(_index, module)| async move {
2140                        Ok(match module {
2141                            crate::module_graph::SingleModuleGraphNode::Module(module) => {
2142                                if module.ident().to_string().owned().await? == "[test]/d.js" {
2143                                    Some(*module)
2144                                } else {
2145                                    None
2146                                }
2147                            }
2148                            crate::module_graph::SingleModuleGraphNode::VisitedModule {
2149                                ..
2150                            } => None,
2151                        })
2152                    })
2153                    .try_flat_join()
2154                    .await?
2155                    .into_iter()
2156                    .next()
2157                    .unwrap();
2158
2159                async fn get_reverse_from(
2160                    graph: &ModuleGraphLayer,
2161                    module: ResolvedVc<Box<dyn Module>>,
2162                ) -> Result<Vec<RcStr>> {
2163                    let mut visited = Vec::new();
2164                    graph.traverse_edges_reverse_dfs(
2165                        vec![module],
2166                        &mut (),
2167                        |_parent, child, _state_| {
2168                            visited.push(child);
2169                            Ok(GraphTraversalAction::Continue)
2170                        },
2171                        |_, _, _| Ok(()),
2172                    )?;
2173                    visited
2174                        .iter()
2175                        .map(|m| m.ident().to_string().owned())
2176                        .try_join()
2177                        .await
2178                }
2179
2180                Ok(ReverseTraversalResults {
2181                    forward,
2182                    reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2183                    reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2184                }
2185                .cell())
2186            }
2187
2188            let traversal_results = reverse_traversal_results_operation()
2189                .read_strongly_consistent()
2190                .await?;
2191
2192            assert_eq!(
2193                traversal_results.forward,
2194                vec![
2195                    rcstr!("[test]/a.js"),
2196                    rcstr!("[test]/b.js"),
2197                    rcstr!("[test]/d.js")
2198                ]
2199            );
2200
2201            assert_eq!(
2202                traversal_results.reverse_from_d,
2203                vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2204            );
2205
2206            assert_eq!(
2207                traversal_results.reverse_from_b,
2208                vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2209            );
2210
2211            Ok(())
2212        })
2213        .await
2214        .unwrap();
2215    }
2216
2217    #[turbo_tasks::value(shared)]
2218    struct TestRepo {
2219        repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2220    }
2221    #[turbo_tasks::value]
2222    struct MockModule {
2223        path: FileSystemPath,
2224        repo: ResolvedVc<TestRepo>,
2225    }
2226    #[turbo_tasks::value_impl]
2227    impl MockModule {
2228        #[turbo_tasks::function]
2229        fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2230            Self { path, repo }.cell()
2231        }
2232    }
2233
2234    #[turbo_tasks::value_impl]
2235    impl Asset for MockModule {
2236        #[turbo_tasks::function]
2237        fn content(&self) -> Vc<AssetContent> {
2238            panic!("MockModule::content shouldn't be called")
2239        }
2240    }
2241
2242    #[turbo_tasks::value_impl]
2243    impl Module for MockModule {
2244        #[turbo_tasks::function]
2245        fn ident(&self) -> Vc<AssetIdent> {
2246            AssetIdent::from_path(self.path.clone())
2247        }
2248
2249        #[turbo_tasks::function]
2250        fn source(&self) -> Vc<crate::source::OptionSource> {
2251            Vc::cell(None)
2252        }
2253
2254        #[turbo_tasks::function]
2255        async fn references(&self) -> Result<Vc<ModuleReferences>> {
2256            let repo = self.repo.await?;
2257            let references = match repo.repo.get(&self.path) {
2258                Some(deps) => {
2259                    deps.iter()
2260                        .map(|p| {
2261                            Vc::upcast::<Box<dyn ModuleReference>>(
2262                                SingleChunkableModuleReference::new(
2263                                    Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2264                                    rcstr!("normal-dep"),
2265                                    ExportUsage::all(),
2266                                ),
2267                            )
2268                            .to_resolved()
2269                        })
2270                        .try_join()
2271                        .await?
2272                }
2273                None => vec![],
2274            };
2275
2276            Ok(Vc::cell(references))
2277        }
2278        #[turbo_tasks::function]
2279        fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2280            ModuleSideEffects::SideEffectful.cell()
2281        }
2282    }
2283
2284    /// Constructs a graph based on the provided dependency adjacency lists and calls the given test
2285    /// function.
2286    ///
2287    /// # Parameters
2288    /// - `entries`: A vector of entry module names (as `RcStr`). These are the starting points for
2289    ///   the graph.
2290    /// - `graph`: A map from module name (`RcStr`) to a vector of its dependency module names
2291    ///   (`RcStr`). Represents the adjacency list of the graph.
2292    /// - `test_fn`: A function that is called with:
2293    ///     - `ReadRef<SingleModuleGraph>`: The constructed module graph.
2294    ///     - `Vec<ResolvedVc<Box<dyn Module>>>`: The resolved entry modules.
2295    ///     - `FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>`: A mapping from module to its name for
2296    ///       easier analysis in tests.
2297    async fn run_graph_test(
2298        entries: Vec<RcStr>,
2299        graph: FxHashMap<RcStr, Vec<RcStr>>,
2300        test_fn: impl FnOnce(
2301            &ModuleGraph,
2302            Vec<ResolvedVc<Box<dyn Module>>>,
2303            FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2304        ) -> Result<()>
2305        + Send
2306        + 'static,
2307    ) {
2308        #[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
2309        struct SetupGraph {
2310            module_graph: ReadRef<ModuleGraph>,
2311            entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2312            module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2313        }
2314
2315        #[turbo_tasks::function(operation)]
2316        async fn setup_graph(
2317            entries: Vec<RcStr>,
2318            graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2319        ) -> Result<Vc<SetupGraph>> {
2320            let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2321            let root = fs.root().await?;
2322
2323            let repo = TestRepo {
2324                repo: graph_entries
2325                    .iter()
2326                    .map(|(k, v)| {
2327                        (
2328                            root.join(k).unwrap(),
2329                            v.iter().map(|f| root.join(f).unwrap()).collect(),
2330                        )
2331                    })
2332                    .collect(),
2333            }
2334            .cell();
2335            let entry_modules = entries
2336                .iter()
2337                .map(|e| {
2338                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2339                        .to_resolved()
2340                })
2341                .try_join()
2342                .await?;
2343            let graph = SingleModuleGraph::new_with_entries(
2344                GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2345                    entry_modules.clone(),
2346                )])),
2347                false,
2348                false,
2349            );
2350
2351            // Create a simple name mapping to make analyzing the visitors easier.
2352            // Technically they could always pull this name off of the
2353            // `module.ident().await?.path.path` themselves but you cannot `await` in visitors.
2354            let module_to_name = graph
2355                .connect()
2356                .await?
2357                .modules
2358                .keys()
2359                .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2360                .try_join()
2361                .await?
2362                .into_iter()
2363                .collect();
2364            let module_graph = ModuleGraph::from_single_graph(graph).connect().await?;
2365
2366            Ok(SetupGraph {
2367                module_graph,
2368                entry_modules,
2369                module_to_name,
2370            }
2371            .cell())
2372        }
2373
2374        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2375            BackendOptions::default(),
2376            noop_backing_storage(),
2377        ));
2378        let graph_entries = graph.into_iter().collect::<Vec<_>>();
2379        tt.run_once(async move {
2380            let setup = setup_graph(entries, graph_entries)
2381                .read_strongly_consistent()
2382                .await?;
2383
2384            test_fn(
2385                &setup.module_graph,
2386                setup.entry_modules.clone(),
2387                setup.module_to_name.clone(),
2388            )
2389        })
2390        .await
2391        .unwrap();
2392    }
2393}