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 keyed::KeyedAccess,
26};
27use turbo_tasks_fs::FileSystemPath;
28
29use crate::{
30 chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType, TracedMode},
31 issue::{ImportTracer, ImportTraces, Issue, IssueExt, IssueSeverity, analyze::AnalyzeIssue},
32 module::Module,
33 module_graph::{
34 async_module_info::{AsyncModulesInfo, compute_async_module_info},
35 binding_usage_info::{BindingUsageInfo, UnusedReferenceEdges},
36 chunk_group_info::{ChunkGroupEntry, ChunkGroupInfo, compute_chunk_group_info},
37 collect::{CollectedModules, collect_graph},
38 merged_modules::{MergedModuleInfo, compute_merged_modules},
39 module_batches::{ModuleBatchesGraph, compute_module_batches},
40 style_groups::{StyleGroups, StyleGroupsAlgorithm, StyleGroupsConfig},
41 style_groups_graph::compute_style_groups_graph,
42 style_groups_loose::compute_style_groups,
43 traced_di_graph::TracedDiGraph,
44 },
45 reference::{
46 ModuleReference, primary_chunkable_referenced_modules,
47 referenced_modules_and_affecting_sources,
48 },
49 resolve::BindingUsage,
50};
51
52pub mod async_module_info;
53pub mod binding_usage_info;
54pub mod chunk_group_info;
55pub mod collect;
56pub mod merged_modules;
57pub mod module_batch;
58pub(crate) mod module_batches;
59mod side_effect_module_info;
60pub mod style_groups;
61pub mod style_groups_graph;
62pub mod style_groups_loose;
63mod traced_di_graph;
64
65pub use self::module_batches::BatchingConfig;
66
67#[derive(
68 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, Serialize, Deserialize, Encode, Decode,
69)]
70pub struct GraphNodeIndex {
71 graph_idx: u32,
72 #[bincode(with_serde)]
73 node_idx: NodeIndex,
74}
75impl GraphNodeIndex {
76 fn new(graph_idx: u32, node_idx: NodeIndex) -> Self {
77 Self {
78 graph_idx,
79 node_idx,
80 }
81 }
82}
83
84unsafe impl NonLocalValue for GraphNodeIndex {}
85
86#[derive(
87 Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash, PartialEq, NonLocalValue, Encode, Decode,
88)]
89pub struct GraphEdgeIndex {
90 graph_idx: u32,
91 #[turbo_tasks(unsafe_ignore)]
92 #[bincode(with_serde)]
93 edge_idx: EdgeIndex,
94}
95
96impl GraphEdgeIndex {
97 fn new(graph_idx: u32, edge_idx: EdgeIndex) -> Self {
98 Self {
99 graph_idx,
100 edge_idx,
101 }
102 }
103}
104
105#[turbo_tasks::value]
106#[derive(Clone, Debug)]
107pub struct VisitedModules {
108 #[bincode(with = "turbo_bincode::indexmap")]
109 pub modules: FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
110 next_graph_idx: u32,
111}
112
113#[turbo_tasks::value_impl]
114impl VisitedModules {
115 #[turbo_tasks::function(operation)]
116 pub fn empty() -> Vc<Self> {
117 Self {
118 modules: Default::default(),
119 next_graph_idx: 0,
120 }
121 .cell()
122 }
123
124 #[turbo_tasks::function(operation)]
125 pub async fn from_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
126 Ok(Self {
127 modules: graph
128 .connect()
129 .await?
130 .enumerate_nodes()
131 .flat_map(|(node_idx, module)| match module {
132 SingleModuleGraphNode::Module(module) => Some((
133 *module,
134 GraphNodeIndex {
135 graph_idx: 0,
136 node_idx,
137 },
138 )),
139 SingleModuleGraphNode::VisitedModule { .. } => None,
140 })
141 .collect(),
142 next_graph_idx: 1,
143 }
144 .cell())
145 }
146
147 #[turbo_tasks::function(operation)]
148 pub async fn with_incremented_index(this: OperationVc<Self>) -> Result<Vc<Self>> {
149 let this = this.connect().await?;
150 Ok(Self {
151 modules: this.modules.clone(),
152 next_graph_idx: this.next_graph_idx + 1,
153 }
154 .cell())
155 }
156
157 #[turbo_tasks::function(operation)]
158 pub async fn concatenate(
159 this: OperationVc<Self>,
160 graph: OperationVc<SingleModuleGraph>,
161 ) -> Result<Vc<Self>> {
162 let graph = graph.connect().await?;
163 let this = this.connect().await?;
164 let iter = this
165 .modules
166 .iter()
167 .map(|(module, idx)| (*module, *idx))
168 .chain(
169 graph
170 .enumerate_nodes()
171 .flat_map(|(node_idx, module)| match module {
172 SingleModuleGraphNode::Module(module) => Some((
173 *module,
174 GraphNodeIndex {
175 graph_idx: this.next_graph_idx,
176 node_idx,
177 },
178 )),
179 SingleModuleGraphNode::VisitedModule { .. } => None,
180 }),
181 );
182
183 let mut map = FxIndexMap::with_capacity_and_hasher(
184 this.modules.len() + graph.number_of_modules,
185 Default::default(),
186 );
187 for (k, v) in iter {
188 map.entry(k).or_insert(v);
189 }
190 map.shrink_to_fit();
191
192 Ok(Self {
193 modules: map,
194 next_graph_idx: this.next_graph_idx + 1,
195 }
196 .cell())
197 }
198}
199
200#[turbo_tasks::value(shared, task_input)]
201#[derive(Debug, Clone, Hash, Default)]
202pub struct GraphEntries {
203 chunk_groups: Vec<ChunkGroupEntry>,
205 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
208}
209
210#[turbo_tasks::value_impl]
211impl GraphEntries {
212 #[turbo_tasks::function]
213 pub fn empty() -> Vc<Self> {
214 Self::default().cell()
215 }
216}
217
218impl GraphEntries {
219 pub fn new(
220 chunk_groups: Vec<ChunkGroupEntry>,
221 traced_modules: Vec<ResolvedVc<Box<dyn Module>>>,
222 ) -> Self {
223 Self {
224 chunk_groups,
225 traced_modules,
226 }
227 }
228 pub fn from_chunk_groups(chunk_groups: Vec<ChunkGroupEntry>) -> Self {
229 Self {
230 chunk_groups,
231 traced_modules: vec![],
232 }
233 }
234
235 pub fn concatenate(entries: impl IntoIterator<Item = GraphEntries>) -> Self {
236 let (chunk_groups, traced_modules): (Vec<_>, Vec<_>) = entries
237 .into_iter()
238 .map(|e| (e.chunk_groups, e.traced_modules))
239 .unzip();
240 Self {
241 chunk_groups: chunk_groups.into_iter().flatten().collect(),
242 traced_modules: traced_modules.into_iter().flatten().collect(),
243 }
244 }
245
246 pub fn all_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
248 self.chunk_groups
249 .iter()
250 .flat_map(|e| e.entries())
251 .chain(self.traced_modules.iter().cloned())
252 }
253
254 pub fn all_modules_with_is_traced(
256 &self,
257 ) -> impl Iterator<Item = (ResolvedVc<Box<dyn Module>>, bool)> + '_ {
258 self.chunk_groups
259 .iter()
260 .flat_map(|e| e.entries().map(|m| (m, false)))
261 .chain(self.traced_modules.iter().cloned().map(|m| (m, true)))
262 }
263
264 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
266 self.chunk_groups.iter().flat_map(|e| e.entries())
267 }
268}
269
270#[turbo_tasks::value(cell = "new", eq = "manual")]
271#[derive(Default)]
272pub struct SingleModuleGraph {
273 pub graph: TracedDiGraph<SingleModuleGraphNode, RefData>,
274
275 pub number_of_modules: usize,
277
278 #[turbo_tasks(unsafe_ignore)]
285 #[bincode(with_serde)]
286 modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
287
288 entries: GraphEntries,
289
290 #[turbo_tasks(debug_ignore, unsafe_ignore)]
293 #[bincode(skip, default = "OnceLock::new")]
294 entry_nodes: OnceLock<FxHashSet<NodeIndex>>,
295}
296
297impl Clone for SingleModuleGraph {
298 fn clone(&self) -> Self {
299 Self {
300 graph: self.graph.clone(),
301 number_of_modules: self.number_of_modules,
302 modules: self.modules.clone(),
303 entries: self.entries.clone(),
304 entry_nodes: OnceLock::new(),
306 }
307 }
308}
309
310#[derive(
311 Debug,
312 Clone,
313 Hash,
314 Serialize,
315 Deserialize,
316 Eq,
317 PartialEq,
318 ValueDebugFormat,
319 NonLocalValue,
320 Encode,
321 Decode,
322)]
323pub struct RefData {
324 pub chunking_type: ChunkingType,
325 pub binding_usage: BindingUsage,
326 pub reference: ResolvedVc<Box<dyn ModuleReference>>,
327}
328
329impl SingleModuleGraph {
330 async fn new_inner(
334 entries: &GraphEntries,
335 visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
336 include_traced: bool,
337 include_binding_usage: bool,
338 ) -> Result<Vc<Self>> {
339 let emit_spans = tracing::enabled!(Level::INFO);
340 let root_nodes = entries
341 .all_modules_with_is_traced()
342 .map(|(e, is_traced)| {
343 SingleModuleGraphBuilderNode::new_module(emit_spans, e, is_traced)
344 })
345 .try_join()
346 .await?;
347
348 let children_nodes_iter = AdjacencyMap::new()
349 .visit(
350 root_nodes,
351 SingleModuleGraphBuilder {
352 visited_modules,
353 emit_spans,
354 include_traced,
355 include_binding_usage,
356 },
357 )
358 .await
359 .completed()?;
360 let node_count = children_nodes_iter.len();
361
362 let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
363 node_count,
364 node_count * 4,
367 );
368
369 let mut number_of_modules = 0;
370 let mut modules: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
371 FxHashMap::with_capacity_and_hasher(node_count, Default::default());
372 {
373 let _span = tracing::info_span!("build module graph").entered();
374 for (parent, current) in children_nodes_iter.into_breadth_first_edges() {
375 let (module, graph_node, count) = match current {
376 SingleModuleGraphBuilderNode::Module {
377 module,
378 is_traced: _,
379 ident: _,
380 } => (module, SingleModuleGraphNode::Module(module), 1),
381 SingleModuleGraphBuilderNode::VisitedModule { module, idx } => (
382 module,
383 SingleModuleGraphNode::VisitedModule { idx, module },
384 0,
385 ),
386 };
387
388 let current_idx = if let Some(current_idx) = modules.get(&module) {
390 *current_idx
391 } else {
392 let idx = graph.add_node(graph_node);
393 number_of_modules += count;
394 modules.insert(module, idx);
395 idx
396 };
397 if let Some((SingleModuleGraphBuilderNode::Module { module, .. }, ref_data)) =
399 parent
400 {
401 let parent_idx = *modules.get(&module).unwrap();
402 graph.add_edge(parent_idx, current_idx, ref_data);
403 }
404 }
405 }
406
407 graph.shrink_to_fit();
408
409 #[cfg(debug_assertions)]
410 {
411 use std::sync::LazyLock;
412 static CHECK_FOR_DUPLICATE_MODULES: LazyLock<bool> = LazyLock::new(|| {
413 match std::env::var_os("TURBOPACK_TEMP_DISABLE_DUPLICATE_MODULES_CHECK") {
414 Some(v) => v != "1" && v != "true",
415 None => true,
416 }
417 });
418 if *CHECK_FOR_DUPLICATE_MODULES {
419 let mut duplicates = FxHashSet::default();
420 let mut set = FxHashSet::default();
421 for &module in modules.keys() {
422 let ident = module.ident().to_string().await?;
423 if !set.insert(ident.clone()) {
424 duplicates.insert(ident);
425 }
426 }
427 if !duplicates.is_empty() {
428 use turbo_tasks::TryFlatJoinIterExt;
429
430 let duplicates_clone = duplicates.clone();
431 let duplicate_modules = modules
432 .iter()
433 .map(async |(&m, &idx)| {
434 let id = m.ident().to_string().await?;
435 if duplicates_clone.contains(&id) {
436 let debug = m.value_debug_format(3).try_to_string().await?;
439
440 let parent_modules: Vec<_> = graph
443 .edges_directed(idx, petgraph::Direction::Incoming)
444 .filter_map(|edge| match graph.node_weight(edge.source()) {
445 Some(SingleModuleGraphNode::Module(m)) => Some(*m),
446 Some(SingleModuleGraphNode::VisitedModule {
447 module,
448 ..
449 }) => Some(*module),
450 None => None,
451 })
452 .collect();
453 let parents: Vec<String> = parent_modules
454 .iter()
455 .map(async |p| {
456 let ident = p.ident().to_string().await?;
457 Ok((*ident).to_string())
458 })
459 .try_join()
460 .await?;
461
462 Ok(Some((id, debug, parents)))
463 } else {
464 Ok(None)
465 }
466 })
467 .try_flat_join()
468 .await?;
469 let mut map: FxHashMap<_, Vec<(String, Vec<String>)>> = FxHashMap::default();
471 for (key, debug, parents) in duplicate_modules {
472 map.entry(key).or_default().push((debug, parents));
473 }
474 let result = map
475 .into_iter()
476 .map(|(ident, modules)| {
477 let modules = modules
478 .into_iter()
479 .map(|(debug, parents)| {
480 format!("Module: {debug}, Parents: {parents:?}")
481 })
482 .collect::<Vec<_>>()
483 .join("\n");
484 format!("Ident: {ident}\n{modules}")
485 })
486 .collect::<Vec<_>>()
487 .join("\n\n");
488 bail!("Duplicate module idents in graph: {result}");
489 }
490 }
491 }
492
493 let graph = SingleModuleGraph {
494 graph: TracedDiGraph::new(graph),
495 number_of_modules,
496 modules,
497 entries: entries.clone(),
498 entry_nodes: OnceLock::new(),
499 }
500 .cell();
501
502 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ImportTracer>>(
503 ModuleGraphImportTracer::new(graph).to_resolved().await?,
504 ));
505 Ok(graph)
506 }
507
508 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
511 self.graph.node_weights().filter_map(|n| match n {
512 SingleModuleGraphNode::Module(node) => Some(*node),
513 SingleModuleGraphNode::VisitedModule { .. } => None,
514 })
515 }
516
517 fn entry_nodes(&self) -> &FxHashSet<NodeIndex> {
518 self.entry_nodes.get_or_init(|| {
519 self.entries
520 .all_modules()
521 .filter_map(|module| self.modules.get(&module).copied())
522 .collect()
523 })
524 }
525
526 pub fn has_entry_module(&self, module: ResolvedVc<Box<dyn Module>>) -> bool {
531 self.modules
532 .get(&module)
533 .is_some_and(|index| self.entry_nodes().contains(index))
534 }
535
536 pub fn chunk_group_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
538 self.entries.chunk_group_modules()
539 }
540
541 pub fn enumerate_nodes(
544 &self,
545 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
546 self.graph.node_references()
547 }
548
549 fn traverse_cycles<'l>(
550 &'l self,
551 edge_filter: impl Fn(&'l RefData) -> bool,
552 mut visit_cycle: impl FnMut(&[&'l ResolvedVc<Box<dyn Module>>]) -> Result<()>,
553 graph_idx: u32,
554 unused_references_edges: &'l Option<ReadRef<UnusedReferenceEdges>>,
555 ) -> Result<()> {
556 #[derive(Clone)]
563 struct NodeState {
564 index: u32,
565 lowlink: u32,
566 on_stack: bool,
567 has_self_loop: bool,
568 }
569 enum VisitStep {
570 UnvisitedNode(NodeIndex),
571 EdgeAfterVisit { parent: NodeIndex, child: NodeIndex },
572 AfterVisit(NodeIndex),
573 }
574 let mut node_states = vec![None; self.graph.node_bound()];
575 let mut stack = Vec::new();
576 let mut visit_stack = Vec::new();
577 let mut index = 0;
578 let mut scc = Vec::new();
579 for initial_index in self.graph.node_indices() {
580 if node_states[initial_index.index()].is_some() {
582 continue;
583 }
584 visit_stack.push(VisitStep::UnvisitedNode(initial_index));
585 while let Some(step) = visit_stack.pop() {
586 match step {
587 VisitStep::UnvisitedNode(node) => {
588 node_states[node.index()] = Some(NodeState {
589 index,
590 lowlink: index,
591 on_stack: true,
592 has_self_loop: false,
593 });
594 index += 1;
595 stack.push(node);
596 visit_stack.push(VisitStep::AfterVisit(node));
597 let mut neighbors = self.graph.neighbors(node).detach();
598 while let Some((edge, succ)) = neighbors.next(&self.graph) {
599 if unused_references_edges.as_ref().is_some_and(|edges| {
600 edges.contains_key(&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 unused_references_edges: if let Some(binding_usage) = binding_usage {
840 Some(binding_usage.connect().unused_references_edges().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, 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 unused_references_edges: if let Some(binding_usage) = binding_usage {
980 Some(binding_usage.connect().unused_references_edges().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 unused_references_edges: Option<ReadRef<UnusedReferenceEdges>>,
1024}
1025
1026impl ModuleGraphSnapshot {
1027 fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<GraphNodeIndex> {
1028 if self.graph_idx_override.is_some() {
1029 debug_assert_eq!(self.graphs.len(), 1,);
1030 }
1031
1032 let Some(idx) = self
1033 .graphs
1034 .iter()
1035 .enumerate()
1036 .find_map(|(graph_idx, graph)| {
1037 graph.modules.get(&entry).map(|node_idx| GraphNodeIndex {
1038 graph_idx: self.graph_idx_override.unwrap_or(graph_idx as u32),
1039 node_idx: *node_idx,
1040 })
1041 })
1042 else {
1043 bail!("Couldn't find entry module {entry:?} in module graph");
1044 };
1045 Ok(idx)
1046 }
1047
1048 pub fn all_chunk_group_entries(&self) -> impl Iterator<Item = &ChunkGroupEntry> + '_ {
1050 self.graphs
1051 .iter()
1052 .flat_map(|g| g.entries.chunk_groups.iter())
1053 }
1054
1055 pub fn all_chunk_group_entry_modules(
1057 &self,
1058 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1059 self.graphs
1060 .iter()
1061 .flat_map(|g| g.entries.chunk_group_modules())
1062 }
1063
1064 pub fn all_entry_modules(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1066 self.graphs.iter().flat_map(|g| g.entries.all_modules())
1067 }
1068
1069 fn get_graph(&self, graph_idx: u32) -> &ReadRef<SingleModuleGraph> {
1070 if self.graph_idx_override.is_some() {
1071 self.graphs.first().unwrap()
1072 } else {
1073 &self.graphs[graph_idx as usize]
1074 }
1075 }
1076
1077 fn get_node(&self, node: GraphNodeIndex) -> Result<&SingleModuleGraphNode> {
1078 let graph = self.get_graph(node.graph_idx);
1079 graph
1080 .graph
1081 .node_weight(node.node_idx)
1082 .context("Expected graph node")
1083 }
1084
1085 fn get_edge(&self, edge: GraphEdgeIndex) -> Result<&RefData> {
1086 let graph = self.get_graph(edge.graph_idx);
1087 graph
1088 .graph
1089 .edge_weight(edge.edge_idx)
1090 .context("Expected graph node")
1091 }
1092
1093 fn should_visit_node(&self, node: &SingleModuleGraphNode, direction: Direction) -> bool {
1094 if self.skip_visited_module_children && direction == Direction::Outgoing {
1095 !matches!(node, SingleModuleGraphNode::VisitedModule { .. })
1096 } else {
1097 true
1098 }
1099 }
1100
1101 pub fn enumerate_nodes(
1104 &self,
1105 ) -> impl Iterator<Item = (NodeIndex, &'_ SingleModuleGraphNode)> + '_ {
1106 self.graphs.iter().flat_map(|g| g.enumerate_nodes())
1107 }
1108
1109 pub fn iter_nodes(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
1112 self.graphs.iter().flat_map(|g| g.iter_nodes())
1113 }
1114
1115 fn iter_graphs_neighbors_rev<'a>(
1117 &'a self,
1118 node: GraphNodeIndex,
1119 direction: Direction,
1120 include_traced: bool,
1121 include_unused: bool,
1122 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1123 let graph = &*self.get_graph(node.graph_idx).graph;
1124
1125 if cfg!(debug_assertions) && direction == Direction::Outgoing {
1126 let node_weight = graph.node_weight(node.node_idx).unwrap();
1127 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1128 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1129 }
1130 }
1131
1132 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1133 std::iter::from_fn(move || {
1134 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1135 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1136 if !include_unused
1137 && self
1138 .unused_references_edges
1139 .as_ref()
1140 .is_some_and(|edges| edges.contains_key(&edge_idx))
1141 {
1142 continue;
1144 }
1145
1146 if !include_traced && self.get_edge(edge_idx).unwrap().chunking_type.is_traced() {
1147 continue;
1148 }
1149
1150 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1151 }
1152 None
1153 })
1154 }
1155
1156 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1159 self.iter_nodes()
1160 .map(async |n| Ok((n, n.ident().to_string().await?)))
1161 .join()
1162 .await
1163 .into_iter()
1164 .collect()
1165 }
1166
1167 pub fn traverse_nodes_dfs<S>(
1176 &self,
1177 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1178 state: &mut S,
1179 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1180 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1181 ) -> Result<()> {
1182 let entries = entries.into_iter().collect::<Vec<_>>();
1183
1184 enum Pass {
1185 Visit,
1186 ExpandAndVisit,
1187 }
1188 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1189 for entry in entries.into_iter().rev() {
1190 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1191 }
1192 let mut expanded = FxHashSet::default();
1193 while let Some((pass, current)) = stack.pop() {
1194 let current_node = self.get_node(current)?;
1195 match pass {
1196 Pass::Visit => {
1197 visit_postorder(current_node.module(), state)?;
1198 }
1199 Pass::ExpandAndVisit => {
1200 if !expanded.insert(current) {
1201 continue;
1202 }
1203 let action = visit_preorder(current_node.module(), state)?;
1204 if action == GraphTraversalAction::Exclude {
1205 continue;
1206 }
1207 stack.push((Pass::Visit, current));
1208 if action == GraphTraversalAction::Continue
1209 && self.should_visit_node(current_node, Direction::Outgoing)
1210 {
1211 let current = current_node
1212 .target_idx(Direction::Outgoing)
1213 .unwrap_or(current);
1214 stack.extend(
1215 self.iter_graphs_neighbors_rev(
1216 current,
1217 Direction::Outgoing,
1218 false,
1219 false,
1220 )
1221 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1222 );
1223 }
1224 }
1225 }
1226 }
1227
1228 Ok(())
1229 }
1230
1231 pub fn traverse_edges_bfs(
1242 &self,
1243 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1244 mut visitor: impl FnMut(
1245 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1246 ResolvedVc<Box<dyn Module>>,
1247 ) -> Result<GraphTraversalAction>,
1248 ) -> Result<()> {
1249 let mut queue = VecDeque::from(
1250 entries
1251 .into_iter()
1252 .map(|e| self.get_entry(e))
1253 .collect::<Result<Vec<_>>>()?,
1254 );
1255 let mut visited = FxHashSet::default();
1256 for entry_node in &queue {
1257 visitor(None, self.get_node(*entry_node)?.module())?;
1258 }
1259 while let Some(node) = queue.pop_front() {
1260 if visited.insert(node) {
1261 let node_weight = self.get_node(node)?;
1262 for (edge, succ) in
1263 self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1264 {
1265 let succ_weight = self.get_node(succ)?;
1266 let action = visitor(
1267 Some((node_weight.module(), self.get_edge(edge)?)),
1268 succ_weight.module(),
1269 )?;
1270 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1271 continue;
1272 }
1273 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1274 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1275 queue.push_back(succ);
1276 }
1277 }
1278 }
1279 }
1280
1281 Ok(())
1282 }
1283
1284 pub fn traverse_edges_unordered(
1293 &self,
1294 mut visitor: impl FnMut(
1295 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1296 ResolvedVc<Box<dyn Module>>,
1297 ) -> Result<()>,
1298 ) -> Result<()> {
1299 self.traverse_edges_dfs(
1302 self.all_chunk_group_entry_modules(),
1303 &mut (),
1304 |parent, target, _| {
1305 visitor(parent, target)?;
1306 Ok(GraphTraversalAction::Continue)
1307 },
1308 |_, _, _| Ok(()),
1309 false,
1310 false,
1311 )
1312 }
1313
1314 pub fn traverse_edges_dfs<S>(
1335 &self,
1336 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1337 state: &mut S,
1338 visit_preorder: impl FnMut(
1339 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1340 ResolvedVc<Box<dyn Module>>,
1341 &mut S,
1342 ) -> Result<GraphTraversalAction>,
1343 visit_postorder: impl FnMut(
1344 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1345 ResolvedVc<Box<dyn Module>>,
1346 &mut S,
1347 ) -> Result<()>,
1348 include_traced: bool,
1349 include_unused: bool,
1350 ) -> Result<()> {
1351 self.traverse_edges_dfs_impl::<S>(
1352 entries,
1353 state,
1354 visit_preorder,
1355 visit_postorder,
1356 Direction::Outgoing,
1357 include_traced,
1358 include_unused,
1359 )
1360 }
1361
1362 pub fn traverse_edges_reverse_dfs<S>(
1379 &self,
1380 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1381 state: &mut S,
1382 visit_preorder: impl FnMut(
1383 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1384 ResolvedVc<Box<dyn Module>>,
1385 &mut S,
1386 ) -> Result<GraphTraversalAction>,
1387 visit_postorder: impl FnMut(
1388 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1389 ResolvedVc<Box<dyn Module>>,
1390 &mut S,
1391 ) -> Result<()>,
1392 ) -> Result<()> {
1393 self.traverse_edges_dfs_impl::<S>(
1394 entries,
1395 state,
1396 visit_preorder,
1397 visit_postorder,
1398 Direction::Incoming,
1399 false,
1400 false,
1401 )
1402 }
1403
1404 fn traverse_edges_dfs_impl<S>(
1405 &self,
1406 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1407 state: &mut S,
1408 mut visit_preorder: impl FnMut(
1409 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1410 ResolvedVc<Box<dyn Module>>,
1411 &mut S,
1412 ) -> Result<GraphTraversalAction>,
1413 mut visit_postorder: impl FnMut(
1414 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1415 ResolvedVc<Box<dyn Module>>,
1416 &mut S,
1417 ) -> Result<()>,
1418 direction: Direction,
1419 include_traced: bool,
1420 include_unused: bool,
1421 ) -> Result<()> {
1422 if direction == Direction::Incoming {
1423 debug_assert!(
1424 self.skip_visited_module_children,
1425 "Can only trace reverse edges in a single layer graph. We do not model cross \
1426 graph reverse edges"
1427 );
1428 }
1429 let entries = entries.into_iter().collect::<Vec<_>>();
1430
1431 enum Pass {
1432 Visit,
1433 ExpandAndVisit,
1434 }
1435 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1437 Pass,
1438 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1439 GraphNodeIndex,
1440 )> = Vec::with_capacity(entries.len());
1441 for entry in entries.into_iter().rev() {
1442 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1443 }
1444 let mut expanded = FxHashSet::default();
1445 while let Some((pass, parent, current)) = stack.pop() {
1446 let parent_arg = match parent {
1447 Some((parent_node, parent_edge)) => Some((
1448 self.get_node(parent_node)?.module(),
1449 self.get_edge(parent_edge)?,
1450 )),
1451 None => None,
1452 };
1453 let current_node = self.get_node(current)?;
1454 match pass {
1455 Pass::Visit => {
1456 visit_postorder(parent_arg, current_node.module(), state)?;
1457 }
1458 Pass::ExpandAndVisit => {
1459 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1460 if action == GraphTraversalAction::Exclude {
1461 continue;
1462 }
1463 stack.push((Pass::Visit, parent, current));
1464 if action == GraphTraversalAction::Continue
1465 && expanded.insert(current)
1466 && self.should_visit_node(current_node, direction)
1467 {
1468 let current = current_node.target_idx(direction).unwrap_or(current);
1469 stack.extend(
1470 self.iter_graphs_neighbors_rev(
1471 current,
1472 direction,
1473 include_traced,
1474 include_unused,
1475 )
1476 .map(|(edge, child)| {
1477 (Pass::ExpandAndVisit, Some((current, edge)), child)
1478 }),
1479 );
1480 }
1481 }
1482 }
1483 }
1484
1485 Ok(())
1486 }
1487
1488 pub fn traverse_cycles(
1492 &self,
1493 edge_filter: impl Fn(&RefData) -> bool,
1494 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1495 ) -> Result<()> {
1496 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1497 graph.traverse_cycles(
1498 &edge_filter,
1499 &mut visit_cycle,
1500 graph_idx as u32,
1501 &self.unused_references_edges,
1502 )?;
1503 }
1504 Ok(())
1505 }
1506
1507 pub fn reverse_postorder_ranks(
1524 &self,
1525 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1526 ) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, usize>> {
1527 let canonical = |node: GraphNodeIndex| -> Result<GraphNodeIndex> {
1530 Ok(self
1531 .get_node(node)?
1532 .target_idx(Direction::Outgoing)
1533 .unwrap_or(node))
1534 };
1535
1536 let mut visited: FxHashSet<GraphNodeIndex> = FxHashSet::default();
1537 let mut postorder: Vec<GraphNodeIndex> = Vec::new();
1538 for entry in entries {
1539 let entry = canonical(self.get_entry(entry)?)?;
1540 if !visited.insert(entry) {
1541 continue;
1542 }
1543 let mut stack = vec![(
1544 entry,
1545 self.iter_graphs_neighbors_rev(entry, Direction::Outgoing, false, false),
1546 )];
1547 while let Some((node, children)) = stack.last_mut() {
1548 if let Some((_, child)) = children.next() {
1549 let child = canonical(child)?;
1550 if visited.insert(child) {
1551 stack.push((
1552 child,
1553 self.iter_graphs_neighbors_rev(
1554 child,
1555 Direction::Outgoing,
1556 false,
1557 false,
1558 ),
1559 ));
1560 }
1561 } else {
1562 postorder.push(*node);
1563 stack.pop();
1564 }
1565 }
1566 }
1567
1568 let count = postorder.len();
1569 postorder
1570 .into_iter()
1571 .enumerate()
1572 .map(|(i, node)| Ok((self.get_node(node)?.module(), count - 1 - i)))
1573 .collect()
1574 }
1575
1576 pub fn traverse_edges_fixed_point_with_priority<'graph, S, P: Ord>(
1599 &'graph self,
1600 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1601 state: &mut S,
1602 mut visit: impl FnMut(
1603 Option<(ResolvedVc<Box<dyn Module>>, &'graph RefData, GraphEdgeIndex)>,
1604 ResolvedVc<Box<dyn Module>>,
1605 GraphNodeIndex,
1606 &mut S,
1607 ) -> Result<GraphTraversalAction>,
1608 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1609 ) -> Result<usize> {
1610 if self.skip_visited_module_children {
1611 panic!(
1612 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1613 );
1614 }
1615
1616 let mut visit_order = 0usize;
1617 let mut order = || {
1618 let order = visit_order;
1619 visit_order += 1;
1620 order
1621 };
1622 #[derive(PartialEq, Eq)]
1623 struct NodeWithPriority<T: Ord> {
1624 node: GraphNodeIndex,
1625 priority: T,
1626 visit_order: usize,
1627 }
1628 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1629 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1630 Some(self.cmp(other))
1631 }
1632 }
1633 impl<T: Ord> Ord for NodeWithPriority<T> {
1634 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1635 self.priority
1638 .cmp(&other.priority)
1639 .then(self.visit_order.cmp(&other.visit_order))
1642 }
1643 }
1644
1645 let mut queue_set = FxHashSet::default();
1646 let mut queue = BinaryHeap::new();
1647 for (m, priority) in entries {
1648 let node = self.get_entry(m)?;
1649 if queue_set.insert(node) {
1653 queue.push(NodeWithPriority {
1654 node,
1655 priority,
1656 visit_order: order(),
1657 });
1658 }
1659 }
1660
1661 for entry_node in &queue {
1662 visit(
1663 None,
1664 self.get_node(entry_node.node)?.module(),
1665 entry_node.node,
1666 state,
1667 )?;
1668 }
1669
1670 let mut visit_count = 0usize;
1671 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1672 queue_set.remove(&node);
1673 let node_weight = self.get_node(node)?;
1674 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1675
1676 visit_count += 1;
1677
1678 for (edge, succ) in
1679 self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1680 {
1681 let succ_weight = self.get_node(succ)?;
1682
1683 let action = visit(
1684 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1685 succ_weight.module(),
1686 succ,
1687 state,
1688 )?;
1689
1690 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1691 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1692 queue.push(NodeWithPriority {
1693 node: succ,
1694 priority: priority(succ_weight.module(), state)?,
1695 visit_order: order(),
1696 });
1697 }
1698 }
1699 }
1700
1701 Ok(visit_count)
1702 }
1703
1704 pub fn iter_reachable_modules(
1706 &self,
1707 ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1708 Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1709 SingleModuleGraphNode::Module(m) => Some(*m),
1710 SingleModuleGraphNode::VisitedModule { .. } => None,
1711 }))
1712 }
1713
1714 pub fn iter_reachable_nodes<'a>(
1717 &'a self,
1718 ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1719 ModuleGraphSnapshotNodeIterator::new(self)
1720 }
1721}
1722
1723struct ModuleGraphSnapshotNodeIterator<'a> {
1724 graph: &'a ModuleGraphSnapshot,
1725 visited: FxHashSet<GraphNodeIndex>,
1726 visit_queue: VecDeque<GraphNodeIndex>,
1727}
1728
1729impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1730 fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1731 let entries = graph
1732 .graphs
1733 .iter()
1734 .flat_map(|g| g.chunk_group_modules())
1735 .map(|e| graph.get_entry(e))
1736 .collect::<Result<VecDeque<_>>>()?;
1737
1738 Ok(Self {
1739 graph,
1740 visited: FxHashSet::default(),
1741 visit_queue: entries,
1742 })
1743 }
1744}
1745impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1746 type Item = &'a SingleModuleGraphNode;
1747
1748 fn next(&mut self) -> Option<Self::Item> {
1749 while let Some(node_idx) = self.visit_queue.pop_front() {
1750 if self.visited.insert(node_idx) {
1751 let node_weight = self.graph.get_node(node_idx).unwrap();
1752 if self
1753 .graph
1754 .should_visit_node(node_weight, Direction::Outgoing)
1755 {
1756 let node = node_weight
1757 .target_idx(Direction::Outgoing)
1758 .unwrap_or(node_idx);
1759 self.visit_queue.extend(
1760 self.graph
1761 .iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1762 .map(|(_, succ)| succ)
1763 .filter(|succ| !self.visited.contains(succ)),
1764 );
1765 }
1766 return Some(node_weight);
1767 }
1768 }
1769 None
1770 }
1771}
1772impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1773
1774#[turbo_tasks::value_impl]
1775impl SingleModuleGraph {
1776 #[turbo_tasks::function(operation)]
1777 pub async fn new_with_entry(
1778 entry: ChunkGroupEntry,
1779 include_traced: bool,
1780 include_binding_usage: bool,
1781 ) -> Result<Vc<Self>> {
1782 SingleModuleGraph::new_inner(
1783 &GraphEntries::from_chunk_groups(vec![entry]),
1784 &Default::default(),
1785 include_traced,
1786 include_binding_usage,
1787 )
1788 .await
1789 }
1790
1791 #[turbo_tasks::function(operation)]
1792 pub async fn new_with_entries(
1793 entries: ResolvedVc<GraphEntries>,
1794 include_traced: bool,
1795 include_binding_usage: bool,
1796 ) -> Result<Vc<Self>> {
1797 SingleModuleGraph::new_inner(
1798 &*entries.await?,
1799 &Default::default(),
1800 include_traced,
1801 include_binding_usage,
1802 )
1803 .await
1804 }
1805
1806 #[turbo_tasks::function(operation)]
1807 pub async fn new_with_entries_visited(
1808 entries: ResolvedVc<GraphEntries>,
1809 visited_modules: OperationVc<VisitedModules>,
1810 include_traced: bool,
1811 include_binding_usage: bool,
1812 ) -> Result<Vc<Self>> {
1813 SingleModuleGraph::new_inner(
1814 &*entries.await?,
1815 &visited_modules.connect().await?.modules,
1816 include_traced,
1817 include_binding_usage,
1818 )
1819 .await
1820 }
1821
1822 #[turbo_tasks::function(operation)]
1823 pub async fn new_with_entries_visited_intern(
1824 entries: GraphEntries,
1826 visited_modules: OperationVc<VisitedModules>,
1827 include_traced: bool,
1828 include_binding_usage: bool,
1829 ) -> Result<Vc<Self>> {
1830 SingleModuleGraph::new_inner(
1831 &entries,
1832 &visited_modules.connect().await?.modules,
1833 include_traced,
1834 include_binding_usage,
1835 )
1836 .await
1837 }
1838
1839 #[turbo_tasks::function]
1840 pub async fn module_count(&self) -> Vc<u64> {
1841 Vc::cell(self.number_of_modules as u64)
1842 }
1843
1844 #[turbo_tasks::function]
1845 pub async fn edge_count(&self) -> Vc<u64> {
1846 Vc::cell(self.graph.edge_count() as u64)
1847 }
1848}
1849
1850#[derive(Clone, Debug, Serialize, Deserialize, NonLocalValue)]
1851pub enum SingleModuleGraphNode {
1852 Module(ResolvedVc<Box<dyn Module>>),
1853 VisitedModule {
1855 idx: GraphNodeIndex,
1856 module: ResolvedVc<Box<dyn Module>>,
1857 },
1858}
1859
1860impl SingleModuleGraphNode {
1861 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1862 match self {
1863 SingleModuleGraphNode::Module(module) => *module,
1864 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1865 }
1866 }
1867 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1868 match self {
1869 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1870 Direction::Outgoing => Some(*idx),
1871 Direction::Incoming => None,
1872 },
1873 SingleModuleGraphNode::Module(_) => None,
1874 }
1875 }
1876}
1877
1878#[derive(PartialEq, Eq, Debug)]
1879pub enum GraphTraversalAction {
1880 Continue,
1882 Skip,
1884 Exclude,
1886}
1887
1888#[derive(Clone, Hash, PartialEq, Eq)]
1891enum SingleModuleGraphBuilderNode {
1892 Module {
1894 module: ResolvedVc<Box<dyn Module>>,
1895 ident: Option<ReadRef<RcStr>>,
1897 is_traced: bool,
1899 },
1900 VisitedModule {
1902 module: ResolvedVc<Box<dyn Module>>,
1903 idx: GraphNodeIndex,
1904 },
1905}
1906
1907impl SingleModuleGraphBuilderNode {
1908 async fn new_module(
1909 emit_spans: bool,
1910 module: ResolvedVc<Box<dyn Module>>,
1911 is_traced: bool,
1912 ) -> Result<Self> {
1913 Ok(Self::Module {
1914 module,
1915 ident: if emit_spans {
1916 Some(module.ident_string().untracked().await?)
1918 } else {
1919 None
1920 },
1921 is_traced,
1922 })
1923 }
1924 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1925 Self::VisitedModule { module, idx }
1926 }
1927}
1928
1929struct SingleModuleGraphBuilder<'a> {
1930 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1931
1932 emit_spans: bool,
1933
1934 include_traced: bool,
1936
1937 include_binding_usage: bool,
1939}
1940impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1941 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1942 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1943
1944 fn visit(
1945 &mut self,
1946 node: &SingleModuleGraphBuilderNode,
1947 _edge: Option<&RefData>,
1948 ) -> VisitControlFlow {
1949 match node {
1950 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1951 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1953 }
1954 }
1955
1956 fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1957 let &SingleModuleGraphBuilderNode::Module {
1959 module, is_traced, ..
1960 } = node
1961 else {
1962 unreachable!()
1964 };
1965 let visited_modules = self.visited_modules;
1966 let emit_spans = self.emit_spans;
1967 let include_traced = self.include_traced;
1968 let include_binding_usage = self.include_binding_usage;
1969 async move {
1970 let refs_cell = if !is_traced {
1971 primary_chunkable_referenced_modules(*module, include_traced, include_binding_usage)
1972 } else {
1973 referenced_modules_and_affecting_sources(*module, false)
1975 };
1976 let refs = match refs_cell.await {
1977 Ok(refs) => refs,
1978 Err(e) => {
1979 return Err(e.context(module.ident().to_string().await?));
1980 }
1981 };
1982
1983 refs.iter()
1984 .flat_map(|(reference, resolved)| {
1985 resolved.modules.iter().map(|m| {
1986 (
1987 *reference,
1988 resolved.chunking_type.clone(),
1989 resolved.binding_usage.clone(),
1990 *m,
1991 )
1992 })
1993 })
1994 .filter(|(_, ty, _, _)| {
1995 !matches!(
2004 ty,
2005 ChunkingType::Traced {
2006 mode: TracedMode::Transitive
2007 }
2008 ) || is_traced
2009 })
2010 .map(async |(reference, ty, binding_usage, target)| {
2011 let to = if let Some(idx) = visited_modules.get(&target) {
2012 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
2013 } else {
2014 SingleModuleGraphBuilderNode::new_module(
2015 emit_spans,
2016 target,
2017 is_traced || ty.is_traced(),
2018 )
2019 .await?
2020 };
2021 Ok((
2022 to,
2023 RefData {
2024 chunking_type: ty,
2025 binding_usage,
2026 reference,
2027 },
2028 ))
2029 })
2030 .try_join()
2031 .await
2032 }
2033 }
2034
2035 fn span(
2036 &mut self,
2037 node: &SingleModuleGraphBuilderNode,
2038 edge: Option<&RefData>,
2039 ) -> tracing::Span {
2040 if !self.emit_spans {
2041 return Span::none();
2042 }
2043
2044 let mut span = match node {
2045 SingleModuleGraphBuilderNode::Module {
2046 ident: Some(ident), ..
2047 } => {
2048 tracing::info_span!("module", name = display(ident))
2049 }
2050 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
2051 tracing::info_span!("visited module")
2052 }
2053 _ => unreachable!(),
2054 };
2055
2056 if let Some(edge) = edge {
2057 match &edge.chunking_type {
2058 ChunkingType::Parallel {
2059 inherit_async: _,
2060 hoisted: _,
2061 } => {}
2062 ChunkingType::Traced { .. } => {
2063 let _span = span.entered();
2064 span = tracing::info_span!("traced reference");
2065 }
2066 ChunkingType::Async => {
2067 let _span = span.entered();
2068 span = tracing::info_span!("async reference");
2069 }
2070 ChunkingType::PerEntry => {
2071 let _span = span.entered();
2072 span = tracing::info_span!("per-entry reference");
2073 }
2074 ChunkingType::Emitted { namespace, .. } => {
2075 let _span = span.entered();
2076 span = tracing::info_span!("emitted reference", namespace = debug(&namespace));
2077 }
2078 ChunkingType::Collected { namespace, .. } => {
2079 let _span = span.entered();
2080 span =
2081 tracing::info_span!("collected reference", namespace = debug(&namespace));
2082 }
2083 ChunkingType::Isolated { _ty: ty, merge_tag } => {
2084 let _span = span.entered();
2085 span = tracing::info_span!(
2086 "isolated reference",
2087 ty = debug(&ty),
2088 merge_tag = debug(&merge_tag)
2089 );
2090 }
2091 ChunkingType::Shared {
2092 inherit_async: _,
2093 merge_tag,
2094 } => {
2095 let _span = span.entered();
2096 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
2097 }
2098 };
2099 }
2100
2101 span
2102 }
2103}
2104
2105#[cfg(test)]
2106pub mod tests {
2107 use anyhow::Result;
2108 use rustc_hash::FxHashMap;
2109 use turbo_rcstr::{RcStr, rcstr};
2110 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
2111 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
2112 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
2113
2114 use super::*;
2115 use crate::{
2116 asset::{Asset, AssetContent},
2117 ident::AssetIdent,
2118 issue::{CollectibleIssuesExt, IssueSeverity},
2119 module::{Module, ModuleSideEffects},
2120 module_graph::chunk_group_info::{ChunkGroup, EntryHeuristics},
2121 reference::{ModuleReference, ModuleReferences},
2122 resolve::ModuleResolveResult,
2123 };
2124
2125 #[turbo_tasks::value(shared)]
2126 struct ImportTraceTestResult {
2127 has_entry: bool,
2128 traces: Vec<Vec<RcStr>>,
2129 missing_traces: Vec<Vec<RcStr>>,
2130 }
2131
2132 #[turbo_tasks::function(operation, root)]
2133 async fn import_trace_test_operation(rootless: bool) -> Result<Vc<ImportTraceTestResult>> {
2134 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2135 let root = fs.root().await?;
2136 let repo = TestRepo::new(
2137 &root,
2138 [
2139 ("entry.js", vec!["dependency.js"]),
2140 ("dependency.js", vec!["entry.js"]),
2141 ],
2142 );
2143 let entry = Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join("entry.js")?, repo))
2144 .to_resolved()
2145 .await?;
2146 let graph = SingleModuleGraph::new_with_entries(
2147 GraphEntries::resolved_cell(GraphEntries::new(
2148 vec![ChunkGroupEntry::Entry {
2149 modules: vec![entry],
2150 heuristics: EntryHeuristics::default(),
2151 }],
2152 vec![],
2153 )),
2154 false,
2155 false,
2156 )
2157 .connect()
2158 .to_resolved()
2159 .await?;
2160 let graph = if rootless {
2161 let _ = graph.await?.entry_nodes();
2164 let mut graph = (*graph.await?).clone();
2165 graph.entries = GraphEntries::default();
2166 graph.resolved_cell()
2167 } else {
2168 graph
2169 };
2170
2171 let has_entry = graph.await?.has_entry_module(entry);
2172 let tracer = ModuleGraphImportTracer::new(*graph);
2173 let traces = tracer
2174 .get_traces(root.join("dependency.js")?)
2175 .await?
2176 .0
2177 .iter()
2178 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2179 .collect();
2180 let missing_traces = tracer
2181 .get_traces(root.join("missing.js")?)
2182 .await?
2183 .0
2184 .iter()
2185 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2186 .collect();
2187
2188 Ok(ImportTraceTestResult {
2189 has_entry,
2190 traces,
2191 missing_traces,
2192 }
2193 .cell())
2194 }
2195
2196 #[turbo_tasks::value(shared)]
2197 struct ImportTraceIssues {
2198 issues: Vec<(IssueSeverity, RcStr)>,
2199 }
2200
2201 #[turbo_tasks::function(operation, root)]
2202 async fn import_trace_issues_operation(
2203 trace_operation: OperationVc<ImportTraceTestResult>,
2204 ) -> Result<Vc<ImportTraceIssues>> {
2205 let _ = trace_operation.connect().await?;
2206 let issues = trace_operation
2207 .peek_issues()
2208 .iter()
2209 .map(async |issue| {
2210 let issue = issue.into_trait_ref().await?;
2211 Ok((
2212 issue.severity(),
2213 issue.title().await?.to_unstyled_string().into(),
2214 ))
2215 })
2216 .try_join()
2217 .await?;
2218 Ok(ImportTraceIssues { issues }.cell())
2219 }
2220
2221 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2222 async fn test_import_trace_uses_explicit_entry_as_cycle_root() {
2223 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2224 BackendOptions::default(),
2225 noop_backing_storage(),
2226 ));
2227 tt.run_once(async {
2228 let result = import_trace_test_operation(false)
2229 .read_strongly_consistent()
2230 .await?;
2231 assert!(result.has_entry);
2232 assert_eq!(
2233 result.traces,
2234 vec![vec![rcstr!("dependency.js"), rcstr!("entry.js")]]
2235 );
2236 assert!(result.missing_traces.is_empty());
2237 Ok(())
2238 })
2239 .await
2240 .unwrap();
2241 }
2242
2243 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2244 async fn test_cloned_rootless_import_trace_resets_cache_and_emits_bug_issue() {
2245 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2246 BackendOptions::default(),
2247 noop_backing_storage(),
2248 ));
2249 tt.run_once(async {
2250 let trace_operation = import_trace_test_operation(true);
2251 let result = trace_operation.read_strongly_consistent().await?;
2252 assert!(!result.has_entry);
2253 assert_eq!(result.traces, vec![vec![rcstr!("dependency.js")]]);
2254 assert!(result.missing_traces.is_empty());
2255
2256 let issues = import_trace_issues_operation(trace_operation)
2257 .read_strongly_consistent()
2258 .await?;
2259 assert_eq!(
2260 issues.issues,
2261 vec![(
2262 IssueSeverity::Bug,
2263 rcstr!("Module graph is missing an entry point")
2264 )]
2265 );
2266 Ok(())
2267 })
2268 .await
2269 .unwrap();
2270 }
2271
2272 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2273 async fn test_traverse_dfs_from_entries_diamond() {
2274 run_graph_test(
2275 vec![rcstr!("a.js")],
2276 {
2277 let mut deps = FxHashMap::default();
2278 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2280 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
2281 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
2282 deps
2283 },
2284 |graph, entry_modules, module_to_name| {
2285 let mut preorder_visits = Vec::new();
2286 let mut postorder_visits = Vec::new();
2287
2288 graph.traverse_edges_dfs(
2289 entry_modules,
2290 &mut (),
2291 |parent, target, _| {
2292 preorder_visits.push((
2293 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2294 module_to_name.get(&target).unwrap().clone(),
2295 ));
2296 Ok(GraphTraversalAction::Continue)
2297 },
2298 |parent, target, _| {
2299 postorder_visits.push((
2300 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2301 module_to_name.get(&target).unwrap().clone(),
2302 ));
2303 Ok(())
2304 },
2305 false,
2306 false,
2307 )?;
2308 assert_eq!(
2309 vec![
2310 (None, rcstr!("a.js")),
2311 (Some(rcstr!("a.js")), rcstr!("b.js")),
2312 (Some(rcstr!("b.js")), rcstr!("d.js")),
2313 (Some(rcstr!("a.js")), rcstr!("c.js")),
2314 (Some(rcstr!("c.js")), rcstr!("d.js"))
2315 ],
2316 preorder_visits
2317 );
2318 assert_eq!(
2319 vec![
2320 (Some(rcstr!("b.js")), rcstr!("d.js")),
2321 (Some(rcstr!("a.js")), rcstr!("b.js")),
2322 (Some(rcstr!("c.js")), rcstr!("d.js")),
2323 (Some(rcstr!("a.js")), rcstr!("c.js")),
2324 (None, rcstr!("a.js"))
2325 ],
2326 postorder_visits
2327 );
2328 Ok(())
2329 },
2330 )
2331 .await;
2332 }
2333
2334 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2335 async fn test_traverse_dfs_from_entries_cycle() {
2336 run_graph_test(
2337 vec![rcstr!("a.js")],
2338 {
2339 let mut deps = FxHashMap::default();
2340 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2342 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2343 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2344 deps
2345 },
2346 |graph, entry_modules, module_to_name| {
2347 let mut preorder_visits = Vec::new();
2348 let mut postorder_visits = Vec::new();
2349
2350 graph.traverse_edges_dfs(
2351 entry_modules,
2352 &mut (),
2353 |parent, target, _| {
2354 preorder_visits.push((
2355 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2356 module_to_name.get(&target).unwrap().clone(),
2357 ));
2358 Ok(GraphTraversalAction::Continue)
2359 },
2360 |parent, target, _| {
2361 postorder_visits.push((
2362 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2363 module_to_name.get(&target).unwrap().clone(),
2364 ));
2365 Ok(())
2366 },
2367 false,
2368 false,
2369 )?;
2370 assert_eq!(
2371 vec![
2372 (None, rcstr!("a.js")),
2373 (Some(rcstr!("a.js")), rcstr!("b.js")),
2374 (Some(rcstr!("b.js")), rcstr!("c.js")),
2375 (Some(rcstr!("c.js")), rcstr!("a.js")),
2376 ],
2377 preorder_visits
2378 );
2379 assert_eq!(
2380 vec![
2381 (Some(rcstr!("c.js")), rcstr!("a.js")),
2382 (Some(rcstr!("b.js")), rcstr!("c.js")),
2383 (Some(rcstr!("a.js")), rcstr!("b.js")),
2384 (None, rcstr!("a.js"))
2385 ],
2386 postorder_visits
2387 );
2388 Ok(())
2389 },
2390 )
2391 .await;
2392 }
2393
2394 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2395 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
2396 run_graph_test(
2397 vec![rcstr!("a.js")],
2398 {
2399 let mut deps = FxHashMap::default();
2400 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2402 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2403 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2404 deps
2405 },
2406 |graph, entry_modules, module_to_name| {
2407 let mut visits = Vec::new();
2408 let mut count = 0;
2409
2410 graph.traverse_edges_fixed_point_with_priority(
2411 entry_modules.into_iter().map(|m| (m, 0)),
2412 &mut (),
2413 |parent, target, _, _| {
2414 visits.push((
2415 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2416 module_to_name.get(&target).unwrap().clone(),
2417 ));
2418 count += 1;
2419
2420 Ok(if count < 6 {
2422 GraphTraversalAction::Continue
2423 } else {
2424 GraphTraversalAction::Skip
2425 })
2426 },
2427 |_, _| Ok(0),
2428 )?;
2429 assert_eq!(
2430 vec![
2431 (None, rcstr!("a.js")),
2432 (Some(rcstr!("a.js")), rcstr!("b.js")),
2433 (Some(rcstr!("b.js")), rcstr!("c.js")),
2434 (Some(rcstr!("c.js")), rcstr!("a.js")),
2435 (Some(rcstr!("a.js")), rcstr!("b.js")),
2437 (Some(rcstr!("b.js")), rcstr!("c.js")),
2438 ],
2439 visits
2440 );
2441
2442 Ok(())
2443 },
2444 )
2445 .await;
2446 }
2447
2448 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2449 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
2450 run_graph_test(
2451 vec![rcstr!("a.js")],
2452 {
2453 let mut deps = FxHashMap::default();
2454 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2459 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
2460 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2461 deps
2462 },
2463 |graph, entry_modules, module_to_name| {
2464 let mut visits = Vec::new();
2465 let mut count = 0;
2466
2467 graph.traverse_edges_fixed_point_with_priority(
2468 entry_modules.into_iter().map(|m| (m, 0)),
2469 &mut (),
2470 |parent, target, _, _| {
2471 visits.push((
2472 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2473 module_to_name.get(&target).unwrap().clone(),
2474 ));
2475 count += 1;
2476
2477 Ok(if count < 6 {
2479 GraphTraversalAction::Continue
2480 } else {
2481 GraphTraversalAction::Skip
2482 })
2483 },
2484 |_, _| Ok(0),
2485 )?;
2486
2487 assert_eq!(
2488 vec![
2489 (None, rcstr!("a.js")),
2490 (Some(rcstr!("a.js")), rcstr!("c.js")),
2491 (Some(rcstr!("a.js")), rcstr!("b.js")),
2492 (Some(rcstr!("b.js")), rcstr!("e.js")),
2493 (Some(rcstr!("b.js")), rcstr!("d.js")),
2494 (Some(rcstr!("c.js")), rcstr!("f.js")),
2495 (Some(rcstr!("c.js")), rcstr!("e.js")),
2496 ],
2497 visits
2498 );
2499
2500 Ok(())
2501 },
2502 )
2503 .await;
2504 }
2505
2506 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2507 async fn test_traverse_cycles() {
2508 run_graph_test(
2509 vec![rcstr!("a.js")],
2510 {
2511 let mut deps = FxHashMap::default();
2512 deps.insert(
2519 rcstr!("a.js"),
2520 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2521 );
2522 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2523 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2524 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2525 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2526 deps
2527 },
2528 |graph, _, module_to_name| {
2529 let mut cycles = vec![];
2530
2531 graph.traverse_cycles(
2532 |_| true,
2533 |cycle| {
2534 cycles.push(
2535 cycle
2536 .iter()
2537 .map(|n| module_to_name.get(*n).unwrap().clone())
2538 .collect::<Vec<_>>(),
2539 );
2540 Ok(())
2541 },
2542 )?;
2543
2544 assert_eq!(
2545 cycles,
2546 vec![
2547 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2548 vec![rcstr!("s.js")]
2549 ],
2550 );
2551
2552 Ok(())
2553 },
2554 )
2555 .await;
2556 }
2557
2558 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2559 async fn test_reverse_edges_through_layered_graph() {
2560 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2561 BackendOptions::default(),
2562 noop_backing_storage(),
2563 ));
2564 tt.run_once(async move {
2565 #[turbo_tasks::value]
2566 struct ReverseTraversalResults {
2567 forward: Vec<RcStr>,
2568 reverse_from_d: Vec<RcStr>,
2569 reverse_from_b: Vec<RcStr>,
2570 }
2571
2572 #[turbo_tasks::function(operation, root)]
2573 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2574 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2575 let root = fs.root().await?;
2576
2577 let repo = TestRepo::new(
2580 &root,
2581 [("a.js", vec!["b.js", "d.js"]), ("b.js", vec!["c.js"])],
2582 );
2583 let make_module = |name| {
2584 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2585 .to_resolved()
2586 };
2587 let a_module = make_module("a.js").await?;
2588 let b_module = make_module("b.js").await?;
2589
2590 let parent_graph = SingleModuleGraph::new_with_entries(
2591 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2592 modules: vec![b_module],
2593 heuristics: EntryHeuristics::default(),
2594 }])
2595 .resolved_cell(),
2596 false,
2597 false,
2598 );
2599
2600 let module_graph = ModuleGraph::from_graphs(
2601 vec![
2602 parent_graph,
2603 SingleModuleGraph::new_with_entries_visited(
2604 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2605 modules: vec![a_module],
2606 heuristics: EntryHeuristics::default(),
2607 }])
2608 .resolved_cell(),
2609 VisitedModules::from_graph(parent_graph),
2610 false,
2611 false,
2612 ),
2613 ],
2614 None,
2615 )
2616 .connect();
2617 let child_graph = module_graph
2618 .iter_graphs()
2619 .await?
2620 .get(1)
2621 .unwrap()
2622 .connect()
2623 .await?;
2624
2625 let mut visited_forward = Vec::new();
2627 child_graph.traverse_edges_dfs(
2628 vec![a_module],
2629 &mut (),
2630 |_parent, child, _state_| {
2631 visited_forward.push(child);
2632 Ok(GraphTraversalAction::Continue)
2633 },
2634 |_, _, _| Ok(()),
2635 false,
2636 false,
2637 )?;
2638 let forward = visited_forward
2639 .iter()
2640 .map(|m| m.ident().to_string().owned())
2641 .try_join()
2642 .await?;
2643
2644 let d_module = child_graph
2646 .enumerate_nodes()
2647 .map(async |(_index, module)| {
2648 Ok(match module {
2649 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2650 if module.ident().to_string().owned().await? == "[test]/d.js" {
2651 Some(*module)
2652 } else {
2653 None
2654 }
2655 }
2656 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2657 ..
2658 } => None,
2659 })
2660 })
2661 .try_flat_join()
2662 .await?
2663 .into_iter()
2664 .next()
2665 .unwrap();
2666
2667 async fn get_reverse_from(
2668 graph: &ModuleGraphLayer,
2669 module: ResolvedVc<Box<dyn Module>>,
2670 ) -> Result<Vec<RcStr>> {
2671 let mut visited = Vec::new();
2672 graph.traverse_edges_reverse_dfs(
2673 vec![module],
2674 &mut (),
2675 |_parent, child, _state_| {
2676 visited.push(child);
2677 Ok(GraphTraversalAction::Continue)
2678 },
2679 |_, _, _| Ok(()),
2680 )?;
2681 visited
2682 .iter()
2683 .map(|m| m.ident().to_string().owned())
2684 .try_join()
2685 .await
2686 }
2687
2688 Ok(ReverseTraversalResults {
2689 forward,
2690 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2691 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2692 }
2693 .cell())
2694 }
2695
2696 let traversal_results = reverse_traversal_results_operation()
2697 .read_strongly_consistent()
2698 .await?;
2699
2700 assert_eq!(
2701 traversal_results.forward,
2702 vec![
2703 rcstr!("[test]/a.js"),
2704 rcstr!("[test]/b.js"),
2705 rcstr!("[test]/d.js")
2706 ]
2707 );
2708
2709 assert_eq!(
2710 traversal_results.reverse_from_d,
2711 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2712 );
2713
2714 assert_eq!(
2715 traversal_results.reverse_from_b,
2716 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2717 );
2718
2719 Ok(())
2720 })
2721 .await
2722 .unwrap();
2723 }
2724
2725 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2726 async fn test_iter_nodes_modules_through_layered_graph() {
2727 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2728 BackendOptions::default(),
2729 noop_backing_storage(),
2730 ));
2731 tt.run_once(async move {
2732 #[turbo_tasks::value]
2733 struct Results {
2734 iter_nodes: Vec<RcStr>,
2735 iter_modules: Vec<RcStr>,
2736 iter_nodes_single: Vec<Vec<RcStr>>,
2737 iter_modules_single: Vec<Vec<RcStr>>,
2738 }
2739
2740 #[turbo_tasks::function(operation, root)]
2741 async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2742 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2743 let root = fs.root().await?;
2744
2745 let repo = TestRepo::new_with_chunking_types(
2749 &root,
2750 [
2751 ("a.js", vec!["b.js"]),
2752 ("b.js", vec!["c.js"]),
2753 ("c.js", vec!["x.js"]),
2754 ("x.js", vec!["y.js", "traced.js"]),
2755 ("y.js", vec!["z.js"]),
2756 ],
2757 [(
2758 "x.js",
2759 "traced.js",
2760 ChunkingType::Traced {
2761 mode: TracedMode::Entry,
2762 },
2763 )],
2764 );
2765 let make_module = |name| {
2766 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2767 .to_resolved()
2768 };
2769 let x_module = make_module("x.js").await?;
2770 let a_module = make_module("a.js").await?;
2771
2772 let parent_graph = SingleModuleGraph::new_with_entries(
2773 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2774 modules: vec![x_module],
2775 heuristics: EntryHeuristics::default(),
2776 }])
2777 .resolved_cell(),
2778 true,
2779 false,
2780 );
2781
2782 let module_graph = ModuleGraph::from_graphs(
2783 vec![
2784 parent_graph,
2785 SingleModuleGraph::new_with_entries_visited(
2786 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2787 modules: vec![a_module],
2788 heuristics: EntryHeuristics::default(),
2789 }])
2790 .resolved_cell(),
2791 VisitedModules::from_graph(parent_graph),
2792 true,
2793 false,
2794 ),
2795 ],
2796 None,
2797 )
2798 .connect();
2799 let graph_layers = module_graph.iter_graphs().await?;
2800
2801 Ok(Results {
2802 iter_nodes: module_graph
2803 .await?
2804 .iter_reachable_nodes()?
2805 .map(async |node| {
2806 Ok(match node {
2807 SingleModuleGraphNode::Module(module) => {
2808 module.ident_string().owned().await?
2809 }
2810 SingleModuleGraphNode::VisitedModule { module, .. } => {
2811 format!("visited {}", module.ident_string().owned().await?)
2812 .into()
2813 }
2814 })
2815 })
2816 .try_join()
2817 .await?,
2818 iter_modules: module_graph
2819 .await?
2820 .iter_reachable_modules()?
2821 .map(|m| m.ident_string().owned())
2822 .try_join()
2823 .await?,
2824 iter_nodes_single: graph_layers
2825 .iter()
2826 .map(async |layer| {
2827 layer
2828 .connect()
2829 .await?
2830 .iter_reachable_nodes()?
2831 .map(async |node| {
2832 Ok(match node {
2833 SingleModuleGraphNode::Module(module) => {
2834 module.ident_string().owned().await?
2835 }
2836 SingleModuleGraphNode::VisitedModule { module, .. } => {
2837 format!(
2838 "visited {}",
2839 module.ident_string().owned().await?
2840 )
2841 .into()
2842 }
2843 })
2844 })
2845 .try_join()
2846 .await
2847 })
2848 .try_join()
2849 .await?,
2850 iter_modules_single: graph_layers
2851 .iter()
2852 .map(async |layer| {
2853 layer
2854 .connect()
2855 .await?
2856 .iter_reachable_modules()?
2857 .map(|m| m.ident_string().owned())
2858 .try_join()
2859 .await
2860 })
2861 .try_join()
2862 .await?,
2863 }
2864 .cell())
2865 }
2866
2867 let traversal_results = reverse_traversal_results_operation()
2868 .read_strongly_consistent()
2869 .await?;
2870
2871 assert_eq!(
2872 traversal_results.iter_nodes,
2873 vec![
2874 rcstr!("[test]/x.js"),
2875 rcstr!("[test]/a.js"),
2876 rcstr!("[test]/y.js"),
2877 rcstr!("[test]/b.js"),
2878 rcstr!("[test]/z.js"),
2879 rcstr!("[test]/c.js"),
2880 rcstr!("visited [test]/x.js")
2881 ]
2882 );
2883 assert_eq!(
2884 traversal_results.iter_modules,
2885 vec![
2886 rcstr!("[test]/x.js"),
2887 rcstr!("[test]/a.js"),
2888 rcstr!("[test]/y.js"),
2889 rcstr!("[test]/b.js"),
2890 rcstr!("[test]/z.js"),
2891 rcstr!("[test]/c.js")
2892 ]
2893 );
2894 assert_eq!(
2895 traversal_results.iter_nodes_single,
2896 vec![
2897 vec![
2898 rcstr!("[test]/x.js"),
2899 rcstr!("[test]/y.js"),
2900 rcstr!("[test]/z.js")
2901 ],
2902 vec![
2903 rcstr!("[test]/a.js"),
2904 rcstr!("[test]/b.js"),
2905 rcstr!("[test]/c.js"),
2906 rcstr!("visited [test]/x.js")
2907 ]
2908 ]
2909 );
2910 assert_eq!(
2911 traversal_results.iter_modules_single,
2912 vec![
2913 vec![
2914 rcstr!("[test]/x.js"),
2915 rcstr!("[test]/y.js"),
2916 rcstr!("[test]/z.js")
2917 ],
2918 vec![
2919 rcstr!("[test]/a.js"),
2920 rcstr!("[test]/b.js"),
2921 rcstr!("[test]/c.js")
2922 ]
2923 ]
2924 );
2925
2926 Ok(())
2927 })
2928 .await
2929 .unwrap();
2930 }
2931
2932 #[turbo_tasks::value(shared)]
2933 struct TestRepo {
2934 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2935 chunking_types: FxHashMap<(FileSystemPath, FileSystemPath), ChunkingType>,
2936 }
2937
2938 impl TestRepo {
2939 fn new(
2940 root: &FileSystemPath,
2941 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2942 ) -> Vc<Self> {
2943 Self::new_with_chunking_types(
2944 root,
2945 dependencies,
2946 std::iter::empty::<(RcStr, RcStr, ChunkingType)>(),
2947 )
2948 }
2949
2950 fn new_with_chunking_types(
2951 root: &FileSystemPath,
2952 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2953 chunking_types: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, ChunkingType)>,
2954 ) -> Vc<Self> {
2955 let chunking_types = chunking_types
2956 .into_iter()
2957 .map(|(from, to, ty)| {
2958 (
2959 (
2960 root.join(from.as_ref()).unwrap(),
2961 root.join(to.as_ref()).unwrap(),
2962 ),
2963 ty,
2964 )
2965 })
2966 .collect::<FxHashMap<_, _>>();
2967 Self {
2968 repo: dependencies
2969 .into_iter()
2970 .map(|(k, v)| {
2971 (
2972 root.join(k.as_ref()).unwrap(),
2973 v.iter().map(|f| root.join(f.as_ref()).unwrap()).collect(),
2974 )
2975 })
2976 .collect(),
2977 chunking_types,
2978 }
2979 .cell()
2980 }
2981 }
2982
2983 #[turbo_tasks::value]
2984 struct MockModule {
2985 path: FileSystemPath,
2986 repo: ResolvedVc<TestRepo>,
2987 }
2988 #[turbo_tasks::value_impl]
2989 impl MockModule {
2990 #[turbo_tasks::function]
2991 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2992 Self { path, repo }.cell()
2993 }
2994 }
2995
2996 #[turbo_tasks::value_impl]
2997 impl Asset for MockModule {
2998 #[turbo_tasks::function]
2999 fn content(&self) -> Vc<AssetContent> {
3000 panic!("MockModule::content shouldn't be called")
3001 }
3002 }
3003
3004 #[turbo_tasks::value_impl]
3005 impl Module for MockModule {
3006 #[turbo_tasks::function]
3007 fn ident(&self) -> Vc<AssetIdent> {
3008 AssetIdent::from_path(self.path.clone()).into_vc()
3009 }
3010
3011 #[turbo_tasks::function]
3012 fn source(&self) -> Vc<crate::source::OptionSource> {
3013 Vc::cell(None)
3014 }
3015
3016 #[turbo_tasks::function]
3017 async fn references(&self) -> Result<Vc<ModuleReferences>> {
3018 let repo = self.repo.await?;
3019 let references = match repo.repo.get(&self.path) {
3020 Some(deps) => {
3021 deps.iter()
3022 .map(async |p| {
3023 Vc::upcast::<Box<dyn ModuleReference>>(MockModuleReference::new(
3024 ResolvedVc::upcast(
3025 MockModule::new(p.clone(), *self.repo).to_resolved().await?,
3026 ),
3027 rcstr!("normal-dep"),
3028 repo.chunking_types
3029 .get(&(self.path.clone(), p.clone()))
3030 .cloned()
3031 .unwrap_or(ChunkingType::Parallel {
3032 inherit_async: true,
3033 hoisted: false,
3034 }),
3035 ))
3036 .to_resolved()
3037 .await
3038 })
3039 .try_join()
3040 .await?
3041 }
3042 None => vec![],
3043 };
3044
3045 Ok(Vc::cell(references))
3046 }
3047 #[turbo_tasks::function]
3048 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
3049 ModuleSideEffects::SideEffectful.cell()
3050 }
3051 }
3052
3053 #[turbo_tasks::value]
3054 #[derive(ValueToString)]
3055 #[value_to_string(self.description)]
3056 struct MockModuleReference {
3057 asset: ResolvedVc<Box<dyn Module>>,
3058 description: RcStr,
3059 chunking_type: ChunkingType,
3060 }
3061
3062 impl MockModuleReference {
3063 pub fn new(
3064 asset: ResolvedVc<Box<dyn Module>>,
3065 description: RcStr,
3066 chunking_type: ChunkingType,
3067 ) -> Vc<Self> {
3068 MockModuleReference {
3069 asset,
3070 description,
3071 chunking_type,
3072 }
3073 .cell()
3074 }
3075 }
3076
3077 #[turbo_tasks::value_impl]
3078 impl ModuleReference for MockModuleReference {
3079 #[turbo_tasks::function]
3080 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
3081 *ModuleResolveResult::module(self.asset)
3082 }
3083
3084 fn chunking_type(&self) -> Option<ChunkingType> {
3085 Some(self.chunking_type.clone())
3086 }
3087 }
3088
3089 async fn run_graph_test(
3103 entries: Vec<RcStr>,
3104 graph: FxHashMap<RcStr, Vec<RcStr>>,
3105 test_fn: impl FnOnce(
3106 &ModuleGraph,
3107 Vec<ResolvedVc<Box<dyn Module>>>,
3108 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3109 ) -> Result<()>
3110 + Send
3111 + 'static,
3112 ) {
3113 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
3114 struct SetupGraph {
3115 module_graph: ReadRef<ModuleGraph>,
3116 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
3117 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3118 }
3119
3120 #[turbo_tasks::function(operation, root)]
3121 async fn setup_graph(
3122 entries: Vec<RcStr>,
3123 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
3124 ) -> Result<Vc<SetupGraph>> {
3125 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
3126 let root = fs.root().await?;
3127
3128 let repo = TestRepo::new(&root, graph_entries);
3129 let entry_modules = entries
3130 .iter()
3131 .map(|e| {
3132 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
3133 .to_resolved()
3134 })
3135 .try_join()
3136 .await?;
3137 let graph = SingleModuleGraph::new_with_entries(
3138 GraphEntries::resolved_cell(GraphEntries::new(
3139 vec![ChunkGroupEntry::Entry {
3140 modules: entry_modules.clone(),
3141 heuristics: EntryHeuristics::default(),
3142 }],
3143 vec![],
3144 )),
3145 false,
3146 false,
3147 );
3148
3149 let module_to_name = graph
3153 .connect()
3154 .await?
3155 .modules
3156 .keys()
3157 .map(async |m| Ok((*m, m.ident().await?.path.path.clone())))
3158 .try_join()
3159 .await?
3160 .into_iter()
3161 .collect();
3162 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
3163 .connect()
3164 .await?;
3165
3166 Ok(SetupGraph {
3167 module_graph,
3168 entry_modules,
3169 module_to_name,
3170 }
3171 .cell())
3172 }
3173
3174 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3175 BackendOptions::default(),
3176 noop_backing_storage(),
3177 ));
3178 let graph_entries = graph.into_iter().collect::<Vec<_>>();
3179 tt.run_once(async move {
3180 let setup = setup_graph(entries, graph_entries)
3181 .read_strongly_consistent()
3182 .await?;
3183
3184 test_fn(
3185 &setup.module_graph,
3186 setup.entry_modules.clone(),
3187 setup.module_to_name.clone(),
3188 )
3189 })
3190 .await
3191 .unwrap();
3192 }
3193
3194 fn assert_ranks(
3197 ranks: &FxHashMap<ResolvedVc<Box<dyn Module>>, usize>,
3198 module_to_name: &FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3199 edges: &[(&str, &str)],
3200 ) -> FxHashMap<RcStr, usize> {
3201 let by_name: FxHashMap<RcStr, usize> = ranks
3202 .iter()
3203 .map(|(m, r)| (module_to_name[m].clone(), *r))
3204 .collect();
3205 assert_eq!(by_name.len(), ranks.len(), "modules must be ranked once");
3206 let mut sorted = ranks.values().copied().collect::<Vec<_>>();
3207 sorted.sort();
3208 assert_eq!(sorted, (0..ranks.len()).collect::<Vec<_>>());
3209 for (from, to) in edges {
3210 assert!(
3211 by_name[*from] < by_name[*to],
3212 "expected rank({from}) < rank({to}), ranks: {by_name:?}"
3213 );
3214 }
3215 by_name
3216 }
3217
3218 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3219 async fn test_reverse_postorder_ranks_diamond() {
3220 run_graph_test(
3221 vec![rcstr!("a.js")],
3222 {
3223 let mut deps = FxHashMap::default();
3225 deps.insert(
3226 rcstr!("a.js"),
3227 vec![rcstr!("b.js"), rcstr!("d.js"), rcstr!("e.js")],
3228 );
3229 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
3230 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
3231 deps.insert(rcstr!("e.js"), vec![rcstr!("d.js")]);
3232 deps
3233 },
3234 |graph, entry_modules, module_to_name| {
3235 let ranks = graph.reverse_postorder_ranks(entry_modules.clone())?;
3236 assert_eq!(ranks.len(), 5);
3237 assert_ranks(
3238 &ranks,
3239 &module_to_name,
3240 &[
3241 ("a.js", "b.js"),
3242 ("a.js", "d.js"),
3243 ("a.js", "e.js"),
3244 ("b.js", "c.js"),
3245 ("c.js", "d.js"),
3246 ("e.js", "d.js"),
3247 ],
3248 );
3249
3250 let visit_count = graph.traverse_edges_fixed_point_with_priority(
3253 entry_modules
3254 .iter()
3255 .map(|m| (*m, std::cmp::Reverse(ranks[m])))
3256 .collect::<Vec<_>>(),
3257 &mut (),
3258 |_, _, _, _| Ok(GraphTraversalAction::Continue),
3259 |m, _| Ok(std::cmp::Reverse(ranks[&m])),
3260 )?;
3261 assert_eq!(visit_count, 5);
3262 Ok(())
3263 },
3264 )
3265 .await;
3266 }
3267
3268 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3269 async fn test_reverse_postorder_ranks_cycle() {
3270 run_graph_test(
3271 vec![rcstr!("a.js")],
3272 {
3273 let mut deps = FxHashMap::default();
3274 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
3275 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
3276 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js"), rcstr!("d.js")]);
3277 deps
3278 },
3279 |graph, entry_modules, module_to_name| {
3280 let ranks = graph.reverse_postorder_ranks(entry_modules)?;
3281 assert_eq!(ranks.len(), 4);
3282 assert_ranks(
3284 &ranks,
3285 &module_to_name,
3286 &[("a.js", "b.js"), ("b.js", "c.js"), ("c.js", "d.js")],
3287 );
3288 Ok(())
3289 },
3290 )
3291 .await;
3292 }
3293
3294 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3295 async fn test_reverse_postorder_ranks_multiple_entries() {
3296 run_graph_test(
3297 vec![rcstr!("a.js"), rcstr!("b.js")],
3298 {
3299 let mut deps = FxHashMap::default();
3300 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
3301 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
3302 deps
3303 },
3304 |graph, entry_modules, module_to_name| {
3305 let [a, b] = entry_modules[..] else {
3306 panic!("expected two entries");
3307 };
3308 for entries in [vec![a, b], vec![b, a], vec![b, a, b, a]] {
3311 let ranks = graph.reverse_postorder_ranks(entries)?;
3312 assert_eq!(ranks.len(), 3);
3313 assert_ranks(
3314 &ranks,
3315 &module_to_name,
3316 &[("a.js", "b.js"), ("b.js", "c.js")],
3317 );
3318 }
3319 Ok(())
3320 },
3321 )
3322 .await;
3323 }
3324
3325 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3326 async fn test_reverse_postorder_ranks_through_layered_graph() {
3327 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3328 BackendOptions::default(),
3329 noop_backing_storage(),
3330 ));
3331 tt.run_once(async move {
3332 #[turbo_tasks::value]
3333 struct Results {
3334 ranks_from_child_entry: Vec<(RcStr, usize)>,
3335 ranks_from_all_entries: Vec<(RcStr, usize)>,
3336 }
3337
3338 #[turbo_tasks::function(operation, root)]
3339 async fn layered_ranks_operation() -> Result<Vc<Results>> {
3340 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
3341 let root = fs.root().await?;
3342
3343 let repo = TestRepo::new(
3346 &root,
3347 [
3348 ("a.js", vec!["b.js"]),
3349 ("b.js", vec!["c.js"]),
3350 ("c.js", vec!["x.js"]),
3351 ("x.js", vec!["y.js"]),
3352 ("y.js", vec!["z.js"]),
3353 ],
3354 );
3355 let make_module = |name| {
3356 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
3357 .to_resolved()
3358 };
3359 let x_module = make_module("x.js").await?;
3360 let a_module = make_module("a.js").await?;
3361
3362 let parent_graph = SingleModuleGraph::new_with_entries(
3363 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
3364 modules: vec![x_module],
3365 heuristics: EntryHeuristics::default(),
3366 }])
3367 .resolved_cell(),
3368 false,
3369 false,
3370 );
3371 let module_graph = ModuleGraph::from_graphs(
3372 vec![
3373 parent_graph,
3374 SingleModuleGraph::new_with_entries_visited(
3375 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
3376 modules: vec![a_module],
3377 heuristics: EntryHeuristics::default(),
3378 }])
3379 .resolved_cell(),
3380 VisitedModules::from_graph(parent_graph),
3381 false,
3382 false,
3383 ),
3384 ],
3385 None,
3386 )
3387 .connect()
3388 .await?;
3389
3390 async fn named(
3391 ranks: FxHashMap<ResolvedVc<Box<dyn Module>>, usize>,
3392 ) -> Result<Vec<(RcStr, usize)>> {
3393 let mut ranks = ranks
3394 .into_iter()
3395 .map(async |(m, r)| Ok((m.ident().await?.path.path.clone(), r)))
3396 .try_join()
3397 .await?;
3398 ranks.sort_by_key(|(_, r)| *r);
3399 Ok(ranks)
3400 }
3401
3402 Ok(Results {
3403 ranks_from_child_entry: named(
3404 module_graph.reverse_postorder_ranks([a_module])?,
3405 )
3406 .await?,
3407 ranks_from_all_entries: named(
3408 module_graph.reverse_postorder_ranks([x_module, a_module])?,
3409 )
3410 .await?,
3411 }
3412 .cell())
3413 }
3414
3415 let results = layered_ranks_operation().read_strongly_consistent().await?;
3416
3417 let expected = ["a.js", "b.js", "c.js", "x.js", "y.js", "z.js"]
3420 .into_iter()
3421 .enumerate()
3422 .map(|(i, name)| (RcStr::from(name), i))
3423 .collect::<Vec<_>>();
3424 assert_eq!(results.ranks_from_child_entry, expected);
3425 assert_eq!(results.ranks_from_all_entries, expected);
3426
3427 Ok(())
3428 })
3429 .await
3430 .unwrap();
3431 }
3432
3433 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3434 async fn test_traverse_edges_fixed_point_dedups_entries() {
3435 run_graph_test(
3436 vec![rcstr!("a.js"), rcstr!("b.js")],
3437 {
3438 let mut deps = FxHashMap::default();
3439 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
3440 deps
3441 },
3442 |graph, entry_modules, _| {
3443 let [a, b] = entry_modules[..] else {
3444 panic!("expected two entries");
3445 };
3446 let mut root_visits = 0;
3447 let visit_count = graph.traverse_edges_fixed_point_with_priority(
3451 [(a, 1), (b, 0), (a, 1)],
3452 &mut (),
3453 |parent, _, _, _| {
3454 if parent.is_none() {
3455 root_visits += 1;
3456 }
3457 Ok(GraphTraversalAction::Continue)
3458 },
3459 |_, _| Ok(0),
3460 )?;
3461 assert_eq!(root_visits, 2);
3462 assert_eq!(visit_count, 2);
3463 Ok(())
3464 },
3465 )
3466 .await;
3467 }
3468
3469 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
3470 struct ChunkGroupInfoTestGraph {
3471 module_graph: ReadRef<ModuleGraph>,
3472 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3473 }
3474
3475 #[turbo_tasks::function(operation, root)]
3485 async fn chunk_group_info_test_graph() -> Result<Vc<ChunkGroupInfoTestGraph>> {
3486 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
3487 let root = fs.root().await?;
3488 let repo = TestRepo::new_with_chunking_types(
3489 &root,
3490 [
3491 ("page1.js", vec!["a1.js", "shared.js", "lazy.js"]),
3492 ("a1.js", vec!["a2.js"]),
3493 ("a2.js", vec!["shared.js"]),
3494 ("page2.js", vec!["shared.js", "iso.js"]),
3495 ("shared.js", vec!["lib.js"]),
3496 ("lazy.js", vec!["lib.js"]),
3497 ("iso.js", vec!["lib.js"]),
3498 ],
3499 [
3500 ("page1.js", "lazy.js", ChunkingType::Async),
3501 (
3502 "page2.js",
3503 "iso.js",
3504 ChunkingType::Isolated {
3505 _ty: crate::chunk::ChunkGroupType::Entry,
3506 merge_tag: Some(rcstr!("m")),
3507 },
3508 ),
3509 ],
3510 );
3511 let module = |name: &str| {
3512 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
3513 .to_resolved()
3514 };
3515 let entries = vec![
3516 ChunkGroupEntry::Entry {
3517 modules: vec![module("page1.js").await?],
3518 heuristics: EntryHeuristics {
3519 clusters: vec![0],
3520 high_priority: false,
3521 },
3522 },
3523 ChunkGroupEntry::Entry {
3524 modules: vec![module("page2.js").await?],
3525 heuristics: EntryHeuristics {
3526 clusters: vec![1],
3527 high_priority: true,
3528 },
3529 },
3530 ];
3531 let graph = SingleModuleGraph::new_with_entries(
3532 GraphEntries::resolved_cell(GraphEntries::new(entries, vec![])),
3533 false,
3534 false,
3535 );
3536 let module_to_name = graph
3537 .connect()
3538 .await?
3539 .modules
3540 .keys()
3541 .map(async |m| Ok((*m, m.ident().await?.path.path.clone())))
3542 .try_join()
3543 .await?
3544 .into_iter()
3545 .collect();
3546 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
3547 .connect()
3548 .await?;
3549 Ok(ChunkGroupInfoTestGraph {
3550 module_graph,
3551 module_to_name,
3552 }
3553 .cell())
3554 }
3555
3556 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3557 async fn test_chunk_group_info_semantics() {
3558 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3559 BackendOptions::default(),
3560 noop_backing_storage(),
3561 ));
3562 tt.run_once(async move {
3563 let setup = chunk_group_info_test_graph()
3564 .read_strongly_consistent()
3565 .await?;
3566 let names = &setup.module_to_name;
3567 let name = |m: &ResolvedVc<Box<dyn Module>>| names[m].to_string();
3568 let info = compute_chunk_group_info(&setup.module_graph).await?.await?;
3569
3570 fn describe(
3572 info: &ChunkGroupInfo,
3573 index: usize,
3574 names: &FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3575 ) -> String {
3576 let list = |modules: &[ResolvedVc<Box<dyn Module>>]| {
3577 modules
3578 .iter()
3579 .map(|m| names[m].to_string())
3580 .collect::<Vec<_>>()
3581 .join(",")
3582 };
3583 match info.chunk_groups.get_index(index).unwrap() {
3584 ChunkGroup::Entry(modules) => format!("entry {}", list(modules)),
3585 ChunkGroup::Async(module) => format!("async {}", names[module]),
3586 ChunkGroup::IsolatedMerged {
3587 parent,
3588 merge_tag,
3589 entries,
3590 } => format!(
3591 "isolated merged {merge_tag} [{}] of ({})",
3592 list(entries),
3593 describe(info, *parent, names)
3594 ),
3595 other => panic!("unexpected chunk group {other:?}"),
3596 }
3597 }
3598
3599 let mut membership = info
3600 .module_chunk_groups
3601 .await?
3602 .iter()
3603 .map(|(module, groups)| {
3604 let mut groups = groups
3605 .iter()
3606 .map(|i| describe(&info, i as usize, names))
3607 .collect::<Vec<_>>();
3608 groups.sort();
3609 (name(module), groups)
3610 })
3611 .collect::<Vec<_>>();
3612 membership.sort();
3613
3614 let page1 = "entry page1.js";
3615 let page2 = "entry page2.js";
3616 let lazy = "async lazy.js";
3617 let iso = "isolated merged m [iso.js] of (entry page2.js)";
3618 let expected = |groups: &[&str]| {
3619 let mut groups = groups.iter().map(|s| s.to_string()).collect::<Vec<_>>();
3620 groups.sort();
3621 groups
3622 };
3623 assert_eq!(
3624 membership,
3625 vec![
3626 ("a1.js".to_string(), expected(&[page1])),
3627 ("a2.js".to_string(), expected(&[page1])),
3628 ("iso.js".to_string(), expected(&[iso])),
3629 ("lazy.js".to_string(), expected(&[lazy])),
3630 ("lib.js".to_string(), expected(&[page1, page2, lazy, iso])),
3631 ("page1.js".to_string(), expected(&[page1])),
3632 ("page2.js".to_string(), expected(&[page2])),
3633 ("shared.js".to_string(), expected(&[page1, page2])),
3634 ]
3635 );
3636
3637 let mut heuristics = (0..info.chunk_groups.len())
3640 .map(|i| {
3641 (
3642 describe(&info, i, names),
3643 info.chunking_heuristics.clusters[i].clone(),
3644 info.chunking_heuristics.priority_routes.contains(i as u32),
3645 )
3646 })
3647 .collect::<Vec<_>>();
3648 heuristics.sort();
3649 assert_eq!(
3650 heuristics,
3651 vec![
3652 (lazy.to_string(), vec![0], false),
3653 (page1.to_string(), vec![0], false),
3654 (page2.to_string(), vec![1], true),
3655 (iso.to_string(), vec![1], true),
3656 ]
3657 );
3658
3659 Ok(())
3660 })
3661 .await
3662 .unwrap();
3663 }
3664}