Skip to main content

turbopack_core/module_graph/
mod.rs

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