1use std::{
2 collections::{BinaryHeap, HashSet, VecDeque, hash_map::Entry},
3 future::Future,
4};
5
6use anyhow::{Context, Result, bail};
7use auto_hash_map::AutoSet;
8use petgraph::{
9 graph::{DiGraph, EdgeIndex, NodeIndex},
10 visit::{
11 Dfs, EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed, VisitMap,
12 Visitable,
13 },
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, 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 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
35 merged_modules::{MergedModuleInfo, compute_merged_modules},
36 module_batches::{ModuleBatchesGraph, compute_module_batches},
37 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
38 traced_di_graph::{TracedDiGraph, iter_neighbors_rev},
39 },
40 reference::primary_chunkable_referenced_modules,
41 resolve::ExportUsage,
42};
43
44pub mod async_module_info;
45pub mod chunk_group_info;
46pub mod export_usage;
47pub mod merged_modules;
48pub mod module_batch;
49pub(crate) mod module_batches;
50pub(crate) mod style_groups;
51mod traced_di_graph;
52
53pub use self::module_batches::BatchingConfig;
54
55#[derive(
56 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, TraceRawVcs,
57)]
58pub struct GraphNodeIndex {
59 #[turbo_tasks(trace_ignore)]
60 graph_idx: u32,
61 #[turbo_tasks(trace_ignore)]
62 node_idx: NodeIndex,
63}
64impl GraphNodeIndex {
65 #[inline(always)]
66 fn graph_idx(&self) -> usize {
67 self.graph_idx as usize
68 }
69}
70
71unsafe impl NonLocalValue for GraphNodeIndex {}
72
73#[turbo_tasks::value]
74#[derive(Clone, Debug)]
75pub struct VisitedModules {
76 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
77 next_graph_idx: u32,
78}
79
80#[turbo_tasks::value_impl]
81impl VisitedModules {
82 #[turbo_tasks::function]
83 pub fn empty() -> Vc<Self> {
84 Self {
85 modules: Default::default(),
86 next_graph_idx: 0,
87 }
88 .cell()
89 }
90
91 #[turbo_tasks::function]
92 pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
93 Ok(Self {
94 modules: graph
95 .await?
96 .enumerate_nodes()
97 .flat_map(|(node_idx, module)| match module {
98 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
99 module, ..
100 }) => Some((
101 *module,
102 GraphNodeIndex {
103 graph_idx: 0,
104 node_idx,
105 },
106 )),
107 SingleModuleGraphNode::VisitedModule { .. } => None,
108 })
109 .collect(),
110 next_graph_idx: 1,
111 }
112 .cell())
113 }
114
115 #[turbo_tasks::function]
116 pub fn with_incremented_index(&self) -> Result<Vc<Self>> {
117 Ok(Self {
118 modules: self.modules.clone(),
119 next_graph_idx: self.next_graph_idx + 1,
120 }
121 .cell())
122 }
123
124 #[turbo_tasks::function]
125 pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
126 let graph = graph.await?;
127 let iter = self
128 .modules
129 .iter()
130 .map(|(module, idx)| (*module, *idx))
131 .chain(
132 graph
133 .enumerate_nodes()
134 .flat_map(|(node_idx, module)| match module {
135 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
136 module,
137 ..
138 }) => Some((
139 *module,
140 GraphNodeIndex {
141 graph_idx: self.next_graph_idx,
142 node_idx,
143 },
144 )),
145 SingleModuleGraphNode::VisitedModule { .. } => None,
146 }),
147 );
148
149 let mut map = FxIndexMap::with_capacity_and_hasher(
150 self.modules.len() + graph.number_of_modules,
151 Default::default(),
152 );
153 for (k, v) in iter {
154 map.entry(k).or_insert(v);
155 }
156 map.shrink_to_fit();
157
158 Ok(Self {
159 modules: map,
160 next_graph_idx: self.next_graph_idx + 1,
161 }
162 .cell())
163 }
164}
165
166pub type GraphEntriesT = Vec<ChunkGroupEntry>;
167
168#[turbo_tasks::value(transparent)]
169pub struct GraphEntries(GraphEntriesT);
170
171#[turbo_tasks::value_impl]
172impl GraphEntries {
173 #[turbo_tasks::function]
174 pub fn empty() -> Vc<Self> {
175 Vc::cell(Vec::new())
176 }
177}
178
179#[turbo_tasks::value(cell = "new", eq = "manual")]
180#[derive(Clone, Default)]
181pub struct SingleModuleGraph {
182 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
183
184 pub number_of_modules: usize,
186
187 #[turbo_tasks(trace_ignore)]
194 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
195
196 #[turbo_tasks(trace_ignore)]
197 pub entries: GraphEntriesT,
198}
199
200#[derive(
201 Debug,
202 Clone,
203 Hash,
204 TraceRawVcs,
205 Serialize,
206 Deserialize,
207 Eq,
208 PartialEq,
209 ValueDebugFormat,
210 NonLocalValue,
211)]
212pub struct RefData {
213 pub chunking_type: ChunkingType,
214 pub export: ExportUsage,
215}
216
217impl SingleModuleGraph {
218 async fn new_inner(
222 entries: &GraphEntriesT,
223 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
224 include_traced: bool,
225 ) -> Result<Vc<Self>> {
226 let emit_spans = tracing::enabled!(Level::INFO);
227 let root_edges = entries
228 .iter()
229 .flat_map(|e| e.entries())
230 .map(|e| async move {
231 Ok(SingleModuleGraphBuilderEdge {
232 to: SingleModuleGraphBuilderNode::new_module(emit_spans, e).await?,
233 export: ExportUsage::All,
234 })
235 })
236 .try_join()
237 .await?;
238
239 let (children_nodes_iter, visited_nodes) = AdjacencyMap::new()
240 .skip_duplicates_with_key(|node: &(SingleModuleGraphBuilderNode, ExportUsage)| &node.0)
241 .visit(
242 root_edges,
243 SingleModuleGraphBuilder {
244 visited_modules,
245 emit_spans,
246 include_traced,
247 },
248 )
249 .await
250 .completed()?
251 .into_inner_with_visited();
252 let node_count = visited_nodes.0.len();
253 drop(visited_nodes);
254
255 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
256 node_count,
257 node_count * 4,
260 );
261
262 let mut number_of_modules = 0;
263 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
264 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
265 {
266 let _span = tracing::info_span!("build module graph").entered();
267 for (parent, (current, export)) in children_nodes_iter.into_breadth_first_edges() {
268 let parent_edge = match parent.map(|v| v.0) {
269 Some(SingleModuleGraphBuilderNode::Module { module, .. }) => Some((
270 *modules.get(&module).unwrap(),
271 RefData {
272 chunking_type: COMMON_CHUNKING_TYPE,
273 export,
274 },
275 )),
276 Some(SingleModuleGraphBuilderNode::ChunkableReference { .. }) => {
277 continue;
279 }
280 Some(SingleModuleGraphBuilderNode::VisitedModule { .. }) => unreachable!(),
281 None => None,
282 };
283
284 match current {
285 SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
286 let current_idx = if let Some(current_idx) = modules.get(&module) {
288 *current_idx
289 } else {
290 let idx = graph.add_node(SingleModuleGraphNode::Module(
291 SingleModuleGraphModuleNode { module },
292 ));
293 number_of_modules += 1;
294 modules.insert(module, idx);
295 idx
296 };
297 if let Some((parent_idx, ref_data)) = parent_edge {
299 graph.add_edge(parent_idx, current_idx, ref_data);
300 }
301 }
302 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => {
303 let current_idx = if let Some(current_idx) = modules.get(&module) {
305 *current_idx
306 } else {
307 let idx = graph
308 .add_node(SingleModuleGraphNode::VisitedModule { idx, module });
309 modules.insert(module, idx);
310 idx
311 };
312 if let Some((parent_idx, data)) = parent_edge {
314 graph.add_edge(parent_idx, current_idx, data);
315 }
316 }
317 SingleModuleGraphBuilderNode::ChunkableReference {
318 source,
319 target,
320 ref_data,
321 ..
322 } => {
323 let target_idx = if let Some(target_idx) = modules.get(&target) {
325 *target_idx
326 } else {
327 let target_idx = visited_modules.get(&target);
328 let idx = graph.add_node(match target_idx {
329 Some(idx) => SingleModuleGraphNode::VisitedModule {
330 idx: *idx,
331 module: target,
332 },
333 None => {
334 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode {
335 module: target,
336 })
337 }
338 });
339 modules.insert(target, idx);
340 idx
341 };
342 graph.add_edge(*modules.get(&source).unwrap(), target_idx, ref_data);
343 }
344 }
345 }
346 }
347
348 graph.shrink_to_fit();
349
350 #[cfg(debug_assertions)]
351 {
352 use once_cell::sync::Lazy;
353 static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
354 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
355 Some(v) => v != "1" && v != "true",
356 None => true,
357 }
358 });
359 if *CHECK_FOR_DUPLICATE_MODULES {
360 let mut duplicates = Vec::new();
361 let mut set = FxHashSet::default();
362 for &module in modules.keys() {
363 let ident = module.ident().to_string().await?;
364 if !set.insert(ident.clone()) {
365 duplicates.push(ident)
366 }
367 }
368 if !duplicates.is_empty() {
369 panic!("Duplicate module idents in graph: {duplicates:#?}");
370 }
371 }
372 }
373
374 let graph = SingleModuleGraph {
375 graph: TracedDiGraph::new(graph),
376 number_of_modules,
377 modules,
378 entries: entries.clone(),
379 }
380 .cell();
381
382 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
383 ModuleGraphImportTracer::new(graph).to_resolved().await?,
384 ));
385 Ok(graph)
386 }
387
388 fn get_module(&self, module: ResolvedVc<Box<dyn Module>>) -> Result<NodeIndex> {
389 self.modules
390 .get(&module)
391 .copied()
392 .context("Couldn't find module in graph")
393 }
394
395 pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ SingleModuleGraphModuleNode> + '_ {
397 self.graph.node_weights().filter_map(|n| match n {
398 SingleModuleGraphNode::Module(node) => Some(node),
399 SingleModuleGraphNode::VisitedModule { .. } => None,
400 })
401 }
402
403 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
405 if let Some(index) = self.modules.get(&module) {
406 self.graph
407 .edges_directed(*index, petgraph::Direction::Incoming)
408 .next()
409 .is_none()
410 } else {
411 false
412 }
413 }
414
415 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
417 self.entries.iter().flat_map(|e| e.entries())
418 }
419
420 pub fn enumerate_nodes(
422 &self,
423 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
424 self.graph.node_references()
425 }
426
427 pub fn traverse_from_entry<'a>(
429 &'a self,
430 entry: ResolvedVc<Box<dyn Module>>,
431 mut visitor: impl FnMut(&'a SingleModuleGraphModuleNode),
432 ) -> Result<()> {
433 let entry_node = self.get_module(entry)?;
434
435 let mut dfs = Dfs::new(&*self.graph, entry_node);
436 while let Some(nx) = dfs.next(&*self.graph) {
437 let SingleModuleGraphNode::Module(weight) = self.graph.node_weight(nx).unwrap() else {
438 return Ok(());
439 };
440 visitor(weight);
442 }
443 Ok(())
444 }
445
446 pub fn traverse_nodes_from_entries<'a, S>(
448 &'a self,
449 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
450 state: &mut S,
451 visit_preorder: impl Fn(&'a SingleModuleGraphModuleNode, &mut S) -> Result<GraphTraversalAction>,
452 mut visit_postorder: impl FnMut(&'a SingleModuleGraphModuleNode, &mut S) -> Result<()>,
453 ) -> Result<()> {
454 let graph = &self.graph;
455 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
456
457 enum Pass {
458 Visit,
459 ExpandAndVisit,
460 }
461
462 let mut stack: Vec<(Pass, NodeIndex)> =
463 entries.map(|e| (Pass::ExpandAndVisit, e)).collect();
464 let mut expanded = FxHashSet::default();
465 while let Some((pass, current)) = stack.pop() {
466 match pass {
467 Pass::Visit => {
468 if let SingleModuleGraphNode::Module(current_node) =
469 graph.node_weight(current).unwrap()
470 {
471 visit_postorder(current_node, state)?;
472 }
473 }
474 Pass::ExpandAndVisit => {
475 if expanded.insert(current)
476 && let SingleModuleGraphNode::Module(current_node) =
477 graph.node_weight(current).unwrap()
478 {
479 let action = visit_preorder(current_node, state)?;
480 if action == GraphTraversalAction::Exclude {
481 continue;
482 }
483 stack.push((Pass::Visit, current));
484 if action == GraphTraversalAction::Continue {
485 stack.extend(
486 iter_neighbors_rev(graph, current)
487 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
488 );
489 }
490 }
491 }
492 }
493 }
494
495 Ok(())
496 }
497
498 pub fn traverse_edges_from_entries<'a>(
509 &'a self,
510 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
511 mut visitor: impl FnMut(
512 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
513 &'a SingleModuleGraphModuleNode,
514 ) -> GraphTraversalAction,
515 ) -> Result<()> {
516 let graph = &self.graph;
517 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
518
519 let mut stack = entries.collect::<Vec<_>>();
520 let mut discovered = graph.visit_map();
521 for entry_node in &stack {
523 let SingleModuleGraphNode::Module(entry_weight) =
524 graph.node_weight(*entry_node).unwrap()
525 else {
526 continue;
527 };
528 visitor(None, entry_weight);
529 }
530
531 while let Some(node) = stack.pop() {
532 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
533 else {
534 continue;
535 };
536 if discovered.visit(node) {
537 let neighbors = {
538 let mut neighbors = vec![];
539 let mut walker = graph.neighbors(node).detach();
540 while let Some((edge, succ)) = walker.next(graph) {
541 neighbors.push((edge, succ));
542 }
543 neighbors
544 };
545
546 for (edge, succ) in neighbors {
547 let SingleModuleGraphNode::Module(succ_weight) =
548 graph.node_weight(succ).unwrap()
549 else {
550 continue;
551 };
552 let edge_weight = graph.edge_weight(edge).unwrap();
553 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
554 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
555 stack.push(succ);
556 }
557 }
558 }
559 }
560
561 Ok(())
562 }
563
564 pub fn traverse_edges<'a>(
569 &'a self,
570 mut visitor: impl FnMut(
571 (
572 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
573 &'a SingleModuleGraphModuleNode,
574 ),
575 ) -> GraphTraversalAction,
576 ) -> Result<()> {
577 let graph = &self.graph;
578 let mut stack: Vec<NodeIndex> = self
579 .entries
580 .iter()
581 .flat_map(|e| e.entries())
582 .map(|e| *self.modules.get(&e).unwrap())
583 .collect();
584 let mut discovered = graph.visit_map();
585 for entry_node in &stack {
586 let SingleModuleGraphNode::Module(entry_node) = graph.node_weight(*entry_node).unwrap()
587 else {
588 continue;
589 };
590 visitor((None, entry_node));
591 }
592
593 while let Some(node) = stack.pop() {
594 if discovered.visit(node) {
595 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
596 else {
597 continue;
598 };
599 for edge in graph.edges(node).collect::<Vec<_>>() {
600 let edge_weight = edge.weight();
601 let succ = edge.target();
602 let SingleModuleGraphNode::Module(succ_weight) =
603 graph.node_weight(succ).unwrap()
604 else {
605 continue;
606 };
607 let action = visitor((Some((node_weight, edge_weight)), succ_weight));
608 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
609 stack.push(succ);
610 }
611 }
612 }
613 }
614
615 Ok(())
616 }
617
618 pub fn traverse_edges_from_entries_fixed_point<'a>(
635 &'a self,
636 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
637 mut visit: impl FnMut(
638 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
639 &'a SingleModuleGraphNode,
640 ) -> Result<GraphTraversalAction>,
641 ) -> Result<usize> {
642 let mut queue = VecDeque::default();
643 let mut queue_set = FxHashSet::default();
644
645 for module in entries {
646 let index = self.get_module(module).unwrap();
647 let action = visit(None, self.graph.node_weight(index).unwrap())?;
648 if action == GraphTraversalAction::Continue && queue_set.insert(index) {
649 queue.push_back(index);
650 }
651 }
652
653 let mut visit_count = 0;
654 while let Some(index) = queue.pop_front() {
655 queue_set.remove(&index);
656 let node = match self.graph.node_weight(index).unwrap() {
657 SingleModuleGraphNode::Module(single_module_graph_module_node) => {
658 single_module_graph_module_node
659 }
660 _ => {
661 continue; }
663 };
664 visit_count += 1;
665 for edge in self
666 .graph
667 .edges_directed(index, petgraph::Direction::Outgoing)
668 {
669 let refdata = edge.weight();
670 let target_index = edge.target();
671 let target = self.graph.node_weight(edge.target()).unwrap();
672 let action = visit(Some((node, refdata)), target)?;
673 if action == GraphTraversalAction::Continue && queue_set.insert(target_index) {
674 queue.push_back(target_index);
675 }
676 }
677 }
678
679 Ok(visit_count)
680 }
681
682 pub fn traverse_edges_from_entries_dfs<'a, S>(
701 &'a self,
702 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
703 state: &mut S,
704 mut visit_preorder: impl FnMut(
705 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
706 &'a SingleModuleGraphNode,
707 &mut S,
708 ) -> Result<GraphTraversalAction>,
709 mut visit_postorder: impl FnMut(
710 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
711 &'a SingleModuleGraphNode,
712 &mut S,
713 ) -> Result<()>,
714 ) -> Result<()> {
715 let graph = &self.graph;
716 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
717
718 enum Pass {
719 Visit,
720 ExpandAndVisit,
721 }
722
723 #[allow(clippy::type_complexity)] let mut stack: Vec<(Pass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> =
725 entries.map(|e| (Pass::ExpandAndVisit, None, e)).collect();
726 let mut expanded = FxHashSet::default();
727 while let Some((pass, parent, current)) = stack.pop() {
728 let parent_arg = parent.map(|parent| {
729 (
730 match graph.node_weight(parent.0).unwrap() {
731 SingleModuleGraphNode::Module(node) => node,
732 SingleModuleGraphNode::VisitedModule { .. } => {
733 unreachable!()
734 }
735 },
736 graph.edge_weight(parent.1).unwrap(),
737 )
738 });
739 match pass {
740 Pass::Visit => {
741 visit_postorder(parent_arg, graph.node_weight(current).unwrap(), state)?;
742 }
743 Pass::ExpandAndVisit => match graph.node_weight(current).unwrap() {
744 current_node @ SingleModuleGraphNode::Module(_) => {
745 let action = visit_preorder(parent_arg, current_node, state)?;
746 if action == GraphTraversalAction::Exclude {
747 continue;
748 }
749 stack.push((Pass::Visit, parent, current));
750 if action == GraphTraversalAction::Continue && expanded.insert(current) {
751 stack.extend(iter_neighbors_rev(graph, current).map(
752 |(edge, child)| {
753 (Pass::ExpandAndVisit, Some((current, edge)), child)
754 },
755 ));
756 }
757 }
758 current_node @ SingleModuleGraphNode::VisitedModule { .. } => {
759 visit_preorder(parent_arg, current_node, state)?;
760 visit_postorder(parent_arg, current_node, state)?;
761 }
762 },
763 }
764 }
765
766 Ok(())
767 }
768
769 pub fn traverse_cycles<'l>(
770 &'l self,
771 edge_filter: impl Fn(&'l RefData) -> bool,
772 mut visit_cycle: impl FnMut(&[&'l SingleModuleGraphModuleNode]),
773 ) {
774 #[derive(Clone)]
778 struct NodeState {
779 index: u32,
780 lowlink: u32,
781 on_stack: bool,
782 }
783 enum VisitStep {
784 UnvisitedNode(NodeIndex),
785 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
786 AfterVisit(NodeIndex),
787 }
788 let mut node_states = vec![None; self.graph.node_bound()];
789 let mut stack = Vec::new();
790 let mut visit_stack = Vec::new();
791 let mut index = 0;
792 let mut scc = Vec::new();
793 for initial_index in self.graph.node_indices() {
794 if node_states[initial_index.index()].is_some() {
796 continue;
797 }
798 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
799 while let Some(step) = visit_stack.pop() {
800 match step {
801 VisitStep::UnvisitedNode(node) => {
802 node_states[node.index()] = Some(NodeState {
803 index,
804 lowlink: index,
805 on_stack: true,
806 });
807 index += 1;
808 stack.push(node);
809 visit_stack.push(VisitStep::AfterVisit(node));
810 let mut neighbors = self.graph.neighbors(node).detach();
811 while let Some((edge, succ)) = neighbors.next(&self.graph) {
812 let edge_weight = self.graph.edge_weight(edge).unwrap();
813 if !edge_filter(edge_weight) {
814 continue;
815 }
816 let node_state = &node_states[succ.index()];
817 if let Some(node_state) = node_state {
818 if node_state.on_stack {
819 let index = node_state.index;
820 let parent_state = node_states[node.index()].as_mut().unwrap();
821 parent_state.lowlink = parent_state.lowlink.min(index);
822 }
823 } else {
824 visit_stack.push(VisitStep::EdgeAfterVisit {
825 parent: node,
826 child: succ,
827 });
828 visit_stack.push(VisitStep::UnvisitedNode(succ));
829 }
830 }
831 }
832 VisitStep::EdgeAfterVisit { parent, child } => {
833 let child_state = node_states[child.index()].as_ref().unwrap();
834 let lowlink = child_state.lowlink;
835
836 let parent_state = node_states[parent.index()].as_mut().unwrap();
837 parent_state.lowlink = parent_state.lowlink.min(lowlink);
838 }
839 VisitStep::AfterVisit(node) => {
840 let node_state = node_states[node.index()].as_ref().unwrap();
841 if node_state.lowlink == node_state.index {
842 loop {
843 let poppped = stack.pop().unwrap();
844 let popped_state = node_states[poppped.index()].as_mut().unwrap();
845 popped_state.on_stack = false;
846 if let SingleModuleGraphNode::Module(module) =
847 self.graph.node_weight(poppped).unwrap()
848 {
849 scc.push(module);
850 }
851 if poppped == node {
852 break;
853 }
854 }
855 if scc.len() > 1 {
856 visit_cycle(&scc);
857 }
858 scc.clear();
859 }
860 }
861 }
862 }
863 }
864 }
865
866 pub async fn compute_import_traces_for_issues(
874 &self,
875 issues: &AutoSet<ResolvedVc<Box<dyn Issue>>>,
876 ) -> Result<FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>>> {
877 let issue_paths = issues
878 .iter()
879 .map(|issue| issue.file_path().owned())
880 .try_join()
881 .await?;
882 let mut file_path_to_traces: FxHashMap<FileSystemPath, Vec<ImportTrace>> =
883 FxHashMap::with_capacity_and_hasher(issue_paths.len(), Default::default());
884 for issue in &issue_paths {
886 file_path_to_traces.entry(issue.clone()).or_default();
887 }
888
889 {
890 let modules =
891 self.modules
892 .iter()
893 .map(|(module, &index)| async move {
894 Ok((module.ident().path().owned().await?, index))
895 })
896 .try_join()
897 .await?;
898 let reversed_graph = Reversed(&self.graph.0);
900 for (path, module_idx) in modules {
901 if let Entry::Occupied(mut entry) = file_path_to_traces.entry(path) {
902 let Some((_, path)) = petgraph::algo::astar(
904 &reversed_graph,
905 module_idx,
906 |n| reversed_graph.neighbors(n).next().is_none(),
907 |e| match e.weight().chunking_type {
909 ChunkingType::Parallel { .. } => 0,
911 _ => 1,
912 },
913 |_| 0,
927 ) else {
928 unreachable!("there must be a path to a root");
929 };
930 let path = path
934 .into_iter()
935 .map(async |n| {
936 Ok(self
937 .graph
938 .node_weight(n)
939 .unwrap()
940 .module()
941 .ident()
942 .await?
943 .clone())
944 })
945 .try_join()
946 .await?;
947 entry.get_mut().push(path);
948 }
949 }
950 }
951 let mut issue_to_traces: FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>> =
952 FxHashMap::with_capacity_and_hasher(issues.len(), Default::default());
953 for (path, issue) in issue_paths.iter().zip(issues) {
957 if let Some(traces) = file_path_to_traces.get(path) {
958 issue_to_traces.insert(*issue, traces.clone());
959 }
960 }
961 Ok(issue_to_traces)
962 }
963}
964
965#[turbo_tasks::value]
966struct ModuleGraphImportTracer {
967 graph: ResolvedVc<SingleModuleGraph>,
968}
969
970#[turbo_tasks::value(shared)]
971struct PathToModulesMap {
972 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
973}
974
975#[turbo_tasks::value_impl]
976impl ModuleGraphImportTracer {
977 #[turbo_tasks::function]
978 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
979 Self::cell(Self { graph })
980 }
981
982 #[turbo_tasks::function]
984 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
985 let path_and_modules = self
986 .graph
987 .await?
988 .modules
989 .iter()
990 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
991 .try_join()
992 .await?;
993 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
994 FxHashMap::default();
995 for (path, module) in path_and_modules {
996 map.entry(path).or_default().push(module)
997 }
998 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
999 }
1000}
1001
1002#[turbo_tasks::value_impl]
1003impl ImportTracer for ModuleGraphImportTracer {
1004 #[turbo_tasks::function]
1005 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
1006 let path_to_modules = self.path_to_modules().await?;
1007 let Some(modules) = path_to_modules.map.get(&path) else {
1008 return Ok(Vc::default()); };
1011 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
1012 let graph = &*self.await?.graph.await?;
1013
1014 let reversed_graph = Reversed(&graph.graph.0);
1015 return Ok(ImportTraces::cell(ImportTraces(
1016 modules
1017 .iter()
1018 .map(|m| async move {
1019 let Some(&module_idx) = graph.modules.get(m) else {
1020 bail!("inconsistent read?")
1023 };
1024 let Some((_, path)) = petgraph::algo::astar(
1026 &reversed_graph,
1027 module_idx,
1028 |n| reversed_graph.neighbors(n).next().is_none(),
1029 |e| match e.weight().chunking_type {
1031 ChunkingType::Parallel { .. } => 0,
1033 _ => 1,
1034 },
1035 |_| 0,
1049 ) else {
1050 unreachable!("there must be a path to a root");
1051 };
1052
1053 let path = path
1057 .into_iter()
1058 .map(async |n| {
1059 graph
1060 .graph
1061 .node_weight(n)
1062 .unwrap() .module()
1064 .ident()
1065 .await
1066 })
1067 .try_join()
1068 .await?;
1069 Ok(path)
1070 })
1071 .try_join()
1072 .await?,
1073 )));
1074 }
1075}
1076
1077#[turbo_tasks::value(shared)]
1078#[derive(Clone, Default)]
1079pub struct ModuleGraph {
1080 pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
1081}
1082
1083#[turbo_tasks::value_impl]
1084impl ModuleGraph {
1085 #[turbo_tasks::function]
1086 pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
1087 Self { graphs }.cell()
1088 }
1089
1090 #[turbo_tasks::function]
1091 pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
1092 Self {
1093 graphs: vec![graph],
1094 }
1095 .cell()
1096 }
1097
1098 #[turbo_tasks::function]
1099 pub fn from_entry_module(
1100 module: ResolvedVc<Box<dyn Module>>,
1101 include_traced: bool,
1102 ) -> Vc<Self> {
1103 Self::from_single_graph(SingleModuleGraph::new_with_entries(
1104 Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
1105 include_traced,
1106 ))
1107 }
1108
1109 #[turbo_tasks::function]
1110 pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
1111 Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
1112 }
1113
1114 #[turbo_tasks::function]
1115 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
1116 compute_chunk_group_info(&self.read_graphs().await?).await
1117 }
1118
1119 #[turbo_tasks::function]
1120 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
1121 compute_merged_modules(self).await
1122 }
1123
1124 #[turbo_tasks::function]
1125 pub async fn module_batches(
1126 self: Vc<Self>,
1127 config: Vc<BatchingConfig>,
1128 ) -> Result<Vc<ModuleBatchesGraph>> {
1129 compute_module_batches(self, &*config.await?).await
1130 }
1131
1132 #[turbo_tasks::function]
1133 pub async fn style_groups(
1134 self: Vc<Self>,
1135 chunking_context: Vc<Box<dyn ChunkingContext>>,
1136 config: StyleGroupsConfig,
1137 ) -> Result<Vc<StyleGroups>> {
1138 compute_style_groups(self, chunking_context, &config).await
1139 }
1140
1141 #[turbo_tasks::function]
1142 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
1143 async move {
1146 let result_op = compute_async_module_info(self.to_resolved().await?);
1147 let result_vc = result_op.resolve_strongly_consistent().await?;
1148 result_op.drop_collectibles::<Box<dyn Issue>>();
1149 anyhow::Ok(*result_vc)
1150 }
1151 .instrument(tracing::info_span!("compute async module info"))
1152 .await
1153 }
1154
1155 #[turbo_tasks::function]
1156 pub async fn referenced_async_modules(
1157 self: Vc<Self>,
1158 module: ResolvedVc<Box<dyn Module>>,
1159 ) -> Result<Vc<AsyncModuleInfo>> {
1160 let graph_ref = self.read_graphs().await?;
1161 let graphs = &graph_ref.graphs;
1162 let async_modules_info = self.async_module_info().await?;
1163
1164 let entry = graph_ref.get_entry(module)?;
1165 let referenced_modules =
1166 iter_neighbors_rev(&graphs[entry.graph_idx()].graph, entry.node_idx)
1167 .filter(|(edge_idx, _)| {
1168 let ty = graphs[entry.graph_idx()]
1169 .graph
1170 .edge_weight(*edge_idx)
1171 .unwrap();
1172 ty.chunking_type.is_inherit_async()
1173 })
1174 .map(|(_, child_idx)| {
1175 anyhow::Ok(
1176 get_node!(
1177 graphs,
1178 GraphNodeIndex {
1179 graph_idx: entry.graph_idx,
1180 node_idx: child_idx
1181 }
1182 )?
1183 .module,
1184 )
1185 })
1186 .collect::<Result<Vec<_>>>()?
1187 .into_iter()
1188 .rev()
1189 .filter(|m| async_modules_info.contains(m))
1190 .map(|m| *m)
1191 .collect();
1192
1193 Ok(AsyncModuleInfo::new(referenced_modules))
1194 }
1195}
1196
1197macro_rules! get_node {
1202 ($graphs:expr, $node:expr) => {{
1203 let node_idx = $node;
1204 match $graphs[node_idx.graph_idx()]
1205 .graph
1206 .node_weight(node_idx.node_idx)
1207 {
1208 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1209 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1210 match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1211 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1212 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1213 "Expected visited target node to be module"
1214 )),
1215 None => Err(::anyhow::anyhow!("Expected visited target node")),
1216 }
1217 }
1218 None => Err(::anyhow::anyhow!("Expected graph node")),
1219 }
1220 }};
1221}
1222pub(crate) use get_node;
1223macro_rules! get_node_idx {
1224 ($graphs:expr, $node:expr) => {{
1225 let node_idx = $node;
1226 match $graphs[node_idx.graph_idx()]
1227 .graph
1228 .node_weight(node_idx.node_idx)
1229 {
1230 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, node_idx)),
1231 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1232 match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1233 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, *idx)),
1234 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1235 "Expected visited target node to be module"
1236 )),
1237 None => Err(::anyhow::anyhow!("Expected visited target node")),
1238 }
1239 }
1240 None => Err(::anyhow::anyhow!("Expected graph node")),
1241 }
1242 }};
1243}
1244
1245impl ModuleGraph {
1246 pub async fn read_graphs(self: Vc<ModuleGraph>) -> Result<ModuleGraphRef> {
1247 Ok(ModuleGraphRef {
1248 graphs: self.await?.graphs.iter().try_join().await?,
1249 })
1250 }
1251}
1252
1253pub struct ModuleGraphRef {
1256 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
1257}
1258
1259impl ModuleGraphRef {
1260 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
1261 let Some(idx) = self
1262 .graphs
1263 .iter()
1264 .enumerate()
1265 .find_map(|(graph_idx, graph)| {
1266 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1267 graph_idx: u32::try_from(graph_idx).unwrap(),
1268 node_idx: *node_idx,
1269 })
1270 })
1271 else {
1272 bail!("Couldn't find entry module in module graph");
1273 };
1274 Ok(idx)
1275 }
1276
1277 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1280 Ok(self
1281 .graphs
1282 .iter()
1283 .flat_map(|g| g.iter_nodes())
1284 .map(async |n| Ok((n.module, n.module.ident().to_string().await?)))
1285 .try_join()
1286 .await?
1287 .into_iter()
1288 .collect::<FxHashMap<_, _>>())
1289 }
1290
1291 pub fn traverse_edges_from_entries_bfs(
1302 &self,
1303 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1304 mut visitor: impl FnMut(
1305 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1306 &'_ SingleModuleGraphModuleNode,
1307 ) -> Result<GraphTraversalAction>,
1308 ) -> Result<()> {
1309 let graphs = &self.graphs;
1310
1311 let mut queue = VecDeque::from(
1312 entries
1313 .into_iter()
1314 .map(|e| self.get_entry(e))
1315 .collect::<Result<Vec<_>>>()?,
1316 );
1317 let mut visited = HashSet::new();
1318 for entry_node in &queue {
1319 visitor(None, get_node!(graphs, entry_node)?)?;
1320 }
1321 while let Some(node) = queue.pop_front() {
1322 let graph = &graphs[node.graph_idx()].graph;
1323 let node_weight = get_node!(graphs, node)?;
1324 if visited.insert(node) {
1325 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1326
1327 for (edge, succ) in neighbors {
1328 let succ = GraphNodeIndex {
1329 graph_idx: node.graph_idx,
1330 node_idx: succ,
1331 };
1332 let succ_weight = get_node!(graphs, succ)?;
1333 let edge_weight = graph.edge_weight(edge).unwrap();
1334 let action = visitor(Some((node_weight, edge_weight)), succ_weight)?;
1335 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1336 queue.push_back(succ);
1337 }
1338 }
1339 }
1340 }
1341
1342 Ok(())
1343 }
1344
1345 pub fn traverse_edges_from_entry(
1356 &self,
1357 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1358 mut visitor: impl FnMut(
1359 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1360 &'_ SingleModuleGraphModuleNode,
1361 ) -> GraphTraversalAction,
1362 ) -> Result<()> {
1363 let graphs = &self.graphs;
1364
1365 let entries = entries.into_iter();
1366 let mut stack = Vec::with_capacity(entries.size_hint().0);
1367 for entry in entries {
1368 stack.push(self.get_entry(entry)?);
1369 }
1370 let mut visited = HashSet::new();
1371 for entry_node in &stack {
1372 visitor(None, get_node!(graphs, entry_node)?);
1373 }
1374 while let Some(node) = stack.pop() {
1375 let graph = &graphs[node.graph_idx()].graph;
1376 let node_weight = get_node!(graphs, node)?;
1377 if visited.insert(node) {
1378 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1379
1380 for (edge, succ) in neighbors {
1381 let succ = GraphNodeIndex {
1382 graph_idx: node.graph_idx,
1383 node_idx: succ,
1384 };
1385 let succ_weight = get_node!(graphs, succ)?;
1386 let edge_weight = graph.edge_weight(edge).unwrap();
1387 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
1388 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1389 stack.push(succ);
1390 }
1391 }
1392 }
1393 }
1394
1395 Ok(())
1396 }
1397
1398 pub fn traverse_all_edges_unordered(
1407 &self,
1408 mut visitor: impl FnMut(
1409 (&'_ SingleModuleGraphModuleNode, &'_ RefData),
1410 &'_ SingleModuleGraphModuleNode,
1411 ) -> Result<()>,
1412 ) -> Result<()> {
1413 let graphs = &self.graphs;
1414
1415 for graph in graphs {
1416 let graph = &graph.graph;
1417 for edge in graph.edge_references() {
1418 let source = match graph.node_weight(edge.source()).unwrap() {
1419 SingleModuleGraphNode::Module(node) => node,
1420 SingleModuleGraphNode::VisitedModule { .. } => unreachable!(),
1421 };
1422 let target = match graph.node_weight(edge.target()).unwrap() {
1423 SingleModuleGraphNode::Module(node) => node,
1424 SingleModuleGraphNode::VisitedModule { idx, .. } => get_node!(graphs, idx)?,
1425 };
1426 visitor((source, edge.weight()), target)?;
1427 }
1428 }
1429
1430 Ok(())
1431 }
1432
1433 pub fn traverse_edges_from_entries_dfs<S>(
1453 &self,
1454 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1455 state: &mut S,
1456 mut visit_preorder: impl FnMut(
1457 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1458 &'_ SingleModuleGraphModuleNode,
1459 &mut S,
1460 ) -> Result<GraphTraversalAction>,
1461 mut visit_postorder: impl FnMut(
1462 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1463 &'_ SingleModuleGraphModuleNode,
1464 &mut S,
1465 ) -> Result<()>,
1466 ) -> Result<()> {
1467 let graphs = &self.graphs;
1468
1469 let entries = entries.into_iter().collect::<Vec<_>>();
1470
1471 enum Pass {
1472 Visit,
1473 ExpandAndVisit,
1474 }
1475 #[allow(clippy::type_complexity)] let mut stack: Vec<(Pass, Option<(GraphNodeIndex, EdgeIndex)>, GraphNodeIndex)> =
1477 Vec::with_capacity(entries.len());
1478 for entry in entries.into_iter().rev() {
1479 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1480 }
1481 let mut expanded = HashSet::new();
1482 while let Some((pass, parent, current)) = stack.pop() {
1483 let parent_arg = match parent {
1484 Some((parent_node, parent_edge)) => Some((
1485 get_node!(graphs, parent_node)?,
1486 graphs[parent_node.graph_idx()]
1487 .graph
1488 .edge_weight(parent_edge)
1489 .unwrap(),
1490 )),
1491 None => None,
1492 };
1493 let current_node = get_node!(graphs, current)?;
1494 match pass {
1495 Pass::Visit => {
1496 visit_postorder(parent_arg, current_node, state)?;
1497 }
1498 Pass::ExpandAndVisit => {
1499 let action = visit_preorder(parent_arg, current_node, state)?;
1500 if action == GraphTraversalAction::Exclude {
1501 continue;
1502 }
1503 stack.push((Pass::Visit, parent, current));
1504 if action == GraphTraversalAction::Continue && expanded.insert(current) {
1505 let graph = &graphs[current.graph_idx()].graph;
1506 let (neighbors_rev, current) = match graph
1507 .node_weight(current.node_idx)
1508 .unwrap()
1509 {
1510 SingleModuleGraphNode::Module(_) => {
1511 (iter_neighbors_rev(graph, current.node_idx), current)
1512 }
1513 SingleModuleGraphNode::VisitedModule { idx, .. } => (
1514 iter_neighbors_rev(&graphs[idx.graph_idx()].graph, idx.node_idx),
1516 *idx,
1517 ),
1518 };
1519 stack.extend(neighbors_rev.map(|(edge, child)| {
1520 (
1521 Pass::ExpandAndVisit,
1522 Some((current, edge)),
1523 GraphNodeIndex {
1524 graph_idx: current.graph_idx,
1525 node_idx: child,
1526 },
1527 )
1528 }));
1529 }
1530 }
1531 }
1532 }
1533
1534 Ok(())
1535 }
1536
1537 pub fn traverse_cycles(
1540 &self,
1541 edge_filter: impl Fn(&RefData) -> bool,
1542 mut visit_cycle: impl FnMut(&[&SingleModuleGraphModuleNode]),
1543 ) -> Result<()> {
1544 for graph in &self.graphs {
1545 graph.traverse_cycles(&edge_filter, &mut visit_cycle);
1546 }
1547 Ok(())
1548 }
1549
1550 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1572 &self,
1573 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1574 state: &mut S,
1575 mut visit: impl FnMut(
1576 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1577 &'_ SingleModuleGraphModuleNode,
1578 &mut S,
1579 ) -> Result<GraphTraversalAction>,
1580 priority: impl Fn(&'_ SingleModuleGraphModuleNode, &mut S) -> Result<P>,
1581 ) -> Result<usize> {
1582 let graphs = &self.graphs;
1583
1584 #[derive(PartialEq, Eq)]
1585 struct NodeWithPriority<T: Ord> {
1586 node: GraphNodeIndex,
1587 priority: T,
1588 }
1589 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1590 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1591 Some(self.cmp(other))
1592 }
1593 }
1594 impl<T: Ord> Ord for NodeWithPriority<T> {
1595 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1596 self.priority
1599 .cmp(&other.priority)
1600 .then(other.node.cmp(&self.node))
1602 }
1603 }
1604
1605 let mut queue_set = FxHashSet::default();
1606 let mut queue = BinaryHeap::from_iter(
1607 entries
1608 .into_iter()
1609 .map(|(m, priority)| {
1610 Ok(NodeWithPriority {
1611 node: self.get_entry(m)?,
1612 priority,
1613 })
1614 })
1615 .collect::<Result<Vec<_>>>()?,
1616 );
1617
1618 for entry_node in &queue {
1619 visit(None, get_node!(graphs, entry_node.node)?, state)?;
1620 }
1621
1622 let mut visit_count = 0usize;
1623 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1624 queue_set.remove(&node);
1625 let (node_weight, node) = get_node_idx!(graphs, node)?;
1626 let graph = &graphs[node.graph_idx()].graph;
1627 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1628
1629 visit_count += 1;
1630
1631 for (edge, succ) in neighbors {
1632 let succ = GraphNodeIndex {
1633 graph_idx: node.graph_idx,
1634 node_idx: succ,
1635 };
1636 let (succ_weight, succ) = get_node_idx!(graphs, succ)?;
1637 let edge_weight = graph.edge_weight(edge).unwrap();
1638 let action = visit(Some((node_weight, edge_weight)), succ_weight, state)?;
1639
1640 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1641 queue.push(NodeWithPriority {
1642 node: succ,
1643 priority: priority(succ_weight, state)?,
1644 });
1645 }
1646 }
1647 }
1648
1649 Ok(visit_count)
1650 }
1651}
1652
1653#[turbo_tasks::value_impl]
1654impl SingleModuleGraph {
1655 #[turbo_tasks::function]
1656 pub async fn new_with_entries(
1657 entries: Vc<GraphEntries>,
1658 include_traced: bool,
1659 ) -> Result<Vc<Self>> {
1660 SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1661 }
1662
1663 #[turbo_tasks::function]
1664 pub async fn new_with_entries_visited(
1665 entries: Vc<GraphEntries>,
1666 visited_modules: Vc<VisitedModules>,
1667 include_traced: bool,
1668 ) -> Result<Vc<Self>> {
1669 SingleModuleGraph::new_inner(
1670 &*entries.await?,
1671 &visited_modules.await?.modules,
1672 include_traced,
1673 )
1674 .await
1675 }
1676
1677 #[turbo_tasks::function]
1678 pub async fn new_with_entries_visited_intern(
1679 entries: GraphEntriesT,
1681 visited_modules: Vc<VisitedModules>,
1682 include_traced: bool,
1683 ) -> Result<Vc<Self>> {
1684 SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1685 .await
1686 }
1687}
1688
1689#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1690pub struct SingleModuleGraphModuleNode {
1691 pub module: ResolvedVc<Box<dyn Module>>,
1692}
1693
1694#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1695pub enum SingleModuleGraphNode {
1696 Module(SingleModuleGraphModuleNode),
1697 VisitedModule {
1699 idx: GraphNodeIndex,
1700 module: ResolvedVc<Box<dyn Module>>,
1701 },
1702}
1703
1704impl SingleModuleGraphNode {
1705 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1706 match self {
1707 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode { module }) => *module,
1708 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1709 }
1710 }
1711}
1712
1713#[derive(PartialEq, Eq, Debug)]
1714pub enum GraphTraversalAction {
1715 Continue,
1717 Skip,
1719 Exclude,
1721}
1722
1723#[derive(Clone, Hash, PartialEq, Eq)]
1726enum SingleModuleGraphBuilderNode {
1727 ChunkableReference {
1729 ref_data: RefData,
1730 source: ResolvedVc<Box<dyn Module>>,
1731 target: ResolvedVc<Box<dyn Module>>,
1732 source_ident: Option<ReadRef<RcStr>>,
1735 target_ident: Option<ReadRef<RcStr>>,
1736 },
1737 Module {
1739 module: ResolvedVc<Box<dyn Module>>,
1740 ident: Option<ReadRef<RcStr>>,
1742 },
1743 VisitedModule {
1745 module: ResolvedVc<Box<dyn Module>>,
1746 idx: GraphNodeIndex,
1747 },
1748}
1749
1750impl SingleModuleGraphBuilderNode {
1751 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1752 Ok(Self::Module {
1753 module,
1754 ident: if emit_spans {
1755 Some(module.ident_string().untracked().await?)
1757 } else {
1758 None
1759 },
1760 })
1761 }
1762 async fn new_chunkable_ref(
1763 emit_spans: bool,
1764 source: ResolvedVc<Box<dyn Module>>,
1765 target: ResolvedVc<Box<dyn Module>>,
1766 ref_data: RefData,
1767 ) -> Result<Self> {
1768 Ok(Self::ChunkableReference {
1769 ref_data,
1770 source,
1771 source_ident: if emit_spans {
1772 Some(source.ident_string().untracked().await?)
1774 } else {
1775 None
1776 },
1777 target,
1778 target_ident: if emit_spans {
1779 Some(target.ident_string().untracked().await?)
1781 } else {
1782 None
1783 },
1784 })
1785 }
1786 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1787 Self::VisitedModule { module, idx }
1788 }
1789}
1790struct SingleModuleGraphBuilderEdge {
1791 to: SingleModuleGraphBuilderNode,
1792 export: ExportUsage,
1793}
1794
1795const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1798 inherit_async: true,
1799 hoisted: true,
1800};
1801
1802struct SingleModuleGraphBuilder<'a> {
1803 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1804
1805 emit_spans: bool,
1806
1807 include_traced: bool,
1809}
1810impl Visit<(SingleModuleGraphBuilderNode, ExportUsage)> for SingleModuleGraphBuilder<'_> {
1811 type Edge = SingleModuleGraphBuilderEdge;
1812 type EdgesIntoIter = Vec<Self::Edge>;
1813 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1814
1815 fn visit(
1816 &mut self,
1817 edge: Self::Edge,
1818 ) -> VisitControlFlow<(SingleModuleGraphBuilderNode, ExportUsage)> {
1819 match edge.to {
1820 SingleModuleGraphBuilderNode::Module { .. } => {
1821 VisitControlFlow::Continue((edge.to, edge.export))
1822 }
1823 SingleModuleGraphBuilderNode::ChunkableReference { ref ref_data, .. } => {
1824 match &ref_data.chunking_type {
1825 ChunkingType::Traced => VisitControlFlow::Skip((edge.to, edge.export)),
1826 _ => VisitControlFlow::Continue((edge.to, edge.export)),
1827 }
1828 }
1829 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1831 VisitControlFlow::Skip((edge.to, edge.export))
1832 }
1833 }
1834 }
1835
1836 fn edges(
1837 &mut self,
1838 (node, _): &(SingleModuleGraphBuilderNode, ExportUsage),
1841 ) -> Self::EdgesFuture {
1842 let (module, chunkable_ref_target) = match node {
1844 SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1845 SingleModuleGraphBuilderNode::ChunkableReference {
1846 target, ref_data, ..
1847 } => (None, Some((*target, ref_data.export.clone()))),
1848 SingleModuleGraphBuilderNode::VisitedModule { .. } => unreachable!(),
1850 };
1851 let visited_modules = self.visited_modules;
1852 let emit_spans = self.emit_spans;
1853 let include_traced = self.include_traced;
1854 async move {
1855 Ok(match (module, chunkable_ref_target) {
1856 (Some(module), None) => {
1857 let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1858 let refs = match refs_cell.await {
1859 Ok(refs) => refs,
1860 Err(e) => {
1861 return Err(e.context(module.ident().to_string().await?));
1862 }
1863 };
1864
1865 refs.iter()
1866 .flat_map(|(ty, export, modules)| {
1867 modules.iter().map(|m| (ty.clone(), export.clone(), *m))
1868 })
1869 .map(async |(ty, export, target)| {
1870 let to = if ty == COMMON_CHUNKING_TYPE {
1871 if let Some(idx) = visited_modules.get(&target) {
1872 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1873 } else {
1874 SingleModuleGraphBuilderNode::new_module(emit_spans, target)
1875 .await?
1876 }
1877 } else {
1878 SingleModuleGraphBuilderNode::new_chunkable_ref(
1879 emit_spans,
1880 module,
1881 target,
1882 RefData {
1883 chunking_type: ty,
1884 export: export.clone(),
1885 },
1886 )
1887 .await?
1888 };
1889 Ok(SingleModuleGraphBuilderEdge { to, export })
1890 })
1891 .try_join()
1892 .await?
1893 }
1894 (None, Some((chunkable_ref_target, export))) => {
1895 vec![SingleModuleGraphBuilderEdge {
1896 to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1897 SingleModuleGraphBuilderNode::new_visited_module(
1898 chunkable_ref_target,
1899 *idx,
1900 )
1901 } else {
1902 SingleModuleGraphBuilderNode::new_module(
1903 emit_spans,
1904 chunkable_ref_target,
1905 )
1906 .await?
1907 },
1908 export,
1909 }]
1910 }
1911 _ => unreachable!(),
1912 })
1913 }
1914 }
1915
1916 fn span(&mut self, (node, _): &(SingleModuleGraphBuilderNode, ExportUsage)) -> tracing::Span {
1917 if !self.emit_spans {
1918 return Span::current();
1919 }
1920
1921 match node {
1922 SingleModuleGraphBuilderNode::Module {
1923 ident: Some(ident), ..
1924 } => {
1925 tracing::info_span!("module", name = display(ident))
1926 }
1927 SingleModuleGraphBuilderNode::ChunkableReference {
1928 ref_data,
1929 source_ident: Some(source_ident),
1930 target_ident: Some(target_ident),
1931 ..
1932 } => match &ref_data.chunking_type {
1933 ChunkingType::Parallel {
1934 inherit_async: false,
1935 ..
1936 } => Span::current(),
1937 _ => {
1938 tracing::info_span!(
1939 "chunkable reference",
1940 ty = debug(&ref_data.chunking_type),
1941 source = display(source_ident),
1942 target = display(target_ident)
1943 )
1944 }
1945 },
1946 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1947 tracing::info_span!("visited module")
1948 }
1949 _ => Span::current(),
1950 }
1951 }
1952}
1953
1954#[cfg(test)]
1955pub mod tests {
1956 use anyhow::Result;
1957 use rustc_hash::FxHashMap;
1958 use turbo_rcstr::{RcStr, rcstr};
1959 use turbo_tasks::{ReadRef, ResolvedVc, TryJoinIterExt, Vc};
1960 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1961 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1962
1963 use crate::{
1964 asset::{Asset, AssetContent},
1965 ident::AssetIdent,
1966 module::Module,
1967 module_graph::{
1968 GraphEntries, GraphTraversalAction, SingleModuleGraph,
1969 chunk_group_info::ChunkGroupEntry,
1970 },
1971 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1972 resolve::ExportUsage,
1973 };
1974
1975 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1976 async fn traverse_dfs_from_entries_diamond() {
1977 run_graph_test(
1978 vec![rcstr!("a.js")],
1979 {
1980 let mut deps = FxHashMap::default();
1981 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1983 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1984 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1985 deps
1986 },
1987 |graph, entry_modules, module_to_name| {
1988 let mut preorder_visits = Vec::new();
1989 let mut postorder_visits = Vec::new();
1990
1991 graph.traverse_edges_from_entries_dfs(
1992 entry_modules,
1993 &mut (),
1994 |parent, target, _| {
1995 preorder_visits.push((
1996 parent
1997 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
1998 module_to_name.get(&target.module()).unwrap().clone(),
1999 ));
2000 Ok(GraphTraversalAction::Continue)
2001 },
2002 |parent, target, _| {
2003 postorder_visits.push((
2004 parent
2005 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2006 module_to_name.get(&target.module()).unwrap().clone(),
2007 ));
2008 Ok(())
2009 },
2010 )?;
2011 assert_eq!(
2012 vec![
2013 (None, rcstr!("a.js")),
2014 (Some(rcstr!("a.js")), rcstr!("b.js")),
2015 (Some(rcstr!("b.js")), rcstr!("d.js")),
2016 (Some(rcstr!("a.js")), rcstr!("c.js")),
2017 (Some(rcstr!("c.js")), rcstr!("d.js"))
2018 ],
2019 preorder_visits
2020 );
2021 assert_eq!(
2022 vec![
2023 (Some(rcstr!("b.js")), rcstr!("d.js")),
2024 (Some(rcstr!("a.js")), rcstr!("b.js")),
2025 (Some(rcstr!("c.js")), rcstr!("d.js")),
2026 (Some(rcstr!("a.js")), rcstr!("c.js")),
2027 (None, rcstr!("a.js"))
2028 ],
2029 postorder_visits
2030 );
2031 Ok(())
2032 },
2033 )
2034 .await;
2035 }
2036
2037 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2038 async fn traverse_dfs_from_entries_cycle() {
2039 run_graph_test(
2040 vec![rcstr!("a.js")],
2041 {
2042 let mut deps = FxHashMap::default();
2043 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2045 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2046 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2047 deps
2048 },
2049 |graph, entry_modules, module_to_name| {
2050 let mut preorder_visits = Vec::new();
2051 let mut postorder_visits = Vec::new();
2052
2053 graph.traverse_edges_from_entries_dfs(
2054 entry_modules,
2055 &mut (),
2056 |parent, target, _| {
2057 preorder_visits.push((
2058 parent
2059 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2060 module_to_name.get(&target.module()).unwrap().clone(),
2061 ));
2062 Ok(GraphTraversalAction::Continue)
2063 },
2064 |parent, target, _| {
2065 postorder_visits.push((
2066 parent
2067 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2068 module_to_name.get(&target.module()).unwrap().clone(),
2069 ));
2070 Ok(())
2071 },
2072 )?;
2073 assert_eq!(
2074 vec![
2075 (None, rcstr!("a.js")),
2076 (Some(rcstr!("a.js")), rcstr!("b.js")),
2077 (Some(rcstr!("b.js")), rcstr!("c.js")),
2078 (Some(rcstr!("c.js")), rcstr!("a.js")),
2079 ],
2080 preorder_visits
2081 );
2082 assert_eq!(
2083 vec![
2084 (Some(rcstr!("c.js")), rcstr!("a.js")),
2085 (Some(rcstr!("b.js")), rcstr!("c.js")),
2086 (Some(rcstr!("a.js")), rcstr!("b.js")),
2087 (None, rcstr!("a.js"))
2088 ],
2089 postorder_visits
2090 );
2091 Ok(())
2092 },
2093 )
2094 .await;
2095 }
2096
2097 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2098 async fn traverse_edges_from_entries_fixed_point_cycle() {
2099 run_graph_test(
2100 vec![rcstr!("a.js")],
2101 {
2102 let mut deps = FxHashMap::default();
2103 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2105 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2106 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2107 deps
2108 },
2109 |graph, entry_modules, module_to_name| {
2110 let mut visits = Vec::new();
2111 let mut count = 0;
2112
2113 graph.traverse_edges_from_entries_fixed_point(
2114 entry_modules,
2115 |parent, target| {
2116 visits.push((
2117 parent
2118 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2119 module_to_name.get(&target.module()).unwrap().clone(),
2120 ));
2121 count += 1;
2122
2123 Ok(if count < 6 {
2125 GraphTraversalAction::Continue
2126 } else {
2127 GraphTraversalAction::Skip
2128 })
2129 },
2130 )?;
2131 assert_eq!(
2132 vec![
2133 (None, rcstr!("a.js")),
2134 (Some(rcstr!("a.js")), rcstr!("b.js")),
2135 (Some(rcstr!("b.js")), rcstr!("c.js")),
2136 (Some(rcstr!("c.js")), rcstr!("a.js")),
2137 (Some(rcstr!("a.js")), rcstr!("b.js")),
2139 (Some(rcstr!("b.js")), rcstr!("c.js")),
2140 ],
2141 visits
2142 );
2143
2144 Ok(())
2145 },
2146 )
2147 .await;
2148 }
2149 #[turbo_tasks::value(shared)]
2150 struct TestRepo {
2151 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2152 }
2153 #[turbo_tasks::value]
2154 struct MockModule {
2155 path: FileSystemPath,
2156 repo: ResolvedVc<TestRepo>,
2157 }
2158 #[turbo_tasks::value_impl]
2159 impl MockModule {
2160 #[turbo_tasks::function]
2161 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2162 Self { path, repo }.cell()
2163 }
2164 }
2165
2166 #[turbo_tasks::value_impl]
2167 impl Asset for MockModule {
2168 #[turbo_tasks::function]
2169 fn content(&self) -> Vc<AssetContent> {
2170 panic!("MockModule::content shouldn't be called")
2171 }
2172 }
2173
2174 #[turbo_tasks::value_impl]
2175 impl Module for MockModule {
2176 #[turbo_tasks::function]
2177 fn ident(&self) -> Vc<AssetIdent> {
2178 AssetIdent::from_path(self.path.clone())
2179 }
2180
2181 #[turbo_tasks::function]
2182 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2183 let repo = self.repo.await?;
2184 let references = match repo.repo.get(&self.path) {
2185 Some(deps) => {
2186 deps.iter()
2187 .map(|p| {
2188 Vc::upcast::<Box<dyn ModuleReference>>(
2189 SingleChunkableModuleReference::new(
2190 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2191 rcstr!("normal-dep"),
2192 ExportUsage::all(),
2193 ),
2194 )
2195 .to_resolved()
2196 })
2197 .try_join()
2198 .await?
2199 }
2200 None => vec![],
2201 };
2202
2203 Ok(Vc::cell(references))
2204 }
2205 }
2206
2207 async fn run_graph_test(
2221 entries: Vec<RcStr>,
2222 graph: FxHashMap<RcStr, Vec<RcStr>>,
2223 test_fn: impl FnOnce(
2224 ReadRef<SingleModuleGraph>,
2225 Vec<ResolvedVc<Box<dyn Module>>>,
2226 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2227 ) -> Result<()>
2228 + Send
2229 + 'static,
2230 ) {
2231 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2232 BackendOptions::default(),
2233 noop_backing_storage(),
2234 ));
2235 tt.run_once(async move {
2236 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2237 let root = fs.root().await?;
2238
2239 let repo = TestRepo {
2240 repo: graph
2241 .iter()
2242 .map(|(k, v)| {
2243 (
2244 root.join(k).unwrap(),
2245 v.iter().map(|f| root.join(f).unwrap()).collect(),
2246 )
2247 })
2248 .collect(),
2249 }
2250 .cell();
2251 let entry_modules = entries
2252 .iter()
2253 .map(|e| {
2254 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2255 .to_resolved()
2256 })
2257 .try_join()
2258 .await?;
2259 let graph = SingleModuleGraph::new_with_entries(
2260 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2261 entry_modules.clone(),
2262 )])),
2263 false,
2264 )
2265 .await?;
2266
2267 let module_to_name = graph
2272 .modules
2273 .keys()
2274 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2275 .try_join()
2276 .await?
2277 .into_iter()
2278 .collect();
2279 test_fn(graph, entry_modules, module_to_name)
2280 })
2281 .await
2282 .unwrap();
2283 }
2284}