1use std::{
2 collections::{BinaryHeap, VecDeque},
3 future::Future,
4 iter::FusedIterator,
5 ops::Deref,
6 sync::OnceLock,
7};
8
9use anyhow::{Context, Result, bail};
10use bincode::{Decode, Encode};
11use petgraph::{
12 Direction,
13 graph::{DiGraph, EdgeIndex, NodeIndex},
14 visit::{EdgeRef, IntoNodeReferences, NodeIndexable, Reversed},
15};
16use rustc_hash::{FxHashMap, FxHashSet};
17use serde::{Deserialize, Serialize};
18use tracing::{Instrument, Level, Span};
19use turbo_rcstr::{RcStr, rcstr};
20use turbo_tasks::{
21 CollectiblesSource, FxIndexMap, JoinIterExt, NonLocalValue, OperationVc, ReadRef, ResolvedVc,
22 TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
23 debug::ValueDebugFormat,
24 graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
25};
26use turbo_tasks_fs::FileSystemPath;
27
28use crate::{
29 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType, TracedMode},
30 issue::{ImportTracer, ImportTraces, Issue, IssueExt, IssueSeverity, analyze::AnalyzeIssue},
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 collect::{CollectedModules, collect_graph},
37 merged_modules::{MergedModuleInfo, compute_merged_modules},
38 module_batches::{ModuleBatchesGraph, compute_module_batches},
39 style_groups::{StyleGroups, StyleGroupsAlgorithm, StyleGroupsConfig},
40 style_groups_graph::compute_style_groups_graph,
41 style_groups_loose::compute_style_groups,
42 traced_di_graph::TracedDiGraph,
43 },
44 reference::{
45 ModuleReference, primary_chunkable_referenced_modules,
46 referenced_modules_and_affecting_sources,
47 },
48 resolve::BindingUsage,
49};
50
51pub mod async_module_info;
52pub mod binding_usage_info;
53pub mod chunk_group_info;
54pub mod collect;
55pub mod merged_modules;
56pub mod module_batch;
57pub(crate) mod module_batches;
58mod side_effect_module_info;
59pub mod style_groups;
60pub mod style_groups_graph;
61pub mod style_groups_loose;
62mod traced_di_graph;
63
64pub use self::module_batches::BatchingConfig;
65
66#[derive(
67 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, Encode, Decode,
68)]
69pub struct GraphNodeIndex {
70 graph_idx: u32,
71 #[bincode(with_serde)]
72 node_idx: NodeIndex,
73}
74impl GraphNodeIndex {
75 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
76 Self {
77 graph_idx,
78 node_idx,
79 }
80 }
81}
82
83unsafe impl NonLocalValue for GraphNodeIndex {}
84
85#[derive(
86 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, NonLocalValue, Encode, Decode,
87)]
88pub struct GraphEdgeIndex {
89 graph_idx: u32,
90 #[turbo_tasks(unsafe_ignore)]
91 #[bincode(with_serde)]
92 edge_idx: EdgeIndex,
93}
94
95impl GraphEdgeIndex {
96 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
97 Self {
98 graph_idx,
99 edge_idx,
100 }
101 }
102}
103
104#[turbo_tasks::value]
105#[derive(Clone, Debug)]
106pub struct VisitedModules {
107 #[bincode(with = "turbo_bincode::indexmap")]
108 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
109 next_graph_idx: u32,
110}
111
112#[turbo_tasks::value_impl]
113impl VisitedModules {
114 #[turbo_tasks::function(operation)]
115 pub fn empty() -> Vc<Self> {
116 Self {
117 modules: Default::default(),
118 next_graph_idx: 0,
119 }
120 .cell()
121 }
122
123 #[turbo_tasks::function(operation)]
124 pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
125 Ok(Self {
126 modules: graph
127 .connect()
128 .await?
129 .enumerate_nodes()
130 .flat_map(|(node_idx, module)| match module {
131 SingleModuleGraphNode::Module(module) => Some((
132 *module,
133 GraphNodeIndex {
134 graph_idx: 0,
135 node_idx,
136 },
137 )),
138 SingleModuleGraphNode::VisitedModule { .. } => None,
139 })
140 .collect(),
141 next_graph_idx: 1,
142 }
143 .cell())
144 }
145
146 #[turbo_tasks::function(operation)]
147 pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
148 let this = this.connect().await?;
149 Ok(Self {
150 modules: this.modules.clone(),
151 next_graph_idx: this.next_graph_idx + 1,
152 }
153 .cell())
154 }
155
156 #[turbo_tasks::function(operation)]
157 pub async fn concatenate(
158 this: OperationVc<Self>,
159 graph: OperationVc<SingleModuleGraph>,
160 ) -> Result<Vc<Self>> {
161 let graph = graph.connect().await?;
162 let this = this.connect().await?;
163 let iter = this
164 .modules
165 .iter()
166 .map(|(module, idx)| (*module, *idx))
167 .chain(
168 graph
169 .enumerate_nodes()
170 .flat_map(|(node_idx, module)| match module {
171 SingleModuleGraphNode::Module(module) => Some((
172 *module,
173 GraphNodeIndex {
174 graph_idx: this.next_graph_idx,
175 node_idx,
176 },
177 )),
178 SingleModuleGraphNode::VisitedModule { .. } => None,
179 }),
180 );
181
182 let mut map = FxIndexMap::with_capacity_and_hasher(
183 this.modules.len() + graph.number_of_modules,
184 Default::default(),
185 );
186 for (k, v) in iter {
187 map.entry(k).or_insert(v);
188 }
189 map.shrink_to_fit();
190
191 Ok(Self {
192 modules: map,
193 next_graph_idx: this.next_graph_idx + 1,
194 }
195 .cell())
196 }
197}
198
199#[turbo_tasks::value(shared, task_input)]
200#[derive(Debug, Clone, Hash, Default)]
201pub struct GraphEntries {
202 chunk_groups: Vec<ChunkGroupEntry>,
204 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
207}
208
209#[turbo_tasks::value_impl]
210impl GraphEntries {
211 #[turbo_tasks::function]
212 pub fn empty() -> Vc<Self> {
213 Self::default().cell()
214 }
215}
216
217impl GraphEntries {
218 pub fn new(
219 chunk_groups: Vec<ChunkGroupEntry>,
220 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
221 ) -> Self {
222 Self {
223 chunk_groups,
224 traced_modules,
225 }
226 }
227 pub fn from_chunk_groups(chunk_groups: Vec<ChunkGroupEntry>) -> Self {
228 Self {
229 chunk_groups,
230 traced_modules: vec![],
231 }
232 }
233
234 pub fn concatenate(entries: impl IntoIterator<Item = GraphEntries>) -> Self {
235 let (chunk_groups, traced_modules): (Vec<_>, Vec<_>) = entries
236 .into_iter()
237 .map(|e| (e.chunk_groups, e.traced_modules))
238 .unzip();
239 Self {
240 chunk_groups: chunk_groups.into_iter().flatten().collect(),
241 traced_modules: traced_modules.into_iter().flatten().collect(),
242 }
243 }
244
245 pub fn all_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
247 self.chunk_groups
248 .iter()
249 .flat_map(|e| e.entries())
250 .chain(self.traced_modules.iter().cloned())
251 }
252
253 pub fn all_modules_with_is_traced(
255 &self,
256 ) -> impl Iterator<Item = (ResolvedVc<Box<dyn Module>>, bool)> + '_ {
257 self.chunk_groups
258 .iter()
259 .flat_map(|e| e.entries().map(|m| (m, false)))
260 .chain(self.traced_modules.iter().cloned().map(|m| (m, true)))
261 }
262
263 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
265 self.chunk_groups.iter().flat_map(|e| e.entries())
266 }
267}
268
269#[turbo_tasks::value(cell = "new", eq = "manual")]
270#[derive(Default)]
271pub struct SingleModuleGraph {
272 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
273
274 pub number_of_modules: usize,
276
277 #[turbo_tasks(unsafe_ignore)]
284 #[bincode(with_serde)]
285 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
286
287 entries: GraphEntries,
288
289 #[turbo_tasks(debug_ignore, unsafe_ignore)]
292 #[bincode(skip, default = "OnceLock::new")]
293 entry_nodes: OnceLock<FxHashSet<NodeIndex>>,
294}
295
296impl Clone for SingleModuleGraph {
297 fn clone(&self) -> Self {
298 Self {
299 graph: self.graph.clone(),
300 number_of_modules: self.number_of_modules,
301 modules: self.modules.clone(),
302 entries: self.entries.clone(),
303 entry_nodes: OnceLock::new(),
305 }
306 }
307}
308
309#[derive(
310 Debug,
311 Clone,
312 Hash,
313 Serialize,
314 Deserialize,
315 Eq,
316 PartialEq,
317 ValueDebugFormat,
318 NonLocalValue,
319 Encode,
320 Decode,
321)]
322pub struct RefData {
323 pub chunking_type: ChunkingType,
324 pub binding_usage: BindingUsage,
325 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
326}
327
328impl SingleModuleGraph {
329 async fn new_inner(
333 entries: &GraphEntries,
334 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
335 include_traced: bool,
336 include_binding_usage: bool,
337 ) -> Result<Vc<Self>> {
338 let emit_spans = tracing::enabled!(Level::INFO);
339 let root_nodes = entries
340 .all_modules_with_is_traced()
341 .map(|(e, is_traced)| {
342 SingleModuleGraphBuilderNode::new_module(emit_spans, e, is_traced)
343 })
344 .try_join()
345 .await?;
346
347 let children_nodes_iter = AdjacencyMap::new()
348 .visit(
349 root_nodes,
350 SingleModuleGraphBuilder {
351 visited_modules,
352 emit_spans,
353 include_traced,
354 include_binding_usage,
355 },
356 )
357 .await
358 .completed()?;
359 let node_count = children_nodes_iter.len();
360
361 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
362 node_count,
363 node_count * 4,
366 );
367
368 let mut number_of_modules = 0;
369 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
370 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
371 {
372 let _span = tracing::info_span!("build module graph").entered();
373 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
374 let (module, graph_node, count) = match current {
375 SingleModuleGraphBuilderNode::Module {
376 module,
377 is_traced: _,
378 ident: _,
379 } => (module, SingleModuleGraphNode::Module(module), 1),
380 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
381 module,
382 SingleModuleGraphNode::VisitedModule { idx, module },
383 0,
384 ),
385 };
386
387 let current_idx = if let Some(current_idx) = modules.get(&module) {
389 *current_idx
390 } else {
391 let idx = graph.add_node(graph_node);
392 number_of_modules += count;
393 modules.insert(module, idx);
394 idx
395 };
396 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
398 parent
399 {
400 let parent_idx = *modules.get(&module).unwrap();
401 graph.add_edge(parent_idx, current_idx, ref_data);
402 }
403 }
404 }
405
406 graph.shrink_to_fit();
407
408 #[cfg(debug_assertions)]
409 {
410 use std::sync::LazyLock;
411 static CHECK_FOR_DUPLICATE_MODULES: LazyLock<bool> = LazyLock::new(|| {
412 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
413 Some(v) => v != "1" && v != "true",
414 None => true,
415 }
416 });
417 if *CHECK_FOR_DUPLICATE_MODULES {
418 let mut duplicates = FxHashSet::default();
419 let mut set = FxHashSet::default();
420 for &module in modules.keys() {
421 let ident = module.ident().to_string().await?;
422 if !set.insert(ident.clone()) {
423 duplicates.insert(ident);
424 }
425 }
426 if !duplicates.is_empty() {
427 use turbo_tasks::TryFlatJoinIterExt;
428
429 let duplicates_clone = duplicates.clone();
430 let duplicate_modules = modules
431 .iter()
432 .map(async |(&m, &idx)| {
433 let id = m.ident().to_string().await?;
434 if duplicates_clone.contains(&id) {
435 let debug = m.value_debug_format(3).try_to_string().await?;
438
439 let parent_modules: Vec<_> = graph
442 .edges_directed(idx, petgraph::Direction::Incoming)
443 .filter_map(|edge| match graph.node_weight(edge.source()) {
444 Some(SingleModuleGraphNode::Module(m)) => Some(*m),
445 Some(SingleModuleGraphNode::VisitedModule {
446 module,
447 ..
448 }) => Some(*module),
449 None => None,
450 })
451 .collect();
452 let parents: Vec<String> = parent_modules
453 .iter()
454 .map(async |p| {
455 let ident = p.ident().to_string().await?;
456 Ok((*ident).to_string())
457 })
458 .try_join()
459 .await?;
460
461 Ok(Some((id, debug, parents)))
462 } else {
463 Ok(None)
464 }
465 })
466 .try_flat_join()
467 .await?;
468 let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
470 for (key, debug, parents) in duplicate_modules {
471 map.entry(key).or_default().push((debug, parents));
472 }
473 let result = map
474 .into_iter()
475 .map(|(ident, modules)| {
476 let modules = modules
477 .into_iter()
478 .map(|(debug, parents)| {
479 format!("Module: {debug}, Parents: {parents:?}")
480 })
481 .collect::<Vec<_>>()
482 .join("\n");
483 format!("Ident: {ident}\n{modules}")
484 })
485 .collect::<Vec<_>>()
486 .join("\n\n");
487 bail!("Duplicate module idents in graph: {result}");
488 }
489 }
490 }
491
492 let graph = SingleModuleGraph {
493 graph: TracedDiGraph::new(graph),
494 number_of_modules,
495 modules,
496 entries: entries.clone(),
497 entry_nodes: OnceLock::new(),
498 }
499 .cell();
500
501 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
502 ModuleGraphImportTracer::new(graph).to_resolved().await?,
503 ));
504 Ok(graph)
505 }
506
507 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
510 self.graph.node_weights().filter_map(|n| match n {
511 SingleModuleGraphNode::Module(node) => Some(*node),
512 SingleModuleGraphNode::VisitedModule { .. } => None,
513 })
514 }
515
516 fn entry_nodes(&self) -> &FxHashSet<NodeIndex> {
517 self.entry_nodes.get_or_init(|| {
518 self.entries
519 .all_modules()
520 .filter_map(|module| self.modules.get(&module).copied())
521 .collect()
522 })
523 }
524
525 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
530 self.modules
531 .get(&module)
532 .is_some_and(|index| self.entry_nodes().contains(index))
533 }
534
535 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
537 self.entries.chunk_group_modules()
538 }
539
540 pub fn enumerate_nodes(
543 &self,
544 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
545 self.graph.node_references()
546 }
547
548 fn traverse_cycles<'l>(
549 &'l self,
550 edge_filter: impl Fn(&'l RefData) -> bool,
551 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
552 graph_idx: u32,
553 binding_usage: &'l Option<ReadRef<BindingUsageInfo>>,
554 ) -> Result<()> {
555 #[derive(Clone)]
562 struct NodeState {
563 index: u32,
564 lowlink: u32,
565 on_stack: bool,
566 has_self_loop: bool,
567 }
568 enum VisitStep {
569 UnvisitedNode(NodeIndex),
570 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
571 AfterVisit(NodeIndex),
572 }
573 let mut node_states = vec![None; self.graph.node_bound()];
574 let mut stack = Vec::new();
575 let mut visit_stack = Vec::new();
576 let mut index = 0;
577 let mut scc = Vec::new();
578 for initial_index in self.graph.node_indices() {
579 if node_states[initial_index.index()].is_some() {
581 continue;
582 }
583 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
584 while let Some(step) = visit_stack.pop() {
585 match step {
586 VisitStep::UnvisitedNode(node) => {
587 node_states[node.index()] = Some(NodeState {
588 index,
589 lowlink: index,
590 on_stack: true,
591 has_self_loop: false,
592 });
593 index += 1;
594 stack.push(node);
595 visit_stack.push(VisitStep::AfterVisit(node));
596 let mut neighbors = self.graph.neighbors(node).detach();
597 while let Some((edge, succ)) = neighbors.next(&self.graph) {
598 if binding_usage.as_ref().is_some_and(|binding_usage| {
599 binding_usage
600 .is_reference_unused_edge(&GraphEdgeIndex::new(graph_idx, edge))
601 }) {
602 continue;
603 }
604
605 let edge_weight = self.graph.edge_weight(edge).unwrap();
606 if !edge_filter(edge_weight) {
607 continue;
608 }
609 let node_state = &node_states[succ.index()];
610 if let Some(node_state) = node_state {
611 if node_state.on_stack {
612 let index = node_state.index;
613 let parent_state = node_states[node.index()].as_mut().unwrap();
614 parent_state.lowlink = parent_state.lowlink.min(index);
615 if succ == node {
616 parent_state.has_self_loop = true;
617 }
618 }
619 } else {
620 visit_stack.push(VisitStep::EdgeAfterVisit {
621 parent: node,
622 child: succ,
623 });
624 visit_stack.push(VisitStep::UnvisitedNode(succ));
625 }
626 }
627 }
628 VisitStep::EdgeAfterVisit { parent, child } => {
629 let child_state = node_states[child.index()].as_ref().unwrap();
630 let lowlink = child_state.lowlink;
631
632 let parent_state = node_states[parent.index()].as_mut().unwrap();
633 parent_state.lowlink = parent_state.lowlink.min(lowlink);
634 }
635 VisitStep::AfterVisit(node) => {
636 let node_state = node_states[node.index()].as_ref().unwrap();
637 let node_has_self_loop = node_state.has_self_loop;
638 if node_state.lowlink == node_state.index {
639 loop {
640 let poppped = stack.pop().unwrap();
641 let popped_state = node_states[poppped.index()].as_mut().unwrap();
642 popped_state.on_stack = false;
643 if let SingleModuleGraphNode::Module(module) =
644 self.graph.node_weight(poppped).unwrap()
645 {
646 scc.push(module);
647 }
648 if poppped == node {
649 break;
650 }
651 }
652 if scc.len() > 1 || node_has_self_loop {
653 visit_cycle(&scc)?;
654 }
655 scc.clear();
656 }
657 }
658 }
659 }
660 }
661 Ok(())
662 }
663}
664
665#[turbo_tasks::value]
666struct ModuleGraphImportTracer {
667 graph: ResolvedVc<SingleModuleGraph>,
668}
669
670#[turbo_tasks::value(shared)]
671struct PathToModulesMap {
672 map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>>,
673}
674
675#[turbo_tasks::value_impl]
676impl ModuleGraphImportTracer {
677 #[turbo_tasks::function]
678 fn new(graph: ResolvedVc<SingleModuleGraph>) -> Vc<Self> {
679 Self::cell(Self { graph })
680 }
681
682 #[turbo_tasks::function]
684 async fn path_to_modules(&self) -> Result<Vc<PathToModulesMap>> {
685 let path_and_modules = self
686 .graph
687 .await?
688 .modules
689 .iter()
690 .map(async |(&module, _)| Ok((module.ident().await?.path.clone(), module)))
691 .try_join()
692 .await?;
693 let mut map: FxHashMap<FileSystemPath, Vec<ResolvedVc<Box<dyn Module>>>> =
694 FxHashMap::default();
695 for (path, module) in path_and_modules {
696 map.entry(path).or_default().push(module)
697 }
698 Ok(PathToModulesMap::cell(PathToModulesMap { map }))
699 }
700}
701
702#[turbo_tasks::value_impl]
703impl ImportTracer for ModuleGraphImportTracer {
704 #[turbo_tasks::function]
705 async fn get_traces(self: Vc<Self>, path: FileSystemPath) -> Result<Vc<ImportTraces>> {
706 let path_to_modules = self.path_to_modules().await?;
707 let Some(modules) = path_to_modules.map.get(&path) else {
708 return Ok(Vc::default()); };
711 debug_assert!(!modules.is_empty(), "modules should not be an empty vec");
712 let graph = &*self.await?.graph.await?;
713
714 let reversed_graph = Reversed(&graph.graph.0);
715 let root_nodes = graph.entry_nodes();
718 return Ok(ImportTraces::cell(ImportTraces(
719 modules
720 .iter()
721 .map(async |m| {
722 let Some(&module_idx) = graph.modules.get(m) else {
723 bail!("inconsistent read?")
726 };
727 let path = match petgraph::algo::astar(
729 &reversed_graph,
730 module_idx,
731 |n| root_nodes.contains(&n),
732 |e| match e.weight().chunking_type {
734 ChunkingType::Parallel { .. } => 0,
736 _ => 1,
737 },
738 |_| 0,
752 ) {
753 Some((_, path)) => path,
754 None => {
755 let module = graph
756 .graph
757 .node_weight(module_idx)
758 .expect("module index must be present in the graph")
759 .module();
760 AnalyzeIssue::new(
761 IssueSeverity::Bug,
762 module.ident(),
763 rcstr!("Module graph is missing an entry point"),
764 rcstr!(
765 "The module cannot reach any of the explicit entry points in \
766 its module graph."
767 ),
768 None,
769 None,
770 )
771 .to_resolved()
772 .await?
773 .emit();
774 vec![module_idx]
775 }
776 };
777
778 let path = path
782 .into_iter()
783 .map(|n| {
784 graph
785 .graph
786 .node_weight(n)
787 .unwrap() .module()
789 .ident()
790 })
791 .try_join()
792 .await?;
793 Ok(path)
794 })
795 .try_join()
796 .await?,
797 )));
798 }
799}
800
801#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
804pub struct ModuleGraph {
805 input_graphs: Vec<OperationVc<SingleModuleGraph>>,
806 input_binding_usage: Option<OperationVc<BindingUsageInfo>>,
807
808 snapshot: ModuleGraphSnapshot,
809}
810
811#[turbo_tasks::value_impl]
812impl ModuleGraph {
813 #[turbo_tasks::function(operation)]
816 pub async fn from_graphs(
817 graphs: Vec<OperationVc<SingleModuleGraph>>,
818 binding_usage: Option<OperationVc<BindingUsageInfo>>,
819 ) -> Result<Vc<Self>> {
820 let graph = Self::from_graphs_inner(graphs, binding_usage)
821 .read_strongly_consistent()
822 .await?;
823
824 Ok(ReadRef::cell(graph))
825 }
826
827 #[turbo_tasks::function(operation, root)]
828 async fn from_graphs_inner(
829 graphs: Vec<OperationVc<SingleModuleGraph>>,
830 binding_usage: Option<OperationVc<BindingUsageInfo>>,
831 ) -> Result<Vc<ModuleGraph>> {
832 Ok(ModuleGraph {
833 input_graphs: graphs.clone(),
834 input_binding_usage: binding_usage,
835 snapshot: ModuleGraphSnapshot {
836 graphs: graphs.iter().map(|g| g.connect()).try_join().await?,
837 skip_visited_module_children: false,
838 graph_idx_override: None,
839 binding_usage: if let Some(binding_usage) = binding_usage {
840 Some(binding_usage.connect().await?)
841 } else {
842 None
843 },
844 },
845 }
846 .cell())
847 }
848
849 #[turbo_tasks::function]
850 pub async fn collected_modules(self: Vc<Self>) -> Result<Vc<CollectedModules>> {
851 collect_graph(self).await
852 }
853
854 #[turbo_tasks::function]
855 pub async fn chunk_group_info(self: Vc<Self>) -> Result<Vc<ChunkGroupInfo>> {
856 compute_chunk_group_info(&*self.await?).await
857 }
858
859 #[turbo_tasks::function]
860 pub async fn merged_modules(self: Vc<Self>) -> Result<Vc<MergedModuleInfo>> {
861 compute_merged_modules(self).await
862 }
863
864 #[turbo_tasks::function]
865 pub async fn module_batches(
866 self: Vc<Self>,
867 config: Vc<BatchingConfig>,
868 ) -> Result<Vc<ModuleBatchesGraph>> {
869 compute_module_batches(self, &*config.await?).await
870 }
871
872 #[turbo_tasks::function]
873 pub async fn style_groups(
874 self: Vc<Self>,
875 chunking_context: Vc<Box<dyn ChunkingContext>>,
876 config: StyleGroupsConfig,
877 ) -> Result<Vc<StyleGroups>> {
878 match &config.algorithm {
879 StyleGroupsAlgorithm::Default => {
880 compute_style_groups(self, chunking_context, &config).await
881 }
882 StyleGroupsAlgorithm::Graph {
883 weight_distribution,
884 request_cost,
885 } => {
886 compute_style_groups_graph(
887 self,
888 chunking_context,
889 request_cost.get(),
890 weight_distribution.get(),
891 config.max_chunk_size as u64,
892 )
893 .await
894 }
895 }
896 }
897
898 #[turbo_tasks::function(root)]
899 pub async fn async_module_info(self: Vc<Self>) -> Result<Vc<AsyncModulesInfo>> {
900 async move {
903 let result_op = compute_async_module_info(self.to_resolved().await?);
904 let result_vc = result_op.resolve().strongly_consistent().await?;
905 result_op.drop_collectibles::<Box<dyn Issue>>();
906 anyhow::Ok(*result_vc)
907 }
908 .instrument(tracing::info_span!("compute async module info"))
909 .await
910 }
911
912 #[turbo_tasks::function]
913 pub async fn referenced_async_modules(
914 self: Vc<Self>,
915 module: ResolvedVc<Box<dyn Module>>,
916 ) -> Result<Vc<AsyncModuleInfo>> {
917 let graph_ref = self.await?;
918 let async_module_info = self.async_module_info();
919
920 let entry = graph_ref.get_entry(module)?;
921 let referenced_modules = graph_ref
922 .iter_graphs_neighbors_rev(entry, Direction::Outgoing, false)
923 .filter(|(edge_idx, _)| {
924 let ty = graph_ref.get_edge(*edge_idx).unwrap();
925 ty.chunking_type.is_inherit_async()
926 })
927 .map(|(_, child_idx)| anyhow::Ok(graph_ref.get_node(child_idx)?.module()))
928 .collect::<Result<Vec<_>>>()?
929 .into_iter()
930 .rev()
931 .map(async |m| Ok(async_module_info.is_async(m).await?.then_some(*m)))
932 .try_flat_join()
933 .await?;
934
935 Ok(AsyncModuleInfo::new(referenced_modules))
936 }
937
938 #[turbo_tasks::function]
940 pub fn iter_graphs(&self) -> Vc<ModuleGraphLayers> {
941 Vc::cell(
942 self.input_graphs
943 .iter()
944 .enumerate()
945 .map(|(graph_idx, graph)| {
946 ModuleGraphLayer::new(*graph, graph_idx as u32, self.input_binding_usage)
947 })
948 .collect(),
949 )
950 }
951}
952
953impl Deref for ModuleGraph {
954 type Target = ModuleGraphSnapshot;
955
956 fn deref(&self) -> &Self::Target {
957 &self.snapshot
958 }
959}
960
961#[turbo_tasks::value(shared, serialization = "skip", eq = "manual", cell = "new")]
962pub struct ModuleGraphLayer {
963 snapshot: ModuleGraphSnapshot,
964}
965
966#[turbo_tasks::value_impl]
967impl ModuleGraphLayer {
968 #[turbo_tasks::function(operation, root)]
969 async fn new(
970 graph: OperationVc<SingleModuleGraph>,
971 graph_idx: u32,
972 binding_usage: Option<OperationVc<BindingUsageInfo>>,
973 ) -> Result<Vc<Self>> {
974 Ok(Self {
975 snapshot: ModuleGraphSnapshot {
976 graphs: vec![graph.connect().await?],
977 skip_visited_module_children: true,
978 graph_idx_override: Some(graph_idx),
979 binding_usage: if let Some(binding_usage) = binding_usage {
980 Some(binding_usage.connect().await?)
981 } else {
982 None
983 },
984 },
985 }
986 .cell())
987 }
988}
989
990impl Deref for ModuleGraphLayer {
991 type Target = ModuleGraphSnapshot;
992
993 fn deref(&self) -> &Self::Target {
994 &self.snapshot
995 }
996}
997
998#[turbo_tasks::value(transparent)]
999pub struct ModuleGraphLayers(Vec<OperationVc<ModuleGraphLayer>>);
1000
1001#[derive(ValueDebugFormat, NonLocalValue)]
1011pub struct ModuleGraphSnapshot {
1012 pub graphs: Vec<ReadRef<SingleModuleGraph>>,
1014 skip_visited_module_children: bool,
1018
1019 graph_idx_override: Option<u32>,
1020
1021 binding_usage: Option<ReadRef<BindingUsageInfo>>,
1022}
1023
1024impl ModuleGraphSnapshot {
1025 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
1026 if self.graph_idx_override.is_some() {
1027 debug_assert_eq!(self.graphs.len(), 1,);
1028 }
1029
1030 let Some(idx) = self
1031 .graphs
1032 .iter()
1033 .enumerate()
1034 .find_map(|(graph_idx, graph)| {
1035 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1036 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
1037 node_idx: *node_idx,
1038 })
1039 })
1040 else {
1041 bail!("Couldn't find entry module {entry:?} in module graph");
1042 };
1043 Ok(idx)
1044 }
1045
1046 pub fn all_chunk_group_entries(&self) -> impl Iterator<Item = &ChunkGroupEntry> + '_ {
1048 self.graphs
1049 .iter()
1050 .flat_map(|g| g.entries.chunk_groups.iter())
1051 }
1052
1053 pub fn all_chunk_group_entry_modules(
1055 &self,
1056 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1057 self.graphs
1058 .iter()
1059 .flat_map(|g| g.entries.chunk_group_modules())
1060 }
1061
1062 pub fn all_entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1064 self.graphs.iter().flat_map(|g| g.entries.all_modules())
1065 }
1066
1067 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
1068 if self.graph_idx_override.is_some() {
1069 self.graphs.first().unwrap()
1070 } else {
1071 &self.graphs[graph_idx as usize]
1072 }
1073 }
1074
1075 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
1076 let graph = self.get_graph(node.graph_idx);
1077 graph
1078 .graph
1079 .node_weight(node.node_idx)
1080 .context("Expected graph node")
1081 }
1082
1083 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
1084 let graph = self.get_graph(edge.graph_idx);
1085 graph
1086 .graph
1087 .edge_weight(edge.edge_idx)
1088 .context("Expected graph node")
1089 }
1090
1091 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
1092 if self.skip_visited_module_children && direction == Direction::Outgoing {
1093 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
1094 } else {
1095 true
1096 }
1097 }
1098
1099 pub fn enumerate_nodes(
1102 &self,
1103 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
1104 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
1105 }
1106
1107 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1110 self.graphs.iter().flat_map(|g| g.iter_nodes())
1111 }
1112
1113 fn iter_graphs_neighbors_rev<'a>(
1115 &'a self,
1116 node: GraphNodeIndex,
1117 direction: Direction,
1118 include_traced: bool,
1119 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1120 let graph = &*self.get_graph(node.graph_idx).graph;
1121
1122 if cfg!(debug_assertions) && direction == Direction::Outgoing {
1123 let node_weight = graph.node_weight(node.node_idx).unwrap();
1124 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1125 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1126 }
1127 }
1128
1129 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1130 std::iter::from_fn(move || {
1131 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1132 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1133 if self
1134 .binding_usage
1135 .as_ref()
1136 .is_some_and(|binding_usage| binding_usage.is_reference_unused_edge(&edge_idx))
1137 {
1138 continue;
1140 }
1141
1142 if !include_traced && self.get_edge(edge_idx).unwrap().chunking_type.is_traced() {
1143 continue;
1144 }
1145
1146 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1147 }
1148 None
1149 })
1150 }
1151
1152 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1155 self.iter_nodes()
1156 .map(async |n| Ok((n, n.ident().to_string().await?)))
1157 .join()
1158 .await
1159 .into_iter()
1160 .collect()
1161 }
1162
1163 pub fn traverse_nodes_dfs<S>(
1172 &self,
1173 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1174 state: &mut S,
1175 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1176 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1177 ) -> Result<()> {
1178 let entries = entries.into_iter().collect::<Vec<_>>();
1179
1180 enum Pass {
1181 Visit,
1182 ExpandAndVisit,
1183 }
1184 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1185 for entry in entries.into_iter().rev() {
1186 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1187 }
1188 let mut expanded = FxHashSet::default();
1189 while let Some((pass, current)) = stack.pop() {
1190 let current_node = self.get_node(current)?;
1191 match pass {
1192 Pass::Visit => {
1193 visit_postorder(current_node.module(), state)?;
1194 }
1195 Pass::ExpandAndVisit => {
1196 if !expanded.insert(current) {
1197 continue;
1198 }
1199 let action = visit_preorder(current_node.module(), state)?;
1200 if action == GraphTraversalAction::Exclude {
1201 continue;
1202 }
1203 stack.push((Pass::Visit, current));
1204 if action == GraphTraversalAction::Continue
1205 && self.should_visit_node(current_node, Direction::Outgoing)
1206 {
1207 let current = current_node
1208 .target_idx(Direction::Outgoing)
1209 .unwrap_or(current);
1210 stack.extend(
1211 self.iter_graphs_neighbors_rev(current, Direction::Outgoing, false)
1212 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1213 );
1214 }
1215 }
1216 }
1217 }
1218
1219 Ok(())
1220 }
1221
1222 pub fn traverse_edges_bfs(
1233 &self,
1234 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1235 mut visitor: impl FnMut(
1236 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1237 ResolvedVc<Box<dyn Module>>,
1238 ) -> Result<GraphTraversalAction>,
1239 ) -> Result<()> {
1240 let mut queue = VecDeque::from(
1241 entries
1242 .into_iter()
1243 .map(|e| self.get_entry(e))
1244 .collect::<Result<Vec<_>>>()?,
1245 );
1246 let mut visited = FxHashSet::default();
1247 for entry_node in &queue {
1248 visitor(None, self.get_node(*entry_node)?.module())?;
1249 }
1250 while let Some(node) = queue.pop_front() {
1251 if visited.insert(node) {
1252 let node_weight = self.get_node(node)?;
1253 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1254 {
1255 let succ_weight = self.get_node(succ)?;
1256 let action = visitor(
1257 Some((node_weight.module(), self.get_edge(edge)?)),
1258 succ_weight.module(),
1259 )?;
1260 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1261 continue;
1262 }
1263 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1264 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1265 queue.push_back(succ);
1266 }
1267 }
1268 }
1269 }
1270
1271 Ok(())
1272 }
1273
1274 pub fn traverse_edges_unordered(
1283 &self,
1284 mut visitor: impl FnMut(
1285 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1286 ResolvedVc<Box<dyn Module>>,
1287 ) -> Result<()>,
1288 ) -> Result<()> {
1289 self.traverse_edges_dfs(
1292 self.all_chunk_group_entry_modules(),
1293 &mut (),
1294 |parent, target, _| {
1295 visitor(parent, target)?;
1296 Ok(GraphTraversalAction::Continue)
1297 },
1298 |_, _, _| Ok(()),
1299 false,
1300 )
1301 }
1302
1303 pub fn traverse_edges_dfs<S>(
1322 &self,
1323 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1324 state: &mut S,
1325 visit_preorder: impl FnMut(
1326 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1327 ResolvedVc<Box<dyn Module>>,
1328 &mut S,
1329 ) -> Result<GraphTraversalAction>,
1330 visit_postorder: impl FnMut(
1331 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1332 ResolvedVc<Box<dyn Module>>,
1333 &mut S,
1334 ) -> Result<()>,
1335 include_traced: bool,
1336 ) -> Result<()> {
1337 self.traverse_edges_dfs_impl::<S>(
1338 entries,
1339 state,
1340 visit_preorder,
1341 visit_postorder,
1342 Direction::Outgoing,
1343 include_traced,
1344 )
1345 }
1346
1347 pub fn traverse_edges_reverse_dfs<S>(
1364 &self,
1365 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1366 state: &mut S,
1367 visit_preorder: impl FnMut(
1368 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1369 ResolvedVc<Box<dyn Module>>,
1370 &mut S,
1371 ) -> Result<GraphTraversalAction>,
1372 visit_postorder: impl FnMut(
1373 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1374 ResolvedVc<Box<dyn Module>>,
1375 &mut S,
1376 ) -> Result<()>,
1377 ) -> Result<()> {
1378 self.traverse_edges_dfs_impl::<S>(
1379 entries,
1380 state,
1381 visit_preorder,
1382 visit_postorder,
1383 Direction::Incoming,
1384 false,
1385 )
1386 }
1387
1388 fn traverse_edges_dfs_impl<S>(
1389 &self,
1390 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1391 state: &mut S,
1392 mut visit_preorder: impl FnMut(
1393 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1394 ResolvedVc<Box<dyn Module>>,
1395 &mut S,
1396 ) -> Result<GraphTraversalAction>,
1397 mut visit_postorder: impl FnMut(
1398 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1399 ResolvedVc<Box<dyn Module>>,
1400 &mut S,
1401 ) -> Result<()>,
1402 direction: Direction,
1403 include_traced: bool,
1404 ) -> Result<()> {
1405 if direction == Direction::Incoming {
1406 debug_assert!(
1407 self.skip_visited_module_children,
1408 "Can only trace reverse edges in a single layer graph. We do not model cross \
1409 graph reverse edges"
1410 );
1411 }
1412 let entries = entries.into_iter().collect::<Vec<_>>();
1413
1414 enum Pass {
1415 Visit,
1416 ExpandAndVisit,
1417 }
1418 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1420 Pass,
1421 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1422 GraphNodeIndex,
1423 )> = Vec::with_capacity(entries.len());
1424 for entry in entries.into_iter().rev() {
1425 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1426 }
1427 let mut expanded = FxHashSet::default();
1428 while let Some((pass, parent, current)) = stack.pop() {
1429 let parent_arg = match parent {
1430 Some((parent_node, parent_edge)) => Some((
1431 self.get_node(parent_node)?.module(),
1432 self.get_edge(parent_edge)?,
1433 )),
1434 None => None,
1435 };
1436 let current_node = self.get_node(current)?;
1437 match pass {
1438 Pass::Visit => {
1439 visit_postorder(parent_arg, current_node.module(), state)?;
1440 }
1441 Pass::ExpandAndVisit => {
1442 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1443 if action == GraphTraversalAction::Exclude {
1444 continue;
1445 }
1446 stack.push((Pass::Visit, parent, current));
1447 if action == GraphTraversalAction::Continue
1448 && expanded.insert(current)
1449 && self.should_visit_node(current_node, direction)
1450 {
1451 let current = current_node.target_idx(direction).unwrap_or(current);
1452 stack.extend(
1453 self.iter_graphs_neighbors_rev(current, direction, include_traced)
1454 .map(|(edge, child)| {
1455 (Pass::ExpandAndVisit, Some((current, edge)), child)
1456 }),
1457 );
1458 }
1459 }
1460 }
1461 }
1462
1463 Ok(())
1464 }
1465
1466 pub fn traverse_cycles(
1470 &self,
1471 edge_filter: impl Fn(&RefData) -> bool,
1472 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1473 ) -> Result<()> {
1474 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1475 graph.traverse_cycles(
1476 &edge_filter,
1477 &mut visit_cycle,
1478 graph_idx as u32,
1479 &self.binding_usage,
1480 )?;
1481 }
1482 Ok(())
1483 }
1484
1485 pub fn traverse_edges_fixed_point_with_priority<'graph, S, P: Ord>(
1507 &'graph self,
1508 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1509 state: &mut S,
1510 mut visit: impl FnMut(
1511 Option<(ResolvedVc<Box<dyn Module>>, &'graph RefData, GraphEdgeIndex)>,
1512 ResolvedVc<Box<dyn Module>>,
1513 GraphNodeIndex,
1514 &mut S,
1515 ) -> Result<GraphTraversalAction>,
1516 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1517 ) -> Result<usize> {
1518 if self.skip_visited_module_children {
1519 panic!(
1520 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1521 );
1522 }
1523
1524 let mut visit_order = 0usize;
1525 let mut order = || {
1526 let order = visit_order;
1527 visit_order += 1;
1528 order
1529 };
1530 #[derive(PartialEq, Eq)]
1531 struct NodeWithPriority<T: Ord> {
1532 node: GraphNodeIndex,
1533 priority: T,
1534 visit_order: usize,
1535 }
1536 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1537 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1538 Some(self.cmp(other))
1539 }
1540 }
1541 impl<T: Ord> Ord for NodeWithPriority<T> {
1542 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1543 self.priority
1546 .cmp(&other.priority)
1547 .then(self.visit_order.cmp(&other.visit_order))
1550 }
1551 }
1552
1553 let mut queue_set = FxHashSet::default();
1554 let mut queue = BinaryHeap::from_iter(
1555 entries
1556 .into_iter()
1557 .map(|(m, priority)| {
1558 Ok(NodeWithPriority {
1559 node: self.get_entry(m)?,
1560 priority,
1561 visit_order: order(),
1562 })
1563 })
1564 .collect::<Result<Vec<_>>>()?,
1565 );
1566
1567 for entry_node in &queue {
1568 visit(
1569 None,
1570 self.get_node(entry_node.node)?.module(),
1571 entry_node.node,
1572 state,
1573 )?;
1574 }
1575
1576 let mut visit_count = 0usize;
1577 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1578 queue_set.remove(&node);
1579 let node_weight = self.get_node(node)?;
1580 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1581
1582 visit_count += 1;
1583
1584 for (edge, succ) in self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false) {
1585 let succ_weight = self.get_node(succ)?;
1586
1587 let action = visit(
1588 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1589 succ_weight.module(),
1590 succ,
1591 state,
1592 )?;
1593
1594 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1595 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1596 queue.push(NodeWithPriority {
1597 node: succ,
1598 priority: priority(succ_weight.module(), state)?,
1599 visit_order: order(),
1600 });
1601 }
1602 }
1603 }
1604
1605 Ok(visit_count)
1606 }
1607
1608 pub fn iter_reachable_modules(
1610 &self,
1611 ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1612 Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1613 SingleModuleGraphNode::Module(m) => Some(*m),
1614 SingleModuleGraphNode::VisitedModule { .. } => None,
1615 }))
1616 }
1617
1618 pub fn iter_reachable_nodes<'a>(
1621 &'a self,
1622 ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1623 ModuleGraphSnapshotNodeIterator::new(self)
1624 }
1625}
1626
1627struct ModuleGraphSnapshotNodeIterator<'a> {
1628 graph: &'a ModuleGraphSnapshot,
1629 visited: FxHashSet<GraphNodeIndex>,
1630 visit_queue: VecDeque<GraphNodeIndex>,
1631}
1632
1633impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1634 fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1635 let entries = graph
1636 .graphs
1637 .iter()
1638 .flat_map(|g| g.chunk_group_modules())
1639 .map(|e| graph.get_entry(e))
1640 .collect::<Result<VecDeque<_>>>()?;
1641
1642 Ok(Self {
1643 graph,
1644 visited: FxHashSet::default(),
1645 visit_queue: entries,
1646 })
1647 }
1648}
1649impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1650 type Item = &'a SingleModuleGraphNode;
1651
1652 fn next(&mut self) -> Option<Self::Item> {
1653 while let Some(node_idx) = self.visit_queue.pop_front() {
1654 if self.visited.insert(node_idx) {
1655 let node_weight = self.graph.get_node(node_idx).unwrap();
1656 if self
1657 .graph
1658 .should_visit_node(node_weight, Direction::Outgoing)
1659 {
1660 let node = node_weight
1661 .target_idx(Direction::Outgoing)
1662 .unwrap_or(node_idx);
1663 self.visit_queue.extend(
1664 self.graph
1665 .iter_graphs_neighbors_rev(node, Direction::Outgoing, false)
1666 .map(|(_, succ)| succ)
1667 .filter(|succ| !self.visited.contains(succ)),
1668 );
1669 }
1670 return Some(node_weight);
1671 }
1672 }
1673 None
1674 }
1675}
1676impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1677
1678#[turbo_tasks::value_impl]
1679impl SingleModuleGraph {
1680 #[turbo_tasks::function(operation)]
1681 pub async fn new_with_entry(
1682 entry: ChunkGroupEntry,
1683 include_traced: bool,
1684 include_binding_usage: bool,
1685 ) -> Result<Vc<Self>> {
1686 SingleModuleGraph::new_inner(
1687 &GraphEntries::from_chunk_groups(vec![entry]),
1688 &Default::default(),
1689 include_traced,
1690 include_binding_usage,
1691 )
1692 .await
1693 }
1694
1695 #[turbo_tasks::function(operation)]
1696 pub async fn new_with_entries(
1697 entries: ResolvedVc<GraphEntries>,
1698 include_traced: bool,
1699 include_binding_usage: bool,
1700 ) -> Result<Vc<Self>> {
1701 SingleModuleGraph::new_inner(
1702 &*entries.await?,
1703 &Default::default(),
1704 include_traced,
1705 include_binding_usage,
1706 )
1707 .await
1708 }
1709
1710 #[turbo_tasks::function(operation)]
1711 pub async fn new_with_entries_visited(
1712 entries: ResolvedVc<GraphEntries>,
1713 visited_modules: OperationVc<VisitedModules>,
1714 include_traced: bool,
1715 include_binding_usage: bool,
1716 ) -> Result<Vc<Self>> {
1717 SingleModuleGraph::new_inner(
1718 &*entries.await?,
1719 &visited_modules.connect().await?.modules,
1720 include_traced,
1721 include_binding_usage,
1722 )
1723 .await
1724 }
1725
1726 #[turbo_tasks::function(operation)]
1727 pub async fn new_with_entries_visited_intern(
1728 entries: GraphEntries,
1730 visited_modules: OperationVc<VisitedModules>,
1731 include_traced: bool,
1732 include_binding_usage: bool,
1733 ) -> Result<Vc<Self>> {
1734 SingleModuleGraph::new_inner(
1735 &entries,
1736 &visited_modules.connect().await?.modules,
1737 include_traced,
1738 include_binding_usage,
1739 )
1740 .await
1741 }
1742
1743 #[turbo_tasks::function]
1744 pub async fn module_count(&self) -> Vc<u64> {
1745 Vc::cell(self.number_of_modules as u64)
1746 }
1747
1748 #[turbo_tasks::function]
1749 pub async fn edge_count(&self) -> Vc<u64> {
1750 Vc::cell(self.graph.edge_count() as u64)
1751 }
1752}
1753
1754#[derive(Clone, Debug, Serialize, Deserialize, NonLocalValue)]
1755pub enum SingleModuleGraphNode {
1756 Module(ResolvedVc<Box<dyn Module>>),
1757 VisitedModule {
1759 idx: GraphNodeIndex,
1760 module: ResolvedVc<Box<dyn Module>>,
1761 },
1762}
1763
1764impl SingleModuleGraphNode {
1765 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1766 match self {
1767 SingleModuleGraphNode::Module(module) => *module,
1768 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1769 }
1770 }
1771 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1772 match self {
1773 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1774 Direction::Outgoing => Some(*idx),
1775 Direction::Incoming => None,
1776 },
1777 SingleModuleGraphNode::Module(_) => None,
1778 }
1779 }
1780}
1781
1782#[derive(PartialEq, Eq, Debug)]
1783pub enum GraphTraversalAction {
1784 Continue,
1786 Skip,
1788 Exclude,
1790}
1791
1792#[derive(Clone, Hash, PartialEq, Eq)]
1795enum SingleModuleGraphBuilderNode {
1796 Module {
1798 module: ResolvedVc<Box<dyn Module>>,
1799 ident: Option<ReadRef<RcStr>>,
1801 is_traced: bool,
1803 },
1804 VisitedModule {
1806 module: ResolvedVc<Box<dyn Module>>,
1807 idx: GraphNodeIndex,
1808 },
1809}
1810
1811impl SingleModuleGraphBuilderNode {
1812 async fn new_module(
1813 emit_spans: bool,
1814 module: ResolvedVc<Box<dyn Module>>,
1815 is_traced: bool,
1816 ) -> Result<Self> {
1817 Ok(Self::Module {
1818 module,
1819 ident: if emit_spans {
1820 Some(module.ident_string().untracked().await?)
1822 } else {
1823 None
1824 },
1825 is_traced,
1826 })
1827 }
1828 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1829 Self::VisitedModule { module, idx }
1830 }
1831}
1832
1833struct SingleModuleGraphBuilder<'a> {
1834 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1835
1836 emit_spans: bool,
1837
1838 include_traced: bool,
1840
1841 include_binding_usage: bool,
1843}
1844impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1845 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1846 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1847
1848 fn visit(
1849 &mut self,
1850 node: &SingleModuleGraphBuilderNode,
1851 _edge: Option<&RefData>,
1852 ) -> VisitControlFlow {
1853 match node {
1854 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1855 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1857 }
1858 }
1859
1860 fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1861 let &SingleModuleGraphBuilderNode::Module {
1863 module, is_traced, ..
1864 } = node
1865 else {
1866 unreachable!()
1868 };
1869 let visited_modules = self.visited_modules;
1870 let emit_spans = self.emit_spans;
1871 let include_traced = self.include_traced;
1872 let include_binding_usage = self.include_binding_usage;
1873 async move {
1874 let refs_cell = if !is_traced {
1875 primary_chunkable_referenced_modules(*module, include_traced, include_binding_usage)
1876 } else {
1877 referenced_modules_and_affecting_sources(*module, false)
1879 };
1880 let refs = match refs_cell.await {
1881 Ok(refs) => refs,
1882 Err(e) => {
1883 return Err(e.context(module.ident().to_string().await?));
1884 }
1885 };
1886
1887 refs.iter()
1888 .flat_map(|(reference, resolved)| {
1889 resolved.modules.iter().map(|m| {
1890 (
1891 *reference,
1892 resolved.chunking_type.clone(),
1893 resolved.binding_usage.clone(),
1894 *m,
1895 )
1896 })
1897 })
1898 .filter(|(_, ty, _, _)| {
1899 !matches!(
1908 ty,
1909 ChunkingType::Traced {
1910 mode: TracedMode::Transitive
1911 }
1912 ) || is_traced
1913 })
1914 .map(async |(reference, ty, binding_usage, target)| {
1915 let to = if let Some(idx) = visited_modules.get(&target) {
1916 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1917 } else {
1918 SingleModuleGraphBuilderNode::new_module(
1919 emit_spans,
1920 target,
1921 is_traced || ty.is_traced(),
1922 )
1923 .await?
1924 };
1925 Ok((
1926 to,
1927 RefData {
1928 chunking_type: ty,
1929 binding_usage,
1930 reference,
1931 },
1932 ))
1933 })
1934 .try_join()
1935 .await
1936 }
1937 }
1938
1939 fn span(
1940 &mut self,
1941 node: &SingleModuleGraphBuilderNode,
1942 edge: Option<&RefData>,
1943 ) -> tracing::Span {
1944 if !self.emit_spans {
1945 return Span::none();
1946 }
1947
1948 let mut span = match node {
1949 SingleModuleGraphBuilderNode::Module {
1950 ident: Some(ident), ..
1951 } => {
1952 tracing::info_span!("module", name = display(ident))
1953 }
1954 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1955 tracing::info_span!("visited module")
1956 }
1957 _ => unreachable!(),
1958 };
1959
1960 if let Some(edge) = edge {
1961 match &edge.chunking_type {
1962 ChunkingType::Parallel {
1963 inherit_async: _,
1964 hoisted: _,
1965 } => {}
1966 ChunkingType::Traced { .. } => {
1967 let _span = span.entered();
1968 span = tracing::info_span!("traced reference");
1969 }
1970 ChunkingType::Async => {
1971 let _span = span.entered();
1972 span = tracing::info_span!("async reference");
1973 }
1974 ChunkingType::PerEntry => {
1975 let _span = span.entered();
1976 span = tracing::info_span!("per-entry reference");
1977 }
1978 ChunkingType::Emitted { namespace, .. } => {
1979 let _span = span.entered();
1980 span = tracing::info_span!("emitted reference", namespace = debug(&namespace));
1981 }
1982 ChunkingType::Collected { namespace, .. } => {
1983 let _span = span.entered();
1984 span =
1985 tracing::info_span!("collected reference", namespace = debug(&namespace));
1986 }
1987 ChunkingType::Isolated { _ty: ty, merge_tag } => {
1988 let _span = span.entered();
1989 span = tracing::info_span!(
1990 "isolated reference",
1991 ty = debug(&ty),
1992 merge_tag = debug(&merge_tag)
1993 );
1994 }
1995 ChunkingType::Shared {
1996 inherit_async: _,
1997 merge_tag,
1998 } => {
1999 let _span = span.entered();
2000 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
2001 }
2002 };
2003 }
2004
2005 span
2006 }
2007}
2008
2009#[cfg(test)]
2010pub mod tests {
2011 use anyhow::Result;
2012 use rustc_hash::FxHashMap;
2013 use turbo_rcstr::{RcStr, rcstr};
2014 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
2015 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
2016 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
2017
2018 use super::*;
2019 use crate::{
2020 asset::{Asset, AssetContent},
2021 ident::AssetIdent,
2022 issue::{CollectibleIssuesExt, IssueSeverity},
2023 module::{Module, ModuleSideEffects},
2024 module_graph::chunk_group_info::EntryHeuristics,
2025 reference::{ModuleReference, ModuleReferences},
2026 resolve::ModuleResolveResult,
2027 };
2028
2029 #[turbo_tasks::value(shared)]
2030 struct ImportTraceTestResult {
2031 has_entry: bool,
2032 traces: Vec<Vec<RcStr>>,
2033 missing_traces: Vec<Vec<RcStr>>,
2034 }
2035
2036 #[turbo_tasks::function(operation, root)]
2037 async fn import_trace_test_operation(rootless: bool) -> Result<Vc<ImportTraceTestResult>> {
2038 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2039 let root = fs.root().await?;
2040 let repo = TestRepo::new(
2041 &root,
2042 [
2043 ("entry.js", vec!["dependency.js"]),
2044 ("dependency.js", vec!["entry.js"]),
2045 ],
2046 );
2047 let entry = Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join("entry.js")?, repo))
2048 .to_resolved()
2049 .await?;
2050 let graph = SingleModuleGraph::new_with_entries(
2051 GraphEntries::resolved_cell(GraphEntries::new(
2052 vec![ChunkGroupEntry::Entry {
2053 modules: vec![entry],
2054 heuristics: EntryHeuristics::default(),
2055 }],
2056 vec![],
2057 )),
2058 false,
2059 false,
2060 )
2061 .connect()
2062 .to_resolved()
2063 .await?;
2064 let graph = if rootless {
2065 let _ = graph.await?.entry_nodes();
2068 let mut graph = (*graph.await?).clone();
2069 graph.entries = GraphEntries::default();
2070 graph.resolved_cell()
2071 } else {
2072 graph
2073 };
2074
2075 let has_entry = graph.await?.has_entry_module(entry);
2076 let tracer = ModuleGraphImportTracer::new(*graph);
2077 let traces = tracer
2078 .get_traces(root.join("dependency.js")?)
2079 .await?
2080 .0
2081 .iter()
2082 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2083 .collect();
2084 let missing_traces = tracer
2085 .get_traces(root.join("missing.js")?)
2086 .await?
2087 .0
2088 .iter()
2089 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2090 .collect();
2091
2092 Ok(ImportTraceTestResult {
2093 has_entry,
2094 traces,
2095 missing_traces,
2096 }
2097 .cell())
2098 }
2099
2100 #[turbo_tasks::value(shared)]
2101 struct ImportTraceIssues {
2102 issues: Vec<(IssueSeverity, RcStr)>,
2103 }
2104
2105 #[turbo_tasks::function(operation, root)]
2106 async fn import_trace_issues_operation(
2107 trace_operation: OperationVc<ImportTraceTestResult>,
2108 ) -> Result<Vc<ImportTraceIssues>> {
2109 let _ = trace_operation.connect().await?;
2110 let issues = trace_operation
2111 .peek_issues()
2112 .iter()
2113 .map(async |issue| {
2114 let issue = issue.into_trait_ref().await?;
2115 Ok((
2116 issue.severity(),
2117 issue.title().await?.to_unstyled_string().into(),
2118 ))
2119 })
2120 .try_join()
2121 .await?;
2122 Ok(ImportTraceIssues { issues }.cell())
2123 }
2124
2125 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2126 async fn test_import_trace_uses_explicit_entry_as_cycle_root() {
2127 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2128 BackendOptions::default(),
2129 noop_backing_storage(),
2130 ));
2131 tt.run_once(async {
2132 let result = import_trace_test_operation(false)
2133 .read_strongly_consistent()
2134 .await?;
2135 assert!(result.has_entry);
2136 assert_eq!(
2137 result.traces,
2138 vec![vec![rcstr!("dependency.js"), rcstr!("entry.js")]]
2139 );
2140 assert!(result.missing_traces.is_empty());
2141 Ok(())
2142 })
2143 .await
2144 .unwrap();
2145 }
2146
2147 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2148 async fn test_cloned_rootless_import_trace_resets_cache_and_emits_bug_issue() {
2149 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2150 BackendOptions::default(),
2151 noop_backing_storage(),
2152 ));
2153 tt.run_once(async {
2154 let trace_operation = import_trace_test_operation(true);
2155 let result = trace_operation.read_strongly_consistent().await?;
2156 assert!(!result.has_entry);
2157 assert_eq!(result.traces, vec![vec![rcstr!("dependency.js")]]);
2158 assert!(result.missing_traces.is_empty());
2159
2160 let issues = import_trace_issues_operation(trace_operation)
2161 .read_strongly_consistent()
2162 .await?;
2163 assert_eq!(
2164 issues.issues,
2165 vec![(
2166 IssueSeverity::Bug,
2167 rcstr!("Module graph is missing an entry point")
2168 )]
2169 );
2170 Ok(())
2171 })
2172 .await
2173 .unwrap();
2174 }
2175
2176 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2177 async fn test_traverse_dfs_from_entries_diamond() {
2178 run_graph_test(
2179 vec![rcstr!("a.js")],
2180 {
2181 let mut deps = FxHashMap::default();
2182 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2184 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
2185 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
2186 deps
2187 },
2188 |graph, entry_modules, module_to_name| {
2189 let mut preorder_visits = Vec::new();
2190 let mut postorder_visits = Vec::new();
2191
2192 graph.traverse_edges_dfs(
2193 entry_modules,
2194 &mut (),
2195 |parent, target, _| {
2196 preorder_visits.push((
2197 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2198 module_to_name.get(&target).unwrap().clone(),
2199 ));
2200 Ok(GraphTraversalAction::Continue)
2201 },
2202 |parent, target, _| {
2203 postorder_visits.push((
2204 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2205 module_to_name.get(&target).unwrap().clone(),
2206 ));
2207 Ok(())
2208 },
2209 false,
2210 )?;
2211 assert_eq!(
2212 vec![
2213 (None, rcstr!("a.js")),
2214 (Some(rcstr!("a.js")), rcstr!("b.js")),
2215 (Some(rcstr!("b.js")), rcstr!("d.js")),
2216 (Some(rcstr!("a.js")), rcstr!("c.js")),
2217 (Some(rcstr!("c.js")), rcstr!("d.js"))
2218 ],
2219 preorder_visits
2220 );
2221 assert_eq!(
2222 vec![
2223 (Some(rcstr!("b.js")), rcstr!("d.js")),
2224 (Some(rcstr!("a.js")), rcstr!("b.js")),
2225 (Some(rcstr!("c.js")), rcstr!("d.js")),
2226 (Some(rcstr!("a.js")), rcstr!("c.js")),
2227 (None, rcstr!("a.js"))
2228 ],
2229 postorder_visits
2230 );
2231 Ok(())
2232 },
2233 )
2234 .await;
2235 }
2236
2237 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2238 async fn test_traverse_dfs_from_entries_cycle() {
2239 run_graph_test(
2240 vec![rcstr!("a.js")],
2241 {
2242 let mut deps = FxHashMap::default();
2243 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2245 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2246 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2247 deps
2248 },
2249 |graph, entry_modules, module_to_name| {
2250 let mut preorder_visits = Vec::new();
2251 let mut postorder_visits = Vec::new();
2252
2253 graph.traverse_edges_dfs(
2254 entry_modules,
2255 &mut (),
2256 |parent, target, _| {
2257 preorder_visits.push((
2258 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2259 module_to_name.get(&target).unwrap().clone(),
2260 ));
2261 Ok(GraphTraversalAction::Continue)
2262 },
2263 |parent, target, _| {
2264 postorder_visits.push((
2265 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2266 module_to_name.get(&target).unwrap().clone(),
2267 ));
2268 Ok(())
2269 },
2270 false,
2271 )?;
2272 assert_eq!(
2273 vec![
2274 (None, rcstr!("a.js")),
2275 (Some(rcstr!("a.js")), rcstr!("b.js")),
2276 (Some(rcstr!("b.js")), rcstr!("c.js")),
2277 (Some(rcstr!("c.js")), rcstr!("a.js")),
2278 ],
2279 preorder_visits
2280 );
2281 assert_eq!(
2282 vec![
2283 (Some(rcstr!("c.js")), rcstr!("a.js")),
2284 (Some(rcstr!("b.js")), rcstr!("c.js")),
2285 (Some(rcstr!("a.js")), rcstr!("b.js")),
2286 (None, rcstr!("a.js"))
2287 ],
2288 postorder_visits
2289 );
2290 Ok(())
2291 },
2292 )
2293 .await;
2294 }
2295
2296 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2297 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
2298 run_graph_test(
2299 vec![rcstr!("a.js")],
2300 {
2301 let mut deps = FxHashMap::default();
2302 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2304 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2305 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2306 deps
2307 },
2308 |graph, entry_modules, module_to_name| {
2309 let mut visits = Vec::new();
2310 let mut count = 0;
2311
2312 graph.traverse_edges_fixed_point_with_priority(
2313 entry_modules.into_iter().map(|m| (m, 0)),
2314 &mut (),
2315 |parent, target, _, _| {
2316 visits.push((
2317 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2318 module_to_name.get(&target).unwrap().clone(),
2319 ));
2320 count += 1;
2321
2322 Ok(if count < 6 {
2324 GraphTraversalAction::Continue
2325 } else {
2326 GraphTraversalAction::Skip
2327 })
2328 },
2329 |_, _| Ok(0),
2330 )?;
2331 assert_eq!(
2332 vec![
2333 (None, rcstr!("a.js")),
2334 (Some(rcstr!("a.js")), rcstr!("b.js")),
2335 (Some(rcstr!("b.js")), rcstr!("c.js")),
2336 (Some(rcstr!("c.js")), rcstr!("a.js")),
2337 (Some(rcstr!("a.js")), rcstr!("b.js")),
2339 (Some(rcstr!("b.js")), rcstr!("c.js")),
2340 ],
2341 visits
2342 );
2343
2344 Ok(())
2345 },
2346 )
2347 .await;
2348 }
2349
2350 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2351 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
2352 run_graph_test(
2353 vec![rcstr!("a.js")],
2354 {
2355 let mut deps = FxHashMap::default();
2356 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2361 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
2362 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2363 deps
2364 },
2365 |graph, entry_modules, module_to_name| {
2366 let mut visits = Vec::new();
2367 let mut count = 0;
2368
2369 graph.traverse_edges_fixed_point_with_priority(
2370 entry_modules.into_iter().map(|m| (m, 0)),
2371 &mut (),
2372 |parent, target, _, _| {
2373 visits.push((
2374 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2375 module_to_name.get(&target).unwrap().clone(),
2376 ));
2377 count += 1;
2378
2379 Ok(if count < 6 {
2381 GraphTraversalAction::Continue
2382 } else {
2383 GraphTraversalAction::Skip
2384 })
2385 },
2386 |_, _| Ok(0),
2387 )?;
2388
2389 assert_eq!(
2390 vec![
2391 (None, rcstr!("a.js")),
2392 (Some(rcstr!("a.js")), rcstr!("c.js")),
2393 (Some(rcstr!("a.js")), rcstr!("b.js")),
2394 (Some(rcstr!("b.js")), rcstr!("e.js")),
2395 (Some(rcstr!("b.js")), rcstr!("d.js")),
2396 (Some(rcstr!("c.js")), rcstr!("f.js")),
2397 (Some(rcstr!("c.js")), rcstr!("e.js")),
2398 ],
2399 visits
2400 );
2401
2402 Ok(())
2403 },
2404 )
2405 .await;
2406 }
2407
2408 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2409 async fn test_traverse_cycles() {
2410 run_graph_test(
2411 vec![rcstr!("a.js")],
2412 {
2413 let mut deps = FxHashMap::default();
2414 deps.insert(
2421 rcstr!("a.js"),
2422 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2423 );
2424 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2425 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2426 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2427 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2428 deps
2429 },
2430 |graph, _, module_to_name| {
2431 let mut cycles = vec![];
2432
2433 graph.traverse_cycles(
2434 |_| true,
2435 |cycle| {
2436 cycles.push(
2437 cycle
2438 .iter()
2439 .map(|n| module_to_name.get(*n).unwrap().clone())
2440 .collect::<Vec<_>>(),
2441 );
2442 Ok(())
2443 },
2444 )?;
2445
2446 assert_eq!(
2447 cycles,
2448 vec![
2449 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2450 vec![rcstr!("s.js")]
2451 ],
2452 );
2453
2454 Ok(())
2455 },
2456 )
2457 .await;
2458 }
2459
2460 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2461 async fn test_reverse_edges_through_layered_graph() {
2462 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2463 BackendOptions::default(),
2464 noop_backing_storage(),
2465 ));
2466 tt.run_once(async move {
2467 #[turbo_tasks::value]
2468 struct ReverseTraversalResults {
2469 forward: Vec<RcStr>,
2470 reverse_from_d: Vec<RcStr>,
2471 reverse_from_b: Vec<RcStr>,
2472 }
2473
2474 #[turbo_tasks::function(operation, root)]
2475 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2476 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2477 let root = fs.root().await?;
2478
2479 let repo = TestRepo::new(
2482 &root,
2483 [("a.js", vec!["b.js", "d.js"]), ("b.js", vec!["c.js"])],
2484 );
2485 let make_module = |name| {
2486 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2487 .to_resolved()
2488 };
2489 let a_module = make_module("a.js").await?;
2490 let b_module = make_module("b.js").await?;
2491
2492 let parent_graph = SingleModuleGraph::new_with_entries(
2493 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2494 modules: vec![b_module],
2495 heuristics: EntryHeuristics::default(),
2496 }])
2497 .resolved_cell(),
2498 false,
2499 false,
2500 );
2501
2502 let module_graph = ModuleGraph::from_graphs(
2503 vec![
2504 parent_graph,
2505 SingleModuleGraph::new_with_entries_visited(
2506 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2507 modules: vec![a_module],
2508 heuristics: EntryHeuristics::default(),
2509 }])
2510 .resolved_cell(),
2511 VisitedModules::from_graph(parent_graph),
2512 false,
2513 false,
2514 ),
2515 ],
2516 None,
2517 )
2518 .connect();
2519 let child_graph = module_graph
2520 .iter_graphs()
2521 .await?
2522 .get(1)
2523 .unwrap()
2524 .connect()
2525 .await?;
2526
2527 let mut visited_forward = Vec::new();
2529 child_graph.traverse_edges_dfs(
2530 vec![a_module],
2531 &mut (),
2532 |_parent, child, _state_| {
2533 visited_forward.push(child);
2534 Ok(GraphTraversalAction::Continue)
2535 },
2536 |_, _, _| Ok(()),
2537 false,
2538 )?;
2539 let forward = visited_forward
2540 .iter()
2541 .map(|m| m.ident().to_string().owned())
2542 .try_join()
2543 .await?;
2544
2545 let d_module = child_graph
2547 .enumerate_nodes()
2548 .map(async |(_index, module)| {
2549 Ok(match module {
2550 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2551 if module.ident().to_string().owned().await? == "[test]/d.js" {
2552 Some(*module)
2553 } else {
2554 None
2555 }
2556 }
2557 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2558 ..
2559 } => None,
2560 })
2561 })
2562 .try_flat_join()
2563 .await?
2564 .into_iter()
2565 .next()
2566 .unwrap();
2567
2568 async fn get_reverse_from(
2569 graph: &ModuleGraphLayer,
2570 module: ResolvedVc<Box<dyn Module>>,
2571 ) -> Result<Vec<RcStr>> {
2572 let mut visited = Vec::new();
2573 graph.traverse_edges_reverse_dfs(
2574 vec![module],
2575 &mut (),
2576 |_parent, child, _state_| {
2577 visited.push(child);
2578 Ok(GraphTraversalAction::Continue)
2579 },
2580 |_, _, _| Ok(()),
2581 )?;
2582 visited
2583 .iter()
2584 .map(|m| m.ident().to_string().owned())
2585 .try_join()
2586 .await
2587 }
2588
2589 Ok(ReverseTraversalResults {
2590 forward,
2591 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2592 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2593 }
2594 .cell())
2595 }
2596
2597 let traversal_results = reverse_traversal_results_operation()
2598 .read_strongly_consistent()
2599 .await?;
2600
2601 assert_eq!(
2602 traversal_results.forward,
2603 vec![
2604 rcstr!("[test]/a.js"),
2605 rcstr!("[test]/b.js"),
2606 rcstr!("[test]/d.js")
2607 ]
2608 );
2609
2610 assert_eq!(
2611 traversal_results.reverse_from_d,
2612 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2613 );
2614
2615 assert_eq!(
2616 traversal_results.reverse_from_b,
2617 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2618 );
2619
2620 Ok(())
2621 })
2622 .await
2623 .unwrap();
2624 }
2625
2626 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2627 async fn test_iter_nodes_modules_through_layered_graph() {
2628 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2629 BackendOptions::default(),
2630 noop_backing_storage(),
2631 ));
2632 tt.run_once(async move {
2633 #[turbo_tasks::value]
2634 struct Results {
2635 iter_nodes: Vec<RcStr>,
2636 iter_modules: Vec<RcStr>,
2637 iter_nodes_single: Vec<Vec<RcStr>>,
2638 iter_modules_single: Vec<Vec<RcStr>>,
2639 }
2640
2641 #[turbo_tasks::function(operation, root)]
2642 async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2643 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2644 let root = fs.root().await?;
2645
2646 let repo = TestRepo::new_with_chunking_types(
2650 &root,
2651 [
2652 ("a.js", vec!["b.js"]),
2653 ("b.js", vec!["c.js"]),
2654 ("c.js", vec!["x.js"]),
2655 ("x.js", vec!["y.js", "traced.js"]),
2656 ("y.js", vec!["z.js"]),
2657 ],
2658 [(
2659 "x.js",
2660 "traced.js",
2661 ChunkingType::Traced {
2662 mode: TracedMode::Entry,
2663 },
2664 )],
2665 );
2666 let make_module = |name| {
2667 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2668 .to_resolved()
2669 };
2670 let x_module = make_module("x.js").await?;
2671 let a_module = make_module("a.js").await?;
2672
2673 let parent_graph = SingleModuleGraph::new_with_entries(
2674 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2675 modules: vec![x_module],
2676 heuristics: EntryHeuristics::default(),
2677 }])
2678 .resolved_cell(),
2679 true,
2680 false,
2681 );
2682
2683 let module_graph = ModuleGraph::from_graphs(
2684 vec![
2685 parent_graph,
2686 SingleModuleGraph::new_with_entries_visited(
2687 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2688 modules: vec![a_module],
2689 heuristics: EntryHeuristics::default(),
2690 }])
2691 .resolved_cell(),
2692 VisitedModules::from_graph(parent_graph),
2693 true,
2694 false,
2695 ),
2696 ],
2697 None,
2698 )
2699 .connect();
2700 let graph_layers = module_graph.iter_graphs().await?;
2701
2702 Ok(Results {
2703 iter_nodes: module_graph
2704 .await?
2705 .iter_reachable_nodes()?
2706 .map(async |node| {
2707 Ok(match node {
2708 SingleModuleGraphNode::Module(module) => {
2709 module.ident_string().owned().await?
2710 }
2711 SingleModuleGraphNode::VisitedModule { module, .. } => {
2712 format!("visited {}", module.ident_string().owned().await?)
2713 .into()
2714 }
2715 })
2716 })
2717 .try_join()
2718 .await?,
2719 iter_modules: module_graph
2720 .await?
2721 .iter_reachable_modules()?
2722 .map(|m| m.ident_string().owned())
2723 .try_join()
2724 .await?,
2725 iter_nodes_single: graph_layers
2726 .iter()
2727 .map(async |layer| {
2728 layer
2729 .connect()
2730 .await?
2731 .iter_reachable_nodes()?
2732 .map(async |node| {
2733 Ok(match node {
2734 SingleModuleGraphNode::Module(module) => {
2735 module.ident_string().owned().await?
2736 }
2737 SingleModuleGraphNode::VisitedModule { module, .. } => {
2738 format!(
2739 "visited {}",
2740 module.ident_string().owned().await?
2741 )
2742 .into()
2743 }
2744 })
2745 })
2746 .try_join()
2747 .await
2748 })
2749 .try_join()
2750 .await?,
2751 iter_modules_single: graph_layers
2752 .iter()
2753 .map(async |layer| {
2754 layer
2755 .connect()
2756 .await?
2757 .iter_reachable_modules()?
2758 .map(|m| m.ident_string().owned())
2759 .try_join()
2760 .await
2761 })
2762 .try_join()
2763 .await?,
2764 }
2765 .cell())
2766 }
2767
2768 let traversal_results = reverse_traversal_results_operation()
2769 .read_strongly_consistent()
2770 .await?;
2771
2772 assert_eq!(
2773 traversal_results.iter_nodes,
2774 vec![
2775 rcstr!("[test]/x.js"),
2776 rcstr!("[test]/a.js"),
2777 rcstr!("[test]/y.js"),
2778 rcstr!("[test]/b.js"),
2779 rcstr!("[test]/z.js"),
2780 rcstr!("[test]/c.js"),
2781 rcstr!("visited [test]/x.js")
2782 ]
2783 );
2784 assert_eq!(
2785 traversal_results.iter_modules,
2786 vec![
2787 rcstr!("[test]/x.js"),
2788 rcstr!("[test]/a.js"),
2789 rcstr!("[test]/y.js"),
2790 rcstr!("[test]/b.js"),
2791 rcstr!("[test]/z.js"),
2792 rcstr!("[test]/c.js")
2793 ]
2794 );
2795 assert_eq!(
2796 traversal_results.iter_nodes_single,
2797 vec![
2798 vec![
2799 rcstr!("[test]/x.js"),
2800 rcstr!("[test]/y.js"),
2801 rcstr!("[test]/z.js")
2802 ],
2803 vec![
2804 rcstr!("[test]/a.js"),
2805 rcstr!("[test]/b.js"),
2806 rcstr!("[test]/c.js"),
2807 rcstr!("visited [test]/x.js")
2808 ]
2809 ]
2810 );
2811 assert_eq!(
2812 traversal_results.iter_modules_single,
2813 vec![
2814 vec![
2815 rcstr!("[test]/x.js"),
2816 rcstr!("[test]/y.js"),
2817 rcstr!("[test]/z.js")
2818 ],
2819 vec![
2820 rcstr!("[test]/a.js"),
2821 rcstr!("[test]/b.js"),
2822 rcstr!("[test]/c.js")
2823 ]
2824 ]
2825 );
2826
2827 Ok(())
2828 })
2829 .await
2830 .unwrap();
2831 }
2832
2833 #[turbo_tasks::value(shared)]
2834 struct TestRepo {
2835 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2836 chunking_types: FxHashMap<(FileSystemPath, FileSystemPath), ChunkingType>,
2837 }
2838
2839 impl TestRepo {
2840 fn new(
2841 root: &FileSystemPath,
2842 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2843 ) -> Vc<Self> {
2844 Self::new_with_chunking_types(
2845 root,
2846 dependencies,
2847 std::iter::empty::<(RcStr, RcStr, ChunkingType)>(),
2848 )
2849 }
2850
2851 fn new_with_chunking_types(
2852 root: &FileSystemPath,
2853 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2854 chunking_types: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, ChunkingType)>,
2855 ) -> Vc<Self> {
2856 let chunking_types = chunking_types
2857 .into_iter()
2858 .map(|(from, to, ty)| {
2859 (
2860 (
2861 root.join(from.as_ref()).unwrap(),
2862 root.join(to.as_ref()).unwrap(),
2863 ),
2864 ty,
2865 )
2866 })
2867 .collect::<FxHashMap<_, _>>();
2868 Self {
2869 repo: dependencies
2870 .into_iter()
2871 .map(|(k, v)| {
2872 (
2873 root.join(k.as_ref()).unwrap(),
2874 v.iter().map(|f| root.join(f.as_ref()).unwrap()).collect(),
2875 )
2876 })
2877 .collect(),
2878 chunking_types,
2879 }
2880 .cell()
2881 }
2882 }
2883
2884 #[turbo_tasks::value]
2885 struct MockModule {
2886 path: FileSystemPath,
2887 repo: ResolvedVc<TestRepo>,
2888 }
2889 #[turbo_tasks::value_impl]
2890 impl MockModule {
2891 #[turbo_tasks::function]
2892 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2893 Self { path, repo }.cell()
2894 }
2895 }
2896
2897 #[turbo_tasks::value_impl]
2898 impl Asset for MockModule {
2899 #[turbo_tasks::function]
2900 fn content(&self) -> Vc<AssetContent> {
2901 panic!("MockModule::content shouldn't be called")
2902 }
2903 }
2904
2905 #[turbo_tasks::value_impl]
2906 impl Module for MockModule {
2907 #[turbo_tasks::function]
2908 fn ident(&self) -> Vc<AssetIdent> {
2909 AssetIdent::from_path(self.path.clone()).into_vc()
2910 }
2911
2912 #[turbo_tasks::function]
2913 fn source(&self) -> Vc<crate::source::OptionSource> {
2914 Vc::cell(None)
2915 }
2916
2917 #[turbo_tasks::function]
2918 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2919 let repo = self.repo.await?;
2920 let references = match repo.repo.get(&self.path) {
2921 Some(deps) => {
2922 deps.iter()
2923 .map(async |p| {
2924 Vc::upcast::<Box<dyn ModuleReference>>(MockModuleReference::new(
2925 ResolvedVc::upcast(
2926 MockModule::new(p.clone(), *self.repo).to_resolved().await?,
2927 ),
2928 rcstr!("normal-dep"),
2929 repo.chunking_types
2930 .get(&(self.path.clone(), p.clone()))
2931 .cloned()
2932 .unwrap_or(ChunkingType::Parallel {
2933 inherit_async: true,
2934 hoisted: false,
2935 }),
2936 ))
2937 .to_resolved()
2938 .await
2939 })
2940 .try_join()
2941 .await?
2942 }
2943 None => vec![],
2944 };
2945
2946 Ok(Vc::cell(references))
2947 }
2948 #[turbo_tasks::function]
2949 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2950 ModuleSideEffects::SideEffectful.cell()
2951 }
2952 }
2953
2954 #[turbo_tasks::value]
2955 #[derive(ValueToString)]
2956 #[value_to_string(self.description)]
2957 struct MockModuleReference {
2958 asset: ResolvedVc<Box<dyn Module>>,
2959 description: RcStr,
2960 chunking_type: ChunkingType,
2961 }
2962
2963 impl MockModuleReference {
2964 pub fn new(
2965 asset: ResolvedVc<Box<dyn Module>>,
2966 description: RcStr,
2967 chunking_type: ChunkingType,
2968 ) -> Vc<Self> {
2969 MockModuleReference {
2970 asset,
2971 description,
2972 chunking_type,
2973 }
2974 .cell()
2975 }
2976 }
2977
2978 #[turbo_tasks::value_impl]
2979 impl ModuleReference for MockModuleReference {
2980 #[turbo_tasks::function]
2981 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
2982 *ModuleResolveResult::module(self.asset)
2983 }
2984
2985 fn chunking_type(&self) -> Option<ChunkingType> {
2986 Some(self.chunking_type.clone())
2987 }
2988 }
2989
2990 async fn run_graph_test(
3004 entries: Vec<RcStr>,
3005 graph: FxHashMap<RcStr, Vec<RcStr>>,
3006 test_fn: impl FnOnce(
3007 &ModuleGraph,
3008 Vec<ResolvedVc<Box<dyn Module>>>,
3009 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3010 ) -> Result<()>
3011 + Send
3012 + 'static,
3013 ) {
3014 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
3015 struct SetupGraph {
3016 module_graph: ReadRef<ModuleGraph>,
3017 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
3018 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3019 }
3020
3021 #[turbo_tasks::function(operation, root)]
3022 async fn setup_graph(
3023 entries: Vec<RcStr>,
3024 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
3025 ) -> Result<Vc<SetupGraph>> {
3026 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
3027 let root = fs.root().await?;
3028
3029 let repo = TestRepo::new(&root, graph_entries);
3030 let entry_modules = entries
3031 .iter()
3032 .map(|e| {
3033 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
3034 .to_resolved()
3035 })
3036 .try_join()
3037 .await?;
3038 let graph = SingleModuleGraph::new_with_entries(
3039 GraphEntries::resolved_cell(GraphEntries::new(
3040 vec![ChunkGroupEntry::Entry {
3041 modules: entry_modules.clone(),
3042 heuristics: EntryHeuristics::default(),
3043 }],
3044 vec![],
3045 )),
3046 false,
3047 false,
3048 );
3049
3050 let module_to_name = graph
3054 .connect()
3055 .await?
3056 .modules
3057 .keys()
3058 .map(async |m| Ok((*m, m.ident().await?.path.path.clone())))
3059 .try_join()
3060 .await?
3061 .into_iter()
3062 .collect();
3063 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
3064 .connect()
3065 .await?;
3066
3067 Ok(SetupGraph {
3068 module_graph,
3069 entry_modules,
3070 module_to_name,
3071 }
3072 .cell())
3073 }
3074
3075 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3076 BackendOptions::default(),
3077 noop_backing_storage(),
3078 ));
3079 let graph_entries = graph.into_iter().collect::<Vec<_>>();
3080 tt.run_once(async move {
3081 let setup = setup_graph(entries, graph_entries)
3082 .read_strongly_consistent()
3083 .await?;
3084
3085 test_fn(
3086 &setup.module_graph,
3087 setup.entry_modules.clone(),
3088 setup.module_to_name.clone(),
3089 )
3090 })
3091 .await
3092 .unwrap();
3093 }
3094}