1use core::panic;
2use std::{
3 collections::{BinaryHeap, VecDeque},
4 future::Future,
5 ops::Deref,
6};
7
8use anyhow::{Context, Result, bail};
9use bincode::{Decode, Encode};
10use petgraph::{
11 Direction,
12 graph::{DiGraph, EdgeIndex, NodeIndex},
13 visit::{EdgeRef, IntoNeighbors, IntoNodeReferences, NodeIndexable, Reversed},
14};
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::{Deserialize, Serialize};
17use tracing::{Instrument, Level, Span};
18use turbo_rcstr::RcStr;
19use turbo_tasks::{
20 CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc,
21 TryJoinIterExt, 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::{ImportTracer, ImportTraces, Issue},
31 module::Module,
32 module_graph::{
33 async_module_info::{AsyncModulesInfo, compute_async_module_info},
34 binding_usage_info::BindingUsageInfo,
35 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
36 merged_modules::{MergedModuleInfo, compute_merged_modules},
37 module_batches::{ModuleBatchesGraph, compute_module_batches},
38 style_groups::{StyleGroups, StyleGroupsConfig, compute_style_groups},
39 traced_di_graph::TracedDiGraph,
40 },
41 reference::{ModuleReference, primary_chunkable_referenced_modules},
42 resolve::BindingUsage,
43};
44
45pub mod async_module_info;
46pub mod binding_usage_info;
47pub mod chunk_group_info;
48pub mod merged_modules;
49pub mod module_batch;
50pub(crate) mod module_batches;
51mod side_effect_module_info;
52pub(crate) mod style_groups;
53mod traced_di_graph;
54
55pub use self::module_batches::BatchingConfig;
56
57#[derive(
58 Debug,
59 Copy,
60 Clone,
61 Eq,
62 PartialOrd,
63 Ord,
64 Hash,
65 PartialEq,
66 Serialize,
67 Deserialize,
68 TraceRawVcs,
69 Encode,
70 Decode,
71)]
72pub struct GraphNodeIndex {
73 #[turbo_tasks(trace_ignore)]
74 graph_idx: u32,
75 #[turbo_tasks(trace_ignore)]
76 #[bincode(with_serde)]
77 node_idx: NodeIndex,
78}
79impl GraphNodeIndex {
80 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
81 Self {
82 graph_idx,
83 node_idx,
84 }
85 }
86}
87
88unsafe impl NonLocalValue for GraphNodeIndex {}
89
90#[derive(
91 Debug,
92 Copy,
93 Clone,
94 Eq,
95 PartialOrd,
96 Ord,
97 Hash,
98 PartialEq,
99 TraceRawVcs,
100 NonLocalValue,
101 Encode,
102 Decode,
103)]
104pub struct GraphEdgeIndex {
105 graph_idx: u32,
106 #[turbo_tasks(trace_ignore)]
107 #[bincode(with_serde)]
108 edge_idx: EdgeIndex,
109}
110
111impl GraphEdgeIndex {
112 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
113 Self {
114 graph_idx,
115 edge_idx,
116 }
117 }
118}
119
120#[turbo_tasks::value]
121#[derive(Clone, Debug)]
122pub struct VisitedModules {
123 #[bincode(with = "turbo_bincode::indexmap")]
124 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
125 next_graph_idx: u32,
126}
127
128#[turbo_tasks::value_impl]
129impl VisitedModules {
130 #[turbo_tasks::function(operation)]
131 pub fn empty() -> Vc<Self> {
132 Self {
133 modules: Default::default(),
134 next_graph_idx: 0,
135 }
136 .cell()
137 }
138
139 #[turbo_tasks::function(operation)]
140 pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
141 Ok(Self {
142 modules: graph
143 .connect()
144 .await?
145 .enumerate_nodes()
146 .flat_map(|(node_idx, module)| match module {
147 SingleModuleGraphNode::Module(module) => Some((
148 *module,
149 GraphNodeIndex {
150 graph_idx: 0,
151 node_idx,
152 },
153 )),
154 SingleModuleGraphNode::VisitedModule { .. } => None,
155 })
156 .collect(),
157 next_graph_idx: 1,
158 }
159 .cell())
160 }
161
162 #[turbo_tasks::function(operation)]
163 pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
164 let this = this.connect().await?;
165 Ok(Self {
166 modules: this.modules.clone(),
167 next_graph_idx: this.next_graph_idx + 1,
168 }
169 .cell())
170 }
171
172 #[turbo_tasks::function(operation)]
173 pub async fn concatenate(
174 this: OperationVc<Self>,
175 graph: OperationVc<SingleModuleGraph>,
176 ) -> Result<Vc<Self>> {
177 let graph = graph.connect().await?;
178 let this = this.connect().await?;
179 let iter = this
180 .modules
181 .iter()
182 .map(|(module, idx)| (*module, *idx))
183 .chain(
184 graph
185 .enumerate_nodes()
186 .flat_map(|(node_idx, module)| match module {
187 SingleModuleGraphNode::Module(module) => Some((
188 *module,
189 GraphNodeIndex {
190 graph_idx: this.next_graph_idx,
191 node_idx,
192 },
193 )),
194 SingleModuleGraphNode::VisitedModule { .. } => None,
195 }),
196 );
197
198 let mut map = FxIndexMap::with_capacity_and_hasher(
199 this.modules.len() + graph.number_of_modules,
200 Default::default(),
201 );
202 for (k, v) in iter {
203 map.entry(k).or_insert(v);
204 }
205 map.shrink_to_fit();
206
207 Ok(Self {
208 modules: map,
209 next_graph_idx: this.next_graph_idx + 1,
210 }
211 .cell())
212 }
213}
214
215pub type GraphEntriesT = Vec<ChunkGroupEntry>;
216
217#[turbo_tasks::value(transparent)]
218pub struct GraphEntries(GraphEntriesT);
219
220#[turbo_tasks::value_impl]
221impl GraphEntries {
222 #[turbo_tasks::function]
223 pub fn empty() -> Vc<Self> {
224 Vc::cell(Vec::new())
225 }
226}
227
228#[turbo_tasks::value(cell = "new", eq = "manual")]
229#[derive(Clone, Default)]
230pub struct SingleModuleGraph {
231 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
232
233 pub number_of_modules: usize,
235
236 #[turbo_tasks(trace_ignore)]
243 #[bincode(with_serde)]
244 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
245
246 #[turbo_tasks(trace_ignore)]
247 pub entries: GraphEntriesT,
248}
249
250#[derive(
251 Debug,
252 Clone,
253 Hash,
254 TraceRawVcs,
255 Serialize,
256 Deserialize,
257 Eq,
258 PartialEq,
259 ValueDebugFormat,
260 NonLocalValue,
261)]
262pub struct RefData {
263 pub chunking_type: ChunkingType,
264 pub binding_usage: BindingUsage,
265 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
266}
267
268impl SingleModuleGraph {
269 async fn new_inner(
273 entries: Vec<ChunkGroupEntry>,
274 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
275 include_traced: bool,
276 include_binding_usage: bool,
277 ) -> Result<Vc<Self>> {
278 let emit_spans = tracing::enabled!(Level::INFO);
279 let root_nodes = entries
280 .iter()
281 .flat_map(|e| e.entries())
282 .map(|e| SingleModuleGraphBuilderNode::new_module(emit_spans, e))
283 .try_join()
284 .await?;
285
286 let children_nodes_iter = AdjacencyMap::new()
287 .visit(
288 root_nodes,
289 SingleModuleGraphBuilder {
290 visited_modules,
291 emit_spans,
292 include_traced,
293 include_binding_usage,
294 },
295 )
296 .await
297 .completed()?;
298 let node_count = children_nodes_iter.len();
299
300 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
301 node_count,
302 node_count * 4,
305 );
306
307 let mut number_of_modules = 0;
308 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
309 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
310 {
311 let _span = tracing::info_span!("build module graph").entered();
312 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
313 let (module, graph_node, count) = match current {
314 SingleModuleGraphBuilderNode::Module { module, ident: _ } => {
315 (module, SingleModuleGraphNode::Module(module), 1)
316 }
317 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
318 module,
319 SingleModuleGraphNode::VisitedModule { idx, module },
320 0,
321 ),
322 };
323
324 let current_idx = if let Some(current_idx) = modules.get(&module) {
326 *current_idx
327 } else {
328 let idx = graph.add_node(graph_node);
329 number_of_modules += count;
330 modules.insert(module, idx);
331 idx
332 };
333 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
335 parent
336 {
337 let parent_idx = *modules.get(&module).unwrap();
338 graph.add_edge(parent_idx, current_idx, ref_data);
339 }
340 }
341 }
342
343 graph.shrink_to_fit();
344
345 #[cfg(debug_assertions)]
346 {
347 use once_cell::sync::Lazy;
348 static CHECK_FOR_DUPLICATE_MODULES: Lazy<bool> = Lazy::new(|| {
349 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
350 Some(v) => v != "1" && v != "true",
351 None => true,
352 }
353 });
354 if *CHECK_FOR_DUPLICATE_MODULES {
355 let mut duplicates = Vec::new();
356 let mut set = FxHashSet::default();
357 for &module in modules.keys() {
358 let ident = module.ident().to_string().await?;
359 if !set.insert(ident.clone()) {
360 duplicates.push(ident)
361 }
362 }
363 if !duplicates.is_empty() {
364 panic!(
365 "Duplicate module idents in graph: {duplicates:#?}\n{:#?}",
366 modules
367 );
368 }
369 }
370 }
371
372 let graph = SingleModuleGraph {
373 graph: TracedDiGraph::new(graph),
374 number_of_modules,
375 modules,
376 entries: entries.clone(),
377 }
378 .cell();
379
380 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
381 ModuleGraphImportTracer::new(graph).to_resolved().await?,
382 ));
383 Ok(graph)
384 }
385
386 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
388 self.graph.node_weights().filter_map(|n| match n {
389 SingleModuleGraphNode::Module(node) => Some(*node),
390 SingleModuleGraphNode::VisitedModule { .. } => None,
391 })
392 }
393
394 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
396 if let Some(index) = self.modules.get(&module) {
397 self.graph
398 .edges_directed(*index, Direction::Incoming)
399 .next()
400 .is_none()
401 } else {
402 false
403 }
404 }
405
406 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
408 self.entries.iter().flat_map(|e| e.entries())
409 }
410
411 pub fn enumerate_nodes(
413 &self,
414 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
415 self.graph.node_references()
416 }
417
418 fn traverse_cycles<'l>(
419 &'l self,
420 edge_filter: impl Fn(&'l RefData) -> bool,
421 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
422 graph_idx: u32,
423 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
424 ) -> Result<()> {
425 #[derive(Clone)]
432 struct NodeState {
433 index: u32,
434 lowlink: u32,
435 on_stack: bool,
436 has_self_loop: bool,
437 }
438 enum VisitStep {
439 UnvisitedNode(NodeIndex),
440 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
441 AfterVisit(NodeIndex),
442 }
443 let mut node_states = vec![None; self.graph.node_bound()];
444 let mut stack = Vec::new();
445 let mut visit_stack = Vec::new();
446 let mut index = 0;
447 let mut scc = Vec::new();
448 for initial_index in self.graph.node_indices() {
449 if node_states[initial_index.index()].is_some() {
451 continue;
452 }
453 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
454 while let Some(step) = visit_stack.pop() {
455 match step {
456 VisitStep::UnvisitedNode(node) => {
457 node_states[node.index()] = Some(NodeState {
458 index,
459 lowlink: index,
460 on_stack: true,
461 has_self_loop: false,
462 });
463 index += 1;
464 stack.push(node);
465 visit_stack.push(VisitStep::AfterVisit(node));
466 let mut neighbors = self.graph.neighbors(node).detach();
467 while let Some((edge, succ)) = neighbors.next(&self.graph) {
468 if binding_usage.as_ref().is_some_and(|binding_usage| {
469 binding_usage
470 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
471 }) {
472 continue;
473 }
474
475 let edge_weight = self.graph.edge_weight(edge).unwrap();
476 if !edge_filter(edge_weight) {
477 continue;
478 }
479 let node_state = &node_states[succ.index()];
480 if let Some(node_state) = node_state {
481 if node_state.on_stack {
482 let index = node_state.index;
483 let parent_state = node_states[node.index()].as_mut().unwrap();
484 parent_state.lowlink = parent_state.lowlink.min(index);
485 if succ == node {
486 parent_state.has_self_loop = true;
487 }
488 }
489 } else {
490 visit_stack.push(VisitStep::EdgeAfterVisit {
491 parent: node,
492 child: succ,
493 });
494 visit_stack.push(VisitStep::UnvisitedNode(succ));
495 }
496 }
497 }
498 VisitStep::EdgeAfterVisit { parent, child } => {
499 let child_state = node_states[child.index()].as_ref().unwrap();
500 let lowlink = child_state.lowlink;
501
502 let parent_state = node_states[parent.index()].as_mut().unwrap();
503 parent_state.lowlink = parent_state.lowlink.min(lowlink);
504 }
505 VisitStep::AfterVisit(node) => {
506 let node_state = node_states[node.index()].as_ref().unwrap();
507 let node_has_self_loop = node_state.has_self_loop;
508 if node_state.lowlink == node_state.index {
509 loop {
510 let poppped = stack.pop().unwrap();
511 let popped_state = node_states[poppped.index()].as_mut().unwrap();
512 popped_state.on_stack = false;
513 if let SingleModuleGraphNode::Module(module) =
514 self.graph.node_weight(poppped).unwrap()
515 {
516 scc.push(module);
517 }
518 if poppped == node {
519 break;
520 }
521 }
522 if scc.len() > 1 || node_has_self_loop {
523 visit_cycle(&scc)?;
524 }
525 scc.clear();
526 }
527 }
528 }
529 }
530 }
531 Ok(())
532 }
533}
534
535#[turbo_tasks::value]
536struct ModuleGraphImportTracer {
537 graph: ResolvedVc<SingleModuleGraph>,
538}
539
540#[turbo_tasks::value(shared)]
541struct PathToModulesMap {
542 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
543}
544
545#[turbo_tasks::value_impl]
546impl ModuleGraphImportTracer {
547 #[turbo_tasks::function]
548 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
549 Self::cell(Self { graph })
550 }
551
552 #[turbo_tasks::function]
554 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
555 let path_and_modules = self
556 .graph
557 .await?
558 .modules
559 .iter()
560 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
561 .try_join()
562 .await?;
563 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
564 FxHashMap::default();
565 for (path, module) in path_and_modules {
566 map.entry(path).or_default().push(module)
567 }
568 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
569 }
570}
571
572#[turbo_tasks::value_impl]
573impl ImportTracer for ModuleGraphImportTracer {
574 #[turbo_tasks::function]
575 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
576 let path_to_modules = self.path_to_modules().await?;
577 let Some(modules) = path_to_modules.map.get(&path) else {
578 return Ok(Vc::default()); };
581 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
582 let graph = &*self.await?.graph.await?;
583
584 let reversed_graph = Reversed(&graph.graph.0);
585 return Ok(ImportTraces::cell(ImportTraces(
586 modules
587 .iter()
588 .map(|m| async move {
589 let Some(&module_idx) = graph.modules.get(m) else {
590 bail!("inconsistent read?")
593 };
594 let Some((_, path)) = petgraph::algo::astar(
596 &reversed_graph,
597 module_idx,
598 |n| reversed_graph.neighbors(n).next().is_none(),
599 |e| match e.weight().chunking_type {
601 ChunkingType::Parallel { .. } => 0,
603 _ => 1,
604 },
605 |_| 0,
619 ) else {
620 unreachable!("there must be a path to a root");
621 };
622
623 let path = path
627 .into_iter()
628 .map(async |n| {
629 graph
630 .graph
631 .node_weight(n)
632 .unwrap() .module()
634 .ident()
635 .await
636 })
637 .try_join()
638 .await?;
639 Ok(path)
640 })
641 .try_join()
642 .await?,
643 )));
644 }
645}
646
647#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
650pub struct ModuleGraph {
651 input_graphs: Vec<OperationVc<SingleModuleGraph>>,
652 input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
653
654 snapshot: ModuleGraphSnapshot,
655}
656
657#[turbo_tasks::value_impl]
658impl ModuleGraph {
659 #[turbo_tasks::function(operation)]
660 pub async fn from_single_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
661 let graph = Self::create(vec![graph], None)
662 .read_strongly_consistent()
663 .await?;
664 Ok(ReadRef::cell(graph))
665 }
666
667 #[turbo_tasks::function(operation)]
668 pub async fn from_graphs(graphs: Vec<OperationVc<SingleModuleGraph>>) -> Result<Vc<Self>> {
669 let graph = Self::create(graphs, None)
670 .read_strongly_consistent()
671 .await?;
672 Ok(ReadRef::cell(graph))
673 }
674
675 #[turbo_tasks::function(operation)]
681 pub async fn from_single_graph_without_unused_references(
682 graph: OperationVc<SingleModuleGraph>,
683 binding_usage: OperationVc<BindingUsageInfo>,
684 ) -> Result<Vc<Self>> {
685 let graph = Self::create(vec![graph], Some(binding_usage))
686 .read_strongly_consistent()
687 .await?;
688 Ok(ReadRef::cell(graph))
689 }
690
691 #[turbo_tasks::function(operation)]
697 pub async fn from_graphs_without_unused_references(
698 graphs: Vec<OperationVc<SingleModuleGraph>>,
699 binding_usage: OperationVc<BindingUsageInfo>,
700 ) -> Result<Vc<Self>> {
701 let graph = Self::create(graphs, Some(binding_usage))
702 .read_strongly_consistent()
703 .await?;
704 Ok(ReadRef::cell(graph))
705 }
706
707 #[turbo_tasks::function(operation)]
708 async fn create(
709 graphs: Vec<OperationVc<SingleModuleGraph>>,
710 binding_usage: Option<OperationVc<BindingUsageInfo>>,
711 ) -> Result<Vc<ModuleGraph>> {
712 Ok(ModuleGraph {
713 input_graphs: graphs.clone(),
714 input_binding_usage: binding_usage,
715 snapshot: ModuleGraphSnapshot {
716 graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
717 skip_visited_module_children: false,
718 graph_idx_override: None,
719 binding_usage: if let Some(binding_usage) = binding_usage {
720 Some(binding_usage.connect().await?)
721 } else {
722 None
723 },
724 },
725 }
726 .cell())
727 }
728
729 #[turbo_tasks::function]
730 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
731 compute_chunk_group_info(&*self.await?).await
732 }
733
734 #[turbo_tasks::function]
735 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
736 compute_merged_modules(self).await
737 }
738
739 #[turbo_tasks::function]
740 pub async fn module_batches(
741 self: Vc<Self>,
742 config: Vc<BatchingConfig>,
743 ) -> Result<Vc<ModuleBatchesGraph>> {
744 compute_module_batches(self, &*config.await?).await
745 }
746
747 #[turbo_tasks::function]
748 pub async fn style_groups(
749 self: Vc<Self>,
750 chunking_context: Vc<Box<dyn ChunkingContext>>,
751 config: StyleGroupsConfig,
752 ) -> Result<Vc<StyleGroups>> {
753 compute_style_groups(self, chunking_context, &config).await
754 }
755
756 #[turbo_tasks::function]
757 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
758 async move {
761 let result_op = compute_async_module_info(self.to_resolved().await?);
762 let result_vc = result_op.resolve_strongly_consistent().await?;
763 result_op.drop_collectibles::<Box<dyn Issue>>();
764 anyhow::Ok(*result_vc)
765 }
766 .instrument(tracing::info_span!("compute async module info"))
767 .await
768 }
769
770 #[turbo_tasks::function]
771 pub async fn referenced_async_modules(
772 self: Vc<Self>,
773 module: ResolvedVc<Box<dyn Module>>,
774 ) -> Result<Vc<AsyncModuleInfo>> {
775 let graph_ref = self.await?;
776 let async_modules_info = self.async_module_info().await?;
777
778 let entry = graph_ref.get_entry(module)?;
779 let referenced_modules = graph_ref
780 .iter_graphs_neighbors_rev(entry, Direction::Outgoing)
781 .filter(|(edge_idx, _)| {
782 let ty = graph_ref.get_edge(*edge_idx).unwrap();
783 ty.chunking_type.is_inherit_async()
784 })
785 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
786 .collect::<Result<Vec<_>>>()?
787 .into_iter()
788 .rev()
789 .filter(|m| async_modules_info.contains(m))
790 .map(|m| *m)
791 .collect();
792
793 Ok(AsyncModuleInfo::new(referenced_modules))
794 }
795
796 #[turbo_tasks::function]
798 pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
799 Vc::cell(
800 self.input_graphs
801 .iter()
802 .enumerate()
803 .map(|(graph_idx, graph)| {
804 ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
805 })
806 .collect(),
807 )
808 }
809}
810
811impl Deref for ModuleGraph {
812 type Target = ModuleGraphSnapshot;
813
814 fn deref(&self) -> &Self::Target {
815 &self.snapshot
816 }
817}
818
819#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
820pub struct ModuleGraphLayer {
821 snapshot: ModuleGraphSnapshot,
822}
823
824#[turbo_tasks::value_impl]
825impl ModuleGraphLayer {
826 #[turbo_tasks::function(operation)]
827 async fn new(
828 graph: OperationVc<SingleModuleGraph>,
829 graph_idx: u32,
830 binding_usage: Option<OperationVc<BindingUsageInfo>>,
831 ) -> Result<Vc<Self>> {
832 Ok(Self {
833 snapshot: ModuleGraphSnapshot {
834 graphs: vec![graph.connect().await?],
835 skip_visited_module_children: true,
836 graph_idx_override: Some(graph_idx),
837 binding_usage: if let Some(binding_usage) = binding_usage {
838 Some(binding_usage.connect().await?)
839 } else {
840 None
841 },
842 },
843 }
844 .cell())
845 }
846}
847
848impl Deref for ModuleGraphLayer {
849 type Target = ModuleGraphSnapshot;
850
851 fn deref(&self) -> &Self::Target {
852 &self.snapshot
853 }
854}
855
856#[turbo_tasks::value(transparent)]
857pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
858
859#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
860pub struct ModuleGraphSnapshot {
861 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
862 skip_visited_module_children: bool,
865
866 pub graph_idx_override: Option<u32>,
867
868 pub binding_usage: Option<ReadRef<BindingUsageInfo>>,
869}
870
871impl ModuleGraphSnapshot {
872 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
873 if self.graph_idx_override.is_some() {
874 debug_assert_eq!(self.graphs.len(), 1,);
875 }
876
877 let Some(idx) = self
878 .graphs
879 .iter()
880 .enumerate()
881 .find_map(|(graph_idx, graph)| {
882 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
883 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
884 node_idx: *node_idx,
885 })
886 })
887 else {
888 bail!("Couldn't find entry module {entry:?} in module graph");
889 };
890 Ok(idx)
891 }
892
893 pub fn entries(&self) -> impl Iterator<Item = ChunkGroupEntry> + '_ {
894 self.graphs.iter().flat_map(|g| g.entries.iter().cloned())
895 }
896
897 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
898 if self.graph_idx_override.is_some() {
899 self.graphs.first().unwrap()
900 } else {
901 &self.graphs[graph_idx as usize]
902 }
903 }
904
905 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
906 let graph = self.get_graph(node.graph_idx);
907 graph
908 .graph
909 .node_weight(node.node_idx)
910 .context("Expected graph node")
911 }
912
913 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
914 let graph = self.get_graph(edge.graph_idx);
915 graph
916 .graph
917 .edge_weight(edge.edge_idx)
918 .context("Expected graph node")
919 }
920
921 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
922 if self.skip_visited_module_children && direction == Direction::Outgoing {
923 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
924 } else {
925 true
926 }
927 }
928
929 pub fn enumerate_nodes(
930 &self,
931 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
932 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
933 }
934
935 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
936 self.graphs.iter().flat_map(|g| g.iter_nodes())
937 }
938
939 fn iter_graphs_neighbors_rev<'a>(
941 &'a self,
942 node: GraphNodeIndex,
943 direction: Direction,
944 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
945 let graph = &*self.get_graph(node.graph_idx).graph;
946
947 if cfg!(debug_assertions) && direction == Direction::Outgoing {
948 let node_weight = graph.node_weight(node.node_idx).unwrap();
949 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
950 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
951 }
952 }
953
954 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
955 std::iter::from_fn(move || {
956 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
957 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
958 if self
959 .binding_usage
960 .as_ref()
961 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
962 {
963 continue;
965 }
966
967 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
968 }
969 None
970 })
971 }
972
973 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
976 Ok(self
977 .graphs
978 .iter()
979 .flat_map(|g| g.iter_nodes())
980 .map(async |n| Ok((n, n.ident().to_string().await?)))
981 .try_join()
982 .await?
983 .into_iter()
984 .collect::<FxHashMap<_, _>>())
985 }
986
987 pub fn traverse_nodes_dfs<S>(
996 &self,
997 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
998 state: &mut S,
999 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1000 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1001 ) -> Result<()> {
1002 let entries = entries.into_iter().collect::<Vec<_>>();
1003
1004 enum Pass {
1005 Visit,
1006 ExpandAndVisit,
1007 }
1008 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1009 for entry in entries.into_iter().rev() {
1010 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1011 }
1012 let mut expanded = FxHashSet::default();
1013 while let Some((pass, current)) = stack.pop() {
1014 let current_node = self.get_node(current)?;
1015 match pass {
1016 Pass::Visit => {
1017 visit_postorder(current_node.module(), state)?;
1018 }
1019 Pass::ExpandAndVisit => {
1020 if !expanded.insert(current) {
1021 continue;
1022 }
1023 let action = visit_preorder(current_node.module(), state)?;
1024 if action == GraphTraversalAction::Exclude {
1025 continue;
1026 }
1027 stack.push((Pass::Visit, current));
1028 if action == GraphTraversalAction::Continue
1029 && self.should_visit_node(current_node, Direction::Outgoing)
1030 {
1031 let current = current_node
1032 .target_idx(Direction::Outgoing)
1033 .unwrap_or(current);
1034 stack.extend(
1035 self.iter_graphs_neighbors_rev(current, Direction::Outgoing)
1036 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1037 );
1038 }
1039 }
1040 }
1041 }
1042
1043 Ok(())
1044 }
1045
1046 pub fn traverse_edges_bfs(
1057 &self,
1058 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1059 mut visitor: impl FnMut(
1060 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1061 ResolvedVc<Box<dyn Module>>,
1062 ) -> Result<GraphTraversalAction>,
1063 ) -> Result<()> {
1064 let mut queue = VecDeque::from(
1065 entries
1066 .into_iter()
1067 .map(|e| self.get_entry(e))
1068 .collect::<Result<Vec<_>>>()?,
1069 );
1070 let mut visited = FxHashSet::default();
1071 for entry_node in &queue {
1072 visitor(None, self.get_node(*entry_node)?.module())?;
1073 }
1074 while let Some(node) = queue.pop_front() {
1075 if visited.insert(node) {
1076 let node_weight = self.get_node(node)?;
1077 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1078 let succ_weight = self.get_node(succ)?;
1079 let action = visitor(
1080 Some((node_weight.module(), self.get_edge(edge)?)),
1081 succ_weight.module(),
1082 )?;
1083 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1084 continue;
1085 }
1086 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1087 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1088 queue.push_back(succ);
1089 }
1090 }
1091 }
1092 }
1093
1094 Ok(())
1095 }
1096
1097 pub fn traverse_edges_unordered(
1106 &self,
1107 mut visitor: impl FnMut(
1108 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1109 ResolvedVc<Box<dyn Module>>,
1110 ) -> Result<()>,
1111 ) -> Result<()> {
1112 let entries = self.graphs.iter().flat_map(|g| g.entry_modules());
1113
1114 self.traverse_edges_dfs(
1117 entries,
1118 &mut (),
1119 |parent, target, _| {
1120 visitor(parent, target)?;
1121 Ok(GraphTraversalAction::Continue)
1122 },
1123 |_, _, _| Ok(()),
1124 )
1125 }
1126
1127 pub fn traverse_edges_dfs<S>(
1146 &self,
1147 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1148 state: &mut S,
1149 visit_preorder: impl FnMut(
1150 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1151 ResolvedVc<Box<dyn Module>>,
1152 &mut S,
1153 ) -> Result<GraphTraversalAction>,
1154 visit_postorder: impl FnMut(
1155 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1156 ResolvedVc<Box<dyn Module>>,
1157 &mut S,
1158 ) -> Result<()>,
1159 ) -> Result<()> {
1160 self.traverse_edges_dfs_impl::<S>(
1161 entries,
1162 state,
1163 visit_preorder,
1164 visit_postorder,
1165 Direction::Outgoing,
1166 )
1167 }
1168
1169 pub fn traverse_edges_reverse_dfs<S>(
1186 &self,
1187 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1188 state: &mut S,
1189 visit_preorder: impl FnMut(
1190 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1191 ResolvedVc<Box<dyn Module>>,
1192 &mut S,
1193 ) -> Result<GraphTraversalAction>,
1194 visit_postorder: impl FnMut(
1195 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1196 ResolvedVc<Box<dyn Module>>,
1197 &mut S,
1198 ) -> Result<()>,
1199 ) -> Result<()> {
1200 self.traverse_edges_dfs_impl::<S>(
1201 entries,
1202 state,
1203 visit_preorder,
1204 visit_postorder,
1205 Direction::Incoming,
1206 )
1207 }
1208
1209 fn traverse_edges_dfs_impl<S>(
1210 &self,
1211 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1212 state: &mut S,
1213 mut visit_preorder: impl FnMut(
1214 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1215 ResolvedVc<Box<dyn Module>>,
1216 &mut S,
1217 ) -> Result<GraphTraversalAction>,
1218 mut visit_postorder: impl FnMut(
1219 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1220 ResolvedVc<Box<dyn Module>>,
1221 &mut S,
1222 ) -> Result<()>,
1223 direction: Direction,
1224 ) -> Result<()> {
1225 if direction == Direction::Incoming {
1226 debug_assert!(
1227 self.skip_visited_module_children,
1228 "Can only trace reverse edges in a single layer graph. We do not model cross \
1229 graph reverse edges"
1230 );
1231 }
1232 let entries = entries.into_iter().collect::<Vec<_>>();
1233
1234 enum Pass {
1235 Visit,
1236 ExpandAndVisit,
1237 }
1238 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1240 Pass,
1241 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1242 GraphNodeIndex,
1243 )> = Vec::with_capacity(entries.len());
1244 for entry in entries.into_iter().rev() {
1245 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1246 }
1247 let mut expanded = FxHashSet::default();
1248 while let Some((pass, parent, current)) = stack.pop() {
1249 let parent_arg = match parent {
1250 Some((parent_node, parent_edge)) => Some((
1251 self.get_node(parent_node)?.module(),
1252 self.get_edge(parent_edge)?,
1253 )),
1254 None => None,
1255 };
1256 let current_node = self.get_node(current)?;
1257 match pass {
1258 Pass::Visit => {
1259 visit_postorder(parent_arg, current_node.module(), state)?;
1260 }
1261 Pass::ExpandAndVisit => {
1262 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1263 if action == GraphTraversalAction::Exclude {
1264 continue;
1265 }
1266 stack.push((Pass::Visit, parent, current));
1267 if action == GraphTraversalAction::Continue
1268 && expanded.insert(current)
1269 && self.should_visit_node(current_node, direction)
1270 {
1271 let current = current_node.target_idx(direction).unwrap_or(current);
1272 stack.extend(self.iter_graphs_neighbors_rev(current, direction).map(
1273 |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1274 ));
1275 }
1276 }
1277 }
1278 }
1279
1280 Ok(())
1281 }
1282
1283 pub fn traverse_cycles(
1287 &self,
1288 edge_filter: impl Fn(&RefData) -> bool,
1289 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1290 ) -> Result<()> {
1291 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1292 graph.traverse_cycles(
1293 &edge_filter,
1294 &mut visit_cycle,
1295 graph_idx as u32,
1296 &self.binding_usage,
1297 )?;
1298 }
1299 Ok(())
1300 }
1301
1302 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1324 &self,
1325 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1326 state: &mut S,
1327 mut visit: impl FnMut(
1328 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1329 ResolvedVc<Box<dyn Module>>,
1330 &mut S,
1331 ) -> Result<GraphTraversalAction>,
1332 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1333 ) -> Result<usize> {
1334 if self.skip_visited_module_children {
1335 panic!(
1336 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1337 );
1338 }
1339
1340 let mut visit_order = 0usize;
1341 let mut order = || {
1342 let order = visit_order;
1343 visit_order += 1;
1344 order
1345 };
1346 #[derive(PartialEq, Eq)]
1347 struct NodeWithPriority<T: Ord> {
1348 node: GraphNodeIndex,
1349 priority: T,
1350 visit_order: usize,
1351 }
1352 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1353 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1354 Some(self.cmp(other))
1355 }
1356 }
1357 impl<T: Ord> Ord for NodeWithPriority<T> {
1358 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1359 self.priority
1362 .cmp(&other.priority)
1363 .then(self.visit_order.cmp(&other.visit_order))
1366 }
1367 }
1368
1369 let mut queue_set = FxHashSet::default();
1370 let mut queue = BinaryHeap::from_iter(
1371 entries
1372 .into_iter()
1373 .map(|(m, priority)| {
1374 Ok(NodeWithPriority {
1375 node: self.get_entry(m)?,
1376 priority,
1377 visit_order: order(),
1378 })
1379 })
1380 .collect::<Result<Vec<_>>>()?,
1381 );
1382
1383 for entry_node in &queue {
1384 visit(None, self.get_node(entry_node.node)?.module(), state)?;
1385 }
1386
1387 let mut visit_count = 0usize;
1388 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1389 queue_set.remove(&node);
1390 let node_weight = self.get_node(node)?;
1391 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1392
1393 visit_count += 1;
1394
1395 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1396 let succ_weight = self.get_node(succ)?;
1397
1398 let action = visit(
1399 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1400 succ_weight.module(),
1401 state,
1402 )?;
1403
1404 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1405 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1406 queue.push(NodeWithPriority {
1407 node: succ,
1408 priority: priority(succ_weight.module(), state)?,
1409 visit_order: order(),
1410 });
1411 }
1412 }
1413 }
1414
1415 Ok(visit_count)
1416 }
1417}
1418
1419#[turbo_tasks::value_impl]
1420impl SingleModuleGraph {
1421 #[turbo_tasks::function(operation)]
1422 pub async fn new_with_entry(
1423 entry: ChunkGroupEntry,
1424 include_traced: bool,
1425 include_binding_usage: bool,
1426 ) -> Result<Vc<Self>> {
1427 SingleModuleGraph::new_inner(
1428 vec![entry],
1429 &Default::default(),
1430 include_traced,
1431 include_binding_usage,
1432 )
1433 .await
1434 }
1435
1436 #[turbo_tasks::function(operation)]
1437 pub async fn new_with_entries(
1438 entries: ResolvedVc<GraphEntries>,
1439 include_traced: bool,
1440 include_binding_usage: bool,
1441 ) -> Result<Vc<Self>> {
1442 SingleModuleGraph::new_inner(
1443 entries.owned().await?,
1444 &Default::default(),
1445 include_traced,
1446 include_binding_usage,
1447 )
1448 .await
1449 }
1450
1451 #[turbo_tasks::function(operation)]
1452 pub async fn new_with_entries_visited(
1453 entries: ResolvedVc<GraphEntries>,
1454 visited_modules: OperationVc<VisitedModules>,
1455 include_traced: bool,
1456 include_binding_usage: bool,
1457 ) -> Result<Vc<Self>> {
1458 SingleModuleGraph::new_inner(
1459 entries.owned().await?,
1460 &visited_modules.connect().await?.modules,
1461 include_traced,
1462 include_binding_usage,
1463 )
1464 .await
1465 }
1466
1467 #[turbo_tasks::function(operation)]
1468 pub async fn new_with_entries_visited_intern(
1469 entries: GraphEntriesT,
1471 visited_modules: OperationVc<VisitedModules>,
1472 include_traced: bool,
1473 include_binding_usage: bool,
1474 ) -> Result<Vc<Self>> {
1475 SingleModuleGraph::new_inner(
1476 entries,
1477 &visited_modules.connect().await?.modules,
1478 include_traced,
1479 include_binding_usage,
1480 )
1481 .await
1482 }
1483}
1484
1485#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1486pub enum SingleModuleGraphNode {
1487 Module(ResolvedVc<Box<dyn Module>>),
1488 VisitedModule {
1490 idx: GraphNodeIndex,
1491 module: ResolvedVc<Box<dyn Module>>,
1492 },
1493}
1494
1495impl SingleModuleGraphNode {
1496 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1497 match self {
1498 SingleModuleGraphNode::Module(module) => *module,
1499 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1500 }
1501 }
1502 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1503 match self {
1504 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1505 Direction::Outgoing => Some(*idx),
1506 Direction::Incoming => None,
1507 },
1508 SingleModuleGraphNode::Module(_) => None,
1509 }
1510 }
1511}
1512
1513#[derive(PartialEq, Eq, Debug)]
1514pub enum GraphTraversalAction {
1515 Continue,
1517 Skip,
1519 Exclude,
1521}
1522
1523#[derive(Clone, Hash, PartialEq, Eq)]
1526enum SingleModuleGraphBuilderNode {
1527 Module {
1529 module: ResolvedVc<Box<dyn Module>>,
1530 ident: Option<ReadRef<RcStr>>,
1532 },
1533 VisitedModule {
1535 module: ResolvedVc<Box<dyn Module>>,
1536 idx: GraphNodeIndex,
1537 },
1538}
1539
1540impl SingleModuleGraphBuilderNode {
1541 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1542 Ok(Self::Module {
1543 module,
1544 ident: if emit_spans {
1545 Some(module.ident_string().untracked().await?)
1547 } else {
1548 None
1549 },
1550 })
1551 }
1552 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1553 Self::VisitedModule { module, idx }
1554 }
1555}
1556
1557struct SingleModuleGraphBuilder<'a> {
1558 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1559
1560 emit_spans: bool,
1561
1562 include_traced: bool,
1564
1565 include_binding_usage: bool,
1567}
1568impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1569 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1570 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1571
1572 fn visit(
1573 &mut self,
1574 node: &SingleModuleGraphBuilderNode,
1575 edge: Option<&RefData>,
1576 ) -> VisitControlFlow {
1577 if let Some(edge) = edge
1578 && matches!(edge.chunking_type, ChunkingType::Traced)
1579 {
1580 return VisitControlFlow::Skip;
1582 }
1583 match node {
1584 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1585 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1587 }
1588 }
1589
1590 fn edges(
1591 &mut self,
1592 node: &SingleModuleGraphBuilderNode,
1595 ) -> Self::EdgesFuture {
1596 let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1598 unreachable!()
1600 };
1601 let visited_modules = self.visited_modules;
1602 let emit_spans = self.emit_spans;
1603 let include_traced = self.include_traced;
1604 let include_binding_usage = self.include_binding_usage;
1605 async move {
1606 let refs_cell = primary_chunkable_referenced_modules(
1607 *module,
1608 include_traced,
1609 include_binding_usage,
1610 );
1611 let refs = match refs_cell.await {
1612 Ok(refs) => refs,
1613 Err(e) => {
1614 return Err(e.context(module.ident().to_string().await?));
1615 }
1616 };
1617
1618 refs.iter()
1619 .flat_map(|(reference, resolved)| {
1620 resolved.modules.iter().map(|m| {
1621 (
1622 *reference,
1623 resolved.chunking_type.clone(),
1624 resolved.binding_usage.clone(),
1625 *m,
1626 )
1627 })
1628 })
1629 .map(async |(reference, ty, binding_usage, target)| {
1630 let to = if let Some(idx) = visited_modules.get(&target) {
1631 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1632 } else {
1633 SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1634 };
1635 Ok((
1636 to,
1637 RefData {
1638 chunking_type: ty,
1639 binding_usage,
1640 reference,
1641 },
1642 ))
1643 })
1644 .try_join()
1645 .await
1646 }
1647 }
1648
1649 fn span(
1650 &mut self,
1651 node: &SingleModuleGraphBuilderNode,
1652 edge: Option<&RefData>,
1653 ) -> tracing::Span {
1654 if !self.emit_spans {
1655 return Span::none();
1656 }
1657
1658 let mut span = match node {
1659 SingleModuleGraphBuilderNode::Module {
1660 ident: Some(ident), ..
1661 } => {
1662 tracing::info_span!("module", name = display(ident))
1663 }
1664 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1665 tracing::info_span!("visited module")
1666 }
1667 _ => unreachable!(),
1668 };
1669
1670 if let Some(edge) = edge {
1671 match &edge.chunking_type {
1672 ChunkingType::Parallel {
1673 inherit_async: _,
1674 hoisted: _,
1675 } => {}
1676 ChunkingType::Traced => {
1677 let _span = span.entered();
1678 span = tracing::info_span!("traced reference");
1679 }
1680 ChunkingType::Async => {
1681 let _span = span.entered();
1682 span = tracing::info_span!("async reference");
1683 }
1684 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1685 let _span = span.entered();
1686 span = tracing::info_span!(
1687 "isolated reference",
1688 ty = debug(&ty),
1689 merge_tag = debug(&merge_tag)
1690 );
1691 }
1692 ChunkingType::Shared {
1693 inherit_async: _,
1694 merge_tag,
1695 } => {
1696 let _span = span.entered();
1697 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1698 }
1699 };
1700 }
1701
1702 span
1703 }
1704}
1705
1706#[cfg(test)]
1707pub mod tests {
1708 use anyhow::Result;
1709 use rustc_hash::FxHashMap;
1710 use turbo_rcstr::{RcStr, rcstr};
1711 use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
1712 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1713 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1714
1715 use crate::{
1716 asset::{Asset, AssetContent},
1717 ident::AssetIdent,
1718 module::{Module, ModuleSideEffects},
1719 module_graph::{
1720 GraphEntries, GraphTraversalAction, ModuleGraph, SingleModuleGraph, VisitedModules,
1721 chunk_group_info::ChunkGroupEntry,
1722 },
1723 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1724 resolve::ExportUsage,
1725 };
1726
1727 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1728 async fn test_traverse_dfs_from_entries_diamond() {
1729 run_graph_test(
1730 vec![rcstr!("a.js")],
1731 {
1732 let mut deps = FxHashMap::default();
1733 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1735 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1736 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1737 deps
1738 },
1739 |graph, entry_modules, module_to_name| {
1740 let mut preorder_visits = Vec::new();
1741 let mut postorder_visits = Vec::new();
1742
1743 graph.traverse_edges_dfs(
1744 entry_modules,
1745 &mut (),
1746 |parent, target, _| {
1747 preorder_visits.push((
1748 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1749 module_to_name.get(&target).unwrap().clone(),
1750 ));
1751 Ok(GraphTraversalAction::Continue)
1752 },
1753 |parent, target, _| {
1754 postorder_visits.push((
1755 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1756 module_to_name.get(&target).unwrap().clone(),
1757 ));
1758 Ok(())
1759 },
1760 )?;
1761 assert_eq!(
1762 vec![
1763 (None, rcstr!("a.js")),
1764 (Some(rcstr!("a.js")), rcstr!("b.js")),
1765 (Some(rcstr!("b.js")), rcstr!("d.js")),
1766 (Some(rcstr!("a.js")), rcstr!("c.js")),
1767 (Some(rcstr!("c.js")), rcstr!("d.js"))
1768 ],
1769 preorder_visits
1770 );
1771 assert_eq!(
1772 vec![
1773 (Some(rcstr!("b.js")), rcstr!("d.js")),
1774 (Some(rcstr!("a.js")), rcstr!("b.js")),
1775 (Some(rcstr!("c.js")), rcstr!("d.js")),
1776 (Some(rcstr!("a.js")), rcstr!("c.js")),
1777 (None, rcstr!("a.js"))
1778 ],
1779 postorder_visits
1780 );
1781 Ok(())
1782 },
1783 )
1784 .await;
1785 }
1786
1787 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1788 async fn test_traverse_dfs_from_entries_cycle() {
1789 run_graph_test(
1790 vec![rcstr!("a.js")],
1791 {
1792 let mut deps = FxHashMap::default();
1793 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1795 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1796 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1797 deps
1798 },
1799 |graph, entry_modules, module_to_name| {
1800 let mut preorder_visits = Vec::new();
1801 let mut postorder_visits = Vec::new();
1802
1803 graph.traverse_edges_dfs(
1804 entry_modules,
1805 &mut (),
1806 |parent, target, _| {
1807 preorder_visits.push((
1808 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1809 module_to_name.get(&target).unwrap().clone(),
1810 ));
1811 Ok(GraphTraversalAction::Continue)
1812 },
1813 |parent, target, _| {
1814 postorder_visits.push((
1815 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1816 module_to_name.get(&target).unwrap().clone(),
1817 ));
1818 Ok(())
1819 },
1820 )?;
1821 assert_eq!(
1822 vec![
1823 (None, rcstr!("a.js")),
1824 (Some(rcstr!("a.js")), rcstr!("b.js")),
1825 (Some(rcstr!("b.js")), rcstr!("c.js")),
1826 (Some(rcstr!("c.js")), rcstr!("a.js")),
1827 ],
1828 preorder_visits
1829 );
1830 assert_eq!(
1831 vec![
1832 (Some(rcstr!("c.js")), rcstr!("a.js")),
1833 (Some(rcstr!("b.js")), rcstr!("c.js")),
1834 (Some(rcstr!("a.js")), rcstr!("b.js")),
1835 (None, rcstr!("a.js"))
1836 ],
1837 postorder_visits
1838 );
1839 Ok(())
1840 },
1841 )
1842 .await;
1843 }
1844
1845 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1846 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1847 run_graph_test(
1848 vec![rcstr!("a.js")],
1849 {
1850 let mut deps = FxHashMap::default();
1851 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1853 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1854 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1855 deps
1856 },
1857 |graph, entry_modules, module_to_name| {
1858 let mut visits = Vec::new();
1859 let mut count = 0;
1860
1861 graph.traverse_edges_fixed_point_with_priority(
1862 entry_modules.into_iter().map(|m| (m, 0)),
1863 &mut (),
1864 |parent, target, _| {
1865 visits.push((
1866 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1867 module_to_name.get(&target).unwrap().clone(),
1868 ));
1869 count += 1;
1870
1871 Ok(if count < 6 {
1873 GraphTraversalAction::Continue
1874 } else {
1875 GraphTraversalAction::Skip
1876 })
1877 },
1878 |_, _| Ok(0),
1879 )?;
1880 assert_eq!(
1881 vec![
1882 (None, rcstr!("a.js")),
1883 (Some(rcstr!("a.js")), rcstr!("b.js")),
1884 (Some(rcstr!("b.js")), rcstr!("c.js")),
1885 (Some(rcstr!("c.js")), rcstr!("a.js")),
1886 (Some(rcstr!("a.js")), rcstr!("b.js")),
1888 (Some(rcstr!("b.js")), rcstr!("c.js")),
1889 ],
1890 visits
1891 );
1892
1893 Ok(())
1894 },
1895 )
1896 .await;
1897 }
1898
1899 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1900 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1901 run_graph_test(
1902 vec![rcstr!("a.js")],
1903 {
1904 let mut deps = FxHashMap::default();
1905 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1910 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1911 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1912 deps
1913 },
1914 |graph, entry_modules, module_to_name| {
1915 let mut visits = Vec::new();
1916 let mut count = 0;
1917
1918 graph.traverse_edges_fixed_point_with_priority(
1919 entry_modules.into_iter().map(|m| (m, 0)),
1920 &mut (),
1921 |parent, target, _| {
1922 visits.push((
1923 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1924 module_to_name.get(&target).unwrap().clone(),
1925 ));
1926 count += 1;
1927
1928 Ok(if count < 6 {
1930 GraphTraversalAction::Continue
1931 } else {
1932 GraphTraversalAction::Skip
1933 })
1934 },
1935 |_, _| Ok(0),
1936 )?;
1937
1938 assert_eq!(
1939 vec![
1940 (None, rcstr!("a.js")),
1941 (Some(rcstr!("a.js")), rcstr!("c.js")),
1942 (Some(rcstr!("a.js")), rcstr!("b.js")),
1943 (Some(rcstr!("b.js")), rcstr!("e.js")),
1944 (Some(rcstr!("b.js")), rcstr!("d.js")),
1945 (Some(rcstr!("c.js")), rcstr!("f.js")),
1946 (Some(rcstr!("c.js")), rcstr!("e.js")),
1947 ],
1948 visits
1949 );
1950
1951 Ok(())
1952 },
1953 )
1954 .await;
1955 }
1956
1957 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1958 async fn test_traverse_cycles() {
1959 run_graph_test(
1960 vec![rcstr!("a.js")],
1961 {
1962 let mut deps = FxHashMap::default();
1963 deps.insert(
1970 rcstr!("a.js"),
1971 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
1972 );
1973 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
1974 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
1975 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
1976 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
1977 deps
1978 },
1979 |graph, _, module_to_name| {
1980 let mut cycles = vec![];
1981
1982 graph.traverse_cycles(
1983 |_| true,
1984 |cycle| {
1985 cycles.push(
1986 cycle
1987 .iter()
1988 .map(|n| module_to_name.get(*n).unwrap().clone())
1989 .collect::<Vec<_>>(),
1990 );
1991 Ok(())
1992 },
1993 )?;
1994
1995 assert_eq!(
1996 cycles,
1997 vec![
1998 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
1999 vec![rcstr!("s.js")]
2000 ],
2001 );
2002
2003 Ok(())
2004 },
2005 )
2006 .await;
2007 }
2008
2009 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2010 async fn test_reverse_edges_through_layered_graph() {
2011 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2012 BackendOptions::default(),
2013 noop_backing_storage(),
2014 ));
2015 tt.run_once(async move {
2016 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2017 let root = fs.root().await?;
2018
2019 let graph = {
2022 let mut deps = FxHashMap::default();
2023
2024 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2025 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2026 deps
2027 };
2028 let repo = TestRepo {
2029 repo: graph
2030 .iter()
2031 .map(|(k, v)| {
2032 (
2033 root.join(k).unwrap(),
2034 v.iter().map(|f| root.join(f).unwrap()).collect(),
2035 )
2036 })
2037 .collect(),
2038 }
2039 .cell();
2040 let make_module = |name| {
2041 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2042 .to_resolved()
2043 };
2044 let a_module = make_module("a.js").await?;
2045 let b_module = make_module("b.js").await?;
2046
2047 let parent_graph = SingleModuleGraph::new_with_entries(
2048 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2049 false,
2050 false,
2051 );
2052
2053 let module_graph = ModuleGraph::from_graphs(vec![
2054 parent_graph,
2055 SingleModuleGraph::new_with_entries_visited(
2056 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2057 VisitedModules::from_graph(parent_graph),
2058 false,
2059 false,
2060 ),
2061 ])
2062 .connect();
2063 let child_graph = module_graph
2064 .iter_graphs()
2065 .await?
2066 .get(1)
2067 .unwrap()
2068 .connect()
2069 .await?;
2070 {
2072 let mut visited_forward = Vec::new();
2073 child_graph.traverse_edges_dfs(
2074 vec![a_module],
2075 &mut (),
2076 |_parent, child, _state_| {
2077 visited_forward.push(child);
2078 Ok(GraphTraversalAction::Continue)
2079 },
2080 |_, _, _| Ok(()),
2081 )?;
2082
2083 assert_eq!(
2084 visited_forward
2085 .iter()
2086 .map(|m| m.ident().to_string().owned())
2087 .try_join()
2088 .await?,
2089 vec![
2090 rcstr!("[test]/a.js"),
2091 rcstr!("[test]/b.js"),
2092 rcstr!("[test]/d.js")
2093 ]
2094 );
2095 }
2096
2097 {
2099 use turbo_tasks::TryFlatJoinIterExt;
2100 let d_module = child_graph
2101 .enumerate_nodes()
2102 .map(|(_index, module)| async move {
2103 Ok(match module {
2104 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2105 if module.ident().to_string().owned().await.unwrap()
2106 == "[test]/d.js"
2107 {
2108 Some(*module)
2109 } else {
2110 None
2111 }
2112 }
2113 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2114 ..
2115 } => None,
2116 })
2117 })
2118 .try_flat_join()
2119 .await?
2120 .into_iter()
2121 .next()
2122 .unwrap();
2123 let mut visited_reverse = Vec::new();
2124 child_graph.traverse_edges_reverse_dfs(
2125 vec![d_module],
2126 &mut (),
2127 |_parent, child, _state_| {
2128 visited_reverse.push(child);
2129 Ok(GraphTraversalAction::Continue)
2130 },
2131 |_, _, _| Ok(()),
2132 )?;
2133 assert_eq!(
2134 visited_reverse
2135 .iter()
2136 .map(|m| m.ident().to_string().owned())
2137 .try_join()
2138 .await?,
2139 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2140 );
2141 }
2142 {
2145 let mut visited_reverse = Vec::new();
2146 child_graph.traverse_edges_reverse_dfs(
2147 vec![b_module],
2148 &mut (),
2149 |_parent, child, _state_| {
2150 visited_reverse.push(child);
2151 Ok(GraphTraversalAction::Continue)
2152 },
2153 |_, _, _| Ok(()),
2154 )?;
2155 assert_eq!(
2156 visited_reverse
2157 .iter()
2158 .map(|m| m.ident().to_string().owned())
2159 .try_join()
2160 .await?,
2161 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2162 );
2163 }
2164
2165 Ok(())
2166 })
2167 .await
2168 .unwrap();
2169 }
2170
2171 #[turbo_tasks::value(shared)]
2172 struct TestRepo {
2173 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2174 }
2175 #[turbo_tasks::value]
2176 struct MockModule {
2177 path: FileSystemPath,
2178 repo: ResolvedVc<TestRepo>,
2179 }
2180 #[turbo_tasks::value_impl]
2181 impl MockModule {
2182 #[turbo_tasks::function]
2183 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2184 Self { path, repo }.cell()
2185 }
2186 }
2187
2188 #[turbo_tasks::value_impl]
2189 impl Asset for MockModule {
2190 #[turbo_tasks::function]
2191 fn content(&self) -> Vc<AssetContent> {
2192 panic!("MockModule::content shouldn't be called")
2193 }
2194 }
2195
2196 #[turbo_tasks::value_impl]
2197 impl Module for MockModule {
2198 #[turbo_tasks::function]
2199 fn ident(&self) -> Vc<AssetIdent> {
2200 AssetIdent::from_path(self.path.clone())
2201 }
2202
2203 #[turbo_tasks::function]
2204 fn source(&self) -> Vc<crate::source::OptionSource> {
2205 Vc::cell(None)
2206 }
2207
2208 #[turbo_tasks::function]
2209 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2210 let repo = self.repo.await?;
2211 let references = match repo.repo.get(&self.path) {
2212 Some(deps) => {
2213 deps.iter()
2214 .map(|p| {
2215 Vc::upcast::<Box<dyn ModuleReference>>(
2216 SingleChunkableModuleReference::new(
2217 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2218 rcstr!("normal-dep"),
2219 ExportUsage::all(),
2220 ),
2221 )
2222 .to_resolved()
2223 })
2224 .try_join()
2225 .await?
2226 }
2227 None => vec![],
2228 };
2229
2230 Ok(Vc::cell(references))
2231 }
2232 #[turbo_tasks::function]
2233 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2234 ModuleSideEffects::SideEffectful.cell()
2235 }
2236 }
2237
2238 async fn run_graph_test(
2252 entries: Vec<RcStr>,
2253 graph: FxHashMap<RcStr, Vec<RcStr>>,
2254 test_fn: impl FnOnce(
2255 &ModuleGraph,
2256 Vec<ResolvedVc<Box<dyn Module>>>,
2257 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2258 ) -> Result<()>
2259 + Send
2260 + 'static,
2261 ) {
2262 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2263 BackendOptions::default(),
2264 noop_backing_storage(),
2265 ));
2266 tt.run_once(async move {
2267 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2268 let root = fs.root().await?;
2269
2270 let repo = TestRepo {
2271 repo: graph
2272 .iter()
2273 .map(|(k, v)| {
2274 (
2275 root.join(k).unwrap(),
2276 v.iter().map(|f| root.join(f).unwrap()).collect(),
2277 )
2278 })
2279 .collect(),
2280 }
2281 .cell();
2282 let entry_modules = entries
2283 .iter()
2284 .map(|e| {
2285 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2286 .to_resolved()
2287 })
2288 .try_join()
2289 .await?;
2290 let graph = SingleModuleGraph::new_with_entries(
2291 GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2292 entry_modules.clone(),
2293 )])),
2294 false,
2295 false,
2296 );
2297
2298 let module_to_name = graph
2302 .connect()
2303 .await?
2304 .modules
2305 .keys()
2306 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2307 .try_join()
2308 .await?
2309 .into_iter()
2310 .collect();
2311 test_fn(
2312 &*ModuleGraph::from_single_graph(graph).connect().await?,
2313 entry_modules,
2314 module_to_name,
2315 )
2316 })
2317 .await
2318 .unwrap();
2319 }
2320}