1use core::panic;
2use std::{
3 collections::{BinaryHeap, VecDeque, hash_map::Entry},
4 future::Future,
5};
6
7use anyhow::{Context, Result, bail};
8use auto_hash_map::AutoSet;
9use petgraph::{
10 graph::{DiGraph, EdgeIndex, NodeIndex},
11 visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
12};
13use rustc_hash::{FxHashMap, FxHashSet};
14use serde::{Deserialize, Serialize};
15use tracing::{Instrument, Level, Span};
16use turbo_rcstr::RcStr;
17use turbo_tasks::{
18 CollectiblesSource, FxIndexMap, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt,
19 ValueToString, Vc,
20 debug::ValueDebugFormat,
21 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
22 trace::TraceRawVcs,
23};
24use turbo_tasks_fs::FileSystemPath;
25
26use crate::{
27 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
28 issue::{ImportTrace, ImportTracer, ImportTraces, Issue},
29 module::Module,
30 module_graph::{
31 async_module_info::{AsyncModulesInfo, compute_async_module_info},
32 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
33 merged_modules::{MergedModuleInfo, compute_merged_modules},
34 module_batches::{ModuleBatchesGraph, compute_module_batches},
35 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
36 traced_di_graph::TracedDiGraph,
37 },
38 reference::primary_chunkable_referenced_modules,
39 resolve::ExportUsage,
40};
41
42pub mod async_module_info;
43pub mod chunk_group_info;
44pub mod export_usage;
45pub mod merged_modules;
46pub mod module_batch;
47pub(crate) mod module_batches;
48pub(crate) mod style_groups;
49mod traced_di_graph;
50
51pub use self::module_batches::BatchingConfig;
52
53#[derive(
54 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, TraceRawVcs,
55)]
56pub struct GraphNodeIndex {
57 #[turbo_tasks(trace_ignore)]
58 graph_idx: u32,
59 #[turbo_tasks(trace_ignore)]
60 node_idx: NodeIndex,
61}
62impl GraphNodeIndex {
63 #[inline(always)]
64 fn graph_idx(&self) -> usize {
65 self.graph_idx as usize
66 }
67}
68
69unsafe impl NonLocalValue for GraphNodeIndex {}
70
71#[turbo_tasks::value]
72#[derive(Clone, Debug)]
73pub struct VisitedModules {
74 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
75 next_graph_idx: u32,
76}
77
78#[turbo_tasks::value_impl]
79impl VisitedModules {
80 #[turbo_tasks::function]
81 pub fn empty() -> Vc<Self> {
82 Self {
83 modules: Default::default(),
84 next_graph_idx: 0,
85 }
86 .cell()
87 }
88
89 #[turbo_tasks::function]
90 pub async fn from_graph(graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
91 Ok(Self {
92 modules: graph
93 .await?
94 .enumerate_nodes()
95 .flat_map(|(node_idx, module)| match module {
96 SingleModuleGraphNode::Module(module) => Some((
97 *module,
98 GraphNodeIndex {
99 graph_idx: 0,
100 node_idx,
101 },
102 )),
103 SingleModuleGraphNode::VisitedModule { .. } => None,
104 })
105 .collect(),
106 next_graph_idx: 1,
107 }
108 .cell())
109 }
110
111 #[turbo_tasks::function]
112 pub fn with_incremented_index(&self) -> Result<Vc<Self>> {
113 Ok(Self {
114 modules: self.modules.clone(),
115 next_graph_idx: self.next_graph_idx + 1,
116 }
117 .cell())
118 }
119
120 #[turbo_tasks::function]
121 pub async fn concatenate(&self, graph: Vc<SingleModuleGraph>) -> Result<Vc<Self>> {
122 let graph = graph.await?;
123 let iter = self
124 .modules
125 .iter()
126 .map(|(module, idx)| (*module, *idx))
127 .chain(
128 graph
129 .enumerate_nodes()
130 .flat_map(|(node_idx, module)| match module {
131 SingleModuleGraphNode::Module(module) => Some((
132 *module,
133 GraphNodeIndex {
134 graph_idx: self.next_graph_idx,
135 node_idx,
136 },
137 )),
138 SingleModuleGraphNode::VisitedModule { .. } => None,
139 }),
140 );
141
142 let mut map = FxIndexMap::with_capacity_and_hasher(
143 self.modules.len() + graph.number_of_modules,
144 Default::default(),
145 );
146 for (k, v) in iter {
147 map.entry(k).or_insert(v);
148 }
149 map.shrink_to_fit();
150
151 Ok(Self {
152 modules: map,
153 next_graph_idx: self.next_graph_idx + 1,
154 }
155 .cell())
156 }
157}
158
159pub type GraphEntriesT = Vec<ChunkGroupEntry>;
160
161#[turbo_tasks::value(transparent)]
162pub struct GraphEntries(GraphEntriesT);
163
164#[turbo_tasks::value_impl]
165impl GraphEntries {
166 #[turbo_tasks::function]
167 pub fn empty() -> Vc<Self> {
168 Vc::cell(Vec::new())
169 }
170}
171
172#[turbo_tasks::value(cell = "new", eq = "manual")]
173#[derive(Clone, Default)]
174pub struct SingleModuleGraph {
175 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
176
177 pub number_of_modules: usize,
179
180 #[turbo_tasks(trace_ignore)]
187 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
188
189 #[turbo_tasks(trace_ignore)]
190 pub entries: GraphEntriesT,
191}
192
193#[derive(
194 Debug,
195 Clone,
196 Hash,
197 TraceRawVcs,
198 Serialize,
199 Deserialize,
200 Eq,
201 PartialEq,
202 ValueDebugFormat,
203 NonLocalValue,
204)]
205pub struct RefData {
206 pub chunking_type: ChunkingType,
207 pub export: ExportUsage,
208}
209
210impl SingleModuleGraph {
211 async fn new_inner(
215 entries: &GraphEntriesT,
216 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
217 include_traced: bool,
218 ) -> Result<Vc<Self>> {
219 let emit_spans = tracing::enabled!(Level::INFO);
220 let root_edges = entries
221 .iter()
222 .flat_map(|e| e.entries())
223 .map(|e| async move {
224 Ok(SingleModuleGraphBuilderEdge {
225 to: SingleModuleGraphBuilderNode::new_module(emit_spans, e).await?,
226 export: ExportUsage::All,
227 })
228 })
229 .try_join()
230 .await?;
231
232 let (children_nodes_iter, visited_nodes) = AdjacencyMap::new()
233 .skip_duplicates_with_key(|node: &(SingleModuleGraphBuilderNode, ExportUsage)| &node.0)
234 .visit(
235 root_edges,
236 SingleModuleGraphBuilder {
237 visited_modules,
238 emit_spans,
239 include_traced,
240 },
241 )
242 .await
243 .completed()?
244 .into_inner_with_visited();
245 let node_count = visited_nodes.0.len();
246 drop(visited_nodes);
247
248 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
249 node_count,
250 node_count * 4,
253 );
254
255 let mut number_of_modules = 0;
256 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
257 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
258 {
259 let _span = tracing::info_span!("build module graph").entered();
260 for (parent, (current, export)) in children_nodes_iter.into_breadth_first_edges() {
261 let parent_edge = match parent.map(|v| v.0) {
262 Some(SingleModuleGraphBuilderNode::Module { module, .. }) => Some((
263 *modules.get(&module).unwrap(),
264 RefData {
265 chunking_type: COMMON_CHUNKING_TYPE,
266 export,
267 },
268 )),
269 Some(SingleModuleGraphBuilderNode::ChunkableReference { .. }) => {
270 continue;
272 }
273 Some(SingleModuleGraphBuilderNode::VisitedModule { .. }) => unreachable!(),
274 None => None,
275 };
276
277 match current {
278 SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
279 let current_idx = if let Some(current_idx) = modules.get(&module) {
281 *current_idx
282 } else {
283 let idx = graph.add_node(SingleModuleGraphNode::Module(module));
284 number_of_modules += 1;
285 modules.insert(module, idx);
286 idx
287 };
288 if let Some((parent_idx, ref_data)) = parent_edge {
290 graph.add_edge(parent_idx, current_idx, ref_data);
291 }
292 }
293 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => {
294 let current_idx = if let Some(current_idx) = modules.get(&module) {
296 *current_idx
297 } else {
298 let idx = graph
299 .add_node(SingleModuleGraphNode::VisitedModule { idx, module });
300 modules.insert(module, idx);
301 idx
302 };
303 if let Some((parent_idx, data)) = parent_edge {
305 graph.add_edge(parent_idx, current_idx, data);
306 }
307 }
308 SingleModuleGraphBuilderNode::ChunkableReference {
309 source,
310 target,
311 ref_data,
312 ..
313 } => {
314 let target_idx = if let Some(target_idx) = modules.get(&target) {
316 *target_idx
317 } else {
318 let target_idx = visited_modules.get(&target);
319 let idx = graph.add_node(match target_idx {
320 Some(idx) => SingleModuleGraphNode::VisitedModule {
321 idx: *idx,
322 module: target,
323 },
324 None => SingleModuleGraphNode::Module(target),
325 });
326 modules.insert(target, idx);
327 idx
328 };
329 graph.add_edge(*modules.get(&source).unwrap(), target_idx, ref_data);
330 }
331 }
332 }
333 }
334
335 graph.shrink_to_fit();
336
337 #[cfg(debug_assertions)]
338 {
339 use once_cell::sync::Lazy;
340 static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
341 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
342 Some(v) => v != "1" && v != "true",
343 None => true,
344 }
345 });
346 if *CHECK_FOR_DUPLICATE_MODULES {
347 let mut duplicates = Vec::new();
348 let mut set = FxHashSet::default();
349 for &module in modules.keys() {
350 let ident = module.ident().to_string().await?;
351 if !set.insert(ident.clone()) {
352 duplicates.push(ident)
353 }
354 }
355 if !duplicates.is_empty() {
356 panic!("Duplicate module idents in graph: {duplicates:#?}");
357 }
358 }
359 }
360
361 let graph = SingleModuleGraph {
362 graph: TracedDiGraph::new(graph),
363 number_of_modules,
364 modules,
365 entries: entries.clone(),
366 }
367 .cell();
368
369 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
370 ModuleGraphImportTracer::new(graph).to_resolved().await?,
371 ));
372 Ok(graph)
373 }
374
375 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
377 self.graph.node_weights().filter_map(|n| match n {
378 SingleModuleGraphNode::Module(node) => Some(*node),
379 SingleModuleGraphNode::VisitedModule { .. } => None,
380 })
381 }
382
383 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
385 if let Some(index) = self.modules.get(&module) {
386 self.graph
387 .edges_directed(*index, petgraph::Direction::Incoming)
388 .next()
389 .is_none()
390 } else {
391 false
392 }
393 }
394
395 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
397 self.entries.iter().flat_map(|e| e.entries())
398 }
399
400 pub fn enumerate_nodes(
402 &self,
403 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
404 self.graph.node_references()
405 }
406
407 pub fn read(self: &ReadRef<SingleModuleGraph>) -> ModuleGraphRef {
408 ModuleGraphRef {
409 graphs: vec![self.clone()],
410 skip_visited_module_children: true,
411 }
412 }
413
414 fn traverse_cycles<'l>(
415 &'l self,
416 edge_filter: impl Fn(&'l RefData) -> bool,
417 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
418 ) -> Result<()> {
419 #[derive(Clone)]
423 struct NodeState {
424 index: u32,
425 lowlink: u32,
426 on_stack: bool,
427 }
428 enum VisitStep {
429 UnvisitedNode(NodeIndex),
430 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
431 AfterVisit(NodeIndex),
432 }
433 let mut node_states = vec![None; self.graph.node_bound()];
434 let mut stack = Vec::new();
435 let mut visit_stack = Vec::new();
436 let mut index = 0;
437 let mut scc = Vec::new();
438 for initial_index in self.graph.node_indices() {
439 if node_states[initial_index.index()].is_some() {
441 continue;
442 }
443 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
444 while let Some(step) = visit_stack.pop() {
445 match step {
446 VisitStep::UnvisitedNode(node) => {
447 node_states[node.index()] = Some(NodeState {
448 index,
449 lowlink: index,
450 on_stack: true,
451 });
452 index += 1;
453 stack.push(node);
454 visit_stack.push(VisitStep::AfterVisit(node));
455 let mut neighbors = self.graph.neighbors(node).detach();
456 while let Some((edge, succ)) = neighbors.next(&self.graph) {
457 let edge_weight = self.graph.edge_weight(edge).unwrap();
458 if !edge_filter(edge_weight) {
459 continue;
460 }
461 let node_state = &node_states[succ.index()];
462 if let Some(node_state) = node_state {
463 if node_state.on_stack {
464 let index = node_state.index;
465 let parent_state = node_states[node.index()].as_mut().unwrap();
466 parent_state.lowlink = parent_state.lowlink.min(index);
467 }
468 } else {
469 visit_stack.push(VisitStep::EdgeAfterVisit {
470 parent: node,
471 child: succ,
472 });
473 visit_stack.push(VisitStep::UnvisitedNode(succ));
474 }
475 }
476 }
477 VisitStep::EdgeAfterVisit { parent, child } => {
478 let child_state = node_states[child.index()].as_ref().unwrap();
479 let lowlink = child_state.lowlink;
480
481 let parent_state = node_states[parent.index()].as_mut().unwrap();
482 parent_state.lowlink = parent_state.lowlink.min(lowlink);
483 }
484 VisitStep::AfterVisit(node) => {
485 let node_state = node_states[node.index()].as_ref().unwrap();
486 if node_state.lowlink == node_state.index {
487 loop {
488 let poppped = stack.pop().unwrap();
489 let popped_state = node_states[poppped.index()].as_mut().unwrap();
490 popped_state.on_stack = false;
491 if let SingleModuleGraphNode::Module(module) =
492 self.graph.node_weight(poppped).unwrap()
493 {
494 scc.push(module);
495 }
496 if poppped == node {
497 break;
498 }
499 }
500 if scc.len() > 1 {
501 visit_cycle(&scc)?;
502 }
503 scc.clear();
504 }
505 }
506 }
507 }
508 }
509 Ok(())
510 }
511
512 pub async fn compute_import_traces_for_issues(
520 &self,
521 issues: &AutoSet<ResolvedVc<Box<dyn Issue>>>,
522 ) -> Result<FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>>> {
523 let issue_paths = issues
524 .iter()
525 .map(|issue| issue.file_path().owned())
526 .try_join()
527 .await?;
528 let mut file_path_to_traces: FxHashMap<FileSystemPath, Vec<ImportTrace>> =
529 FxHashMap::with_capacity_and_hasher(issue_paths.len(), Default::default());
530 for issue in &issue_paths {
532 file_path_to_traces.entry(issue.clone()).or_default();
533 }
534
535 {
536 let modules =
537 self.modules
538 .iter()
539 .map(|(module, &index)| async move {
540 Ok((module.ident().path().owned().await?, index))
541 })
542 .try_join()
543 .await?;
544 let reversed_graph = Reversed(&self.graph.0);
546 for (path, module_idx) in modules {
547 if let Entry::Occupied(mut entry) = file_path_to_traces.entry(path) {
548 let Some((_, path)) = petgraph::algo::astar(
550 &reversed_graph,
551 module_idx,
552 |n| reversed_graph.neighbors(n).next().is_none(),
553 |e| match e.weight().chunking_type {
555 ChunkingType::Parallel { .. } => 0,
557 _ => 1,
558 },
559 |_| 0,
573 ) else {
574 unreachable!("there must be a path to a root");
575 };
576 let path = path
580 .into_iter()
581 .map(async |n| {
582 Ok(self
583 .graph
584 .node_weight(n)
585 .unwrap()
586 .module()
587 .ident()
588 .await?
589 .clone())
590 })
591 .try_join()
592 .await?;
593 entry.get_mut().push(path);
594 }
595 }
596 }
597 let mut issue_to_traces: FxHashMap<ResolvedVc<Box<dyn Issue>>, Vec<ImportTrace>> =
598 FxHashMap::with_capacity_and_hasher(issues.len(), Default::default());
599 for (path, issue) in issue_paths.iter().zip(issues) {
603 if let Some(traces) = file_path_to_traces.get(path) {
604 issue_to_traces.insert(*issue, traces.clone());
605 }
606 }
607 Ok(issue_to_traces)
608 }
609}
610
611#[turbo_tasks::value]
612struct ModuleGraphImportTracer {
613 graph: ResolvedVc<SingleModuleGraph>,
614}
615
616#[turbo_tasks::value(shared)]
617struct PathToModulesMap {
618 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
619}
620
621#[turbo_tasks::value_impl]
622impl ModuleGraphImportTracer {
623 #[turbo_tasks::function]
624 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
625 Self::cell(Self { graph })
626 }
627
628 #[turbo_tasks::function]
630 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
631 let path_and_modules = self
632 .graph
633 .await?
634 .modules
635 .iter()
636 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
637 .try_join()
638 .await?;
639 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
640 FxHashMap::default();
641 for (path, module) in path_and_modules {
642 map.entry(path).or_default().push(module)
643 }
644 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
645 }
646}
647
648#[turbo_tasks::value_impl]
649impl ImportTracer for ModuleGraphImportTracer {
650 #[turbo_tasks::function]
651 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
652 let path_to_modules = self.path_to_modules().await?;
653 let Some(modules) = path_to_modules.map.get(&path) else {
654 return Ok(Vc::default()); };
657 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
658 let graph = &*self.await?.graph.await?;
659
660 let reversed_graph = Reversed(&graph.graph.0);
661 return Ok(ImportTraces::cell(ImportTraces(
662 modules
663 .iter()
664 .map(|m| async move {
665 let Some(&module_idx) = graph.modules.get(m) else {
666 bail!("inconsistent read?")
669 };
670 let Some((_, path)) = petgraph::algo::astar(
672 &reversed_graph,
673 module_idx,
674 |n| reversed_graph.neighbors(n).next().is_none(),
675 |e| match e.weight().chunking_type {
677 ChunkingType::Parallel { .. } => 0,
679 _ => 1,
680 },
681 |_| 0,
695 ) else {
696 unreachable!("there must be a path to a root");
697 };
698
699 let path = path
703 .into_iter()
704 .map(async |n| {
705 graph
706 .graph
707 .node_weight(n)
708 .unwrap() .module()
710 .ident()
711 .await
712 })
713 .try_join()
714 .await?;
715 Ok(path)
716 })
717 .try_join()
718 .await?,
719 )));
720 }
721}
722
723#[turbo_tasks::value(shared)]
724#[derive(Clone, Default)]
725pub struct ModuleGraph {
726 pub graphs: Vec<ResolvedVc<SingleModuleGraph>>,
727}
728
729#[turbo_tasks::value_impl]
730impl ModuleGraph {
731 #[turbo_tasks::function]
732 pub fn from_graphs(graphs: Vec<ResolvedVc<SingleModuleGraph>>) -> Vc<Self> {
733 Self { graphs }.cell()
734 }
735
736 #[turbo_tasks::function]
737 pub fn from_single_graph(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
738 Self {
739 graphs: vec![graph],
740 }
741 .cell()
742 }
743
744 #[turbo_tasks::function]
745 pub fn from_entry_module(
746 module: ResolvedVc<Box<dyn Module>>,
747 include_traced: bool,
748 ) -> Vc<Self> {
749 Self::from_single_graph(SingleModuleGraph::new_with_entries(
750 Vc::cell(vec![ChunkGroupEntry::Entry(vec![module])]),
751 include_traced,
752 ))
753 }
754
755 #[turbo_tasks::function]
756 pub fn from_modules(modules: Vc<GraphEntries>, include_traced: bool) -> Vc<Self> {
757 Self::from_single_graph(SingleModuleGraph::new_with_entries(modules, include_traced))
758 }
759
760 #[turbo_tasks::function]
761 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
762 compute_chunk_group_info(&self.read_graphs().await?).await
763 }
764
765 #[turbo_tasks::function]
766 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
767 compute_merged_modules(self).await
768 }
769
770 #[turbo_tasks::function]
771 pub async fn module_batches(
772 self: Vc<Self>,
773 config: Vc<BatchingConfig>,
774 ) -> Result<Vc<ModuleBatchesGraph>> {
775 compute_module_batches(self, &*config.await?).await
776 }
777
778 #[turbo_tasks::function]
779 pub async fn style_groups(
780 self: Vc<Self>,
781 chunking_context: Vc<Box<dyn ChunkingContext>>,
782 config: StyleGroupsConfig,
783 ) -> Result<Vc<StyleGroups>> {
784 compute_style_groups(self, chunking_context, &config).await
785 }
786
787 #[turbo_tasks::function]
788 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
789 async move {
792 let result_op = compute_async_module_info(self.to_resolved().await?);
793 let result_vc = result_op.resolve_strongly_consistent().await?;
794 result_op.drop_collectibles::<Box<dyn Issue>>();
795 anyhow::Ok(*result_vc)
796 }
797 .instrument(tracing::info_span!("compute async module info"))
798 .await
799 }
800
801 #[turbo_tasks::function]
802 pub async fn referenced_async_modules(
803 self: Vc<Self>,
804 module: ResolvedVc<Box<dyn Module>>,
805 ) -> Result<Vc<AsyncModuleInfo>> {
806 let graph_ref = self.read_graphs().await?;
807 let graphs = &graph_ref.graphs;
808 let async_modules_info = self.async_module_info().await?;
809
810 let entry = graph_ref.get_entry(module)?;
811 let referenced_modules = iter_graphs_neighbors_rev(graphs, entry)
812 .filter(|(edge_idx, _)| {
813 let ty = graphs[entry.graph_idx()]
814 .graph
815 .edge_weight(*edge_idx)
816 .unwrap();
817 ty.chunking_type.is_inherit_async()
818 })
819 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
820 .collect::<Result<Vec<_>>>()?
821 .into_iter()
822 .rev()
823 .filter(|m| async_modules_info.contains(m))
824 .map(|m| *m)
825 .collect();
826
827 Ok(AsyncModuleInfo::new(referenced_modules))
828 }
829}
830
831impl ModuleGraph {
832 pub async fn read_graphs(self: Vc<ModuleGraph>) -> Result<ModuleGraphRef> {
833 Ok(ModuleGraphRef {
834 graphs: self.await?.graphs.iter().try_join().await?,
835 skip_visited_module_children: false,
836 })
837 }
838}
839
840pub struct ModuleGraphRef {
843 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
844 skip_visited_module_children: bool,
847}
848
849impl ModuleGraphRef {
850 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
851 let Some(idx) = self
852 .graphs
853 .iter()
854 .enumerate()
855 .find_map(|(graph_idx, graph)| {
856 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
857 graph_idx: u32::try_from(graph_idx).unwrap(),
858 node_idx: *node_idx,
859 })
860 })
861 else {
862 bail!("Couldn't find entry module in module graph");
863 };
864 Ok(idx)
865 }
866
867 fn get_node(&self, entry: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
868 let graph = &self.graphs[entry.graph_idx()];
869 graph
870 .graph
871 .node_weight(entry.node_idx)
872 .context("Expected graph node")
873 }
874
875 fn should_visit_node(&self, node: &SingleModuleGraphNode) -> bool {
876 if self.skip_visited_module_children {
877 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
878 } else {
879 true
880 }
881 }
882
883 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
886 Ok(self
887 .graphs
888 .iter()
889 .flat_map(|g| g.iter_nodes())
890 .map(async |n| Ok((n, n.ident().to_string().await?)))
891 .try_join()
892 .await?
893 .into_iter()
894 .collect::<FxHashMap<_, _>>())
895 }
896
897 pub fn traverse_nodes_from_entries_dfs<S>(
906 &self,
907 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
908 state: &mut S,
909 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
910 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
911 ) -> Result<()> {
912 let graphs = &self.graphs;
913
914 let entries = entries.into_iter().collect::<Vec<_>>();
915
916 enum Pass {
917 Visit,
918 ExpandAndVisit,
919 }
920 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
921 for entry in entries.into_iter().rev() {
922 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
923 }
924 let mut expanded = FxHashSet::default();
925 while let Some((pass, current)) = stack.pop() {
926 let current_node = self.get_node(current)?;
927 match pass {
928 Pass::Visit => {
929 visit_postorder(current_node.module(), state)?;
930 }
931 Pass::ExpandAndVisit => {
932 if !expanded.insert(current) {
933 continue;
934 }
935 let action = visit_preorder(current_node.module(), state)?;
936 if action == GraphTraversalAction::Exclude {
937 continue;
938 }
939 stack.push((Pass::Visit, current));
940 if action == GraphTraversalAction::Continue
941 && self.should_visit_node(current_node)
942 {
943 let current = current_node.target_idx().unwrap_or(current);
944 stack.extend(
945 iter_graphs_neighbors_rev(graphs, current)
946 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
947 );
948 }
949 }
950 }
951 }
952
953 Ok(())
954 }
955
956 pub fn traverse_edges_from_entries_bfs(
967 &self,
968 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
969 mut visitor: impl FnMut(
970 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
971 ResolvedVc<Box<dyn Module>>,
972 ) -> Result<GraphTraversalAction>,
973 ) -> Result<()> {
974 let graphs = &self.graphs;
975
976 let mut queue = VecDeque::from(
977 entries
978 .into_iter()
979 .map(|e| self.get_entry(e))
980 .collect::<Result<Vec<_>>>()?,
981 );
982 let mut visited = FxHashSet::default();
983 for entry_node in &queue {
984 visitor(None, self.get_node(*entry_node)?.module())?;
985 }
986 while let Some(node) = queue.pop_front() {
987 if visited.insert(node) {
988 let node_weight = self.get_node(node)?;
989 let graph = &graphs[node.graph_idx()].graph;
990 for (edge, succ) in iter_graphs_neighbors_rev(graphs, node) {
991 let succ_weight = self.get_node(succ)?;
992 let edge_weight = graph.edge_weight(edge).unwrap();
993 let action = visitor(
994 Some((node_weight.module(), edge_weight)),
995 succ_weight.module(),
996 )?;
997 if !self.should_visit_node(succ_weight) {
998 continue;
999 }
1000 let succ = succ_weight.target_idx().unwrap_or(succ);
1001 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1002 queue.push_back(succ);
1003 }
1004 }
1005 }
1006 }
1007
1008 Ok(())
1009 }
1010
1011 pub fn traverse_edges_from_entry_dfs(
1022 &self,
1023 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1024 mut visitor: impl FnMut(
1025 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1026 ResolvedVc<Box<dyn Module>>,
1027 ) -> GraphTraversalAction,
1028 ) -> Result<()> {
1029 let graphs = &self.graphs;
1030
1031 let mut stack = entries
1032 .into_iter()
1033 .map(|e| self.get_entry(e))
1034 .collect::<Result<Vec<_>>>()?;
1035 let mut visited = FxHashSet::default();
1036 for entry_node in &stack {
1037 visitor(None, self.get_node(*entry_node)?.module());
1038 }
1039 while let Some(node) = stack.pop() {
1040 if visited.insert(node) {
1041 let node_weight = self.get_node(node)?;
1042 let graph = &graphs[node.graph_idx()].graph;
1043 for (edge, succ) in iter_graphs_neighbors_rev(graphs, node) {
1044 let succ_weight = self.get_node(succ)?;
1045 let edge_weight = graph.edge_weight(edge).unwrap();
1046 let action = visitor(
1047 Some((node_weight.module(), edge_weight)),
1048 succ_weight.module(),
1049 );
1050 if !self.should_visit_node(succ_weight) {
1051 continue;
1052 }
1053 let succ = succ_weight.target_idx().unwrap_or(succ);
1054 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1055 stack.push(succ);
1056 }
1057 }
1058 }
1059 }
1060
1061 Ok(())
1062 }
1063
1064 pub fn traverse_all_edges_unordered(
1073 &self,
1074 mut visitor: impl FnMut(
1075 (ResolvedVc<Box<dyn Module>>, &'_ RefData),
1076 ResolvedVc<Box<dyn Module>>,
1077 ) -> Result<()>,
1078 ) -> Result<()> {
1079 for graph in &self.graphs {
1080 let graph = &graph.graph;
1081 for edge in graph.edge_references() {
1082 let source = graph.node_weight(edge.source()).unwrap().module();
1083 let target = graph.node_weight(edge.target()).unwrap().module();
1084 visitor((source, edge.weight()), target)?;
1085 }
1086 }
1087 Ok(())
1088 }
1089
1090 pub fn traverse_edges_from_entries_dfs<S>(
1109 &self,
1110 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1111 state: &mut S,
1112 mut visit_preorder: impl FnMut(
1113 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1114 ResolvedVc<Box<dyn Module>>,
1115 &mut S,
1116 ) -> Result<GraphTraversalAction>,
1117 mut visit_postorder: impl FnMut(
1118 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1119 ResolvedVc<Box<dyn Module>>,
1120 &mut S,
1121 ) -> Result<()>,
1122 ) -> Result<()> {
1123 let graphs = &self.graphs;
1124
1125 let entries = entries.into_iter().collect::<Vec<_>>();
1126
1127 enum Pass {
1128 Visit,
1129 ExpandAndVisit,
1130 }
1131 #[allow(clippy::type_complexity)] let mut stack: Vec<(Pass, Option<(GraphNodeIndex, EdgeIndex)>, GraphNodeIndex)> =
1133 Vec::with_capacity(entries.len());
1134 for entry in entries.into_iter().rev() {
1135 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1136 }
1137 let mut expanded = FxHashSet::default();
1138 while let Some((pass, parent, current)) = stack.pop() {
1139 let parent_arg = match parent {
1140 Some((parent_node, parent_edge)) => Some((
1141 self.get_node(parent_node)?.module(),
1142 graphs[parent_node.graph_idx()]
1143 .graph
1144 .edge_weight(parent_edge)
1145 .unwrap(),
1146 )),
1147 None => None,
1148 };
1149 let current_node = self.get_node(current)?;
1150 match pass {
1151 Pass::Visit => {
1152 visit_postorder(parent_arg, current_node.module(), state)?;
1153 }
1154 Pass::ExpandAndVisit => {
1155 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1156 if action == GraphTraversalAction::Exclude {
1157 continue;
1158 }
1159 stack.push((Pass::Visit, parent, current));
1160 if action == GraphTraversalAction::Continue
1161 && expanded.insert(current)
1162 && self.should_visit_node(current_node)
1163 {
1164 let current = current_node.target_idx().unwrap_or(current);
1165 stack.extend(iter_graphs_neighbors_rev(graphs, current).map(
1166 |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1167 ));
1168 }
1169 }
1170 }
1171 }
1172
1173 Ok(())
1174 }
1175
1176 pub fn traverse_cycles(
1179 &self,
1180 edge_filter: impl Fn(&RefData) -> bool,
1181 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1182 ) -> Result<()> {
1183 for graph in &self.graphs {
1184 graph.traverse_cycles(&edge_filter, &mut visit_cycle)?;
1185 }
1186 Ok(())
1187 }
1188
1189 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1211 &self,
1212 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1213 state: &mut S,
1214 mut visit: impl FnMut(
1215 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1216 ResolvedVc<Box<dyn Module>>,
1217 &mut S,
1218 ) -> Result<GraphTraversalAction>,
1219 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1220 ) -> Result<usize> {
1221 let graphs = &self.graphs;
1222 if self.skip_visited_module_children {
1223 panic!(
1224 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1225 );
1226 }
1227
1228 #[derive(PartialEq, Eq)]
1229 struct NodeWithPriority<T: Ord> {
1230 node: GraphNodeIndex,
1231 priority: T,
1232 }
1233 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1234 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1235 Some(self.cmp(other))
1236 }
1237 }
1238 impl<T: Ord> Ord for NodeWithPriority<T> {
1239 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1240 self.priority
1243 .cmp(&other.priority)
1244 .then(other.node.cmp(&self.node))
1246 }
1247 }
1248
1249 let mut queue_set = FxHashSet::default();
1250 let mut queue = BinaryHeap::from_iter(
1251 entries
1252 .into_iter()
1253 .map(|(m, priority)| {
1254 Ok(NodeWithPriority {
1255 node: self.get_entry(m)?,
1256 priority,
1257 })
1258 })
1259 .collect::<Result<Vec<_>>>()?,
1260 );
1261
1262 for entry_node in &queue {
1263 visit(None, self.get_node(entry_node.node)?.module(), state)?;
1264 }
1265
1266 let mut visit_count = 0usize;
1267 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1268 queue_set.remove(&node);
1269 let node_weight = self.get_node(node)?;
1270 let node = node_weight.target_idx().unwrap_or(node);
1271
1272 visit_count += 1;
1273
1274 let graph = &graphs[node.graph_idx()].graph;
1275 for (edge, succ) in iter_graphs_neighbors_rev(graphs, node) {
1276 let succ_weight = self.get_node(succ)?;
1277
1278 let edge_weight = graph.edge_weight(edge).unwrap();
1279 let action = visit(
1280 Some((node_weight.module(), edge_weight)),
1281 succ_weight.module(),
1282 state,
1283 )?;
1284
1285 let succ = succ_weight.target_idx().unwrap_or(succ);
1286 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1287 queue.push(NodeWithPriority {
1288 node: succ,
1289 priority: priority(succ_weight.module(), state)?,
1290 });
1291 }
1292 }
1293 }
1294
1295 Ok(visit_count)
1296 }
1297}
1298
1299fn iter_graphs_neighbors_rev(
1301 graphs: &[ReadRef<SingleModuleGraph>],
1302 node: GraphNodeIndex,
1303) -> impl Iterator<Item = (EdgeIndex, GraphNodeIndex)> + '_ {
1304 let graph = &*graphs[node.graph_idx()].graph;
1305
1306 if cfg!(debug_assertions) {
1307 let node_weight = graph.node_weight(node.node_idx).unwrap();
1308 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1309 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1310 }
1311 }
1312
1313 let mut walker = graph.neighbors(node.node_idx).detach();
1314 std::iter::from_fn(move || {
1315 walker.next(graph).map(|(edge_idx, succ_idx)| {
1316 (
1317 edge_idx,
1318 GraphNodeIndex {
1319 graph_idx: node.graph_idx,
1320 node_idx: succ_idx,
1321 },
1322 )
1323 })
1324 })
1325}
1326
1327#[turbo_tasks::value_impl]
1328impl SingleModuleGraph {
1329 #[turbo_tasks::function]
1330 pub async fn new_with_entries(
1331 entries: Vc<GraphEntries>,
1332 include_traced: bool,
1333 ) -> Result<Vc<Self>> {
1334 SingleModuleGraph::new_inner(&*entries.await?, &Default::default(), include_traced).await
1335 }
1336
1337 #[turbo_tasks::function]
1338 pub async fn new_with_entries_visited(
1339 entries: Vc<GraphEntries>,
1340 visited_modules: Vc<VisitedModules>,
1341 include_traced: bool,
1342 ) -> Result<Vc<Self>> {
1343 SingleModuleGraph::new_inner(
1344 &*entries.await?,
1345 &visited_modules.await?.modules,
1346 include_traced,
1347 )
1348 .await
1349 }
1350
1351 #[turbo_tasks::function]
1352 pub async fn new_with_entries_visited_intern(
1353 entries: GraphEntriesT,
1355 visited_modules: Vc<VisitedModules>,
1356 include_traced: bool,
1357 ) -> Result<Vc<Self>> {
1358 SingleModuleGraph::new_inner(&entries, &visited_modules.await?.modules, include_traced)
1359 .await
1360 }
1361}
1362
1363#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1364pub enum SingleModuleGraphNode {
1365 Module(ResolvedVc<Box<dyn Module>>),
1366 VisitedModule {
1368 idx: GraphNodeIndex,
1369 module: ResolvedVc<Box<dyn Module>>,
1370 },
1371}
1372
1373impl SingleModuleGraphNode {
1374 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1375 match self {
1376 SingleModuleGraphNode::Module(module) => *module,
1377 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1378 }
1379 }
1380 pub fn target_idx(&self) -> Option<GraphNodeIndex> {
1381 match self {
1382 SingleModuleGraphNode::VisitedModule { idx, .. } => Some(*idx),
1383 SingleModuleGraphNode::Module(_) => None,
1384 }
1385 }
1386}
1387
1388#[derive(PartialEq, Eq, Debug)]
1389pub enum GraphTraversalAction {
1390 Continue,
1392 Skip,
1394 Exclude,
1396}
1397
1398#[derive(Clone, Hash, PartialEq, Eq)]
1401enum SingleModuleGraphBuilderNode {
1402 ChunkableReference {
1404 ref_data: RefData,
1405 source: ResolvedVc<Box<dyn Module>>,
1406 target: ResolvedVc<Box<dyn Module>>,
1407 source_ident: Option<ReadRef<RcStr>>,
1410 target_ident: Option<ReadRef<RcStr>>,
1411 },
1412 Module {
1414 module: ResolvedVc<Box<dyn Module>>,
1415 ident: Option<ReadRef<RcStr>>,
1417 },
1418 VisitedModule {
1420 module: ResolvedVc<Box<dyn Module>>,
1421 idx: GraphNodeIndex,
1422 },
1423}
1424
1425impl SingleModuleGraphBuilderNode {
1426 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1427 Ok(Self::Module {
1428 module,
1429 ident: if emit_spans {
1430 Some(module.ident_string().untracked().await?)
1432 } else {
1433 None
1434 },
1435 })
1436 }
1437 async fn new_chunkable_ref(
1438 emit_spans: bool,
1439 source: ResolvedVc<Box<dyn Module>>,
1440 target: ResolvedVc<Box<dyn Module>>,
1441 ref_data: RefData,
1442 ) -> Result<Self> {
1443 Ok(Self::ChunkableReference {
1444 ref_data,
1445 source,
1446 source_ident: if emit_spans {
1447 Some(source.ident_string().untracked().await?)
1449 } else {
1450 None
1451 },
1452 target,
1453 target_ident: if emit_spans {
1454 Some(target.ident_string().untracked().await?)
1456 } else {
1457 None
1458 },
1459 })
1460 }
1461 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1462 Self::VisitedModule { module, idx }
1463 }
1464}
1465struct SingleModuleGraphBuilderEdge {
1466 to: SingleModuleGraphBuilderNode,
1467 export: ExportUsage,
1468}
1469
1470const COMMON_CHUNKING_TYPE: ChunkingType = ChunkingType::Parallel {
1473 inherit_async: true,
1474 hoisted: true,
1475};
1476
1477struct SingleModuleGraphBuilder<'a> {
1478 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1479
1480 emit_spans: bool,
1481
1482 include_traced: bool,
1484}
1485impl Visit<(SingleModuleGraphBuilderNode, ExportUsage)> for SingleModuleGraphBuilder<'_> {
1486 type Edge = SingleModuleGraphBuilderEdge;
1487 type EdgesIntoIter = Vec<Self::Edge>;
1488 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1489
1490 fn visit(
1491 &mut self,
1492 edge: Self::Edge,
1493 ) -> VisitControlFlow<(SingleModuleGraphBuilderNode, ExportUsage)> {
1494 match edge.to {
1495 SingleModuleGraphBuilderNode::Module { .. } => {
1496 VisitControlFlow::Continue((edge.to, edge.export))
1497 }
1498 SingleModuleGraphBuilderNode::ChunkableReference { ref ref_data, .. } => {
1499 match &ref_data.chunking_type {
1500 ChunkingType::Traced => VisitControlFlow::Skip((edge.to, edge.export)),
1501 _ => VisitControlFlow::Continue((edge.to, edge.export)),
1502 }
1503 }
1504 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1506 VisitControlFlow::Skip((edge.to, edge.export))
1507 }
1508 }
1509 }
1510
1511 fn edges(
1512 &mut self,
1513 (node, _): &(SingleModuleGraphBuilderNode, ExportUsage),
1516 ) -> Self::EdgesFuture {
1517 let (module, chunkable_ref_target) = match node {
1519 SingleModuleGraphBuilderNode::Module { module, .. } => (Some(*module), None),
1520 SingleModuleGraphBuilderNode::ChunkableReference {
1521 target, ref_data, ..
1522 } => (None, Some((*target, ref_data.export.clone()))),
1523 SingleModuleGraphBuilderNode::VisitedModule { .. } => unreachable!(),
1525 };
1526 let visited_modules = self.visited_modules;
1527 let emit_spans = self.emit_spans;
1528 let include_traced = self.include_traced;
1529 async move {
1530 Ok(match (module, chunkable_ref_target) {
1531 (Some(module), None) => {
1532 let refs_cell = primary_chunkable_referenced_modules(*module, include_traced);
1533 let refs = match refs_cell.await {
1534 Ok(refs) => refs,
1535 Err(e) => {
1536 return Err(e.context(module.ident().to_string().await?));
1537 }
1538 };
1539
1540 refs.iter()
1541 .flat_map(|(ty, export, modules)| {
1542 modules.iter().map(|m| (ty.clone(), export.clone(), *m))
1543 })
1544 .map(async |(ty, export, target)| {
1545 let to = if ty == COMMON_CHUNKING_TYPE {
1546 if let Some(idx) = visited_modules.get(&target) {
1547 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1548 } else {
1549 SingleModuleGraphBuilderNode::new_module(emit_spans, target)
1550 .await?
1551 }
1552 } else {
1553 SingleModuleGraphBuilderNode::new_chunkable_ref(
1554 emit_spans,
1555 module,
1556 target,
1557 RefData {
1558 chunking_type: ty,
1559 export: export.clone(),
1560 },
1561 )
1562 .await?
1563 };
1564 Ok(SingleModuleGraphBuilderEdge { to, export })
1565 })
1566 .try_join()
1567 .await?
1568 }
1569 (None, Some((chunkable_ref_target, export))) => {
1570 vec![SingleModuleGraphBuilderEdge {
1571 to: if let Some(idx) = visited_modules.get(&chunkable_ref_target) {
1572 SingleModuleGraphBuilderNode::new_visited_module(
1573 chunkable_ref_target,
1574 *idx,
1575 )
1576 } else {
1577 SingleModuleGraphBuilderNode::new_module(
1578 emit_spans,
1579 chunkable_ref_target,
1580 )
1581 .await?
1582 },
1583 export,
1584 }]
1585 }
1586 _ => unreachable!(),
1587 })
1588 }
1589 }
1590
1591 fn span(&mut self, (node, _): &(SingleModuleGraphBuilderNode, ExportUsage)) -> tracing::Span {
1592 if !self.emit_spans {
1593 return Span::current();
1594 }
1595
1596 match node {
1597 SingleModuleGraphBuilderNode::Module {
1598 ident: Some(ident), ..
1599 } => {
1600 tracing::info_span!("module", name = display(ident))
1601 }
1602 SingleModuleGraphBuilderNode::ChunkableReference {
1603 ref_data,
1604 source_ident: Some(source_ident),
1605 target_ident: Some(target_ident),
1606 ..
1607 } => match &ref_data.chunking_type {
1608 ChunkingType::Parallel {
1609 inherit_async: false,
1610 ..
1611 } => Span::current(),
1612 _ => {
1613 tracing::info_span!(
1614 "chunkable reference",
1615 ty = debug(&ref_data.chunking_type),
1616 source = display(source_ident),
1617 target = display(target_ident)
1618 )
1619 }
1620 },
1621 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1622 tracing::info_span!("visited module")
1623 }
1624 _ => Span::current(),
1625 }
1626 }
1627}
1628
1629#[cfg(test)]
1630pub mod tests {
1631 use anyhow::Result;
1632 use rustc_hash::FxHashMap;
1633 use turbo_rcstr::{RcStr, rcstr};
1634 use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
1635 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1636 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1637
1638 use crate::{
1639 asset::{Asset, AssetContent},
1640 ident::AssetIdent,
1641 module::Module,
1642 module_graph::{
1643 GraphEntries, GraphTraversalAction, ModuleGraph, ModuleGraphRef, SingleModuleGraph,
1644 chunk_group_info::ChunkGroupEntry,
1645 },
1646 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1647 resolve::ExportUsage,
1648 };
1649
1650 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1651 async fn test_traverse_dfs_from_entries_diamond() {
1652 run_graph_test(
1653 vec![rcstr!("a.js")],
1654 {
1655 let mut deps = FxHashMap::default();
1656 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1658 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1659 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1660 deps
1661 },
1662 |graph, entry_modules, module_to_name| {
1663 let mut preorder_visits = Vec::new();
1664 let mut postorder_visits = Vec::new();
1665
1666 graph.traverse_edges_from_entries_dfs(
1667 entry_modules,
1668 &mut (),
1669 |parent, target, _| {
1670 preorder_visits.push((
1671 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1672 module_to_name.get(&target).unwrap().clone(),
1673 ));
1674 Ok(GraphTraversalAction::Continue)
1675 },
1676 |parent, target, _| {
1677 postorder_visits.push((
1678 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1679 module_to_name.get(&target).unwrap().clone(),
1680 ));
1681 Ok(())
1682 },
1683 )?;
1684 assert_eq!(
1685 vec![
1686 (None, rcstr!("a.js")),
1687 (Some(rcstr!("a.js")), rcstr!("b.js")),
1688 (Some(rcstr!("b.js")), rcstr!("d.js")),
1689 (Some(rcstr!("a.js")), rcstr!("c.js")),
1690 (Some(rcstr!("c.js")), rcstr!("d.js"))
1691 ],
1692 preorder_visits
1693 );
1694 assert_eq!(
1695 vec![
1696 (Some(rcstr!("b.js")), rcstr!("d.js")),
1697 (Some(rcstr!("a.js")), rcstr!("b.js")),
1698 (Some(rcstr!("c.js")), rcstr!("d.js")),
1699 (Some(rcstr!("a.js")), rcstr!("c.js")),
1700 (None, rcstr!("a.js"))
1701 ],
1702 postorder_visits
1703 );
1704 Ok(())
1705 },
1706 )
1707 .await;
1708 }
1709
1710 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1711 async fn test_traverse_dfs_from_entries_cycle() {
1712 run_graph_test(
1713 vec![rcstr!("a.js")],
1714 {
1715 let mut deps = FxHashMap::default();
1716 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1718 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1719 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1720 deps
1721 },
1722 |graph, entry_modules, module_to_name| {
1723 let mut preorder_visits = Vec::new();
1724 let mut postorder_visits = Vec::new();
1725
1726 graph.traverse_edges_from_entries_dfs(
1727 entry_modules,
1728 &mut (),
1729 |parent, target, _| {
1730 preorder_visits.push((
1731 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1732 module_to_name.get(&target).unwrap().clone(),
1733 ));
1734 Ok(GraphTraversalAction::Continue)
1735 },
1736 |parent, target, _| {
1737 postorder_visits.push((
1738 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1739 module_to_name.get(&target).unwrap().clone(),
1740 ));
1741 Ok(())
1742 },
1743 )?;
1744 assert_eq!(
1745 vec![
1746 (None, rcstr!("a.js")),
1747 (Some(rcstr!("a.js")), rcstr!("b.js")),
1748 (Some(rcstr!("b.js")), rcstr!("c.js")),
1749 (Some(rcstr!("c.js")), rcstr!("a.js")),
1750 ],
1751 preorder_visits
1752 );
1753 assert_eq!(
1754 vec![
1755 (Some(rcstr!("c.js")), rcstr!("a.js")),
1756 (Some(rcstr!("b.js")), rcstr!("c.js")),
1757 (Some(rcstr!("a.js")), rcstr!("b.js")),
1758 (None, rcstr!("a.js"))
1759 ],
1760 postorder_visits
1761 );
1762 Ok(())
1763 },
1764 )
1765 .await;
1766 }
1767
1768 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1769 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1770 run_graph_test(
1771 vec![rcstr!("a.js")],
1772 {
1773 let mut deps = FxHashMap::default();
1774 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1776 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1777 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1778 deps
1779 },
1780 |graph, entry_modules, module_to_name| {
1781 let mut visits = Vec::new();
1782 let mut count = 0;
1783
1784 graph.traverse_edges_fixed_point_with_priority(
1785 entry_modules.into_iter().map(|m| (m, 0)),
1786 &mut (),
1787 |parent, target, _| {
1788 visits.push((
1789 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1790 module_to_name.get(&target).unwrap().clone(),
1791 ));
1792 count += 1;
1793
1794 Ok(if count < 6 {
1796 GraphTraversalAction::Continue
1797 } else {
1798 GraphTraversalAction::Skip
1799 })
1800 },
1801 |_, _| Ok(0),
1802 )?;
1803 assert_eq!(
1804 vec![
1805 (None, rcstr!("a.js")),
1806 (Some(rcstr!("a.js")), rcstr!("b.js")),
1807 (Some(rcstr!("b.js")), rcstr!("c.js")),
1808 (Some(rcstr!("c.js")), rcstr!("a.js")),
1809 (Some(rcstr!("a.js")), rcstr!("b.js")),
1811 (Some(rcstr!("b.js")), rcstr!("c.js")),
1812 ],
1813 visits
1814 );
1815
1816 Ok(())
1817 },
1818 )
1819 .await;
1820 }
1821 #[turbo_tasks::value(shared)]
1822 struct TestRepo {
1823 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
1824 }
1825 #[turbo_tasks::value]
1826 struct MockModule {
1827 path: FileSystemPath,
1828 repo: ResolvedVc<TestRepo>,
1829 }
1830 #[turbo_tasks::value_impl]
1831 impl MockModule {
1832 #[turbo_tasks::function]
1833 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
1834 Self { path, repo }.cell()
1835 }
1836 }
1837
1838 #[turbo_tasks::value_impl]
1839 impl Asset for MockModule {
1840 #[turbo_tasks::function]
1841 fn content(&self) -> Vc<AssetContent> {
1842 panic!("MockModule::content shouldn't be called")
1843 }
1844 }
1845
1846 #[turbo_tasks::value_impl]
1847 impl Module for MockModule {
1848 #[turbo_tasks::function]
1849 fn ident(&self) -> Vc<AssetIdent> {
1850 AssetIdent::from_path(self.path.clone())
1851 }
1852
1853 #[turbo_tasks::function]
1854 async fn references(&self) -> Result<Vc<ModuleReferences>> {
1855 let repo = self.repo.await?;
1856 let references = match repo.repo.get(&self.path) {
1857 Some(deps) => {
1858 deps.iter()
1859 .map(|p| {
1860 Vc::upcast::<Box<dyn ModuleReference>>(
1861 SingleChunkableModuleReference::new(
1862 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
1863 rcstr!("normal-dep"),
1864 ExportUsage::all(),
1865 ),
1866 )
1867 .to_resolved()
1868 })
1869 .try_join()
1870 .await?
1871 }
1872 None => vec![],
1873 };
1874
1875 Ok(Vc::cell(references))
1876 }
1877 }
1878
1879 async fn run_graph_test(
1893 entries: Vec<RcStr>,
1894 graph: FxHashMap<RcStr, Vec<RcStr>>,
1895 test_fn: impl FnOnce(
1896 ModuleGraphRef,
1897 Vec<ResolvedVc<Box<dyn Module>>>,
1898 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
1899 ) -> Result<()>
1900 + Send
1901 + 'static,
1902 ) {
1903 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
1904 BackendOptions::default(),
1905 noop_backing_storage(),
1906 ));
1907 tt.run_once(async move {
1908 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
1909 let root = fs.root().await?;
1910
1911 let repo = TestRepo {
1912 repo: graph
1913 .iter()
1914 .map(|(k, v)| {
1915 (
1916 root.join(k).unwrap(),
1917 v.iter().map(|f| root.join(f).unwrap()).collect(),
1918 )
1919 })
1920 .collect(),
1921 }
1922 .cell();
1923 let entry_modules = entries
1924 .iter()
1925 .map(|e| {
1926 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
1927 .to_resolved()
1928 })
1929 .try_join()
1930 .await?;
1931 let graph = SingleModuleGraph::new_with_entries(
1932 GraphEntries::cell(GraphEntries(vec![ChunkGroupEntry::Entry(
1933 entry_modules.clone(),
1934 )])),
1935 false,
1936 );
1937
1938 let module_to_name = graph
1942 .await?
1943 .modules
1944 .keys()
1945 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
1946 .try_join()
1947 .await?
1948 .into_iter()
1949 .collect();
1950 test_fn(
1951 ModuleGraph::from_single_graph(graph).read_graphs().await?,
1952 entry_modules,
1953 module_to_name,
1954 )
1955 })
1956 .await
1957 .unwrap();
1958 }
1959}