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    TryFlatJoinIterExt, 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                    bail!("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_module_info = self.async_module_info();
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            .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
832            .try_flat_join()
833            .await?;
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    #[turbo_tasks::function]
1527    pub async fn module_count(&self) -> Result<Vc<u64>> {
1528        Ok(Vc::cell(self.number_of_modules as u64))
1529    }
1530}
1531
1532#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1533pub enum SingleModuleGraphNode {
1534    Module(ResolvedVc<Box<dyn Module>>),
1535    // Models a module that is referenced but has already been visited by an earlier graph.
1536    VisitedModule {
1537        idx: GraphNodeIndex,
1538        module: ResolvedVc<Box<dyn Module>>,
1539    },
1540}
1541
1542impl SingleModuleGraphNode {
1543    pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1544        match self {
1545            SingleModuleGraphNode::Module(module) => *module,
1546            SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1547        }
1548    }
1549    pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1550        match self {
1551            SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1552                Direction::Outgoing => Some(*idx),
1553                Direction::Incoming => None,
1554            },
1555            SingleModuleGraphNode::Module(_) => None,
1556        }
1557    }
1558}
1559
1560#[derive(PartialEq, Eq, Debug)]
1561pub enum GraphTraversalAction {
1562    /// Continue visiting children
1563    Continue,
1564    /// Skip the immediate children, but visit the node in postorder
1565    Skip,
1566    /// Skip the immediate children and the node in postorder
1567    Exclude,
1568}
1569
1570// These nodes are created while walking the Turbopack modules references, and are used to then
1571// afterwards build the SingleModuleGraph.
1572#[derive(Clone, Hash, PartialEq, Eq)]
1573enum SingleModuleGraphBuilderNode {
1574    /// A regular module
1575    Module {
1576        module: ResolvedVc<Box<dyn Module>>,
1577        // module.ident().to_string(), eagerly computed for tracing
1578        ident: Option<ReadRef<RcStr>>,
1579    },
1580    /// A reference to a module that is already listed in visited_modules
1581    VisitedModule {
1582        module: ResolvedVc<Box<dyn Module>>,
1583        idx: GraphNodeIndex,
1584    },
1585}
1586
1587impl SingleModuleGraphBuilderNode {
1588    async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1589        Ok(Self::Module {
1590            module,
1591            ident: if emit_spans {
1592                // INVALIDATION: we don't need to invalidate when the span name changes
1593                Some(module.ident_string().untracked().await?)
1594            } else {
1595                None
1596            },
1597        })
1598    }
1599    fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1600        Self::VisitedModule { module, idx }
1601    }
1602}
1603
1604struct SingleModuleGraphBuilder<'a> {
1605    visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1606
1607    emit_spans: bool,
1608
1609    /// Whether to walk ChunkingType::Traced references
1610    include_traced: bool,
1611
1612    /// Whether to read ModuleReference::binding_usage()
1613    include_binding_usage: bool,
1614}
1615impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1616    type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1617    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1618
1619    fn visit(
1620        &mut self,
1621        node: &SingleModuleGraphBuilderNode,
1622        edge: Option<&RefData>,
1623    ) -> VisitControlFlow {
1624        if let Some(edge) = edge
1625            && matches!(edge.chunking_type, ChunkingType::Traced)
1626        {
1627            // The graph behind traced references is not part of the module graph traversal
1628            return VisitControlFlow::Skip;
1629        }
1630        match node {
1631            SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1632            // Module was already visited previously
1633            SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1634        }
1635    }
1636
1637    fn edges(
1638        &mut self,
1639        // The `skip_duplicates_with_key()` above ensures only a single `edges()` call per module
1640        // (and not per `(module, export)` pair), so the export must not be read here!
1641        node: &SingleModuleGraphBuilderNode,
1642    ) -> Self::EdgesFuture {
1643        // Destructure beforehand to not have to clone the whole node when entering the async block
1644        let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1645            // These are always skipped in `visit()`
1646            unreachable!()
1647        };
1648        let visited_modules = self.visited_modules;
1649        let emit_spans = self.emit_spans;
1650        let include_traced = self.include_traced;
1651        let include_binding_usage = self.include_binding_usage;
1652        async move {
1653            let refs_cell = primary_chunkable_referenced_modules(
1654                *module,
1655                include_traced,
1656                include_binding_usage,
1657            );
1658            let refs = match refs_cell.await {
1659                Ok(refs) => refs,
1660                Err(e) => {
1661                    return Err(e.context(module.ident().to_string().await?));
1662                }
1663            };
1664
1665            refs.iter()
1666                .flat_map(|(reference, resolved)| {
1667                    resolved.modules.iter().map(|m| {
1668                        (
1669                            *reference,
1670                            resolved.chunking_type.clone(),
1671                            resolved.binding_usage.clone(),
1672                            *m,
1673                        )
1674                    })
1675                })
1676                .map(async |(reference, ty, binding_usage, target)| {
1677                    let to = if let Some(idx) = visited_modules.get(&target) {
1678                        SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1679                    } else {
1680                        SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1681                    };
1682                    Ok((
1683                        to,
1684                        RefData {
1685                            chunking_type: ty,
1686                            binding_usage,
1687                            reference,
1688                        },
1689                    ))
1690                })
1691                .try_join()
1692                .await
1693        }
1694    }
1695
1696    fn span(
1697        &mut self,
1698        node: &SingleModuleGraphBuilderNode,
1699        edge: Option<&RefData>,
1700    ) -> tracing::Span {
1701        if !self.emit_spans {
1702            return Span::none();
1703        }
1704
1705        let mut span = match node {
1706            SingleModuleGraphBuilderNode::Module {
1707                ident: Some(ident), ..
1708            } => {
1709                tracing::info_span!("module", name = display(ident))
1710            }
1711            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1712                tracing::info_span!("visited module")
1713            }
1714            _ => unreachable!(),
1715        };
1716
1717        if let Some(edge) = edge {
1718            match &edge.chunking_type {
1719                ChunkingType::Parallel {
1720                    inherit_async: _,
1721                    hoisted: _,
1722                } => {}
1723                ChunkingType::Traced => {
1724                    let _span = span.entered();
1725                    span = tracing::info_span!("traced reference");
1726                }
1727                ChunkingType::Async => {
1728                    let _span = span.entered();
1729                    span = tracing::info_span!("async reference");
1730                }
1731                ChunkingType::Isolated { _ty: ty, merge_tag } => {
1732                    let _span = span.entered();
1733                    span = tracing::info_span!(
1734                        "isolated reference",
1735                        ty = debug(&ty),
1736                        merge_tag = debug(&merge_tag)
1737                    );
1738                }
1739                ChunkingType::Shared {
1740                    inherit_async: _,
1741                    merge_tag,
1742                } => {
1743                    let _span = span.entered();
1744                    span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1745                }
1746            };
1747        }
1748
1749        span
1750    }
1751}
1752
1753#[cfg(test)]
1754pub mod tests {
1755    use anyhow::Result;
1756    use rustc_hash::FxHashMap;
1757    use turbo_rcstr::{RcStr, rcstr};
1758    use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
1759    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1760    use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1761
1762    use super::*;
1763    use crate::{
1764        asset::{Asset, AssetContent},
1765        ident::AssetIdent,
1766        module::{Module, ModuleSideEffects},
1767        reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1768        resolve::ExportUsage,
1769    };
1770
1771    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1772    async fn test_traverse_dfs_from_entries_diamond() {
1773        run_graph_test(
1774            vec![rcstr!("a.js")],
1775            {
1776                let mut deps = FxHashMap::default();
1777                // A classic diamond dependency on d
1778                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1779                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1780                deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1781                deps
1782            },
1783            |graph, entry_modules, module_to_name| {
1784                let mut preorder_visits = Vec::new();
1785                let mut postorder_visits = Vec::new();
1786
1787                graph.traverse_edges_dfs(
1788                    entry_modules,
1789                    &mut (),
1790                    |parent, target, _| {
1791                        preorder_visits.push((
1792                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1793                            module_to_name.get(&target).unwrap().clone(),
1794                        ));
1795                        Ok(GraphTraversalAction::Continue)
1796                    },
1797                    |parent, target, _| {
1798                        postorder_visits.push((
1799                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1800                            module_to_name.get(&target).unwrap().clone(),
1801                        ));
1802                        Ok(())
1803                    },
1804                )?;
1805                assert_eq!(
1806                    vec![
1807                        (None, rcstr!("a.js")),
1808                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1809                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1810                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1811                        (Some(rcstr!("c.js")), rcstr!("d.js"))
1812                    ],
1813                    preorder_visits
1814                );
1815                assert_eq!(
1816                    vec![
1817                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1818                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1819                        (Some(rcstr!("c.js")), rcstr!("d.js")),
1820                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1821                        (None, rcstr!("a.js"))
1822                    ],
1823                    postorder_visits
1824                );
1825                Ok(())
1826            },
1827        )
1828        .await;
1829    }
1830
1831    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1832    async fn test_traverse_dfs_from_entries_cycle() {
1833        run_graph_test(
1834            vec![rcstr!("a.js")],
1835            {
1836                let mut deps = FxHashMap::default();
1837                // A cycle of length 3
1838                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1839                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1840                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1841                deps
1842            },
1843            |graph, entry_modules, module_to_name| {
1844                let mut preorder_visits = Vec::new();
1845                let mut postorder_visits = Vec::new();
1846
1847                graph.traverse_edges_dfs(
1848                    entry_modules,
1849                    &mut (),
1850                    |parent, target, _| {
1851                        preorder_visits.push((
1852                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1853                            module_to_name.get(&target).unwrap().clone(),
1854                        ));
1855                        Ok(GraphTraversalAction::Continue)
1856                    },
1857                    |parent, target, _| {
1858                        postorder_visits.push((
1859                            parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1860                            module_to_name.get(&target).unwrap().clone(),
1861                        ));
1862                        Ok(())
1863                    },
1864                )?;
1865                assert_eq!(
1866                    vec![
1867                        (None, rcstr!("a.js")),
1868                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1869                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1870                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1871                    ],
1872                    preorder_visits
1873                );
1874                assert_eq!(
1875                    vec![
1876                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1877                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1878                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1879                        (None, rcstr!("a.js"))
1880                    ],
1881                    postorder_visits
1882                );
1883                Ok(())
1884            },
1885        )
1886        .await;
1887    }
1888
1889    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1890    async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1891        run_graph_test(
1892            vec![rcstr!("a.js")],
1893            {
1894                let mut deps = FxHashMap::default();
1895                // A cycle of length 3
1896                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1897                deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1898                deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1899                deps
1900            },
1901            |graph, entry_modules, module_to_name| {
1902                let mut visits = Vec::new();
1903                let mut count = 0;
1904
1905                graph.traverse_edges_fixed_point_with_priority(
1906                    entry_modules.into_iter().map(|m| (m, 0)),
1907                    &mut (),
1908                    |parent, target, _| {
1909                        visits.push((
1910                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1911                            module_to_name.get(&target).unwrap().clone(),
1912                        ));
1913                        count += 1;
1914
1915                        // We are a cycle so we need to break the loop eventually
1916                        Ok(if count < 6 {
1917                            GraphTraversalAction::Continue
1918                        } else {
1919                            GraphTraversalAction::Skip
1920                        })
1921                    },
1922                    |_, _| Ok(0),
1923                )?;
1924                assert_eq!(
1925                    vec![
1926                        (None, rcstr!("a.js")),
1927                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1928                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1929                        (Some(rcstr!("c.js")), rcstr!("a.js")),
1930                        // we start following the cycle again
1931                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1932                        (Some(rcstr!("b.js")), rcstr!("c.js")),
1933                    ],
1934                    visits
1935                );
1936
1937                Ok(())
1938            },
1939        )
1940        .await;
1941    }
1942
1943    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1944    async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1945        run_graph_test(
1946            vec![rcstr!("a.js")],
1947            {
1948                let mut deps = FxHashMap::default();
1949                // a simple triangle
1950                //        a
1951                //      b   c
1952                //   d    e    f
1953                deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1954                deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1955                deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1956                deps
1957            },
1958            |graph, entry_modules, module_to_name| {
1959                let mut visits = Vec::new();
1960                let mut count = 0;
1961
1962                graph.traverse_edges_fixed_point_with_priority(
1963                    entry_modules.into_iter().map(|m| (m, 0)),
1964                    &mut (),
1965                    |parent, target, _| {
1966                        visits.push((
1967                            parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1968                            module_to_name.get(&target).unwrap().clone(),
1969                        ));
1970                        count += 1;
1971
1972                        // We are a cycle so we need to break the loop eventually
1973                        Ok(if count < 6 {
1974                            GraphTraversalAction::Continue
1975                        } else {
1976                            GraphTraversalAction::Skip
1977                        })
1978                    },
1979                    |_, _| Ok(0),
1980                )?;
1981
1982                assert_eq!(
1983                    vec![
1984                        (None, rcstr!("a.js")),
1985                        (Some(rcstr!("a.js")), rcstr!("c.js")),
1986                        (Some(rcstr!("a.js")), rcstr!("b.js")),
1987                        (Some(rcstr!("b.js")), rcstr!("e.js")),
1988                        (Some(rcstr!("b.js")), rcstr!("d.js")),
1989                        (Some(rcstr!("c.js")), rcstr!("f.js")),
1990                        (Some(rcstr!("c.js")), rcstr!("e.js")),
1991                    ],
1992                    visits
1993                );
1994
1995                Ok(())
1996            },
1997        )
1998        .await;
1999    }
2000
2001    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2002    async fn test_traverse_cycles() {
2003        run_graph_test(
2004            vec![rcstr!("a.js")],
2005            {
2006                let mut deps = FxHashMap::default();
2007                // The cycles are: (i, j, k), and (s) which a self-import
2008                //          a
2009                //      /   |    \
2010                //     /i   s-\   x
2011                //     |j   \-/
2012                //     \k
2013                deps.insert(
2014                    rcstr!("a.js"),
2015                    vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2016                );
2017                deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2018                deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2019                deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2020                deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2021                deps
2022            },
2023            |graph, _, module_to_name| {
2024                let mut cycles = vec![];
2025
2026                graph.traverse_cycles(
2027                    |_| true,
2028                    |cycle| {
2029                        cycles.push(
2030                            cycle
2031                                .iter()
2032                                .map(|n| module_to_name.get(*n).unwrap().clone())
2033                                .collect::<Vec<_>>(),
2034                        );
2035                        Ok(())
2036                    },
2037                )?;
2038
2039                assert_eq!(
2040                    cycles,
2041                    vec![
2042                        vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2043                        vec![rcstr!("s.js")]
2044                    ],
2045                );
2046
2047                Ok(())
2048            },
2049        )
2050        .await;
2051    }
2052
2053    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2054    async fn test_reverse_edges_through_layered_graph() {
2055        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2056            BackendOptions::default(),
2057            noop_backing_storage(),
2058        ));
2059        tt.run_once(async move {
2060            #[turbo_tasks::value]
2061            struct ReverseTraversalResults {
2062                forward: Vec<RcStr>,
2063                reverse_from_d: Vec<RcStr>,
2064                reverse_from_b: Vec<RcStr>,
2065            }
2066
2067            #[turbo_tasks::function(operation)]
2068            async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2069                let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2070                let root = fs.root().await?;
2071
2072                // a simple linear graph a -> b ->c
2073                // but b->c is in a parent graph and a is in the child
2074                let graph = {
2075                    let mut deps = FxHashMap::default();
2076
2077                    deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2078                    deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2079                    deps
2080                };
2081                let repo = TestRepo {
2082                    repo: graph
2083                        .iter()
2084                        .map(|(k, v)| {
2085                            (
2086                                root.join(k).unwrap(),
2087                                v.iter().map(|f| root.join(f).unwrap()).collect(),
2088                            )
2089                        })
2090                        .collect(),
2091                }
2092                .cell();
2093                let make_module = |name| {
2094                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2095                        .to_resolved()
2096                };
2097                let a_module = make_module("a.js").await?;
2098                let b_module = make_module("b.js").await?;
2099
2100                let parent_graph = SingleModuleGraph::new_with_entries(
2101                    ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2102                    false,
2103                    false,
2104                );
2105
2106                let module_graph = ModuleGraph::from_graphs(vec![
2107                    parent_graph,
2108                    SingleModuleGraph::new_with_entries_visited(
2109                        ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2110                        VisitedModules::from_graph(parent_graph),
2111                        false,
2112                        false,
2113                    ),
2114                ])
2115                .connect();
2116                let child_graph = module_graph
2117                    .iter_graphs()
2118                    .await?
2119                    .get(1)
2120                    .unwrap()
2121                    .connect()
2122                    .await?;
2123
2124                // test traversing forward from a in the child graph
2125                let mut visited_forward = Vec::new();
2126                child_graph.traverse_edges_dfs(
2127                    vec![a_module],
2128                    &mut (),
2129                    |_parent, child, _state_| {
2130                        visited_forward.push(child);
2131                        Ok(GraphTraversalAction::Continue)
2132                    },
2133                    |_, _, _| Ok(()),
2134                )?;
2135                let forward = visited_forward
2136                    .iter()
2137                    .map(|m| m.ident().to_string().owned())
2138                    .try_join()
2139                    .await?;
2140
2141                // test traversing backwards from 'd' which is only in the child graph
2142                let d_module = child_graph
2143                    .enumerate_nodes()
2144                    .map(|(_index, module)| async move {
2145                        Ok(match module {
2146                            crate::module_graph::SingleModuleGraphNode::Module(module) => {
2147                                if module.ident().to_string().owned().await? == "[test]/d.js" {
2148                                    Some(*module)
2149                                } else {
2150                                    None
2151                                }
2152                            }
2153                            crate::module_graph::SingleModuleGraphNode::VisitedModule {
2154                                ..
2155                            } => None,
2156                        })
2157                    })
2158                    .try_flat_join()
2159                    .await?
2160                    .into_iter()
2161                    .next()
2162                    .unwrap();
2163
2164                async fn get_reverse_from(
2165                    graph: &ModuleGraphLayer,
2166                    module: ResolvedVc<Box<dyn Module>>,
2167                ) -> Result<Vec<RcStr>> {
2168                    let mut visited = Vec::new();
2169                    graph.traverse_edges_reverse_dfs(
2170                        vec![module],
2171                        &mut (),
2172                        |_parent, child, _state_| {
2173                            visited.push(child);
2174                            Ok(GraphTraversalAction::Continue)
2175                        },
2176                        |_, _, _| Ok(()),
2177                    )?;
2178                    visited
2179                        .iter()
2180                        .map(|m| m.ident().to_string().owned())
2181                        .try_join()
2182                        .await
2183                }
2184
2185                Ok(ReverseTraversalResults {
2186                    forward,
2187                    reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2188                    reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2189                }
2190                .cell())
2191            }
2192
2193            let traversal_results = reverse_traversal_results_operation()
2194                .read_strongly_consistent()
2195                .await?;
2196
2197            assert_eq!(
2198                traversal_results.forward,
2199                vec![
2200                    rcstr!("[test]/a.js"),
2201                    rcstr!("[test]/b.js"),
2202                    rcstr!("[test]/d.js")
2203                ]
2204            );
2205
2206            assert_eq!(
2207                traversal_results.reverse_from_d,
2208                vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2209            );
2210
2211            assert_eq!(
2212                traversal_results.reverse_from_b,
2213                vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2214            );
2215
2216            Ok(())
2217        })
2218        .await
2219        .unwrap();
2220    }
2221
2222    #[turbo_tasks::value(shared)]
2223    struct TestRepo {
2224        repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2225    }
2226    #[turbo_tasks::value]
2227    struct MockModule {
2228        path: FileSystemPath,
2229        repo: ResolvedVc<TestRepo>,
2230    }
2231    #[turbo_tasks::value_impl]
2232    impl MockModule {
2233        #[turbo_tasks::function]
2234        fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2235            Self { path, repo }.cell()
2236        }
2237    }
2238
2239    #[turbo_tasks::value_impl]
2240    impl Asset for MockModule {
2241        #[turbo_tasks::function]
2242        fn content(&self) -> Vc<AssetContent> {
2243            panic!("MockModule::content shouldn't be called")
2244        }
2245    }
2246
2247    #[turbo_tasks::value_impl]
2248    impl Module for MockModule {
2249        #[turbo_tasks::function]
2250        fn ident(&self) -> Vc<AssetIdent> {
2251            AssetIdent::from_path(self.path.clone())
2252        }
2253
2254        #[turbo_tasks::function]
2255        fn source(&self) -> Vc<crate::source::OptionSource> {
2256            Vc::cell(None)
2257        }
2258
2259        #[turbo_tasks::function]
2260        async fn references(&self) -> Result<Vc<ModuleReferences>> {
2261            let repo = self.repo.await?;
2262            let references = match repo.repo.get(&self.path) {
2263                Some(deps) => {
2264                    deps.iter()
2265                        .map(|p| {
2266                            Vc::upcast::<Box<dyn ModuleReference>>(
2267                                SingleChunkableModuleReference::new(
2268                                    Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2269                                    rcstr!("normal-dep"),
2270                                    ExportUsage::all(),
2271                                ),
2272                            )
2273                            .to_resolved()
2274                        })
2275                        .try_join()
2276                        .await?
2277                }
2278                None => vec![],
2279            };
2280
2281            Ok(Vc::cell(references))
2282        }
2283        #[turbo_tasks::function]
2284        fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2285            ModuleSideEffects::SideEffectful.cell()
2286        }
2287    }
2288
2289    /// Constructs a graph based on the provided dependency adjacency lists and calls the given test
2290    /// function.
2291    ///
2292    /// # Parameters
2293    /// - `entries`: A vector of entry module names (as `RcStr`). These are the starting points for
2294    ///   the graph.
2295    /// - `graph`: A map from module name (`RcStr`) to a vector of its dependency module names
2296    ///   (`RcStr`). Represents the adjacency list of the graph.
2297    /// - `test_fn`: A function that is called with:
2298    ///     - `ReadRef<SingleModuleGraph>`: The constructed module graph.
2299    ///     - `Vec<ResolvedVc<Box<dyn Module>>>`: The resolved entry modules.
2300    ///     - `FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>`: A mapping from module to its name for
2301    ///       easier analysis in tests.
2302    async fn run_graph_test(
2303        entries: Vec<RcStr>,
2304        graph: FxHashMap<RcStr, Vec<RcStr>>,
2305        test_fn: impl FnOnce(
2306            &ModuleGraph,
2307            Vec<ResolvedVc<Box<dyn Module>>>,
2308            FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2309        ) -> Result<()>
2310        + Send
2311        + 'static,
2312    ) {
2313        #[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
2314        struct SetupGraph {
2315            module_graph: ReadRef<ModuleGraph>,
2316            entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
2317            module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2318        }
2319
2320        #[turbo_tasks::function(operation)]
2321        async fn setup_graph(
2322            entries: Vec<RcStr>,
2323            graph_entries: Vec<(RcStr, Vec<RcStr>)>,
2324        ) -> Result<Vc<SetupGraph>> {
2325            let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2326            let root = fs.root().await?;
2327
2328            let repo = TestRepo {
2329                repo: graph_entries
2330                    .iter()
2331                    .map(|(k, v)| {
2332                        (
2333                            root.join(k).unwrap(),
2334                            v.iter().map(|f| root.join(f).unwrap()).collect(),
2335                        )
2336                    })
2337                    .collect(),
2338            }
2339            .cell();
2340            let entry_modules = entries
2341                .iter()
2342                .map(|e| {
2343                    Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2344                        .to_resolved()
2345                })
2346                .try_join()
2347                .await?;
2348            let graph = SingleModuleGraph::new_with_entries(
2349                GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2350                    entry_modules.clone(),
2351                )])),
2352                false,
2353                false,
2354            );
2355
2356            // Create a simple name mapping to make analyzing the visitors easier.
2357            // Technically they could always pull this name off of the
2358            // `module.ident().await?.path.path` themselves but you cannot `await` in visitors.
2359            let module_to_name = graph
2360                .connect()
2361                .await?
2362                .modules
2363                .keys()
2364                .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2365                .try_join()
2366                .await?
2367                .into_iter()
2368                .collect();
2369            let module_graph = ModuleGraph::from_single_graph(graph).connect().await?;
2370
2371            Ok(SetupGraph {
2372                module_graph,
2373                entry_modules,
2374                module_to_name,
2375            }
2376            .cell())
2377        }
2378
2379        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2380            BackendOptions::default(),
2381            noop_backing_storage(),
2382        ));
2383        let graph_entries = graph.into_iter().collect::<Vec<_>>();
2384        tt.run_once(async move {
2385            let setup = setup_graph(entries, graph_entries)
2386                .read_strongly_consistent()
2387                .await?;
2388
2389            test_fn(
2390                &setup.module_graph,
2391                setup.entry_modules.clone(),
2392                setup.module_to_name.clone(),
2393            )
2394        })
2395        .await
2396        .unwrap();
2397    }
2398}