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", into = "new")]
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_edges_from_entries<'a>(
457 &'a self,
458 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
459 mut visitor: impl FnMut(
460 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
461 &'a SingleModuleGraphModuleNode,
462 ) -> GraphTraversalAction,
463 ) -> Result<()> {
464 let graph = &self.graph;
465 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
466
467 let mut stack = entries.collect::<Vec<_>>();
468 let mut discovered = graph.visit_map();
469 for entry_node in &stack {
471 let SingleModuleGraphNode::Module(entry_weight) =
472 graph.node_weight(*entry_node).unwrap()
473 else {
474 continue;
475 };
476 visitor(None, entry_weight);
477 }
478
479 while let Some(node) = stack.pop() {
480 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
481 else {
482 continue;
483 };
484 if discovered.visit(node) {
485 let neighbors = {
486 let mut neighbors = vec![];
487 let mut walker = graph.neighbors(node).detach();
488 while let Some((edge, succ)) = walker.next(graph) {
489 neighbors.push((edge, succ));
490 }
491 neighbors
492 };
493
494 for (edge, succ) in neighbors {
495 let SingleModuleGraphNode::Module(succ_weight) =
496 graph.node_weight(succ).unwrap()
497 else {
498 continue;
499 };
500 let edge_weight = graph.edge_weight(edge).unwrap();
501 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
502 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
503 stack.push(succ);
504 }
505 }
506 }
507 }
508
509 Ok(())
510 }
511
512 pub fn traverse_edges<'a>(
517 &'a self,
518 mut visitor: impl FnMut(
519 (
520 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
521 &'a SingleModuleGraphModuleNode,
522 ),
523 ) -> GraphTraversalAction,
524 ) -> Result<()> {
525 let graph = &self.graph;
526 let mut stack: Vec<NodeIndex> = self
527 .entries
528 .iter()
529 .flat_map(|e| e.entries())
530 .map(|e| *self.modules.get(&e).unwrap())
531 .collect();
532 let mut discovered = graph.visit_map();
533 for entry_node in &stack {
534 let SingleModuleGraphNode::Module(entry_node) = graph.node_weight(*entry_node).unwrap()
535 else {
536 continue;
537 };
538 visitor((None, entry_node));
539 }
540
541 while let Some(node) = stack.pop() {
542 if discovered.visit(node) {
543 let SingleModuleGraphNode::Module(node_weight) = graph.node_weight(node).unwrap()
544 else {
545 continue;
546 };
547 for edge in graph.edges(node).collect::<Vec<_>>() {
548 let edge_weight = edge.weight();
549 let succ = edge.target();
550 let SingleModuleGraphNode::Module(succ_weight) =
551 graph.node_weight(succ).unwrap()
552 else {
553 continue;
554 };
555 let action = visitor((Some((node_weight, edge_weight)), succ_weight));
556 if !discovered.is_visited(&succ) && action == GraphTraversalAction::Continue {
557 stack.push(succ);
558 }
559 }
560 }
561 }
562
563 Ok(())
564 }
565
566 pub fn traverse_edges_from_entries_fixed_point<'a>(
583 &'a self,
584 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
585 mut visit: impl FnMut(
586 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
587 &'a SingleModuleGraphNode,
588 ) -> Result<GraphTraversalAction>,
589 ) -> Result<usize> {
590 let mut queue = VecDeque::default();
591 let mut queue_set = FxHashSet::default();
592
593 for module in entries {
594 let index = self.get_module(module).unwrap();
595 let action = visit(None, self.graph.node_weight(index).unwrap())?;
596 if action == GraphTraversalAction::Continue && queue_set.insert(index) {
597 queue.push_back(index);
598 }
599 }
600
601 let mut visit_count = 0;
602 while let Some(index) = queue.pop_front() {
603 queue_set.remove(&index);
604 let node = match self.graph.node_weight(index).unwrap() {
605 SingleModuleGraphNode::Module(single_module_graph_module_node) => {
606 single_module_graph_module_node
607 }
608 _ => {
609 continue; }
611 };
612 visit_count += 1;
613 for edge in self
614 .graph
615 .edges_directed(index, petgraph::Direction::Outgoing)
616 {
617 let refdata = edge.weight();
618 let target_index = edge.target();
619 let target = self.graph.node_weight(edge.target()).unwrap();
620 let action = visit(Some((node, refdata)), target)?;
621 if action == GraphTraversalAction::Continue && queue_set.insert(target_index) {
622 queue.push_back(target_index);
623 }
624 }
625 }
626
627 Ok(visit_count)
628 }
629
630 pub fn traverse_edges_from_entries_dfs<'a, S>(
649 &'a self,
650 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
651 state: &mut S,
652 mut visit_preorder: impl FnMut(
653 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
654 &'a SingleModuleGraphNode,
655 &mut S,
656 ) -> Result<GraphTraversalAction>,
657 mut visit_postorder: impl FnMut(
658 Option<(&'a SingleModuleGraphModuleNode, &'a RefData)>,
659 &'a SingleModuleGraphNode,
660 &mut S,
661 ) -> Result<()>,
662 ) -> Result<()> {
663 let graph = &self.graph;
664 let entries = entries.into_iter().map(|e| self.get_module(e).unwrap());
665
666 enum Pass {
667 Visit,
668 ExpandAndVisit,
669 }
670
671 #[allow(clippy::type_complexity)] let mut stack: Vec<(Pass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> =
673 entries.map(|e| (Pass::ExpandAndVisit, None, e)).collect();
674 let mut expanded = FxHashSet::default();
675 while let Some((pass, parent, current)) = stack.pop() {
676 let parent_arg = parent.map(|parent| {
677 (
678 match graph.node_weight(parent.0).unwrap() {
679 SingleModuleGraphNode::Module(node) => node,
680 SingleModuleGraphNode::VisitedModule { .. } => {
681 unreachable!()
682 }
683 },
684 graph.edge_weight(parent.1).unwrap(),
685 )
686 });
687 match pass {
688 Pass::Visit => {
689 visit_postorder(parent_arg, graph.node_weight(current).unwrap(), state)?;
690 }
691 Pass::ExpandAndVisit => match graph.node_weight(current).unwrap() {
692 current_node @ SingleModuleGraphNode::Module(_) => {
693 let action = visit_preorder(parent_arg, current_node, state)?;
694 if action == GraphTraversalAction::Exclude {
695 continue;
696 }
697 stack.push((Pass::Visit, parent, current));
698 if action == GraphTraversalAction::Continue && expanded.insert(current) {
699 stack.extend(iter_neighbors_rev(graph, current).map(
700 |(edge, child)| {
701 (Pass::ExpandAndVisit, Some((current, edge)), child)
702 },
703 ));
704 }
705 }
706 current_node @ SingleModuleGraphNode::VisitedModule { .. } => {
707 visit_preorder(parent_arg, current_node, state)?;
708 visit_postorder(parent_arg, current_node, state)?;
709 }
710 },
711 }
712 }
713
714 Ok(())
715 }
716
717 pub fn traverse_cycles<'l>(
718 &'l self,
719 edge_filter: impl Fn(&'l RefData) -> bool,
720 mut visit_cycle: impl FnMut(&[&'l SingleModuleGraphModuleNode]),
721 ) {
722 #[derive(Clone)]
726 struct NodeState {
727 index: u32,
728 lowlink: u32,
729 on_stack: bool,
730 }
731 enum VisitStep {
732 UnvisitedNode(NodeIndex),
733 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
734 AfterVisit(NodeIndex),
735 }
736 let mut node_states = vec![None; self.graph.node_bound()];
737 let mut stack = Vec::new();
738 let mut visit_stack = Vec::new();
739 let mut index = 0;
740 let mut scc = Vec::new();
741 for initial_index in self.graph.node_indices() {
742 if node_states[initial_index.index()].is_some() {
744 continue;
745 }
746 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
747 while let Some(step) = visit_stack.pop() {
748 match step {
749 VisitStep::UnvisitedNode(node) => {
750 node_states[node.index()] = Some(NodeState {
751 index,
752 lowlink: index,
753 on_stack: true,
754 });
755 index += 1;
756 stack.push(node);
757 visit_stack.push(VisitStep::AfterVisit(node));
758 let mut neighbors = self.graph.neighbors(node).detach();
759 while let Some((edge, succ)) = neighbors.next(&self.graph) {
760 let edge_weight = self.graph.edge_weight(edge).unwrap();
761 if !edge_filter(edge_weight) {
762 continue;
763 }
764 let node_state = &node_states[succ.index()];
765 if let Some(node_state) = node_state {
766 if node_state.on_stack {
767 let index = node_state.index;
768 let parent_state = node_states[node.index()].as_mut().unwrap();
769 parent_state.lowlink = parent_state.lowlink.min(index);
770 }
771 } else {
772 visit_stack.push(VisitStep::EdgeAfterVisit {
773 parent: node,
774 child: succ,
775 });
776 visit_stack.push(VisitStep::UnvisitedNode(succ));
777 }
778 }
779 }
780 VisitStep::EdgeAfterVisit { parent, child } => {
781 let child_state = node_states[child.index()].as_ref().unwrap();
782 let lowlink = child_state.lowlink;
783
784 let parent_state = node_states[parent.index()].as_mut().unwrap();
785 parent_state.lowlink = parent_state.lowlink.min(lowlink);
786 }
787 VisitStep::AfterVisit(node) => {
788 let node_state = node_states[node.index()].as_ref().unwrap();
789 if node_state.lowlink == node_state.index {
790 loop {
791 let poppped = stack.pop().unwrap();
792 let popped_state = node_states[poppped.index()].as_mut().unwrap();
793 popped_state.on_stack = false;
794 if let SingleModuleGraphNode::Module(module) =
795 self.graph.node_weight(poppped).unwrap()
796 {
797 scc.push(module);
798 }
799 if poppped == node {
800 break;
801 }
802 }
803 if scc.len() > 1 {
804 visit_cycle(&scc);
805 }
806 scc.clear();
807 }
808 }
809 }
810 }
811 }
812 }
813
814 pub async fn compute_import_traces_for_issues(
822 &self,
823 issues: &AutoSet<ResolvedVc<Box<dyn Issue>>>,
824 ) -> Result<FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>>> {
825 let issue_paths = issues
826 .iter()
827 .map(|issue| issue.file_path().owned())
828 .try_join()
829 .await?;
830 let mut file_path_to_traces: FxHashMap<FileSystemPath, Vec<ImportTrace>> =
831 FxHashMap::with_capacity_and_hasher(issue_paths.len(), Default::default());
832 for issue in &issue_paths {
834 file_path_to_traces.entry(issue.clone()).or_default();
835 }
836
837 {
838 let modules =
839 self.modules
840 .iter()
841 .map(|(module, &index)| async move {
842 Ok((module.ident().path().owned().await?, index))
843 })
844 .try_join()
845 .await?;
846 let reversed_graph = Reversed(&self.graph.0);
848 for (path, module_idx) in modules {
849 if let Entry::Occupied(mut entry) = file_path_to_traces.entry(path) {
850 let Some((_, path)) = petgraph::algo::astar(
852 &reversed_graph,
853 module_idx,
854 |n| reversed_graph.neighbors(n).next().is_none(),
855 |e| match e.weight().chunking_type {
857 ChunkingType::Parallel { .. } => 0,
859 _ => 1,
860 },
861 |_| 0,
875 ) else {
876 unreachable!("there must be a path to a root");
877 };
878 let path = path
882 .into_iter()
883 .map(async |n| {
884 Ok(self
885 .graph
886 .node_weight(n)
887 .unwrap()
888 .module()
889 .ident()
890 .await?
891 .clone())
892 })
893 .try_join()
894 .await?;
895 entry.get_mut().push(path);
896 }
897 }
898 }
899 let mut issue_to_traces: FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>> =
900 FxHashMap::with_capacity_and_hasher(issues.len(), Default::default());
901 for (path, issue) in issue_paths.iter().zip(issues) {
905 if let Some(traces) = file_path_to_traces.get(path) {
906 issue_to_traces.insert(*issue, traces.clone());
907 }
908 }
909 Ok(issue_to_traces)
910 }
911}
912
913#[turbo_tasks::value]
914struct ModuleGraphImportTracer {
915 graph: ResolvedVc<SingleModuleGraph>,
916}
917
918#[turbo_tasks::value(shared)]
919struct PathToModulesMap {
920 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
921}
922
923#[turbo_tasks::value_impl]
924impl ModuleGraphImportTracer {
925 #[turbo_tasks::function]
926 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
927 Self::cell(Self { graph })
928 }
929
930 #[turbo_tasks::function]
932 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
933 let path_and_modules = self
934 .graph
935 .await?
936 .modules
937 .iter()
938 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
939 .try_join()
940 .await?;
941 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
942 FxHashMap::default();
943 for (path, module) in path_and_modules {
944 map.entry(path).or_default().push(module)
945 }
946 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
947 }
948}
949
950#[turbo_tasks::value_impl]
951impl ImportTracer for ModuleGraphImportTracer {
952 #[turbo_tasks::function]
953 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
954 let path_to_modules = self.path_to_modules().await?;
955 let Some(modules) = path_to_modules.map.get(&path) else {
956 return Ok(Vc::default()); };
959 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
960 let graph = &*self.await?.graph.await?;
961
962 let reversed_graph = Reversed(&graph.graph.0);
963 return Ok(ImportTraces::cell(ImportTraces(
964 modules
965 .iter()
966 .map(|m| async move {
967 let Some(&module_idx) = graph.modules.get(m) else {
968 bail!("inconsistent read?")
971 };
972 let Some((_, path)) = petgraph::algo::astar(
974 &reversed_graph,
975 module_idx,
976 |n| reversed_graph.neighbors(n).next().is_none(),
977 |e| match e.weight().chunking_type {
979 ChunkingType::Parallel { .. } => 0,
981 _ => 1,
982 },
983 |_| 0,
997 ) else {
998 unreachable!("there must be a path to a root");
999 };
1000
1001 let path = path
1005 .into_iter()
1006 .map(async |n| {
1007 graph
1008 .graph
1009 .node_weight(n)
1010 .unwrap() .module()
1012 .ident()
1013 .await
1014 })
1015 .try_join()
1016 .await?;
1017 Ok(path)
1018 })
1019 .try_join()
1020 .await?,
1021 )));
1022 }
1023}
1024
1025#[turbo_tasks::value(shared)]
1026#[derive(Clone, Default)]
1027pub struct ModuleGraph {
1028 pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
1029}
1030
1031#[turbo_tasks::value_impl]
1032impl ModuleGraph {
1033 #[turbo_tasks::function]
1034 pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
1035 Self { graphs }.cell()
1036 }
1037
1038 #[turbo_tasks::function]
1039 pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
1040 Self {
1041 graphs: vec![graph],
1042 }
1043 .cell()
1044 }
1045
1046 #[turbo_tasks::function]
1047 pub fn from_entry_module(
1048 module: ResolvedVc<Box<dyn Module>>,
1049 include_traced: bool,
1050 ) -> Vc<Self> {
1051 Self::from_single_graph(SingleModuleGraph::new_with_entries(
1052 Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
1053 include_traced,
1054 ))
1055 }
1056
1057 #[turbo_tasks::function]
1058 pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
1059 Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
1060 }
1061
1062 #[turbo_tasks::function]
1063 pub async fn chunk_group_info(&self) -> Result<Vc<ChunkGroupInfo>> {
1064 compute_chunk_group_info(self).await
1065 }
1066
1067 #[turbo_tasks::function]
1068 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
1069 compute_merged_modules(self).await
1070 }
1071
1072 #[turbo_tasks::function]
1073 pub async fn module_batches(
1074 self: Vc<Self>,
1075 config: Vc<BatchingConfig>,
1076 ) -> Result<Vc<ModuleBatchesGraph>> {
1077 compute_module_batches(self, &*config.await?).await
1078 }
1079
1080 #[turbo_tasks::function]
1081 pub async fn style_groups(
1082 self: Vc<Self>,
1083 chunking_context: Vc<Box<dyn ChunkingContext>>,
1084 config: StyleGroupsConfig,
1085 ) -> Result<Vc<StyleGroups>> {
1086 compute_style_groups(self, chunking_context, &config).await
1087 }
1088
1089 #[turbo_tasks::function]
1090 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
1091 async move {
1094 let result_op = compute_async_module_info(self.to_resolved().await?);
1095 let result_vc = result_op.resolve_strongly_consistent().await?;
1096 let _issues = result_op.take_collectibles::<Box<dyn Issue>>();
1097 anyhow::Ok(*result_vc)
1098 }
1099 .instrument(tracing::info_span!("compute async module info"))
1100 .await
1101 }
1102
1103 #[turbo_tasks::function]
1104 pub async fn referenced_async_modules(
1105 self: Vc<Self>,
1106 module: ResolvedVc<Box<dyn Module>>,
1107 ) -> Result<Vc<AsyncModuleInfo>> {
1108 let this = self.await?;
1109 let graphs = this.get_graphs().await?;
1110 let async_modules_info = self.async_module_info().await?;
1111
1112 let entry = ModuleGraph::get_entry(&graphs, module).await?;
1113 let referenced_modules =
1114 iter_neighbors_rev(&graphs[entry.graph_idx()].graph, entry.node_idx)
1115 .filter(|(edge_idx, _)| {
1116 let ty = graphs[entry.graph_idx()]
1117 .graph
1118 .edge_weight(*edge_idx)
1119 .unwrap();
1120 ty.chunking_type.is_inherit_async()
1121 })
1122 .map(|(_, child_idx)| {
1123 anyhow::Ok(
1124 get_node!(
1125 graphs,
1126 GraphNodeIndex {
1127 graph_idx: entry.graph_idx,
1128 node_idx: child_idx
1129 }
1130 )?
1131 .module,
1132 )
1133 })
1134 .collect::<Result<Vec<_>>>()?
1135 .into_iter()
1136 .rev()
1137 .filter(|m| async_modules_info.contains(m))
1138 .map(|m| *m)
1139 .collect();
1140
1141 Ok(AsyncModuleInfo::new(referenced_modules))
1142 }
1143}
1144
1145macro_rules! get_node {
1150 ($graphs:expr, $node:expr) => {{
1151 let node_idx = $node;
1152 match $graphs[node_idx.graph_idx()]
1153 .graph
1154 .node_weight(node_idx.node_idx)
1155 {
1156 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1157 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1158 match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1159 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok(node),
1160 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1161 "Expected visited target node to be module"
1162 )),
1163 None => Err(::anyhow::anyhow!("Expected visited target node")),
1164 }
1165 }
1166 None => Err(::anyhow::anyhow!("Expected graph node")),
1167 }
1168 }};
1169}
1170pub(crate) use get_node;
1171macro_rules! get_node_idx {
1172 ($graphs:expr, $node:expr) => {{
1173 let node_idx = $node;
1174 match $graphs[node_idx.graph_idx()]
1175 .graph
1176 .node_weight(node_idx.node_idx)
1177 {
1178 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, node_idx)),
1179 Some(SingleModuleGraphNode::VisitedModule { idx, .. }) => {
1180 match $graphs[idx.graph_idx()].graph.node_weight(idx.node_idx) {
1181 Some(SingleModuleGraphNode::Module(node)) => ::anyhow::Ok((node, *idx)),
1182 Some(SingleModuleGraphNode::VisitedModule { .. }) => Err(::anyhow::anyhow!(
1183 "Expected visited target node to be module"
1184 )),
1185 None => Err(::anyhow::anyhow!("Expected visited target node")),
1186 }
1187 }
1188 None => Err(::anyhow::anyhow!("Expected graph node")),
1189 }
1190 }};
1191}
1192pub(crate) use get_node_idx;
1193
1194impl ModuleGraph {
1195 pub async fn get_graphs(&self) -> Result<Vec<ReadRef<SingleModuleGraph>>> {
1196 self.graphs.iter().try_join().await
1197 }
1198
1199 async fn get_entry(
1200 graphs: &[ReadRef<SingleModuleGraph>],
1201 entry: ResolvedVc<Box<dyn Module>>,
1202 ) -> Result<GraphNodeIndex> {
1203 let Some(idx) = graphs.iter().enumerate().find_map(|(graph_idx, graph)| {
1204 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1205 graph_idx: u32::try_from(graph_idx).unwrap(),
1206 node_idx: *node_idx,
1207 })
1208 }) else {
1209 bail!(
1210 "Couldn't find entry module {} in module graph (potential entries: {:?})",
1211 entry.ident().to_string().await?,
1212 graphs
1213 .iter()
1214 .flat_map(|g| g.entries.iter())
1215 .flat_map(|e| e.entries())
1216 .map(|e| e.ident().to_string())
1217 .try_join()
1218 .await?
1219 );
1220 };
1221 Ok(idx)
1222 }
1223
1224 pub async fn traverse_edges_from_entries_bfs(
1235 &self,
1236 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1237 mut visitor: impl FnMut(
1238 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1239 &'_ SingleModuleGraphModuleNode,
1240 ) -> Result<GraphTraversalAction>,
1241 ) -> Result<()> {
1242 let graphs = self.get_graphs().await?;
1243
1244 let mut queue = VecDeque::from(
1245 entries
1246 .into_iter()
1247 .map(|e| ModuleGraph::get_entry(&graphs, e))
1248 .try_join()
1249 .await?,
1250 );
1251 let mut visited = HashSet::new();
1252 for entry_node in &queue {
1253 visitor(None, get_node!(graphs, entry_node)?)?;
1254 }
1255 while let Some(node) = queue.pop_front() {
1256 let graph = &graphs[node.graph_idx()].graph;
1257 let node_weight = get_node!(graphs, node)?;
1258 if visited.insert(node) {
1259 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1260
1261 for (edge, succ) in neighbors {
1262 let succ = GraphNodeIndex {
1263 graph_idx: node.graph_idx,
1264 node_idx: succ,
1265 };
1266 let succ_weight = get_node!(graphs, succ)?;
1267 let edge_weight = graph.edge_weight(edge).unwrap();
1268 let action = visitor(Some((node_weight, edge_weight)), succ_weight)?;
1269 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1270 queue.push_back(succ);
1271 }
1272 }
1273 }
1274 }
1275
1276 Ok(())
1277 }
1278
1279 pub async fn traverse_edges_from_entry(
1290 &self,
1291 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1292 mut visitor: impl FnMut(
1293 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1294 &'_ SingleModuleGraphModuleNode,
1295 ) -> GraphTraversalAction,
1296 ) -> Result<()> {
1297 let graphs = self.get_graphs().await?;
1298
1299 let entries = entries.into_iter();
1300 let mut stack = Vec::with_capacity(entries.size_hint().0);
1301 for entry in entries {
1302 stack.push(ModuleGraph::get_entry(&graphs, entry).await?);
1303 }
1304 let mut visited = HashSet::new();
1305 for entry_node in &stack {
1306 visitor(None, get_node!(graphs, entry_node)?);
1307 }
1308 while let Some(node) = stack.pop() {
1309 let graph = &graphs[node.graph_idx()].graph;
1310 let node_weight = get_node!(graphs, node)?;
1311 if visited.insert(node) {
1312 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1313
1314 for (edge, succ) in neighbors {
1315 let succ = GraphNodeIndex {
1316 graph_idx: node.graph_idx,
1317 node_idx: succ,
1318 };
1319 let succ_weight = get_node!(graphs, succ)?;
1320 let edge_weight = graph.edge_weight(edge).unwrap();
1321 let action = visitor(Some((node_weight, edge_weight)), succ_weight);
1322 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1323 stack.push(succ);
1324 }
1325 }
1326 }
1327 }
1328
1329 Ok(())
1330 }
1331
1332 pub async fn traverse_all_edges_unordered(
1341 &self,
1342 mut visitor: impl FnMut(
1343 (&'_ SingleModuleGraphModuleNode, &'_ RefData),
1344 &'_ SingleModuleGraphModuleNode,
1345 ) -> Result<()>,
1346 ) -> Result<()> {
1347 let graphs = self.get_graphs().await?;
1348
1349 for graph in &graphs {
1350 let graph = &graph.graph;
1351 for edge in graph.edge_references() {
1352 let source = match graph.node_weight(edge.source()).unwrap() {
1353 SingleModuleGraphNode::Module(node) => node,
1354 SingleModuleGraphNode::VisitedModule { .. } => unreachable!(),
1355 };
1356 let target = match graph.node_weight(edge.target()).unwrap() {
1357 SingleModuleGraphNode::Module(node) => node,
1358 SingleModuleGraphNode::VisitedModule { idx, .. } => get_node!(graphs, idx)?,
1359 };
1360 visitor((source, edge.weight()), target)?;
1361 }
1362 }
1363
1364 Ok(())
1365 }
1366
1367 pub async fn traverse_edges_from_entries_dfs<S>(
1387 &self,
1388 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1389 state: &mut S,
1390 mut visit_preorder: impl FnMut(
1391 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1392 &'_ SingleModuleGraphModuleNode,
1393 &mut S,
1394 ) -> Result<GraphTraversalAction>,
1395 mut visit_postorder: impl FnMut(
1396 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1397 &'_ SingleModuleGraphModuleNode,
1398 &mut S,
1399 ) -> Result<()>,
1400 ) -> Result<()> {
1401 let graphs = self.get_graphs().await?;
1402
1403 let entries = entries.into_iter().collect::<Vec<_>>();
1404
1405 enum Pass {
1406 Visit,
1407 ExpandAndVisit,
1408 }
1409 #[allow(clippy::type_complexity)] let mut stack: Vec<(Pass, Option<(GraphNodeIndex, EdgeIndex)>, GraphNodeIndex)> =
1411 Vec::with_capacity(entries.len());
1412 for entry in entries.into_iter().rev() {
1413 stack.push((
1414 Pass::ExpandAndVisit,
1415 None,
1416 ModuleGraph::get_entry(&graphs, entry).await?,
1417 ));
1418 }
1419 let mut expanded = HashSet::new();
1420 while let Some((pass, parent, current)) = stack.pop() {
1421 let parent_arg = match parent {
1422 Some((parent_node, parent_edge)) => Some((
1423 get_node!(graphs, parent_node)?,
1424 graphs[parent_node.graph_idx()]
1425 .graph
1426 .edge_weight(parent_edge)
1427 .unwrap(),
1428 )),
1429 None => None,
1430 };
1431 let current_node = get_node!(graphs, current)?;
1432 match pass {
1433 Pass::Visit => {
1434 visit_postorder(parent_arg, current_node, state)?;
1435 }
1436 Pass::ExpandAndVisit => {
1437 let action = visit_preorder(parent_arg, current_node, state)?;
1438 if action == GraphTraversalAction::Exclude {
1439 continue;
1440 }
1441 stack.push((Pass::Visit, parent, current));
1442 if action == GraphTraversalAction::Continue && expanded.insert(current) {
1443 let graph = &graphs[current.graph_idx()].graph;
1444 let (neighbors_rev, current) = match graph
1445 .node_weight(current.node_idx)
1446 .unwrap()
1447 {
1448 SingleModuleGraphNode::Module(_) => {
1449 (iter_neighbors_rev(graph, current.node_idx), current)
1450 }
1451 SingleModuleGraphNode::VisitedModule { idx, .. } => (
1452 iter_neighbors_rev(&graphs[idx.graph_idx()].graph, idx.node_idx),
1454 *idx,
1455 ),
1456 };
1457 stack.extend(neighbors_rev.map(|(edge, child)| {
1458 (
1459 Pass::ExpandAndVisit,
1460 Some((current, edge)),
1461 GraphNodeIndex {
1462 graph_idx: current.graph_idx,
1463 node_idx: child,
1464 },
1465 )
1466 }));
1467 }
1468 }
1469 }
1470 }
1471
1472 Ok(())
1473 }
1474
1475 pub async fn traverse_cycles(
1478 &self,
1479 edge_filter: impl Fn(&RefData) -> bool,
1480 mut visit_cycle: impl FnMut(&[&SingleModuleGraphModuleNode]),
1481 ) -> Result<()> {
1482 for graph in &self.graphs {
1483 graph.await?.traverse_cycles(&edge_filter, &mut visit_cycle);
1484 }
1485 Ok(())
1486 }
1487
1488 pub async fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1510 &self,
1511 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1512 state: &mut S,
1513 mut visit: impl FnMut(
1514 Option<(&'_ SingleModuleGraphModuleNode, &'_ RefData)>,
1515 &'_ SingleModuleGraphModuleNode,
1516 &mut S,
1517 ) -> Result<GraphTraversalAction>,
1518 priority: impl Fn(&'_ SingleModuleGraphModuleNode, &mut S) -> Result<P>,
1519 ) -> Result<usize> {
1520 let graphs = self.get_graphs().await?;
1521
1522 #[derive(PartialEq, Eq)]
1523 struct NodeWithPriority<T: Ord> {
1524 node: GraphNodeIndex,
1525 priority: T,
1526 }
1527 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1528 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1529 Some(self.cmp(other))
1530 }
1531 }
1532 impl<T: Ord> Ord for NodeWithPriority<T> {
1533 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1534 self.priority
1537 .cmp(&other.priority)
1538 .then(other.node.cmp(&self.node))
1540 }
1541 }
1542
1543 let mut queue_set = FxHashSet::default();
1544 let mut queue = BinaryHeap::from_iter(
1545 entries
1546 .into_iter()
1547 .map(async |(m, priority)| {
1548 Ok(NodeWithPriority {
1549 node: ModuleGraph::get_entry(&graphs, m).await?,
1550 priority,
1551 })
1552 })
1553 .try_join()
1554 .await?,
1555 );
1556 for entry_node in &queue {
1557 visit(None, get_node!(graphs, entry_node.node)?, state)?;
1558 }
1559
1560 let mut visit_count = 0usize;
1561 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1562 queue_set.remove(&node);
1563 let (node_weight, node) = get_node_idx!(graphs, node)?;
1564 let graph = &graphs[node.graph_idx()].graph;
1565 let neighbors = iter_neighbors_rev(graph, node.node_idx);
1566
1567 visit_count += 1;
1568
1569 for (edge, succ) in neighbors {
1570 let succ = GraphNodeIndex {
1571 graph_idx: node.graph_idx,
1572 node_idx: succ,
1573 };
1574 let (succ_weight, succ) = get_node_idx!(graphs, succ)?;
1575 let edge_weight = graph.edge_weight(edge).unwrap();
1576 let action = visit(Some((node_weight, edge_weight)), succ_weight, state)?;
1577
1578 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1579 queue.push(NodeWithPriority {
1580 node: succ,
1581 priority: priority(succ_weight, state)?,
1582 });
1583 }
1584 }
1585 }
1586
1587 Ok(visit_count)
1588 }
1589}
1590
1591#[turbo_tasks::value_impl]
1592impl SingleModuleGraph {
1593 #[turbo_tasks::function]
1594 pub async fn new_with_entries(
1595 entries: Vc<GraphEntries>,
1596 include_traced: bool,
1597 ) -> Result<Vc<Self>> {
1598 SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1599 }
1600
1601 #[turbo_tasks::function]
1602 pub async fn new_with_entries_visited(
1603 entries: Vc<GraphEntries>,
1604 visited_modules: Vc<VisitedModules>,
1605 include_traced: bool,
1606 ) -> Result<Vc<Self>> {
1607 SingleModuleGraph::new_inner(
1608 &*entries.await?,
1609 &visited_modules.await?.modules,
1610 include_traced,
1611 )
1612 .await
1613 }
1614
1615 #[turbo_tasks::function]
1616 pub async fn new_with_entries_visited_intern(
1617 entries: GraphEntriesT,
1619 visited_modules: Vc<VisitedModules>,
1620 include_traced: bool,
1621 ) -> Result<Vc<Self>> {
1622 SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1623 .await
1624 }
1625}
1626
1627#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1628pub struct SingleModuleGraphModuleNode {
1629 pub module: ResolvedVc<Box<dyn Module>>,
1630}
1631
1632#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1633pub enum SingleModuleGraphNode {
1634 Module(SingleModuleGraphModuleNode),
1635 VisitedModule {
1637 idx: GraphNodeIndex,
1638 module: ResolvedVc<Box<dyn Module>>,
1639 },
1640}
1641
1642impl SingleModuleGraphNode {
1643 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1644 match self {
1645 SingleModuleGraphNode::Module(SingleModuleGraphModuleNode { module }) => *module,
1646 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1647 }
1648 }
1649}
1650
1651#[derive(PartialEq, Eq, Debug)]
1652pub enum GraphTraversalAction {
1653 Continue,
1655 Skip,
1657 Exclude,
1659}
1660
1661#[derive(Clone, Hash, PartialEq, Eq)]
1664enum SingleModuleGraphBuilderNode {
1665 ChunkableReference {
1667 ref_data: RefData,
1668 source: ResolvedVc<Box<dyn Module>>,
1669 target: ResolvedVc<Box<dyn Module>>,
1670 source_ident: Option<ReadRef<RcStr>>,
1673 target_ident: Option<ReadRef<RcStr>>,
1674 },
1675 Module {
1677 module: ResolvedVc<Box<dyn Module>>,
1678 ident: Option<ReadRef<RcStr>>,
1680 },
1681 VisitedModule {
1683 module: ResolvedVc<Box<dyn Module>>,
1684 idx: GraphNodeIndex,
1685 },
1686}
1687
1688impl SingleModuleGraphBuilderNode {
1689 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1690 let ident = module.ident();
1691 Ok(Self::Module {
1692 module,
1693 ident: if emit_spans {
1694 Some(ident.to_string().await?)
1695 } else {
1696 None
1697 },
1698 })
1699 }
1700 async fn new_chunkable_ref(
1701 emit_spans: bool,
1702 source: ResolvedVc<Box<dyn Module>>,
1703 target: ResolvedVc<Box<dyn Module>>,
1704 ref_data: RefData,
1705 ) -> Result<Self> {
1706 Ok(Self::ChunkableReference {
1707 ref_data,
1708 source,
1709 source_ident: if emit_spans {
1710 Some(source.ident().to_string().await?)
1711 } else {
1712 None
1713 },
1714 target,
1715 target_ident: if emit_spans {
1716 Some(target.ident().to_string().await?)
1717 } else {
1718 None
1719 },
1720 })
1721 }
1722 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1723 Self::VisitedModule { module, idx }
1724 }
1725}
1726struct SingleModuleGraphBuilderEdge {
1727 to: SingleModuleGraphBuilderNode,
1728 export: ExportUsage,
1729}
1730
1731const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1734 inherit_async: true,
1735 hoisted: true,
1736};
1737
1738struct SingleModuleGraphBuilder<'a> {
1739 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1740
1741 emit_spans: bool,
1742
1743 include_traced: bool,
1745}
1746impl Visit<(SingleModuleGraphBuilderNode, ExportUsage)> for SingleModuleGraphBuilder<'_> {
1747 type Edge = SingleModuleGraphBuilderEdge;
1748 type EdgesIntoIter = Vec<Self::Edge>;
1749 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1750
1751 fn visit(
1752 &mut self,
1753 edge: Self::Edge,
1754 ) -> VisitControlFlow<(SingleModuleGraphBuilderNode, ExportUsage)> {
1755 match edge.to {
1756 SingleModuleGraphBuilderNode::Module { .. } => {
1757 VisitControlFlow::Continue((edge.to, edge.export))
1758 }
1759 SingleModuleGraphBuilderNode::ChunkableReference { ref ref_data, .. } => {
1760 match &ref_data.chunking_type {
1761 ChunkingType::Traced => VisitControlFlow::Skip((edge.to, edge.export)),
1762 _ => VisitControlFlow::Continue((edge.to, edge.export)),
1763 }
1764 }
1765 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1767 VisitControlFlow::Skip((edge.to, edge.export))
1768 }
1769 }
1770 }
1771
1772 fn edges(
1773 &mut self,
1774 (node, _): &(SingleModuleGraphBuilderNode, ExportUsage),
1777 ) -> Self::EdgesFuture {
1778 let (module, chunkable_ref_target) = match node {
1780 SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1781 SingleModuleGraphBuilderNode::ChunkableReference {
1782 target, ref_data, ..
1783 } => (None, Some((*target, ref_data.export.clone()))),
1784 SingleModuleGraphBuilderNode::VisitedModule { .. } => unreachable!(),
1786 };
1787 let visited_modules = self.visited_modules;
1788 let emit_spans = self.emit_spans;
1789 let include_traced = self.include_traced;
1790 async move {
1791 Ok(match (module, chunkable_ref_target) {
1792 (Some(module), None) => {
1793 let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1794 let refs = match refs_cell.await {
1795 Ok(refs) => refs,
1796 Err(e) => {
1797 return Err(e.context(module.ident().to_string().await?));
1798 }
1799 };
1800
1801 refs.iter()
1802 .flat_map(|(ty, export, modules)| {
1803 modules.iter().map(|m| (ty.clone(), export.clone(), *m))
1804 })
1805 .map(async |(ty, export, target)| {
1806 let to = if ty == COMMON_CHUNKING_TYPE {
1807 if let Some(idx) = visited_modules.get(&target) {
1808 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1809 } else {
1810 SingleModuleGraphBuilderNode::new_module(emit_spans, target)
1811 .await?
1812 }
1813 } else {
1814 SingleModuleGraphBuilderNode::new_chunkable_ref(
1815 emit_spans,
1816 module,
1817 target,
1818 RefData {
1819 chunking_type: ty,
1820 export: export.clone(),
1821 },
1822 )
1823 .await?
1824 };
1825 Ok(SingleModuleGraphBuilderEdge { to, export })
1826 })
1827 .try_join()
1828 .await?
1829 }
1830 (None, Some((chunkable_ref_target, export))) => {
1831 vec![SingleModuleGraphBuilderEdge {
1832 to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1833 SingleModuleGraphBuilderNode::new_visited_module(
1834 chunkable_ref_target,
1835 *idx,
1836 )
1837 } else {
1838 SingleModuleGraphBuilderNode::new_module(
1839 emit_spans,
1840 chunkable_ref_target,
1841 )
1842 .await?
1843 },
1844 export,
1845 }]
1846 }
1847 _ => unreachable!(),
1848 })
1849 }
1850 }
1851
1852 fn span(&mut self, (node, _): &(SingleModuleGraphBuilderNode, ExportUsage)) -> tracing::Span {
1853 if !self.emit_spans {
1854 return Span::current();
1855 }
1856
1857 match node {
1858 SingleModuleGraphBuilderNode::Module {
1859 ident: Some(ident), ..
1860 } => {
1861 tracing::info_span!("module", name = display(ident))
1862 }
1863 SingleModuleGraphBuilderNode::ChunkableReference {
1864 ref_data,
1865 source_ident: Some(source_ident),
1866 target_ident: Some(target_ident),
1867 ..
1868 } => match &ref_data.chunking_type {
1869 ChunkingType::Parallel {
1870 inherit_async: false,
1871 ..
1872 } => Span::current(),
1873 _ => {
1874 tracing::info_span!(
1875 "chunkable reference",
1876 ty = debug(&ref_data.chunking_type),
1877 source = display(source_ident),
1878 target = display(target_ident)
1879 )
1880 }
1881 },
1882 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1883 tracing::info_span!("visited module")
1884 }
1885 _ => Span::current(),
1886 }
1887 }
1888}
1889
1890#[cfg(test)]
1891pub mod tests {
1892 use anyhow::Result;
1893 use rustc_hash::FxHashMap;
1894 use turbo_rcstr::{RcStr, rcstr};
1895 use turbo_tasks::{ReadRef, ResolvedVc, TryJoinIterExt, Vc};
1896 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1897 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1898
1899 use crate::{
1900 asset::{Asset, AssetContent},
1901 ident::AssetIdent,
1902 module::Module,
1903 module_graph::{
1904 GraphEntries, GraphTraversalAction, SingleModuleGraph,
1905 chunk_group_info::ChunkGroupEntry,
1906 },
1907 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1908 resolve::ExportUsage,
1909 };
1910
1911 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1912 async fn traverse_dfs_from_entries_diamond() {
1913 run_graph_test(
1914 vec![rcstr!("a.js")],
1915 {
1916 let mut deps = FxHashMap::default();
1917 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1919 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1920 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1921 deps
1922 },
1923 |graph, entry_modules, module_to_name| {
1924 let mut preorder_visits = Vec::new();
1925 let mut postorder_visits = Vec::new();
1926
1927 graph.traverse_edges_from_entries_dfs(
1928 entry_modules,
1929 &mut (),
1930 |parent, target, _| {
1931 preorder_visits.push((
1932 parent
1933 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
1934 module_to_name.get(&target.module()).unwrap().clone(),
1935 ));
1936 Ok(GraphTraversalAction::Continue)
1937 },
1938 |parent, target, _| {
1939 postorder_visits.push((
1940 parent
1941 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
1942 module_to_name.get(&target.module()).unwrap().clone(),
1943 ));
1944 Ok(())
1945 },
1946 )?;
1947 assert_eq!(
1948 vec![
1949 (None, rcstr!("a.js")),
1950 (Some(rcstr!("a.js")), rcstr!("b.js")),
1951 (Some(rcstr!("b.js")), rcstr!("d.js")),
1952 (Some(rcstr!("a.js")), rcstr!("c.js")),
1953 (Some(rcstr!("c.js")), rcstr!("d.js"))
1954 ],
1955 preorder_visits
1956 );
1957 assert_eq!(
1958 vec![
1959 (Some(rcstr!("b.js")), rcstr!("d.js")),
1960 (Some(rcstr!("a.js")), rcstr!("b.js")),
1961 (Some(rcstr!("c.js")), rcstr!("d.js")),
1962 (Some(rcstr!("a.js")), rcstr!("c.js")),
1963 (None, rcstr!("a.js"))
1964 ],
1965 postorder_visits
1966 );
1967 Ok(())
1968 },
1969 )
1970 .await;
1971 }
1972
1973 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1974 async fn traverse_dfs_from_entries_cycle() {
1975 run_graph_test(
1976 vec![rcstr!("a.js")],
1977 {
1978 let mut deps = FxHashMap::default();
1979 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1981 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1982 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1983 deps
1984 },
1985 |graph, entry_modules, module_to_name| {
1986 let mut preorder_visits = Vec::new();
1987 let mut postorder_visits = Vec::new();
1988
1989 graph.traverse_edges_from_entries_dfs(
1990 entry_modules,
1991 &mut (),
1992 |parent, target, _| {
1993 preorder_visits.push((
1994 parent
1995 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
1996 module_to_name.get(&target.module()).unwrap().clone(),
1997 ));
1998 Ok(GraphTraversalAction::Continue)
1999 },
2000 |parent, target, _| {
2001 postorder_visits.push((
2002 parent
2003 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2004 module_to_name.get(&target.module()).unwrap().clone(),
2005 ));
2006 Ok(())
2007 },
2008 )?;
2009 assert_eq!(
2010 vec![
2011 (None, rcstr!("a.js")),
2012 (Some(rcstr!("a.js")), rcstr!("b.js")),
2013 (Some(rcstr!("b.js")), rcstr!("c.js")),
2014 (Some(rcstr!("c.js")), rcstr!("a.js")),
2015 ],
2016 preorder_visits
2017 );
2018 assert_eq!(
2019 vec![
2020 (Some(rcstr!("c.js")), rcstr!("a.js")),
2021 (Some(rcstr!("b.js")), rcstr!("c.js")),
2022 (Some(rcstr!("a.js")), rcstr!("b.js")),
2023 (None, rcstr!("a.js"))
2024 ],
2025 postorder_visits
2026 );
2027 Ok(())
2028 },
2029 )
2030 .await;
2031 }
2032
2033 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2034 async fn traverse_edges_from_entries_fixed_point_cycle() {
2035 run_graph_test(
2036 vec![rcstr!("a.js")],
2037 {
2038 let mut deps = FxHashMap::default();
2039 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2041 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2042 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2043 deps
2044 },
2045 |graph, entry_modules, module_to_name| {
2046 let mut visits = Vec::new();
2047 let mut count = 0;
2048
2049 graph.traverse_edges_from_entries_fixed_point(
2050 entry_modules,
2051 |parent, target| {
2052 visits.push((
2053 parent
2054 .map(|(node, _)| module_to_name.get(&node.module).unwrap().clone()),
2055 module_to_name.get(&target.module()).unwrap().clone(),
2056 ));
2057 count += 1;
2058
2059 Ok(if count < 6 {
2061 GraphTraversalAction::Continue
2062 } else {
2063 GraphTraversalAction::Skip
2064 })
2065 },
2066 )?;
2067 assert_eq!(
2068 vec![
2069 (None, rcstr!("a.js")),
2070 (Some(rcstr!("a.js")), rcstr!("b.js")),
2071 (Some(rcstr!("b.js")), rcstr!("c.js")),
2072 (Some(rcstr!("c.js")), rcstr!("a.js")),
2073 (Some(rcstr!("a.js")), rcstr!("b.js")),
2075 (Some(rcstr!("b.js")), rcstr!("c.js")),
2076 ],
2077 visits
2078 );
2079
2080 Ok(())
2081 },
2082 )
2083 .await;
2084 }
2085 #[turbo_tasks::value(shared)]
2086 struct TestRepo {
2087 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2088 }
2089 #[turbo_tasks::value]
2090 struct MockModule {
2091 path: FileSystemPath,
2092 repo: ResolvedVc<TestRepo>,
2093 }
2094 #[turbo_tasks::value_impl]
2095 impl MockModule {
2096 #[turbo_tasks::function]
2097 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2098 Self { path, repo }.cell()
2099 }
2100 }
2101
2102 #[turbo_tasks::value_impl]
2103 impl Asset for MockModule {
2104 #[turbo_tasks::function]
2105 fn content(&self) -> Vc<AssetContent> {
2106 panic!("MockModule::content shouldn't be called")
2107 }
2108 }
2109
2110 #[turbo_tasks::value_impl]
2111 impl Module for MockModule {
2112 #[turbo_tasks::function]
2113 fn ident(&self) -> Vc<AssetIdent> {
2114 AssetIdent::from_path(self.path.clone())
2115 }
2116
2117 #[turbo_tasks::function]
2118 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2119 let repo = self.repo.await?;
2120 let references = match repo.repo.get(&self.path) {
2121 Some(deps) => {
2122 deps.iter()
2123 .map(|p| {
2124 Vc::upcast::<Box<dyn ModuleReference>>(
2125 SingleChunkableModuleReference::new(
2126 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2127 rcstr!("normal-dep"),
2128 ExportUsage::all(),
2129 ),
2130 )
2131 .to_resolved()
2132 })
2133 .try_join()
2134 .await?
2135 }
2136 None => vec![],
2137 };
2138
2139 Ok(Vc::cell(references))
2140 }
2141 }
2142
2143 async fn run_graph_test(
2157 entries: Vec<RcStr>,
2158 graph: FxHashMap<RcStr, Vec<RcStr>>,
2159 test_fn: impl FnOnce(
2160 ReadRef<SingleModuleGraph>,
2161 Vec<ResolvedVc<Box<dyn Module>>>,
2162 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2163 ) -> Result<()>
2164 + Send
2165 + 'static,
2166 ) {
2167 crate::register();
2168
2169 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2170 BackendOptions::default(),
2171 noop_backing_storage(),
2172 ));
2173 tt.run_once(async move {
2174 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2175 let root = fs.root().await?;
2176
2177 let repo = TestRepo {
2178 repo: graph
2179 .iter()
2180 .map(|(k, v)| {
2181 (
2182 root.join(k).unwrap(),
2183 v.iter().map(|f| root.join(f).unwrap()).collect(),
2184 )
2185 })
2186 .collect(),
2187 }
2188 .cell();
2189 let entry_modules = entries
2190 .iter()
2191 .map(|e| {
2192 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2193 .to_resolved()
2194 })
2195 .try_join()
2196 .await?;
2197 let graph = SingleModuleGraph::new_with_entries(
2198 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2199 entry_modules.clone(),
2200 )])),
2201 false,
2202 )
2203 .await?;
2204
2205 let module_to_name = graph
2210 .modules
2211 .keys()
2212 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2213 .try_join()
2214 .await?
2215 .into_iter()
2216 .collect();
2217 test_fn(graph, entry_modules, module_to_name)
2218 })
2219 .await
2220 .unwrap();
2221 }
2222}