turbopack_core/module_graph/
mod.rs

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