turbopack_core/module_graph/
mod.rs

1use std::{
2    collections::{BinaryHeap, HashSet, VecDeque},
3    future::Future,
4};
5
6use anyhow::{Context, Result, bail};
7use petgraph::{
8    graph::{DiGraph, EdgeIndex, NodeIndex},
9    visit::{Dfs, EdgeRef, IntoNodeReferences, NodeIndexable, VisitMap, Visitable},
10};
11use rustc_hash::{FxHashMap, FxHashSet};
12use serde::{Deserialize, Serialize};
13use tracing::{Instrument, Span};
14use turbo_rcstr::RcStr;
15use turbo_tasks::{
16    CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt,
17    ValueToString, Vc,
18    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
19    trace::TraceRawVcs,
20};
21
22use crate::{
23    chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
24    issue::Issue,
25    module::Module,
26    module_graph::{
27        async_module_info::{AsyncModulesInfo, compute_async_module_info},
28        chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
29        module_batches::{ModuleBatchesGraph, compute_module_batches},
30        style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
31        traced_di_graph::{TracedDiGraph, iter_neighbors_rev},
32    },
33    reference::primary_chunkable_referenced_modules,
34};
35
36pub mod async_module_info;
37pub mod chunk_group_info;
38pub mod module_batch;
39pub(crate) mod module_batches;
40pub(crate) mod style_groups;
41mod traced_di_graph;
42
43pub use self::module_batches::BatchingConfig;
44
45#[derive(
46    Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, TraceRawVcs,
47)]
48pub struct GraphNodeIndex {
49    #[turbo_tasks(trace_ignore)]
50    graph_idx: usize,
51    #[turbo_tasks(trace_ignore)]
52    node_idx: NodeIndex,
53}
54unsafe impl NonLocalValue for GraphNodeIndex {}
55
56#[turbo_tasks::value]
57#[derive(Clone, Debug)]
58pub struct VisitedModules {
59    pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
60    next_graph_idx: usize,
61}
62
63#[turbo_tasks::value_impl]
64impl VisitedModules {
65    #[turbo_tasks::function]
66    pub async fn empty() -> Vc<Self> {
67        Self {
68            modules: Default::default(),
69            next_graph_idx: 0,
70        }
71        .cell()
72    }
73
74    #[turbo_tasks::function]
75    pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
76        Ok(Self {
77            modules: graph
78                .await?
79                .enumerate_nodes()
80                .flat_map(|(node_idx, module)| match module {
81                    SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
82                        module, ..
83                    }) => Some((
84                        *module,
85                        GraphNodeIndex {
86                            graph_idx: 0,
87                            node_idx,
88                        },
89                    )),
90                    SingleModuleGraphNode::VisitedModule { .. } => None,
91                })
92                .collect(),
93            next_graph_idx: 1,
94        }
95        .cell())
96    }
97
98    #[turbo_tasks::function]
99    pub async fn with_incremented_index(&self) -> Result<Vc<Self>> {
100        Ok(Self {
101            modules: self.modules.clone(),
102            next_graph_idx: self.next_graph_idx + 1,
103        }
104        .cell())
105    }
106
107    #[turbo_tasks::function]
108    pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
109        let graph = graph.await?;
110        let iter = self
111            .modules
112            .iter()
113            .map(|(module, idx)| (*module, *idx))
114            .chain(
115                graph
116                    .enumerate_nodes()
117                    .flat_map(|(node_idx, module)| match module {
118                        SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
119                            module,
120                            ..
121                        }) => Some((
122                            *module,
123                            GraphNodeIndex {
124                                graph_idx: self.next_graph_idx,
125                                node_idx,
126                            },
127                        )),
128                        SingleModuleGraphNode::VisitedModule { .. } => None,
129                    }),
130            );
131
132        let mut map = FxIndexMap::with_capacity_and_hasher(
133            self.modules.len() + graph.number_of_modules,
134            Default::default(),
135        );
136        for (k, v) in iter {
137            map.entry(k).or_insert(v);
138        }
139        map.shrink_to_fit();
140
141        Ok(Self {
142            modules: map,
143            next_graph_idx: self.next_graph_idx + 1,
144        }
145        .cell())
146    }
147}
148
149pub type GraphEntriesT = Vec<ChunkGroupEntry>;
150
151#[turbo_tasks::value(transparent)]
152pub struct GraphEntries(GraphEntriesT);
153
154#[turbo_tasks::value_impl]
155impl GraphEntries {
156    #[turbo_tasks::function]
157    pub fn empty() -> Vc<Self> {
158        Vc::cell(Vec::new())
159    }
160}
161
162#[turbo_tasks::value(cell = "new", eq = "manual", into = "new")]
163#[derive(Clone, Default)]
164pub struct SingleModuleGraph {
165    graph: TracedDiGraph<SingleModuleGraphNode, ChunkingType>,
166
167    /// The number of modules in the graph (excluding VisitedModule nodes)
168    pub number_of_modules: usize,
169
170    // NodeIndex isn't necessarily stable (because of swap_remove), but we never remove nodes.
171    //
172    // HashMaps have nondeterministic order, but this map is only used for lookups (in
173    // `get_module`) and not iteration.
174    //
175    // This contains Vcs, but they are already contained in the graph, so no need to trace this.
176    #[turbo_tasks(trace_ignore)]
177    modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
178
179    #[turbo_tasks(trace_ignore)]
180    pub entries: GraphEntriesT,
181}
182
183impl SingleModuleGraph {
184    /// Walks the graph starting from the given entries and collects all reachable nodes, skipping
185    /// nodes listed in `visited_modules`
186    /// The resulting graph's outgoing edges are in reverse order.
187    async fn new_inner(
188        entries: &GraphEntriesT,
189        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
190        include_traced: bool,
191    ) -> Result<Vc<Self>> {
192        let root_edges = entries
193            .iter()
194            .flat_map(|e| e.entries())
195            .map(|e| async move {
196                Ok(SingleModuleGraphBuilderEdge {
197                    to: SingleModuleGraphBuilderNode::new_module(e).await?,
198                })
199            })
200            .try_join()
201            .await?;
202
203        let (children_nodes_iter, visited_nodes) = AdjacencyMap::new()
204            .skip_duplicates()
205            .visit(
206                root_edges,
207                SingleModuleGraphBuilder {
208                    visited_modules,
209                    include_traced,
210                },
211            )
212            .await
213            .completed()?
214            .into_inner_with_visited();
215        let node_count = visited_nodes.0.len();
216        drop(visited_nodes);
217
218        let mut graph = DiGraph::with_capacity(
219            node_count,
220            // From real world measurements each module has about 3-4 children
221            // If it has more this would cause an additional allocation, but that's fine
222            node_count * 4,
223        );
224
225        let mut number_of_modules = 0;
226        let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
227            FxHashMap::with_capacity_and_hasher(node_count, Default::default());
228        {
229            let _span = tracing::info_span!("build module graph").entered();
230            for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
231                let parent_edge = match parent {
232                    Some(SingleModuleGraphBuilderNode::Module { module, .. }) => {
233                        Some((*modules.get(&module).unwrap(), COMMON_CHUNKING_TYPE))
234                    }
235                    Some(SingleModuleGraphBuilderNode::ChunkableReference { .. }) => {
236                        // Handled when visiting ChunkableReference below
237                        continue;
238                    }
239                    Some(
240                        SingleModuleGraphBuilderNode::VisitedModule { .. }
241                        | SingleModuleGraphBuilderNode::Issues { .. },
242                    ) => unreachable!(),
243                    None => None,
244                };
245
246                match current {
247                    SingleModuleGraphBuilderNode::Module {
248                        module,
249                        layer,
250                        ident: _,
251                    } => {
252                        // Find the current node, if it was already added
253                        let current_idx = if let Some(current_idx) = modules.get(&module) {
254                            *current_idx
255                        } else {
256                            let idx = graph.add_node(SingleModuleGraphNode::Module(
257                                SingleModuleGraphModuleNode {
258                                    module,
259                                    issues: Default::default(),
260                                    layer,
261                                },
262                            ));
263                            number_of_modules += 1;
264                            modules.insert(module, idx);
265                            idx
266                        };
267                        // Add the edge
268                        if let Some((parent_idx, chunking_type)) = parent_edge {
269                            graph.add_edge(parent_idx, current_idx, chunking_type);
270                        }
271                    }
272                    SingleModuleGraphBuilderNode::VisitedModule { module, idx } => {
273                        // Find the current node, if it was already added
274                        let current_idx = if let Some(current_idx) = modules.get(&module) {
275                            *current_idx
276                        } else {
277                            let idx = graph
278                                .add_node(SingleModuleGraphNode::VisitedModule { idx, module });
279                            modules.insert(module, idx);
280                            idx
281                        };
282                        // Add the edge
283                        if let Some((parent_idx, chunking_type)) = parent_edge {
284                            graph.add_edge(parent_idx, current_idx, chunking_type);
285                        }
286                    }
287                    SingleModuleGraphBuilderNode::ChunkableReference {
288                        source,
289                        target,
290                        target_layer,
291                        chunking_type,
292                        ..
293                    } => {
294                        // Find the current node, if it was already added
295                        let target_idx = if let Some(target_idx) = modules.get(&target) {
296                            *target_idx
297                        } else {
298                            let target_idx = visited_modules.get(&target);
299                            let idx = graph.add_node(match target_idx {
300                                Some(idx) => SingleModuleGraphNode::VisitedModule {
301                                    idx: *idx,
302                                    module: target,
303                                },
304                                None => {
305                                    SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
306                                        module: target,
307                                        issues: Default::default(),
308                                        layer: target_layer,
309                                    })
310                                }
311                            });
312                            modules.insert(target, idx);
313                            idx
314                        };
315                        graph.add_edge(*modules.get(&source).unwrap(), target_idx, chunking_type);
316                    }
317                    SingleModuleGraphBuilderNode::Issues(new_issues) => {
318                        let (parent_idx, _) = parent_edge.unwrap();
319                        let SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
320                            issues,
321                            ..
322                        }) = graph.node_weight_mut(parent_idx).unwrap()
323                        else {
324                            bail!("Expected Module node");
325                        };
326
327                        issues.extend(new_issues);
328                    }
329                }
330            }
331        }
332
333        graph.shrink_to_fit();
334
335        #[cfg(debug_assertions)]
336        {
337            let mut duplicates = Vec::new();
338            let mut set = FxHashSet::default();
339            for &module in modules.keys() {
340                let ident = module.ident().to_string().await?;
341                if !set.insert(ident.clone()) {
342                    duplicates.push(ident);
343                }
344            }
345            if !duplicates.is_empty() {
346                panic!("Duplicate module idents in graph: {duplicates:#?}");
347            }
348        }
349
350        Ok(SingleModuleGraph {
351            graph: TracedDiGraph::new(graph),
352            number_of_modules,
353            modules,
354            entries: entries.clone(),
355        }
356        .cell())
357    }
358
359    fn get_module(&self, module: ResolvedVc<Box<dyn Module>>) -> Result<NodeIndex> {
360        self.modules
361            .get(&module)
362            .copied()
363            .context("Couldn't find module in graph")
364    }
365
366    /// Iterate over all nodes in the graph
367    pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ SingleModuleGraphModuleNode> + '_ {
368        self.graph.node_weights().filter_map(|n| match n {
369            SingleModuleGraphNode::Module(node) => Some(node),
370            SingleModuleGraphNode::VisitedModule { .. } => None,
371        })
372    }
373
374    /// Iterate over all nodes in the graph
375    pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
376        self.entries.iter().flat_map(|e| e.entries())
377    }
378
379    /// Enumerate all nodes in the graph
380    pub fn enumerate_nodes(
381        &self,
382    ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
383        self.graph.node_references()
384    }
385
386    /// Traverses all reachable nodes (once)
387    pub fn traverse_from_entry<'a>(
388        &'a self,
389        entry: ResolvedVc<Box<dyn Module>>,
390        mut visitor: impl FnMut(&'a SingleModuleGraphModuleNode),
391    ) -> Result<()> {
392        let entry_node = self.get_module(entry)?;
393
394        let mut dfs = Dfs::new(&*self.graph, entry_node);
395        while let Some(nx) = dfs.next(&*self.graph) {
396            let SingleModuleGraphNode::Module(weight) = self.graph.node_weight(nx).unwrap() else {
397                return Ok(());
398            };
399            // weight.emit_issues();
400            visitor(weight);
401        }
402        Ok(())
403    }
404
405    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
406    /// target.
407    ///
408    /// This means that target nodes can be revisited (once per incoming edge).
409    ///
410    /// * `entry` - The entry module to start the traversal from
411    /// * `visitor` - Called before visiting the children of a node.
412    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
413    ///      &SingleModuleGraphNode, state &S
414    ///    - Can return [GraphTraversalAction]s to control the traversal
415    pub fn traverse_edges_from_entries<'a>(
416        &'a self,
417        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
418        mut visitor: impl FnMut(
419            Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
420            &'a SingleModuleGraphModuleNode,
421        ) -> GraphTraversalAction,
422    ) -> Result<()> {
423        let graph = &self.graph;
424        let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
425
426        let mut stack = entries.collect::<Vec<_>>();
427        let mut discovered = graph.visit_map();
428        // entry_weight.emit_issues();
429        for entry_node in &stack {
430            let SingleModuleGraphNode::Module(entry_weight) =
431                graph.node_weight(*entry_node).unwrap()
432            else {
433                continue;
434            };
435            visitor(None, entry_weight);
436        }
437
438        while let Some(node) = stack.pop() {
439            let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
440            else {
441                continue;
442            };
443            if discovered.visit(node) {
444                let neighbors = {
445                    let mut neighbors = vec![];
446                    let mut walker = graph.neighbors(node).detach();
447                    while let Some((edge, succ)) = walker.next(graph) {
448                        neighbors.push((edge, succ));
449                    }
450                    neighbors
451                };
452
453                for (edge, succ) in neighbors {
454                    let SingleModuleGraphNode::Module(succ_weight) =
455                        graph.node_weight(succ).unwrap()
456                    else {
457                        continue;
458                    };
459                    let edge_weight = graph.edge_weight(edge).unwrap();
460                    let action = visitor(Some((node_weight, edge_weight)), succ_weight);
461                    if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
462                        stack.push(succ);
463                    }
464                }
465            }
466        }
467
468        Ok(())
469    }
470
471    /// Traverses all edges exactly once and calls the visitor with the edge source and
472    /// target.
473    ///
474    /// This means that target nodes can be revisited (once per incoming edge).
475    pub fn traverse_edges<'a>(
476        &'a self,
477        mut visitor: impl FnMut(
478            (
479                Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
480                &'a SingleModuleGraphModuleNode,
481            ),
482        ) -> GraphTraversalAction,
483    ) -> Result<()> {
484        let graph = &self.graph;
485        let mut stack: Vec<NodeIndex> = self
486            .entries
487            .iter()
488            .flat_map(|e| e.entries())
489            .map(|e| *self.modules.get(&e).unwrap())
490            .collect();
491        let mut discovered = graph.visit_map();
492        for entry_node in &stack {
493            let SingleModuleGraphNode::Module(entry_node) = graph.node_weight(*entry_node).unwrap()
494            else {
495                continue;
496            };
497            visitor((None, entry_node));
498        }
499
500        while let Some(node) = stack.pop() {
501            if discovered.visit(node) {
502                let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
503                else {
504                    continue;
505                };
506                for edge in graph.edges(node).collect::<Vec<_>>() {
507                    let edge_weight = edge.weight();
508                    let succ = edge.target();
509                    let SingleModuleGraphNode::Module(succ_weight) =
510                        graph.node_weight(succ).unwrap()
511                    else {
512                        continue;
513                    };
514                    let action = visitor((Some((node_weight, edge_weight)), succ_weight));
515                    if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
516                        stack.push(succ);
517                    }
518                }
519            }
520        }
521
522        Ok(())
523    }
524
525    /// Traverses all reachable edges in topological order. The preorder visitor can be used to
526    /// forward state down the graph, and to skip subgraphs
527    ///
528    /// Use this to collect modules in evaluation order.
529    ///
530    /// Target nodes can be revisited (once per incoming edge).
531    /// Edges are traversed in normal order, so should correspond to reference order.
532    ///
533    /// * `entry` - The entry module to start the traversal from
534    /// * `state` - The state to be passed to the visitors
535    /// * `visit_preorder` - Called before visiting the children of a node.
536    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
537    ///      &SingleModuleGraphNode, state &S
538    ///    - Can return [GraphTraversalAction]s to control the traversal
539    /// * `visit_postorder` - Called after visiting the children of a node. Return
540    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
541    ///      &SingleModuleGraphNode, state &S
542    pub fn traverse_edges_from_entries_topological<'a, S>(
543        &'a self,
544        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
545        state: &mut S,
546        mut visit_preorder: impl FnMut(
547            Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
548            &'a SingleModuleGraphNode,
549            &mut S,
550        ) -> Result<GraphTraversalAction>,
551        mut visit_postorder: impl FnMut(
552            Option<(&'a SingleModuleGraphModuleNode, &'a ChunkingType)>,
553            &'a SingleModuleGraphNode,
554            &mut S,
555        ),
556    ) -> Result<()> {
557        let graph = &self.graph;
558        let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
559
560        enum TopologicalPass {
561            Visit,
562            ExpandAndVisit,
563        }
564
565        #[allow(clippy::type_complexity)] // This is a temporary internal structure
566        let mut stack: Vec<(TopologicalPass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> = entries
567            .map(|e| (TopologicalPass::ExpandAndVisit, None, e))
568            .collect();
569        let mut expanded = FxHashSet::default();
570        while let Some((pass, parent, current)) = stack.pop() {
571            let parent_arg = parent.map(|parent| {
572                (
573                    match graph.node_weight(parent.0).unwrap() {
574                        SingleModuleGraphNode::Module(node) => node,
575                        SingleModuleGraphNode::VisitedModule { .. } => {
576                            unreachable!()
577                        }
578                    },
579                    graph.edge_weight(parent.1).unwrap(),
580                )
581            });
582            match pass {
583                TopologicalPass::Visit => {
584                    visit_postorder(parent_arg, graph.node_weight(current).unwrap(), state);
585                }
586                TopologicalPass::ExpandAndVisit => match graph.node_weight(current).unwrap() {
587                    current_node @ SingleModuleGraphNode::Module(_) => {
588                        let action = visit_preorder(parent_arg, current_node, state)?;
589                        if action == GraphTraversalAction::Exclude {
590                            continue;
591                        }
592                        stack.push((TopologicalPass::Visit, parent, current));
593                        if action == GraphTraversalAction::Continue && expanded.insert(current) {
594                            stack.extend(iter_neighbors_rev(graph, current).map(
595                                |(edge, child)| {
596                                    (
597                                        TopologicalPass::ExpandAndVisit,
598                                        Some((current, edge)),
599                                        child,
600                                    )
601                                },
602                            ));
603                        }
604                    }
605                    current_node @ SingleModuleGraphNode::VisitedModule { .. } => {
606                        visit_preorder(parent_arg, current_node, state)?;
607                        visit_postorder(parent_arg, current_node, state);
608                    }
609                },
610            }
611        }
612
613        Ok(())
614    }
615
616    pub fn traverse_cycles<'l>(
617        &'l self,
618        edge_filter: impl Fn(&'l ChunkingType) -> bool,
619        mut visit_cycle: impl FnMut(&[&'l SingleModuleGraphModuleNode]),
620    ) {
621        // see https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
622        // but iteratively instead of recursively
623
624        #[derive(Clone)]
625        struct NodeState {
626            index: u32,
627            lowlink: u32,
628            on_stack: bool,
629        }
630        enum VisitStep {
631            UnvisitedNode(NodeIndex),
632            EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
633            AfterVisit(NodeIndex),
634        }
635        let mut node_states = vec![None; self.graph.node_bound()];
636        let mut stack = Vec::new();
637        let mut visit_stack = Vec::new();
638        let mut index = 0;
639        let mut scc = Vec::new();
640        for initial_index in self.graph.node_indices() {
641            // Skip over already visited nodes
642            if node_states[initial_index.index()].is_some() {
643                continue;
644            }
645            visit_stack.push(VisitStep::UnvisitedNode(initial_index));
646            while let Some(step) = visit_stack.pop() {
647                match step {
648                    VisitStep::UnvisitedNode(node) => {
649                        node_states[node.index()] = Some(NodeState {
650                            index,
651                            lowlink: index,
652                            on_stack: true,
653                        });
654                        index += 1;
655                        stack.push(node);
656                        visit_stack.push(VisitStep::AfterVisit(node));
657                        let mut neighbors = self.graph.neighbors(node).detach();
658                        while let Some((edge, succ)) = neighbors.next(&self.graph) {
659                            let edge_weight = self.graph.edge_weight(edge).unwrap();
660                            if !edge_filter(edge_weight) {
661                                continue;
662                            }
663                            let node_state = &node_states[succ.index()];
664                            if let Some(node_state) = node_state {
665                                if node_state.on_stack {
666                                    let index = node_state.index;
667                                    let parent_state = node_states[node.index()].as_mut().unwrap();
668                                    parent_state.lowlink = parent_state.lowlink.min(index);
669                                }
670                            } else {
671                                visit_stack.push(VisitStep::EdgeAfterVisit {
672                                    parent: node,
673                                    child: succ,
674                                });
675                                visit_stack.push(VisitStep::UnvisitedNode(succ));
676                            }
677                        }
678                    }
679                    VisitStep::EdgeAfterVisit { parent, child } => {
680                        let child_state = node_states[child.index()].as_ref().unwrap();
681                        let lowlink = child_state.lowlink;
682
683                        let parent_state = node_states[parent.index()].as_mut().unwrap();
684                        parent_state.lowlink = parent_state.lowlink.min(lowlink);
685                    }
686                    VisitStep::AfterVisit(node) => {
687                        let node_state = node_states[node.index()].as_ref().unwrap();
688                        if node_state.lowlink == node_state.index {
689                            loop {
690                                let poppped = stack.pop().unwrap();
691                                let popped_state = node_states[poppped.index()].as_mut().unwrap();
692                                popped_state.on_stack = false;
693                                if let SingleModuleGraphNode::Module(module) =
694                                    self.graph.node_weight(poppped).unwrap()
695                                {
696                                    scc.push(module);
697                                }
698                                if poppped == node {
699                                    break;
700                                }
701                            }
702                            if scc.len() > 1 {
703                                visit_cycle(&scc);
704                            }
705                            scc.clear();
706                        }
707                    }
708                }
709            }
710        }
711    }
712}
713
714#[turbo_tasks::value(shared)]
715#[derive(Clone, Default)]
716pub struct ModuleGraph {
717    pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
718}
719
720#[turbo_tasks::value_impl]
721impl ModuleGraph {
722    #[turbo_tasks::function]
723    pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
724        Self { graphs }.cell()
725    }
726
727    #[turbo_tasks::function]
728    pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
729        Self {
730            graphs: vec![graph],
731        }
732        .cell()
733    }
734
735    #[turbo_tasks::function]
736    pub fn from_entry_module(
737        module: ResolvedVc<Box<dyn Module>>,
738        include_traced: bool,
739    ) -> Vc<Self> {
740        Self::from_single_graph(SingleModuleGraph::new_with_entries(
741            Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
742            include_traced,
743        ))
744    }
745
746    #[turbo_tasks::function]
747    pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
748        Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
749    }
750
751    #[turbo_tasks::function]
752    pub async fn chunk_group_info(&self) -> Result<Vc<ChunkGroupInfo>> {
753        compute_chunk_group_info(self).await
754    }
755
756    #[turbo_tasks::function]
757    pub async fn module_batches(
758        self: Vc<Self>,
759        config: Vc<BatchingConfig>,
760    ) -> Result<Vc<ModuleBatchesGraph>> {
761        compute_module_batches(self, &*config.await?).await
762    }
763
764    #[turbo_tasks::function]
765    pub async fn style_groups(
766        self: Vc<Self>,
767        chunking_context: Vc<Box<dyn ChunkingContext>>,
768        config: StyleGroupsConfig,
769    ) -> Result<Vc<StyleGroups>> {
770        compute_style_groups(self, chunking_context, &config).await
771    }
772
773    #[turbo_tasks::function]
774    pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
775        // `compute_async_module_info` calls `module.is_self_async()`, so we need to again ignore
776        // all issues such that they aren't emitted multiple times.
777        async move {
778            let result_op = compute_async_module_info(self.to_resolved().await?);
779            let result_vc = result_op.resolve_strongly_consistent().await?;
780            let _issues = result_op.take_collectibles::<Box<dyn Issue>>();
781            anyhow::Ok(*result_vc)
782        }
783        .instrument(tracing::info_span!("compute async module info"))
784        .await
785    }
786
787    #[turbo_tasks::function]
788    pub async fn referenced_async_modules(
789        self: Vc<Self>,
790        module: ResolvedVc<Box<dyn Module>>,
791    ) -> Result<Vc<AsyncModuleInfo>> {
792        let this = self.await?;
793        let graphs = this.get_graphs().await?;
794        let async_modules_info = self.async_module_info().await?;
795
796        let entry = ModuleGraph::get_entry(&graphs, module).await?;
797        let referenced_modules = iter_neighbors_rev(&graphs[entry.graph_idx].graph, entry.node_idx)
798            .filter(|(edge_idx, _)| {
799                let ty = graphs[entry.graph_idx]
800                    .graph
801                    .edge_weight(*edge_idx)
802                    .unwrap();
803                ty.is_inherit_async()
804            })
805            .map(|(_, child_idx)| {
806                anyhow::Ok(
807                    get_node!(
808                        graphs,
809                        GraphNodeIndex {
810                            graph_idx: entry.graph_idx,
811                            node_idx: child_idx
812                        }
813                    )?
814                    .module,
815                )
816            })
817            .collect::<Result<Vec<_>>>()?
818            .into_iter()
819            .rev()
820            .filter(|m| async_modules_info.contains(m))
821            .map(|m| *m)
822            .collect();
823
824        Ok(AsyncModuleInfo::new(referenced_modules))
825    }
826}
827
828// fn get_node<T>(
829//     graphs: Vec<ReadRef<SingleModuleGraph>>,
830//     node: GraphNodeIndex,
831// ) -> Result<&'static SingleModuleGraphModuleNode> {
832macro_rules! get_node {
833    ($graphs:expr, $node:expr) => {{
834        let node_idx = $node;
835        match $graphs[node_idx.graph_idx]
836            .graph
837            .node_weight(node_idx.node_idx)
838        {
839            Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
840            Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
841                match $graphs[idx.graph_idx].graph.node_weight(idx.node_idx) {
842                    Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
843                    Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
844                        "Expected visited target node to be module"
845                    )),
846                    None => Err(::anyhow::anyhow!("Expected visited target node")),
847                }
848            }
849            None => Err(::anyhow::anyhow!("Expected graph node")),
850        }
851    }};
852}
853pub(crate) use get_node;
854macro_rules! get_node_idx {
855    ($graphs:expr, $node:expr) => {{
856        let node_idx = $node;
857        match $graphs[node_idx.graph_idx]
858            .graph
859            .node_weight(node_idx.node_idx)
860        {
861            Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, node_idx)),
862            Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
863                match $graphs[idx.graph_idx].graph.node_weight(idx.node_idx) {
864                    Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, *idx)),
865                    Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
866                        "Expected visited target node to be module"
867                    )),
868                    None => Err(::anyhow::anyhow!("Expected visited target node")),
869                }
870            }
871            None => Err(::anyhow::anyhow!("Expected graph node")),
872        }
873    }};
874}
875pub(crate) use get_node_idx;
876
877impl ModuleGraph {
878    pub async fn get_graphs(&self) -> Result<Vec<ReadRef<SingleModuleGraph>>> {
879        self.graphs.iter().try_join().await
880    }
881
882    async fn get_entry(
883        graphs: &[ReadRef<SingleModuleGraph>],
884        entry: ResolvedVc<Box<dyn Module>>,
885    ) -> Result<GraphNodeIndex> {
886        let Some(idx) = graphs.iter().enumerate().find_map(|(graph_idx, graph)| {
887            graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
888                graph_idx,
889                node_idx: *node_idx,
890            })
891        }) else {
892            bail!(
893                "Couldn't find entry module {} in module graph (potential entries: {:?})",
894                entry.ident().to_string().await?,
895                graphs
896                    .iter()
897                    .flat_map(|g| g.entries.iter())
898                    .flat_map(|e| e.entries())
899                    .map(|e| e.ident().to_string())
900                    .try_join()
901                    .await?
902            );
903        };
904        Ok(idx)
905    }
906
907    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
908    /// target.
909    ///
910    /// This means that target nodes can be revisited (once per incoming edge).
911    ///
912    /// * `entry` - The entry module to start the traversal from
913    /// * `visitor` - Called before visiting the children of a node.
914    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
915    ///      &SingleModuleGraphNode, state &S
916    ///    - Can return [GraphTraversalAction]s to control the traversal
917    pub async fn traverse_edges_from_entries_bfs(
918        &self,
919        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
920        mut visitor: impl FnMut(
921            Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
922            &'_ SingleModuleGraphModuleNode,
923        ) -> GraphTraversalAction,
924    ) -> Result<()> {
925        let graphs = self.get_graphs().await?;
926
927        let mut queue = VecDeque::from(
928            entries
929                .into_iter()
930                .map(|e| ModuleGraph::get_entry(&graphs, e))
931                .try_join()
932                .await?,
933        );
934        let mut visited = HashSet::new();
935        for entry_node in &queue {
936            visitor(None, get_node!(graphs, entry_node)?);
937        }
938        while let Some(node) = queue.pop_front() {
939            let graph = &graphs[node.graph_idx].graph;
940            let node_weight = get_node!(graphs, node)?;
941            if visited.insert(node) {
942                let neighbors = iter_neighbors_rev(graph, node.node_idx);
943
944                for (edge, succ) in neighbors {
945                    let succ = GraphNodeIndex {
946                        graph_idx: node.graph_idx,
947                        node_idx: succ,
948                    };
949                    let succ_weight = get_node!(graphs, succ)?;
950                    let edge_weight = graph.edge_weight(edge).unwrap();
951                    let action = visitor(Some((node_weight, edge_weight)), succ_weight);
952                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
953                        queue.push_back(succ);
954                    }
955                }
956            }
957        }
958
959        Ok(())
960    }
961
962    /// Traverses all reachable edges exactly once and calls the visitor with the edge source and
963    /// target.
964    ///
965    /// This means that target nodes can be revisited (once per incoming edge).
966    ///
967    /// * `entry` - The entry module to start the traversal from
968    /// * `visitor` - Called before visiting the children of a node.
969    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
970    ///      &SingleModuleGraphNode, state &S
971    ///    - Can return [GraphTraversalAction]s to control the traversal
972    pub async fn traverse_edges_from_entry(
973        &self,
974        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
975        mut visitor: impl FnMut(
976            Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
977            &'_ SingleModuleGraphModuleNode,
978        ) -> GraphTraversalAction,
979    ) -> Result<()> {
980        let graphs = self.get_graphs().await?;
981
982        let entries = entries.into_iter();
983        let mut stack = Vec::with_capacity(entries.size_hint().0);
984        for entry in entries {
985            stack.push(ModuleGraph::get_entry(&graphs, entry).await?);
986        }
987        let mut visited = HashSet::new();
988        for entry_node in &stack {
989            visitor(None, get_node!(graphs, entry_node)?);
990        }
991        while let Some(node) = stack.pop() {
992            let graph = &graphs[node.graph_idx].graph;
993            let node_weight = get_node!(graphs, node)?;
994            if visited.insert(node) {
995                let neighbors = iter_neighbors_rev(graph, node.node_idx);
996
997                for (edge, succ) in neighbors {
998                    let succ = GraphNodeIndex {
999                        graph_idx: node.graph_idx,
1000                        node_idx: succ,
1001                    };
1002                    let succ_weight = get_node!(graphs, succ)?;
1003                    let edge_weight = graph.edge_weight(edge).unwrap();
1004                    let action = visitor(Some((node_weight, edge_weight)), succ_weight);
1005                    if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1006                        stack.push(succ);
1007                    }
1008                }
1009            }
1010        }
1011
1012        Ok(())
1013    }
1014
1015    /// Traverses all edges exactly once (in an unspecified order) and calls the visitor with the
1016    /// edge source and target.
1017    ///
1018    /// This means that target nodes can be revisited (once per incoming edge).
1019    ///
1020    /// * `visitor` - Called before visiting the children of a node.
1021    ///    - Receives (originating &SingleModuleGraphNode, edge &ChunkingType), target
1022    ///      &SingleModuleGraphNode
1023    pub async fn traverse_all_edges_unordered(
1024        &self,
1025        mut visitor: impl FnMut(
1026            (&'_ SingleModuleGraphModuleNode, &'_ ChunkingType),
1027            &'_ SingleModuleGraphModuleNode,
1028        ) -> Result<()>,
1029    ) -> Result<()> {
1030        let graphs = self.get_graphs().await?;
1031
1032        for graph in &graphs {
1033            let graph = &graph.graph;
1034            for edge in graph.edge_references() {
1035                let source = match graph.node_weight(edge.source()).unwrap() {
1036                    SingleModuleGraphNode::Module(node) => node,
1037                    SingleModuleGraphNode::VisitedModule { .. } => unreachable!(),
1038                };
1039                let target = match graph.node_weight(edge.target()).unwrap() {
1040                    SingleModuleGraphNode::Module(node) => node,
1041                    SingleModuleGraphNode::VisitedModule { idx, .. } => get_node!(graphs, idx)?,
1042                };
1043                visitor((source, edge.weight()), target)?;
1044            }
1045        }
1046
1047        Ok(())
1048    }
1049
1050    /// Traverses all reachable edges in topological order. The preorder visitor can be used to
1051    /// forward state down the graph, and to skip subgraphs
1052    ///
1053    /// Use this to collect modules in evaluation order.
1054    ///
1055    /// Target nodes can be revisited (once per incoming edge).
1056    /// Edges are traversed in normal order, so should correspond to reference order.
1057    ///
1058    /// * `entry` - The entry module to start the traversal from
1059    /// * `state` - The state to be passed to the visitors
1060    /// * `visit_preorder` - Called before visiting the children of a node.
1061    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1062    ///      &SingleModuleGraphNode, state &S
1063    ///    - Can return [GraphTraversalAction]s to control the traversal
1064    /// * `visit_postorder` - Called after visiting the children of a node. Return
1065    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1066    ///      &SingleModuleGraphNode, state &S
1067    ///    - Can return [GraphTraversalAction]s to control the traversal
1068    pub async fn traverse_edges_from_entries_topological<S>(
1069        &self,
1070        entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1071        state: &mut S,
1072        mut visit_preorder: impl FnMut(
1073            Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1074            &'_ SingleModuleGraphModuleNode,
1075            &mut S,
1076        ) -> Result<GraphTraversalAction>,
1077        mut visit_postorder: impl FnMut(
1078            Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1079            &'_ SingleModuleGraphModuleNode,
1080            &mut S,
1081        ),
1082    ) -> Result<()> {
1083        let graphs = self.get_graphs().await?;
1084
1085        let entries = entries.into_iter().collect::<Vec<_>>();
1086
1087        enum TopologicalPass {
1088            Visit,
1089            ExpandAndVisit,
1090        }
1091        #[allow(clippy::type_complexity)] // This is a temporary internal structure
1092        let mut stack: Vec<(
1093            TopologicalPass,
1094            Option<(GraphNodeIndex, EdgeIndex)>,
1095            GraphNodeIndex,
1096        )> = Vec::with_capacity(entries.len());
1097        for entry in entries.into_iter().rev() {
1098            stack.push((
1099                TopologicalPass::ExpandAndVisit,
1100                None,
1101                ModuleGraph::get_entry(&graphs, entry).await?,
1102            ));
1103        }
1104        let mut expanded = HashSet::new();
1105        while let Some((pass, parent, current)) = stack.pop() {
1106            let parent_arg = match parent {
1107                Some((parent_node, parent_edge)) => Some((
1108                    get_node!(graphs, parent_node)?,
1109                    graphs[parent_node.graph_idx]
1110                        .graph
1111                        .edge_weight(parent_edge)
1112                        .unwrap(),
1113                )),
1114                None => None,
1115            };
1116            let current_node = get_node!(graphs, current)?;
1117            match pass {
1118                TopologicalPass::Visit => {
1119                    visit_postorder(parent_arg, current_node, state);
1120                }
1121                TopologicalPass::ExpandAndVisit => {
1122                    let action = visit_preorder(parent_arg, current_node, state)?;
1123                    if action == GraphTraversalAction::Exclude {
1124                        continue;
1125                    }
1126                    stack.push((TopologicalPass::Visit, parent, current));
1127                    if action == GraphTraversalAction::Continue && expanded.insert(current) {
1128                        let graph = &graphs[current.graph_idx].graph;
1129                        let (neighbors_rev, current) =
1130                            match graph.node_weight(current.node_idx).unwrap() {
1131                                SingleModuleGraphNode::Module(_) => {
1132                                    (iter_neighbors_rev(graph, current.node_idx), current)
1133                                }
1134                                SingleModuleGraphNode::VisitedModule { idx, .. } => (
1135                                    // We switch graphs
1136                                    iter_neighbors_rev(&graphs[idx.graph_idx].graph, idx.node_idx),
1137                                    *idx,
1138                                ),
1139                            };
1140                        stack.extend(neighbors_rev.map(|(edge, child)| {
1141                            (
1142                                TopologicalPass::ExpandAndVisit,
1143                                Some((current, edge)),
1144                                GraphNodeIndex {
1145                                    graph_idx: current.graph_idx,
1146                                    node_idx: child,
1147                                },
1148                            )
1149                        }));
1150                    }
1151                }
1152            }
1153        }
1154
1155        Ok(())
1156    }
1157
1158    /// Traverse all cycles in the graph (where the edge filter returns true for the whole cycle)
1159    /// and call the visitor with the nodes in the cycle.
1160    pub async fn traverse_cycles(
1161        &self,
1162        edge_filter: impl Fn(&ChunkingType) -> bool,
1163        mut visit_cycle: impl FnMut(&[&SingleModuleGraphModuleNode]),
1164    ) -> Result<()> {
1165        for graph in &self.graphs {
1166            graph.await?.traverse_cycles(&edge_filter, &mut visit_cycle);
1167        }
1168        Ok(())
1169    }
1170
1171    /// Traverses all reachable nodes and also continue revisiting them as long the visitor returns
1172    /// GraphTraversalAction::Continue. The visitor is responsible for the runtime complexity and
1173    /// eventual termination of the traversal. This corresponds to computing a fixed point state for
1174    /// the graph.
1175    ///
1176    /// Nodes are (re)visited according to the returned priority of the node, prioritizing high
1177    /// values. This priority is intended to be used a heuristic to reduce the number of
1178    /// retraversals.
1179    ///
1180    /// * `entries` - The entry modules to start the traversal from
1181    /// * `state` - The state to be passed to the callbacks
1182    /// * `visit` - Called for a specific edge
1183    ///    - Receives: (originating &SingleModuleGraphNode, edge &ChunkingType), target
1184    ///      &SingleModuleGraphNode, state &S
1185    ///    - Return [GraphTraversalAction]s to control the traversal
1186    /// * `priority` - Called for before visiting the children of a node to determine its priority.
1187    ///    - Receives: target &SingleModuleGraphNode, state &S
1188    ///    - Return a priority value for the node
1189    ///
1190    /// Returns the number of node visits (i.e. higher than the node count if there are
1191    /// retraversals).
1192    pub async fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1193        &self,
1194        entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1195        state: &mut S,
1196        mut visit: impl FnMut(
1197            Option<(&'_ SingleModuleGraphModuleNode, &'_ ChunkingType)>,
1198            &'_ SingleModuleGraphModuleNode,
1199            &mut S,
1200        ) -> GraphTraversalAction,
1201        priority: impl Fn(&'_ SingleModuleGraphModuleNode, &mut S) -> P,
1202    ) -> Result<usize> {
1203        let graphs = self.get_graphs().await?;
1204
1205        #[derive(PartialEq, Eq)]
1206        struct NodeWithPriority<T: Ord> {
1207            node: GraphNodeIndex,
1208            priority: T,
1209        }
1210        impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1211            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1212                Some(self.cmp(other))
1213            }
1214        }
1215        impl<T: Ord> Ord for NodeWithPriority<T> {
1216            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1217                // BinaryHeap prioritizes high values
1218
1219                self.priority
1220                    .cmp(&other.priority)
1221                    // include GraphNodeIndex for total and deterministic ordering
1222                    .then(other.node.cmp(&self.node))
1223            }
1224        }
1225
1226        let mut queue_set = FxHashSet::default();
1227        let mut queue = BinaryHeap::from_iter(
1228            entries
1229                .into_iter()
1230                .map(async |(m, priority)| {
1231                    Ok(NodeWithPriority {
1232                        node: ModuleGraph::get_entry(&graphs, m).await?,
1233                        priority,
1234                    })
1235                })
1236                .try_join()
1237                .await?,
1238        );
1239        for entry_node in &queue {
1240            visit(None, get_node!(graphs, entry_node.node)?, state);
1241        }
1242
1243        let mut visit_count = 0usize;
1244        while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1245            queue_set.remove(&node);
1246            let (node_weight, node) = get_node_idx!(graphs, node)?;
1247            let graph = &graphs[node.graph_idx].graph;
1248            let neighbors = iter_neighbors_rev(graph, node.node_idx);
1249
1250            visit_count += 1;
1251
1252            for (edge, succ) in neighbors {
1253                let succ = GraphNodeIndex {
1254                    graph_idx: node.graph_idx,
1255                    node_idx: succ,
1256                };
1257                let (succ_weight, succ) = get_node_idx!(graphs, succ)?;
1258                let edge_weight = graph.edge_weight(edge).unwrap();
1259                let action = visit(Some((node_weight, edge_weight)), succ_weight, state);
1260
1261                if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1262                    queue.push(NodeWithPriority {
1263                        node: succ,
1264                        priority: priority(succ_weight, state),
1265                    });
1266                }
1267            }
1268        }
1269
1270        Ok(visit_count)
1271    }
1272}
1273
1274#[turbo_tasks::value_impl]
1275impl SingleModuleGraph {
1276    #[turbo_tasks::function]
1277    pub async fn new_with_entries(
1278        entries: Vc<GraphEntries>,
1279        include_traced: bool,
1280    ) -> Result<Vc<Self>> {
1281        SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1282    }
1283
1284    #[turbo_tasks::function]
1285    pub async fn new_with_entries_visited(
1286        entries: Vc<GraphEntries>,
1287        visited_modules: Vc<VisitedModules>,
1288        include_traced: bool,
1289    ) -> Result<Vc<Self>> {
1290        SingleModuleGraph::new_inner(
1291            &*entries.await?,
1292            &visited_modules.await?.modules,
1293            include_traced,
1294        )
1295        .await
1296    }
1297
1298    #[turbo_tasks::function]
1299    pub async fn new_with_entries_visited_intern(
1300        // This must not be a Vc<Vec<_>> to ensure layout segment optimization hits the cache
1301        entries: GraphEntriesT,
1302        visited_modules: Vc<VisitedModules>,
1303        include_traced: bool,
1304    ) -> Result<Vc<Self>> {
1305        SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1306            .await
1307    }
1308}
1309
1310#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1311pub struct SingleModuleGraphModuleNode {
1312    pub module: ResolvedVc<Box<dyn Module>>,
1313    pub layer: Option<ReadRef<RcStr>>,
1314    pub issues: Vec<ResolvedVc<Box<dyn Issue>>>,
1315}
1316
1317#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1318pub enum SingleModuleGraphNode {
1319    Module(SingleModuleGraphModuleNode),
1320    VisitedModule {
1321        idx: GraphNodeIndex,
1322        module: ResolvedVc<Box<dyn Module>>,
1323    },
1324}
1325
1326impl SingleModuleGraphNode {
1327    pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1328        match self {
1329            SingleModuleGraphNode::Module(SingleModuleGraphModuleNode { module, .. }) => *module,
1330            SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1331        }
1332    }
1333
1334    // fn emit_issues(&self) {
1335    //     match self {
1336    //         SingleModuleGraphNode::Module { issues, .. } => {
1337    //             for issue in issues {
1338    //                 issue.emit();
1339    //             }
1340    //         }
1341    //         SingleModuleGraphNode::VisitedModule { .. } => todo!(),
1342    //     }
1343    // }
1344}
1345
1346#[derive(PartialEq, Eq, Debug)]
1347pub enum GraphTraversalAction {
1348    /// Continue visiting children
1349    Continue,
1350    /// Skip the immediate children, but visit the node in postorder
1351    Skip,
1352    /// Skip the immediate children and the node in postorder
1353    Exclude,
1354}
1355
1356// These nodes are created while walking the Turbopack modules references, and are used to then
1357// afterwards build the SingleModuleGraph.
1358#[derive(Clone, Hash, PartialEq, Eq)]
1359enum SingleModuleGraphBuilderNode {
1360    /// This edge is represented as a node: source Module -> ChunkableReference ->  target Module
1361    ChunkableReference {
1362        chunking_type: ChunkingType,
1363        source: ResolvedVc<Box<dyn Module>>,
1364        source_ident: ReadRef<RcStr>,
1365        target: ResolvedVc<Box<dyn Module>>,
1366        target_ident: ReadRef<RcStr>,
1367        target_layer: Option<ReadRef<RcStr>>,
1368    },
1369    /// A regular module
1370    Module {
1371        module: ResolvedVc<Box<dyn Module>>,
1372        layer: Option<ReadRef<RcStr>>,
1373        ident: ReadRef<RcStr>,
1374    },
1375    /// A reference to a module that is already listed in visited_modules
1376    VisitedModule {
1377        module: ResolvedVc<Box<dyn Module>>,
1378        idx: GraphNodeIndex,
1379    },
1380    /// Issues to be added to the parent Module node
1381    #[allow(dead_code)]
1382    Issues(Vec<ResolvedVc<Box<dyn Issue>>>),
1383}
1384
1385impl SingleModuleGraphBuilderNode {
1386    async fn new_module(module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1387        let ident = module.ident();
1388        Ok(Self::Module {
1389            module,
1390            layer: match ident.await?.layer {
1391                Some(layer) => Some(layer.await?),
1392                None => None,
1393            },
1394            ident: ident.to_string().await?,
1395        })
1396    }
1397    async fn new_chunkable_ref(
1398        source: ResolvedVc<Box<dyn Module>>,
1399        target: ResolvedVc<Box<dyn Module>>,
1400        chunking_type: ChunkingType,
1401    ) -> Result<Self> {
1402        Ok(Self::ChunkableReference {
1403            chunking_type,
1404            source,
1405            source_ident: source.ident().to_string().await?,
1406            target,
1407            target_ident: target.ident().to_string().await?,
1408            target_layer: match target.ident().await?.layer {
1409                Some(layer) => Some(layer.await?),
1410                None => None,
1411            },
1412        })
1413    }
1414    fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1415        Self::VisitedModule { module, idx }
1416    }
1417}
1418struct SingleModuleGraphBuilderEdge {
1419    to: SingleModuleGraphBuilderNode,
1420}
1421
1422/// The chunking type that occurs most often, is handled more efficiently by not creating
1423/// intermediate SingleModuleGraphBuilderNode::ChunkableReference nodes.
1424const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1425    inherit_async: true,
1426    hoisted: true,
1427};
1428
1429struct SingleModuleGraphBuilder<'a> {
1430    visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1431    /// Whether to walk ChunkingType::Traced references
1432    include_traced: bool,
1433}
1434impl Visit<SingleModuleGraphBuilderNode> for SingleModuleGraphBuilder<'_> {
1435    type Edge = SingleModuleGraphBuilderEdge;
1436    type EdgesIntoIter = Vec<Self::Edge>;
1437    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1438
1439    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<SingleModuleGraphBuilderNode> {
1440        match edge.to {
1441            SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue(edge.to),
1442            SingleModuleGraphBuilderNode::ChunkableReference {
1443                ref chunking_type, ..
1444            } => match chunking_type {
1445                ChunkingType::Traced => VisitControlFlow::Skip(edge.to),
1446                _ => VisitControlFlow::Continue(edge.to),
1447            },
1448            // Module was already visited previously
1449            SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip(edge.to),
1450            // Issues doen't have any children
1451            SingleModuleGraphBuilderNode::Issues(_) => VisitControlFlow::Skip(edge.to),
1452        }
1453    }
1454
1455    fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1456        // Destructure beforehand to not have to clone the whole node when entering the async block
1457        let (module, chunkable_ref_target) = match node {
1458            SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1459            SingleModuleGraphBuilderNode::ChunkableReference { target, .. } => {
1460                (None, Some(*target))
1461            }
1462            // These are always skipped in `visit()`
1463            SingleModuleGraphBuilderNode::VisitedModule { .. }
1464            | SingleModuleGraphBuilderNode::Issues(_) => unreachable!(),
1465        };
1466        let visited_modules = self.visited_modules;
1467        let include_traced = self.include_traced;
1468        async move {
1469            Ok(match (module, chunkable_ref_target) {
1470                (Some(module), None) => {
1471                    let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1472                    let refs = match refs_cell.await {
1473                        Ok(refs) => refs,
1474                        Err(e) => {
1475                            return Err(e.context(module.ident().to_string().await?));
1476                        }
1477                    };
1478                    // TODO This is currently too slow
1479                    // let refs_issues = refs_cell
1480                    //     .take_collectibles::<Box<dyn Issue>>()
1481                    //     .iter()
1482                    //     .map(|issue| issue.to_resolved())
1483                    //     .try_join()
1484                    // .await?;
1485
1486                    refs.iter()
1487                        .flat_map(|(ty, modules)| modules.iter().map(|m| (ty.clone(), *m)))
1488                        .map(async |(ty, target)| {
1489                            let to = if ty == COMMON_CHUNKING_TYPE {
1490                                if let Some(idx) = visited_modules.get(&target) {
1491                                    SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1492                                } else {
1493                                    SingleModuleGraphBuilderNode::new_module(target).await?
1494                                }
1495                            } else {
1496                                SingleModuleGraphBuilderNode::new_chunkable_ref(module, target, ty)
1497                                    .await?
1498                            };
1499                            Ok(SingleModuleGraphBuilderEdge { to })
1500                        })
1501                        .try_join()
1502                        .await?
1503                }
1504                (None, Some(chunkable_ref_target)) => {
1505                    vec![SingleModuleGraphBuilderEdge {
1506                        to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1507                            SingleModuleGraphBuilderNode::new_visited_module(
1508                                chunkable_ref_target,
1509                                *idx,
1510                            )
1511                        } else {
1512                            SingleModuleGraphBuilderNode::new_module(chunkable_ref_target).await?
1513                        },
1514                    }]
1515                }
1516                _ => unreachable!(),
1517            })
1518        }
1519    }
1520
1521    fn span(&mut self, node: &SingleModuleGraphBuilderNode) -> tracing::Span {
1522        match node {
1523            SingleModuleGraphBuilderNode::Module { ident, .. } => {
1524                tracing::info_span!("module", name = display(ident))
1525            }
1526            SingleModuleGraphBuilderNode::Issues(_) => {
1527                tracing::info_span!("issues")
1528            }
1529            SingleModuleGraphBuilderNode::ChunkableReference {
1530                chunking_type,
1531                source_ident,
1532                target_ident,
1533                ..
1534            } => match chunking_type {
1535                ChunkingType::Parallel {
1536                    inherit_async: false,
1537                    ..
1538                } => Span::current(),
1539                _ => {
1540                    tracing::info_span!(
1541                        "chunkable reference",
1542                        ty = debug(chunking_type),
1543                        source = display(source_ident),
1544                        target = display(target_ident)
1545                    )
1546                }
1547            },
1548            SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1549                tracing::info_span!("visited module")
1550            }
1551        }
1552    }
1553}