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!("Duplicate module idents in graph: {duplicates:#?}");
365 }
366 }
367 }
368
369 let graph = SingleModuleGraph {
370 graph: TracedDiGraph::new(graph),
371 number_of_modules,
372 modules,
373 entries: entries.clone(),
374 }
375 .cell();
376
377 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
378 ModuleGraphImportTracer::new(graph).to_resolved().await?,
379 ));
380 Ok(graph)
381 }
382
383 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
385 self.graph.node_weights().filter_map(|n| match n {
386 SingleModuleGraphNode::Module(node) => Some(*node),
387 SingleModuleGraphNode::VisitedModule { .. } => None,
388 })
389 }
390
391 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
393 if let Some(index) = self.modules.get(&module) {
394 self.graph
395 .edges_directed(*index, Direction::Incoming)
396 .next()
397 .is_none()
398 } else {
399 false
400 }
401 }
402
403 pub fn entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
405 self.entries.iter().flat_map(|e| e.entries())
406 }
407
408 pub fn enumerate_nodes(
410 &self,
411 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
412 self.graph.node_references()
413 }
414
415 fn traverse_cycles<'l>(
416 &'l self,
417 edge_filter: impl Fn(&'l RefData) -> bool,
418 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
419 graph_idx: u32,
420 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
421 ) -> Result<()> {
422 #[derive(Clone)]
429 struct NodeState {
430 index: u32,
431 lowlink: u32,
432 on_stack: bool,
433 has_self_loop: bool,
434 }
435 enum VisitStep {
436 UnvisitedNode(NodeIndex),
437 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
438 AfterVisit(NodeIndex),
439 }
440 let mut node_states = vec![None; self.graph.node_bound()];
441 let mut stack = Vec::new();
442 let mut visit_stack = Vec::new();
443 let mut index = 0;
444 let mut scc = Vec::new();
445 for initial_index in self.graph.node_indices() {
446 if node_states[initial_index.index()].is_some() {
448 continue;
449 }
450 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
451 while let Some(step) = visit_stack.pop() {
452 match step {
453 VisitStep::UnvisitedNode(node) => {
454 node_states[node.index()] = Some(NodeState {
455 index,
456 lowlink: index,
457 on_stack: true,
458 has_self_loop: false,
459 });
460 index += 1;
461 stack.push(node);
462 visit_stack.push(VisitStep::AfterVisit(node));
463 let mut neighbors = self.graph.neighbors(node).detach();
464 while let Some((edge, succ)) = neighbors.next(&self.graph) {
465 if binding_usage.as_ref().is_some_and(|binding_usage| {
466 binding_usage
467 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
468 }) {
469 continue;
470 }
471
472 let edge_weight = self.graph.edge_weight(edge).unwrap();
473 if !edge_filter(edge_weight) {
474 continue;
475 }
476 let node_state = &node_states[succ.index()];
477 if let Some(node_state) = node_state {
478 if node_state.on_stack {
479 let index = node_state.index;
480 let parent_state = node_states[node.index()].as_mut().unwrap();
481 parent_state.lowlink = parent_state.lowlink.min(index);
482 if succ == node {
483 parent_state.has_self_loop = true;
484 }
485 }
486 } else {
487 visit_stack.push(VisitStep::EdgeAfterVisit {
488 parent: node,
489 child: succ,
490 });
491 visit_stack.push(VisitStep::UnvisitedNode(succ));
492 }
493 }
494 }
495 VisitStep::EdgeAfterVisit { parent, child } => {
496 let child_state = node_states[child.index()].as_ref().unwrap();
497 let lowlink = child_state.lowlink;
498
499 let parent_state = node_states[parent.index()].as_mut().unwrap();
500 parent_state.lowlink = parent_state.lowlink.min(lowlink);
501 }
502 VisitStep::AfterVisit(node) => {
503 let node_state = node_states[node.index()].as_ref().unwrap();
504 let node_has_self_loop = node_state.has_self_loop;
505 if node_state.lowlink == node_state.index {
506 loop {
507 let poppped = stack.pop().unwrap();
508 let popped_state = node_states[poppped.index()].as_mut().unwrap();
509 popped_state.on_stack = false;
510 if let SingleModuleGraphNode::Module(module) =
511 self.graph.node_weight(poppped).unwrap()
512 {
513 scc.push(module);
514 }
515 if poppped == node {
516 break;
517 }
518 }
519 if scc.len() > 1 || node_has_self_loop {
520 visit_cycle(&scc)?;
521 }
522 scc.clear();
523 }
524 }
525 }
526 }
527 }
528 Ok(())
529 }
530}
531
532#[turbo_tasks::value]
533struct ModuleGraphImportTracer {
534 graph: ResolvedVc<SingleModuleGraph>,
535}
536
537#[turbo_tasks::value(shared)]
538struct PathToModulesMap {
539 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
540}
541
542#[turbo_tasks::value_impl]
543impl ModuleGraphImportTracer {
544 #[turbo_tasks::function]
545 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
546 Self::cell(Self { graph })
547 }
548
549 #[turbo_tasks::function]
551 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
552 let path_and_modules = self
553 .graph
554 .await?
555 .modules
556 .iter()
557 .map(|(&module, _)| async move { Ok((module.ident().path().owned().await?, module)) })
558 .try_join()
559 .await?;
560 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
561 FxHashMap::default();
562 for (path, module) in path_and_modules {
563 map.entry(path).or_default().push(module)
564 }
565 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
566 }
567}
568
569#[turbo_tasks::value_impl]
570impl ImportTracer for ModuleGraphImportTracer {
571 #[turbo_tasks::function]
572 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
573 let path_to_modules = self.path_to_modules().await?;
574 let Some(modules) = path_to_modules.map.get(&path) else {
575 return Ok(Vc::default()); };
578 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
579 let graph = &*self.await?.graph.await?;
580
581 let reversed_graph = Reversed(&graph.graph.0);
582 return Ok(ImportTraces::cell(ImportTraces(
583 modules
584 .iter()
585 .map(|m| async move {
586 let Some(&module_idx) = graph.modules.get(m) else {
587 bail!("inconsistent read?")
590 };
591 let Some((_, path)) = petgraph::algo::astar(
593 &reversed_graph,
594 module_idx,
595 |n| reversed_graph.neighbors(n).next().is_none(),
596 |e| match e.weight().chunking_type {
598 ChunkingType::Parallel { .. } => 0,
600 _ => 1,
601 },
602 |_| 0,
616 ) else {
617 unreachable!("there must be a path to a root");
618 };
619
620 let path = path
624 .into_iter()
625 .map(async |n| {
626 graph
627 .graph
628 .node_weight(n)
629 .unwrap() .module()
631 .ident()
632 .await
633 })
634 .try_join()
635 .await?;
636 Ok(path)
637 })
638 .try_join()
639 .await?,
640 )));
641 }
642}
643
644#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
647pub struct ModuleGraph {
648 input_graphs: Vec<OperationVc<SingleModuleGraph>>,
649 input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
650
651 snapshot: ModuleGraphSnapshot,
652}
653
654#[turbo_tasks::value_impl]
655impl ModuleGraph {
656 #[turbo_tasks::function(operation)]
657 pub async fn from_single_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
658 let graph = Self::create(vec![graph], None)
659 .read_strongly_consistent()
660 .await?;
661 Ok(ReadRef::cell(graph))
662 }
663
664 #[turbo_tasks::function(operation)]
665 pub async fn from_graphs(graphs: Vec<OperationVc<SingleModuleGraph>>) -> Result<Vc<Self>> {
666 let graph = Self::create(graphs, None)
667 .read_strongly_consistent()
668 .await?;
669 Ok(ReadRef::cell(graph))
670 }
671
672 #[turbo_tasks::function(operation)]
678 pub async fn from_single_graph_without_unused_references(
679 graph: OperationVc<SingleModuleGraph>,
680 binding_usage: OperationVc<BindingUsageInfo>,
681 ) -> Result<Vc<Self>> {
682 let graph = Self::create(vec![graph], Some(binding_usage))
683 .read_strongly_consistent()
684 .await?;
685 Ok(ReadRef::cell(graph))
686 }
687
688 #[turbo_tasks::function(operation)]
694 pub async fn from_graphs_without_unused_references(
695 graphs: Vec<OperationVc<SingleModuleGraph>>,
696 binding_usage: OperationVc<BindingUsageInfo>,
697 ) -> Result<Vc<Self>> {
698 let graph = Self::create(graphs, Some(binding_usage))
699 .read_strongly_consistent()
700 .await?;
701 Ok(ReadRef::cell(graph))
702 }
703
704 #[turbo_tasks::function(operation)]
705 async fn create(
706 graphs: Vec<OperationVc<SingleModuleGraph>>,
707 binding_usage: Option<OperationVc<BindingUsageInfo>>,
708 ) -> Result<Vc<ModuleGraph>> {
709 Ok(ModuleGraph {
710 input_graphs: graphs.clone(),
711 input_binding_usage: binding_usage,
712 snapshot: ModuleGraphSnapshot {
713 graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
714 skip_visited_module_children: false,
715 graph_idx_override: None,
716 binding_usage: if let Some(binding_usage) = binding_usage {
717 Some(binding_usage.connect().await?)
718 } else {
719 None
720 },
721 },
722 }
723 .cell())
724 }
725
726 #[turbo_tasks::function]
727 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
728 compute_chunk_group_info(&*self.await?).await
729 }
730
731 #[turbo_tasks::function]
732 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
733 compute_merged_modules(self).await
734 }
735
736 #[turbo_tasks::function]
737 pub async fn module_batches(
738 self: Vc<Self>,
739 config: Vc<BatchingConfig>,
740 ) -> Result<Vc<ModuleBatchesGraph>> {
741 compute_module_batches(self, &*config.await?).await
742 }
743
744 #[turbo_tasks::function]
745 pub async fn style_groups(
746 self: Vc<Self>,
747 chunking_context: Vc<Box<dyn ChunkingContext>>,
748 config: StyleGroupsConfig,
749 ) -> Result<Vc<StyleGroups>> {
750 compute_style_groups(self, chunking_context, &config).await
751 }
752
753 #[turbo_tasks::function]
754 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
755 async move {
758 let result_op = compute_async_module_info(self.to_resolved().await?);
759 let result_vc = result_op.resolve_strongly_consistent().await?;
760 result_op.drop_collectibles::<Box<dyn Issue>>();
761 anyhow::Ok(*result_vc)
762 }
763 .instrument(tracing::info_span!("compute async module info"))
764 .await
765 }
766
767 #[turbo_tasks::function]
768 pub async fn referenced_async_modules(
769 self: Vc<Self>,
770 module: ResolvedVc<Box<dyn Module>>,
771 ) -> Result<Vc<AsyncModuleInfo>> {
772 let graph_ref = self.await?;
773 let async_modules_info = self.async_module_info().await?;
774
775 let entry = graph_ref.get_entry(module)?;
776 let referenced_modules = graph_ref
777 .iter_graphs_neighbors_rev(entry, Direction::Outgoing)
778 .filter(|(edge_idx, _)| {
779 let ty = graph_ref.get_edge(*edge_idx).unwrap();
780 ty.chunking_type.is_inherit_async()
781 })
782 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
783 .collect::<Result<Vec<_>>>()?
784 .into_iter()
785 .rev()
786 .filter(|m| async_modules_info.contains(m))
787 .map(|m| *m)
788 .collect();
789
790 Ok(AsyncModuleInfo::new(referenced_modules))
791 }
792
793 #[turbo_tasks::function]
795 pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
796 Vc::cell(
797 self.input_graphs
798 .iter()
799 .enumerate()
800 .map(|(graph_idx, graph)| {
801 ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
802 })
803 .collect(),
804 )
805 }
806}
807
808impl Deref for ModuleGraph {
809 type Target = ModuleGraphSnapshot;
810
811 fn deref(&self) -> &Self::Target {
812 &self.snapshot
813 }
814}
815
816#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
817pub struct ModuleGraphLayer {
818 snapshot: ModuleGraphSnapshot,
819}
820
821#[turbo_tasks::value_impl]
822impl ModuleGraphLayer {
823 #[turbo_tasks::function(operation)]
824 async fn new(
825 graph: OperationVc<SingleModuleGraph>,
826 graph_idx: u32,
827 binding_usage: Option<OperationVc<BindingUsageInfo>>,
828 ) -> Result<Vc<Self>> {
829 Ok(Self {
830 snapshot: ModuleGraphSnapshot {
831 graphs: vec![graph.connect().await?],
832 skip_visited_module_children: true,
833 graph_idx_override: Some(graph_idx),
834 binding_usage: if let Some(binding_usage) = binding_usage {
835 Some(binding_usage.connect().await?)
836 } else {
837 None
838 },
839 },
840 }
841 .cell())
842 }
843}
844
845impl Deref for ModuleGraphLayer {
846 type Target = ModuleGraphSnapshot;
847
848 fn deref(&self) -> &Self::Target {
849 &self.snapshot
850 }
851}
852
853#[turbo_tasks::value(transparent)]
854pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
855
856#[derive(TraceRawVcs, ValueDebugFormat, NonLocalValue)]
857pub struct ModuleGraphSnapshot {
858 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
859 skip_visited_module_children: bool,
862
863 pub graph_idx_override: Option<u32>,
864
865 pub binding_usage: Option<ReadRef<BindingUsageInfo>>,
866}
867
868impl ModuleGraphSnapshot {
869 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
870 if self.graph_idx_override.is_some() {
871 debug_assert_eq!(self.graphs.len(), 1,);
872 }
873
874 let Some(idx) = self
875 .graphs
876 .iter()
877 .enumerate()
878 .find_map(|(graph_idx, graph)| {
879 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
880 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
881 node_idx: *node_idx,
882 })
883 })
884 else {
885 bail!("Couldn't find entry module {entry:?} in module graph");
886 };
887 Ok(idx)
888 }
889
890 pub fn entries(&self) -> impl Iterator<Item = ChunkGroupEntry> + '_ {
891 self.graphs.iter().flat_map(|g| g.entries.iter().cloned())
892 }
893
894 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
895 if self.graph_idx_override.is_some() {
896 self.graphs.first().unwrap()
897 } else {
898 &self.graphs[graph_idx as usize]
899 }
900 }
901
902 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
903 let graph = self.get_graph(node.graph_idx);
904 graph
905 .graph
906 .node_weight(node.node_idx)
907 .context("Expected graph node")
908 }
909
910 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
911 let graph = self.get_graph(edge.graph_idx);
912 graph
913 .graph
914 .edge_weight(edge.edge_idx)
915 .context("Expected graph node")
916 }
917
918 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
919 if self.skip_visited_module_children && direction == Direction::Outgoing {
920 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
921 } else {
922 true
923 }
924 }
925
926 pub fn enumerate_nodes(
927 &self,
928 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
929 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
930 }
931
932 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
933 self.graphs.iter().flat_map(|g| g.iter_nodes())
934 }
935
936 fn iter_graphs_neighbors_rev<'a>(
938 &'a self,
939 node: GraphNodeIndex,
940 direction: Direction,
941 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
942 let graph = &*self.get_graph(node.graph_idx).graph;
943
944 if cfg!(debug_assertions) && direction == Direction::Outgoing {
945 let node_weight = graph.node_weight(node.node_idx).unwrap();
946 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
947 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
948 }
949 }
950
951 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
952 std::iter::from_fn(move || {
953 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
954 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
955 if self
956 .binding_usage
957 .as_ref()
958 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
959 {
960 continue;
962 }
963
964 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
965 }
966 None
967 })
968 }
969
970 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
973 Ok(self
974 .graphs
975 .iter()
976 .flat_map(|g| g.iter_nodes())
977 .map(async |n| Ok((n, n.ident().to_string().await?)))
978 .try_join()
979 .await?
980 .into_iter()
981 .collect::<FxHashMap<_, _>>())
982 }
983
984 pub fn traverse_nodes_dfs<S>(
993 &self,
994 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
995 state: &mut S,
996 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
997 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
998 ) -> Result<()> {
999 let entries = entries.into_iter().collect::<Vec<_>>();
1000
1001 enum Pass {
1002 Visit,
1003 ExpandAndVisit,
1004 }
1005 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1006 for entry in entries.into_iter().rev() {
1007 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1008 }
1009 let mut expanded = FxHashSet::default();
1010 while let Some((pass, current)) = stack.pop() {
1011 let current_node = self.get_node(current)?;
1012 match pass {
1013 Pass::Visit => {
1014 visit_postorder(current_node.module(), state)?;
1015 }
1016 Pass::ExpandAndVisit => {
1017 if !expanded.insert(current) {
1018 continue;
1019 }
1020 let action = visit_preorder(current_node.module(), state)?;
1021 if action == GraphTraversalAction::Exclude {
1022 continue;
1023 }
1024 stack.push((Pass::Visit, current));
1025 if action == GraphTraversalAction::Continue
1026 && self.should_visit_node(current_node, Direction::Outgoing)
1027 {
1028 let current = current_node
1029 .target_idx(Direction::Outgoing)
1030 .unwrap_or(current);
1031 stack.extend(
1032 self.iter_graphs_neighbors_rev(current, Direction::Outgoing)
1033 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1034 );
1035 }
1036 }
1037 }
1038 }
1039
1040 Ok(())
1041 }
1042
1043 pub fn traverse_edges_bfs(
1054 &self,
1055 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1056 mut visitor: impl FnMut(
1057 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1058 ResolvedVc<Box<dyn Module>>,
1059 ) -> Result<GraphTraversalAction>,
1060 ) -> Result<()> {
1061 let mut queue = VecDeque::from(
1062 entries
1063 .into_iter()
1064 .map(|e| self.get_entry(e))
1065 .collect::<Result<Vec<_>>>()?,
1066 );
1067 let mut visited = FxHashSet::default();
1068 for entry_node in &queue {
1069 visitor(None, self.get_node(*entry_node)?.module())?;
1070 }
1071 while let Some(node) = queue.pop_front() {
1072 if visited.insert(node) {
1073 let node_weight = self.get_node(node)?;
1074 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1075 let succ_weight = self.get_node(succ)?;
1076 let action = visitor(
1077 Some((node_weight.module(), self.get_edge(edge)?)),
1078 succ_weight.module(),
1079 )?;
1080 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1081 continue;
1082 }
1083 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1084 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1085 queue.push_back(succ);
1086 }
1087 }
1088 }
1089 }
1090
1091 Ok(())
1092 }
1093
1094 pub fn traverse_edges_unordered(
1103 &self,
1104 mut visitor: impl FnMut(
1105 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1106 ResolvedVc<Box<dyn Module>>,
1107 ) -> Result<()>,
1108 ) -> Result<()> {
1109 let entries = self.graphs.iter().flat_map(|g| g.entry_modules());
1110
1111 self.traverse_edges_dfs(
1114 entries,
1115 &mut (),
1116 |parent, target, _| {
1117 visitor(parent, target)?;
1118 Ok(GraphTraversalAction::Continue)
1119 },
1120 |_, _, _| Ok(()),
1121 )
1122 }
1123
1124 pub fn traverse_edges_dfs<S>(
1143 &self,
1144 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1145 state: &mut S,
1146 visit_preorder: impl FnMut(
1147 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1148 ResolvedVc<Box<dyn Module>>,
1149 &mut S,
1150 ) -> Result<GraphTraversalAction>,
1151 visit_postorder: impl FnMut(
1152 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1153 ResolvedVc<Box<dyn Module>>,
1154 &mut S,
1155 ) -> Result<()>,
1156 ) -> Result<()> {
1157 self.traverse_edges_dfs_impl::<S>(
1158 entries,
1159 state,
1160 visit_preorder,
1161 visit_postorder,
1162 Direction::Outgoing,
1163 )
1164 }
1165
1166 pub fn traverse_edges_reverse_dfs<S>(
1183 &self,
1184 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1185 state: &mut S,
1186 visit_preorder: impl FnMut(
1187 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1188 ResolvedVc<Box<dyn Module>>,
1189 &mut S,
1190 ) -> Result<GraphTraversalAction>,
1191 visit_postorder: impl FnMut(
1192 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1193 ResolvedVc<Box<dyn Module>>,
1194 &mut S,
1195 ) -> Result<()>,
1196 ) -> Result<()> {
1197 self.traverse_edges_dfs_impl::<S>(
1198 entries,
1199 state,
1200 visit_preorder,
1201 visit_postorder,
1202 Direction::Incoming,
1203 )
1204 }
1205
1206 fn traverse_edges_dfs_impl<S>(
1207 &self,
1208 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1209 state: &mut S,
1210 mut visit_preorder: impl FnMut(
1211 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1212 ResolvedVc<Box<dyn Module>>,
1213 &mut S,
1214 ) -> Result<GraphTraversalAction>,
1215 mut visit_postorder: impl FnMut(
1216 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1217 ResolvedVc<Box<dyn Module>>,
1218 &mut S,
1219 ) -> Result<()>,
1220 direction: Direction,
1221 ) -> Result<()> {
1222 if direction == Direction::Incoming {
1223 debug_assert!(
1224 self.skip_visited_module_children,
1225 "Can only trace reverse edges in a single layer graph. We do not model cross \
1226 graph reverse edges"
1227 );
1228 }
1229 let entries = entries.into_iter().collect::<Vec<_>>();
1230
1231 enum Pass {
1232 Visit,
1233 ExpandAndVisit,
1234 }
1235 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1237 Pass,
1238 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1239 GraphNodeIndex,
1240 )> = Vec::with_capacity(entries.len());
1241 for entry in entries.into_iter().rev() {
1242 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1243 }
1244 let mut expanded = FxHashSet::default();
1245 while let Some((pass, parent, current)) = stack.pop() {
1246 let parent_arg = match parent {
1247 Some((parent_node, parent_edge)) => Some((
1248 self.get_node(parent_node)?.module(),
1249 self.get_edge(parent_edge)?,
1250 )),
1251 None => None,
1252 };
1253 let current_node = self.get_node(current)?;
1254 match pass {
1255 Pass::Visit => {
1256 visit_postorder(parent_arg, current_node.module(), state)?;
1257 }
1258 Pass::ExpandAndVisit => {
1259 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1260 if action == GraphTraversalAction::Exclude {
1261 continue;
1262 }
1263 stack.push((Pass::Visit, parent, current));
1264 if action == GraphTraversalAction::Continue
1265 && expanded.insert(current)
1266 && self.should_visit_node(current_node, direction)
1267 {
1268 let current = current_node.target_idx(direction).unwrap_or(current);
1269 stack.extend(self.iter_graphs_neighbors_rev(current, direction).map(
1270 |(edge, child)| (Pass::ExpandAndVisit, Some((current, edge)), child),
1271 ));
1272 }
1273 }
1274 }
1275 }
1276
1277 Ok(())
1278 }
1279
1280 pub fn traverse_cycles(
1284 &self,
1285 edge_filter: impl Fn(&RefData) -> bool,
1286 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1287 ) -> Result<()> {
1288 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1289 graph.traverse_cycles(
1290 &edge_filter,
1291 &mut visit_cycle,
1292 graph_idx as u32,
1293 &self.binding_usage,
1294 )?;
1295 }
1296 Ok(())
1297 }
1298
1299 pub fn traverse_edges_fixed_point_with_priority<S, P: Ord>(
1321 &self,
1322 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1323 state: &mut S,
1324 mut visit: impl FnMut(
1325 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, GraphEdgeIndex)>,
1326 ResolvedVc<Box<dyn Module>>,
1327 &mut S,
1328 ) -> Result<GraphTraversalAction>,
1329 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1330 ) -> Result<usize> {
1331 if self.skip_visited_module_children {
1332 panic!(
1333 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1334 );
1335 }
1336
1337 let mut visit_order = 0usize;
1338 let mut order = || {
1339 let order = visit_order;
1340 visit_order += 1;
1341 order
1342 };
1343 #[derive(PartialEq, Eq)]
1344 struct NodeWithPriority<T: Ord> {
1345 node: GraphNodeIndex,
1346 priority: T,
1347 visit_order: usize,
1348 }
1349 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1350 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1351 Some(self.cmp(other))
1352 }
1353 }
1354 impl<T: Ord> Ord for NodeWithPriority<T> {
1355 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1356 self.priority
1359 .cmp(&other.priority)
1360 .then(self.visit_order.cmp(&other.visit_order))
1363 }
1364 }
1365
1366 let mut queue_set = FxHashSet::default();
1367 let mut queue = BinaryHeap::from_iter(
1368 entries
1369 .into_iter()
1370 .map(|(m, priority)| {
1371 Ok(NodeWithPriority {
1372 node: self.get_entry(m)?,
1373 priority,
1374 visit_order: order(),
1375 })
1376 })
1377 .collect::<Result<Vec<_>>>()?,
1378 );
1379
1380 for entry_node in &queue {
1381 visit(None, self.get_node(entry_node.node)?.module(), state)?;
1382 }
1383
1384 let mut visit_count = 0usize;
1385 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1386 queue_set.remove(&node);
1387 let node_weight = self.get_node(node)?;
1388 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1389
1390 visit_count += 1;
1391
1392 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing) {
1393 let succ_weight = self.get_node(succ)?;
1394
1395 let action = visit(
1396 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1397 succ_weight.module(),
1398 state,
1399 )?;
1400
1401 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1402 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1403 queue.push(NodeWithPriority {
1404 node: succ,
1405 priority: priority(succ_weight.module(), state)?,
1406 visit_order: order(),
1407 });
1408 }
1409 }
1410 }
1411
1412 Ok(visit_count)
1413 }
1414}
1415
1416#[turbo_tasks::value_impl]
1417impl SingleModuleGraph {
1418 #[turbo_tasks::function(operation)]
1419 pub async fn new_with_entry(
1420 entry: ChunkGroupEntry,
1421 include_traced: bool,
1422 include_binding_usage: bool,
1423 ) -> Result<Vc<Self>> {
1424 SingleModuleGraph::new_inner(
1425 vec![entry],
1426 &Default::default(),
1427 include_traced,
1428 include_binding_usage,
1429 )
1430 .await
1431 }
1432
1433 #[turbo_tasks::function(operation)]
1434 pub async fn new_with_entries(
1435 entries: ResolvedVc<GraphEntries>,
1436 include_traced: bool,
1437 include_binding_usage: bool,
1438 ) -> Result<Vc<Self>> {
1439 SingleModuleGraph::new_inner(
1440 entries.owned().await?,
1441 &Default::default(),
1442 include_traced,
1443 include_binding_usage,
1444 )
1445 .await
1446 }
1447
1448 #[turbo_tasks::function(operation)]
1449 pub async fn new_with_entries_visited(
1450 entries: ResolvedVc<GraphEntries>,
1451 visited_modules: OperationVc<VisitedModules>,
1452 include_traced: bool,
1453 include_binding_usage: bool,
1454 ) -> Result<Vc<Self>> {
1455 SingleModuleGraph::new_inner(
1456 entries.owned().await?,
1457 &visited_modules.connect().await?.modules,
1458 include_traced,
1459 include_binding_usage,
1460 )
1461 .await
1462 }
1463
1464 #[turbo_tasks::function(operation)]
1465 pub async fn new_with_entries_visited_intern(
1466 entries: GraphEntriesT,
1468 visited_modules: OperationVc<VisitedModules>,
1469 include_traced: bool,
1470 include_binding_usage: bool,
1471 ) -> Result<Vc<Self>> {
1472 SingleModuleGraph::new_inner(
1473 entries,
1474 &visited_modules.connect().await?.modules,
1475 include_traced,
1476 include_binding_usage,
1477 )
1478 .await
1479 }
1480}
1481
1482#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
1483pub enum SingleModuleGraphNode {
1484 Module(ResolvedVc<Box<dyn Module>>),
1485 VisitedModule {
1487 idx: GraphNodeIndex,
1488 module: ResolvedVc<Box<dyn Module>>,
1489 },
1490}
1491
1492impl SingleModuleGraphNode {
1493 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1494 match self {
1495 SingleModuleGraphNode::Module(module) => *module,
1496 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1497 }
1498 }
1499 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1500 match self {
1501 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1502 Direction::Outgoing => Some(*idx),
1503 Direction::Incoming => None,
1504 },
1505 SingleModuleGraphNode::Module(_) => None,
1506 }
1507 }
1508}
1509
1510#[derive(PartialEq, Eq, Debug)]
1511pub enum GraphTraversalAction {
1512 Continue,
1514 Skip,
1516 Exclude,
1518}
1519
1520#[derive(Clone, Hash, PartialEq, Eq)]
1523enum SingleModuleGraphBuilderNode {
1524 Module {
1526 module: ResolvedVc<Box<dyn Module>>,
1527 ident: Option<ReadRef<RcStr>>,
1529 },
1530 VisitedModule {
1532 module: ResolvedVc<Box<dyn Module>>,
1533 idx: GraphNodeIndex,
1534 },
1535}
1536
1537impl SingleModuleGraphBuilderNode {
1538 async fn new_module(emit_spans: bool, module: ResolvedVc<Box<dyn Module>>) -> Result<Self> {
1539 Ok(Self::Module {
1540 module,
1541 ident: if emit_spans {
1542 Some(module.ident_string().untracked().await?)
1544 } else {
1545 None
1546 },
1547 })
1548 }
1549 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1550 Self::VisitedModule { module, idx }
1551 }
1552}
1553
1554struct SingleModuleGraphBuilder<'a> {
1555 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1556
1557 emit_spans: bool,
1558
1559 include_traced: bool,
1561
1562 include_binding_usage: bool,
1564}
1565impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1566 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1567 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1568
1569 fn visit(
1570 &mut self,
1571 node: &SingleModuleGraphBuilderNode,
1572 edge: Option<&RefData>,
1573 ) -> VisitControlFlow {
1574 if let Some(edge) = edge
1575 && matches!(edge.chunking_type, ChunkingType::Traced)
1576 {
1577 return VisitControlFlow::Skip;
1579 }
1580 match node {
1581 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1582 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1584 }
1585 }
1586
1587 fn edges(
1588 &mut self,
1589 node: &SingleModuleGraphBuilderNode,
1592 ) -> Self::EdgesFuture {
1593 let &SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1595 unreachable!()
1597 };
1598 let visited_modules = self.visited_modules;
1599 let emit_spans = self.emit_spans;
1600 let include_traced = self.include_traced;
1601 let include_binding_usage = self.include_binding_usage;
1602 async move {
1603 let refs_cell = primary_chunkable_referenced_modules(
1604 *module,
1605 include_traced,
1606 include_binding_usage,
1607 );
1608 let refs = match refs_cell.await {
1609 Ok(refs) => refs,
1610 Err(e) => {
1611 return Err(e.context(module.ident().to_string().await?));
1612 }
1613 };
1614
1615 refs.iter()
1616 .flat_map(|(reference, resolved)| {
1617 resolved.modules.iter().map(|m| {
1618 (
1619 *reference,
1620 resolved.chunking_type.clone(),
1621 resolved.binding_usage.clone(),
1622 *m,
1623 )
1624 })
1625 })
1626 .map(async |(reference, ty, binding_usage, target)| {
1627 let to = if let Some(idx) = visited_modules.get(&target) {
1628 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1629 } else {
1630 SingleModuleGraphBuilderNode::new_module(emit_spans, target).await?
1631 };
1632 Ok((
1633 to,
1634 RefData {
1635 chunking_type: ty,
1636 binding_usage,
1637 reference,
1638 },
1639 ))
1640 })
1641 .try_join()
1642 .await
1643 }
1644 }
1645
1646 fn span(
1647 &mut self,
1648 node: &SingleModuleGraphBuilderNode,
1649 edge: Option<&RefData>,
1650 ) -> tracing::Span {
1651 if !self.emit_spans {
1652 return Span::none();
1653 }
1654
1655 let mut span = match node {
1656 SingleModuleGraphBuilderNode::Module {
1657 ident: Some(ident), ..
1658 } => {
1659 tracing::info_span!("module", name = display(ident))
1660 }
1661 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1662 tracing::info_span!("visited module")
1663 }
1664 _ => unreachable!(),
1665 };
1666
1667 if let Some(edge) = edge {
1668 match &edge.chunking_type {
1669 ChunkingType::Parallel {
1670 inherit_async: _,
1671 hoisted: _,
1672 } => {}
1673 ChunkingType::Traced => {
1674 let _span = span.entered();
1675 span = tracing::info_span!("traced reference");
1676 }
1677 ChunkingType::Async => {
1678 let _span = span.entered();
1679 span = tracing::info_span!("async reference");
1680 }
1681 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1682 let _span = span.entered();
1683 span = tracing::info_span!(
1684 "isolated reference",
1685 ty = debug(&ty),
1686 merge_tag = debug(&merge_tag)
1687 );
1688 }
1689 ChunkingType::Shared {
1690 inherit_async: _,
1691 merge_tag,
1692 } => {
1693 let _span = span.entered();
1694 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
1695 }
1696 };
1697 }
1698
1699 span
1700 }
1701}
1702
1703#[cfg(test)]
1704pub mod tests {
1705 use anyhow::Result;
1706 use rustc_hash::FxHashMap;
1707 use turbo_rcstr::{RcStr, rcstr};
1708 use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
1709 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1710 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
1711
1712 use crate::{
1713 asset::{Asset, AssetContent},
1714 ident::AssetIdent,
1715 module::{Module, ModuleSideEffects},
1716 module_graph::{
1717 GraphEntries, GraphTraversalAction, ModuleGraph, SingleModuleGraph, VisitedModules,
1718 chunk_group_info::ChunkGroupEntry,
1719 },
1720 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
1721 resolve::ExportUsage,
1722 };
1723
1724 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1725 async fn test_traverse_dfs_from_entries_diamond() {
1726 run_graph_test(
1727 vec![rcstr!("a.js")],
1728 {
1729 let mut deps = FxHashMap::default();
1730 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1732 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
1733 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
1734 deps
1735 },
1736 |graph, entry_modules, module_to_name| {
1737 let mut preorder_visits = Vec::new();
1738 let mut postorder_visits = Vec::new();
1739
1740 graph.traverse_edges_dfs(
1741 entry_modules,
1742 &mut (),
1743 |parent, target, _| {
1744 preorder_visits.push((
1745 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1746 module_to_name.get(&target).unwrap().clone(),
1747 ));
1748 Ok(GraphTraversalAction::Continue)
1749 },
1750 |parent, target, _| {
1751 postorder_visits.push((
1752 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1753 module_to_name.get(&target).unwrap().clone(),
1754 ));
1755 Ok(())
1756 },
1757 )?;
1758 assert_eq!(
1759 vec![
1760 (None, rcstr!("a.js")),
1761 (Some(rcstr!("a.js")), rcstr!("b.js")),
1762 (Some(rcstr!("b.js")), rcstr!("d.js")),
1763 (Some(rcstr!("a.js")), rcstr!("c.js")),
1764 (Some(rcstr!("c.js")), rcstr!("d.js"))
1765 ],
1766 preorder_visits
1767 );
1768 assert_eq!(
1769 vec![
1770 (Some(rcstr!("b.js")), rcstr!("d.js")),
1771 (Some(rcstr!("a.js")), rcstr!("b.js")),
1772 (Some(rcstr!("c.js")), rcstr!("d.js")),
1773 (Some(rcstr!("a.js")), rcstr!("c.js")),
1774 (None, rcstr!("a.js"))
1775 ],
1776 postorder_visits
1777 );
1778 Ok(())
1779 },
1780 )
1781 .await;
1782 }
1783
1784 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1785 async fn test_traverse_dfs_from_entries_cycle() {
1786 run_graph_test(
1787 vec![rcstr!("a.js")],
1788 {
1789 let mut deps = FxHashMap::default();
1790 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1792 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1793 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1794 deps
1795 },
1796 |graph, entry_modules, module_to_name| {
1797 let mut preorder_visits = Vec::new();
1798 let mut postorder_visits = Vec::new();
1799
1800 graph.traverse_edges_dfs(
1801 entry_modules,
1802 &mut (),
1803 |parent, target, _| {
1804 preorder_visits.push((
1805 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1806 module_to_name.get(&target).unwrap().clone(),
1807 ));
1808 Ok(GraphTraversalAction::Continue)
1809 },
1810 |parent, target, _| {
1811 postorder_visits.push((
1812 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
1813 module_to_name.get(&target).unwrap().clone(),
1814 ));
1815 Ok(())
1816 },
1817 )?;
1818 assert_eq!(
1819 vec![
1820 (None, rcstr!("a.js")),
1821 (Some(rcstr!("a.js")), rcstr!("b.js")),
1822 (Some(rcstr!("b.js")), rcstr!("c.js")),
1823 (Some(rcstr!("c.js")), rcstr!("a.js")),
1824 ],
1825 preorder_visits
1826 );
1827 assert_eq!(
1828 vec![
1829 (Some(rcstr!("c.js")), rcstr!("a.js")),
1830 (Some(rcstr!("b.js")), rcstr!("c.js")),
1831 (Some(rcstr!("a.js")), rcstr!("b.js")),
1832 (None, rcstr!("a.js"))
1833 ],
1834 postorder_visits
1835 );
1836 Ok(())
1837 },
1838 )
1839 .await;
1840 }
1841
1842 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1843 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
1844 run_graph_test(
1845 vec![rcstr!("a.js")],
1846 {
1847 let mut deps = FxHashMap::default();
1848 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
1850 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
1851 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
1852 deps
1853 },
1854 |graph, entry_modules, module_to_name| {
1855 let mut visits = Vec::new();
1856 let mut count = 0;
1857
1858 graph.traverse_edges_fixed_point_with_priority(
1859 entry_modules.into_iter().map(|m| (m, 0)),
1860 &mut (),
1861 |parent, target, _| {
1862 visits.push((
1863 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1864 module_to_name.get(&target).unwrap().clone(),
1865 ));
1866 count += 1;
1867
1868 Ok(if count < 6 {
1870 GraphTraversalAction::Continue
1871 } else {
1872 GraphTraversalAction::Skip
1873 })
1874 },
1875 |_, _| Ok(0),
1876 )?;
1877 assert_eq!(
1878 vec![
1879 (None, rcstr!("a.js")),
1880 (Some(rcstr!("a.js")), rcstr!("b.js")),
1881 (Some(rcstr!("b.js")), rcstr!("c.js")),
1882 (Some(rcstr!("c.js")), rcstr!("a.js")),
1883 (Some(rcstr!("a.js")), rcstr!("b.js")),
1885 (Some(rcstr!("b.js")), rcstr!("c.js")),
1886 ],
1887 visits
1888 );
1889
1890 Ok(())
1891 },
1892 )
1893 .await;
1894 }
1895
1896 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1897 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
1898 run_graph_test(
1899 vec![rcstr!("a.js")],
1900 {
1901 let mut deps = FxHashMap::default();
1902 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
1907 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
1908 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
1909 deps
1910 },
1911 |graph, entry_modules, module_to_name| {
1912 let mut visits = Vec::new();
1913 let mut count = 0;
1914
1915 graph.traverse_edges_fixed_point_with_priority(
1916 entry_modules.into_iter().map(|m| (m, 0)),
1917 &mut (),
1918 |parent, target, _| {
1919 visits.push((
1920 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
1921 module_to_name.get(&target).unwrap().clone(),
1922 ));
1923 count += 1;
1924
1925 Ok(if count < 6 {
1927 GraphTraversalAction::Continue
1928 } else {
1929 GraphTraversalAction::Skip
1930 })
1931 },
1932 |_, _| Ok(0),
1933 )?;
1934
1935 assert_eq!(
1936 vec![
1937 (None, rcstr!("a.js")),
1938 (Some(rcstr!("a.js")), rcstr!("c.js")),
1939 (Some(rcstr!("a.js")), rcstr!("b.js")),
1940 (Some(rcstr!("b.js")), rcstr!("e.js")),
1941 (Some(rcstr!("b.js")), rcstr!("d.js")),
1942 (Some(rcstr!("c.js")), rcstr!("f.js")),
1943 (Some(rcstr!("c.js")), rcstr!("e.js")),
1944 ],
1945 visits
1946 );
1947
1948 Ok(())
1949 },
1950 )
1951 .await;
1952 }
1953
1954 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1955 async fn test_traverse_cycles() {
1956 run_graph_test(
1957 vec![rcstr!("a.js")],
1958 {
1959 let mut deps = FxHashMap::default();
1960 deps.insert(
1967 rcstr!("a.js"),
1968 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
1969 );
1970 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
1971 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
1972 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
1973 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
1974 deps
1975 },
1976 |graph, _, module_to_name| {
1977 let mut cycles = vec![];
1978
1979 graph.traverse_cycles(
1980 |_| true,
1981 |cycle| {
1982 cycles.push(
1983 cycle
1984 .iter()
1985 .map(|n| module_to_name.get(*n).unwrap().clone())
1986 .collect::<Vec<_>>(),
1987 );
1988 Ok(())
1989 },
1990 )?;
1991
1992 assert_eq!(
1993 cycles,
1994 vec![
1995 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
1996 vec![rcstr!("s.js")]
1997 ],
1998 );
1999
2000 Ok(())
2001 },
2002 )
2003 .await;
2004 }
2005
2006 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2007 async fn test_reverse_edges_through_layered_graph() {
2008 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2009 BackendOptions::default(),
2010 noop_backing_storage(),
2011 ));
2012 tt.run_once(async move {
2013 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2014 let root = fs.root().await?;
2015
2016 let graph = {
2019 let mut deps = FxHashMap::default();
2020
2021 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("d.js")]);
2022 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2023 deps
2024 };
2025 let repo = TestRepo {
2026 repo: graph
2027 .iter()
2028 .map(|(k, v)| {
2029 (
2030 root.join(k).unwrap(),
2031 v.iter().map(|f| root.join(f).unwrap()).collect(),
2032 )
2033 })
2034 .collect(),
2035 }
2036 .cell();
2037 let make_module = |name| {
2038 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2039 .to_resolved()
2040 };
2041 let a_module = make_module("a.js").await?;
2042 let b_module = make_module("b.js").await?;
2043
2044 let parent_graph = SingleModuleGraph::new_with_entries(
2045 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![b_module])]),
2046 false,
2047 false,
2048 );
2049
2050 let module_graph = ModuleGraph::from_graphs(vec![
2051 parent_graph,
2052 SingleModuleGraph::new_with_entries_visited(
2053 ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
2054 VisitedModules::from_graph(parent_graph),
2055 false,
2056 false,
2057 ),
2058 ])
2059 .connect();
2060 let child_graph = module_graph
2061 .iter_graphs()
2062 .await?
2063 .get(1)
2064 .unwrap()
2065 .connect()
2066 .await?;
2067 {
2069 let mut visited_forward = Vec::new();
2070 child_graph.traverse_edges_dfs(
2071 vec![a_module],
2072 &mut (),
2073 |_parent, child, _state_| {
2074 visited_forward.push(child);
2075 Ok(GraphTraversalAction::Continue)
2076 },
2077 |_, _, _| Ok(()),
2078 )?;
2079
2080 assert_eq!(
2081 visited_forward
2082 .iter()
2083 .map(|m| m.ident().to_string().owned())
2084 .try_join()
2085 .await?,
2086 vec![
2087 rcstr!("[test]/a.js"),
2088 rcstr!("[test]/b.js"),
2089 rcstr!("[test]/d.js")
2090 ]
2091 );
2092 }
2093
2094 {
2096 use turbo_tasks::TryFlatJoinIterExt;
2097 let d_module = child_graph
2098 .enumerate_nodes()
2099 .map(|(_index, module)| async move {
2100 Ok(match module {
2101 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2102 if module.ident().to_string().owned().await.unwrap()
2103 == "[test]/d.js"
2104 {
2105 Some(*module)
2106 } else {
2107 None
2108 }
2109 }
2110 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2111 ..
2112 } => None,
2113 })
2114 })
2115 .try_flat_join()
2116 .await?
2117 .into_iter()
2118 .next()
2119 .unwrap();
2120 let mut visited_reverse = Vec::new();
2121 child_graph.traverse_edges_reverse_dfs(
2122 vec![d_module],
2123 &mut (),
2124 |_parent, child, _state_| {
2125 visited_reverse.push(child);
2126 Ok(GraphTraversalAction::Continue)
2127 },
2128 |_, _, _| Ok(()),
2129 )?;
2130 assert_eq!(
2131 visited_reverse
2132 .iter()
2133 .map(|m| m.ident().to_string().owned())
2134 .try_join()
2135 .await?,
2136 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2137 );
2138 }
2139 {
2142 let mut visited_reverse = Vec::new();
2143 child_graph.traverse_edges_reverse_dfs(
2144 vec![b_module],
2145 &mut (),
2146 |_parent, child, _state_| {
2147 visited_reverse.push(child);
2148 Ok(GraphTraversalAction::Continue)
2149 },
2150 |_, _, _| Ok(()),
2151 )?;
2152 assert_eq!(
2153 visited_reverse
2154 .iter()
2155 .map(|m| m.ident().to_string().owned())
2156 .try_join()
2157 .await?,
2158 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2159 );
2160 }
2161
2162 Ok(())
2163 })
2164 .await
2165 .unwrap();
2166 }
2167
2168 #[turbo_tasks::value(shared)]
2169 struct TestRepo {
2170 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2171 }
2172 #[turbo_tasks::value]
2173 struct MockModule {
2174 path: FileSystemPath,
2175 repo: ResolvedVc<TestRepo>,
2176 }
2177 #[turbo_tasks::value_impl]
2178 impl MockModule {
2179 #[turbo_tasks::function]
2180 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2181 Self { path, repo }.cell()
2182 }
2183 }
2184
2185 #[turbo_tasks::value_impl]
2186 impl Asset for MockModule {
2187 #[turbo_tasks::function]
2188 fn content(&self) -> Vc<AssetContent> {
2189 panic!("MockModule::content shouldn't be called")
2190 }
2191 }
2192
2193 #[turbo_tasks::value_impl]
2194 impl Module for MockModule {
2195 #[turbo_tasks::function]
2196 fn ident(&self) -> Vc<AssetIdent> {
2197 AssetIdent::from_path(self.path.clone())
2198 }
2199
2200 #[turbo_tasks::function]
2201 fn source(&self) -> Vc<crate::source::OptionSource> {
2202 Vc::cell(None)
2203 }
2204
2205 #[turbo_tasks::function]
2206 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2207 let repo = self.repo.await?;
2208 let references = match repo.repo.get(&self.path) {
2209 Some(deps) => {
2210 deps.iter()
2211 .map(|p| {
2212 Vc::upcast::<Box<dyn ModuleReference>>(
2213 SingleChunkableModuleReference::new(
2214 Vc::upcast(MockModule::new(p.clone(), *self.repo)),
2215 rcstr!("normal-dep"),
2216 ExportUsage::all(),
2217 ),
2218 )
2219 .to_resolved()
2220 })
2221 .try_join()
2222 .await?
2223 }
2224 None => vec![],
2225 };
2226
2227 Ok(Vc::cell(references))
2228 }
2229 #[turbo_tasks::function]
2230 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2231 ModuleSideEffects::SideEffectful.cell()
2232 }
2233 }
2234
2235 async fn run_graph_test(
2249 entries: Vec<RcStr>,
2250 graph: FxHashMap<RcStr, Vec<RcStr>>,
2251 test_fn: impl FnOnce(
2252 &ModuleGraph,
2253 Vec<ResolvedVc<Box<dyn Module>>>,
2254 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
2255 ) -> Result<()>
2256 + Send
2257 + 'static,
2258 ) {
2259 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2260 BackendOptions::default(),
2261 noop_backing_storage(),
2262 ));
2263 tt.run_once(async move {
2264 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2265 let root = fs.root().await?;
2266
2267 let repo = TestRepo {
2268 repo: graph
2269 .iter()
2270 .map(|(k, v)| {
2271 (
2272 root.join(k).unwrap(),
2273 v.iter().map(|f| root.join(f).unwrap()).collect(),
2274 )
2275 })
2276 .collect(),
2277 }
2278 .cell();
2279 let entry_modules = entries
2280 .iter()
2281 .map(|e| {
2282 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
2283 .to_resolved()
2284 })
2285 .try_join()
2286 .await?;
2287 let graph = SingleModuleGraph::new_with_entries(
2288 GraphEntries::resolved_cell(GraphEntries(vec![ChunkGroupEntry::Entry(
2289 entry_modules.clone(),
2290 )])),
2291 false,
2292 false,
2293 );
2294
2295 let module_to_name = graph
2299 .connect()
2300 .await?
2301 .modules
2302 .keys()
2303 .map(|m| async move { Ok((*m, m.ident().await?.path.path.clone())) })
2304 .try_join()
2305 .await?
2306 .into_iter()
2307 .collect();
2308 test_fn(
2309 &*ModuleGraph::from_single_graph(graph).connect().await?,
2310 entry_modules,
2311 module_to_name,
2312 )
2313 })
2314 .await
2315 .unwrap();
2316 }
2317}