turbopack_core/module_graph/
mod.rs

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