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, 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 include_unused: bool,
1120 ) -> impl Iterator<Item = (GraphEdgeIndex, GraphNodeIndex)> + 'a {
1121 let graph = &*self.get_graph(node.graph_idx).graph;
1122
1123 if cfg!(debug_assertions) && direction == Direction::Outgoing {
1124 let node_weight = graph.node_weight(node.node_idx).unwrap();
1125 if let SingleModuleGraphNode::VisitedModule { .. } = node_weight {
1126 panic!("iter_graphs_neighbors_rev called on VisitedModule node");
1127 }
1128 }
1129
1130 let mut walker = graph.neighbors_directed(node.node_idx, direction).detach();
1131 std::iter::from_fn(move || {
1132 while let Some((edge_idx, succ_idx)) = walker.next(graph) {
1133 let edge_idx = GraphEdgeIndex::new(node.graph_idx, edge_idx);
1134 if !include_unused
1135 && self.binding_usage.as_ref().is_some_and(|binding_usage| {
1136 binding_usage.is_reference_unused_edge(&edge_idx)
1137 })
1138 {
1139 continue;
1141 }
1142
1143 if !include_traced && self.get_edge(edge_idx).unwrap().chunking_type.is_traced() {
1144 continue;
1145 }
1146
1147 return Some((edge_idx, GraphNodeIndex::new(node.graph_idx, succ_idx)));
1148 }
1149 None
1150 })
1151 }
1152
1153 pub async fn get_ids(&self) -> Result<FxHashMap<ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>>> {
1156 self.iter_nodes()
1157 .map(async |n| Ok((n, n.ident().to_string().await?)))
1158 .join()
1159 .await
1160 .into_iter()
1161 .collect()
1162 }
1163
1164 pub fn traverse_nodes_dfs<S>(
1173 &self,
1174 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1175 state: &mut S,
1176 visit_preorder: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<GraphTraversalAction>,
1177 mut visit_postorder: impl FnMut(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<()>,
1178 ) -> Result<()> {
1179 let entries = entries.into_iter().collect::<Vec<_>>();
1180
1181 enum Pass {
1182 Visit,
1183 ExpandAndVisit,
1184 }
1185 let mut stack: Vec<(Pass, GraphNodeIndex)> = Vec::with_capacity(entries.len());
1186 for entry in entries.into_iter().rev() {
1187 stack.push((Pass::ExpandAndVisit, self.get_entry(entry)?));
1188 }
1189 let mut expanded = FxHashSet::default();
1190 while let Some((pass, current)) = stack.pop() {
1191 let current_node = self.get_node(current)?;
1192 match pass {
1193 Pass::Visit => {
1194 visit_postorder(current_node.module(), state)?;
1195 }
1196 Pass::ExpandAndVisit => {
1197 if !expanded.insert(current) {
1198 continue;
1199 }
1200 let action = visit_preorder(current_node.module(), state)?;
1201 if action == GraphTraversalAction::Exclude {
1202 continue;
1203 }
1204 stack.push((Pass::Visit, current));
1205 if action == GraphTraversalAction::Continue
1206 && self.should_visit_node(current_node, Direction::Outgoing)
1207 {
1208 let current = current_node
1209 .target_idx(Direction::Outgoing)
1210 .unwrap_or(current);
1211 stack.extend(
1212 self.iter_graphs_neighbors_rev(
1213 current,
1214 Direction::Outgoing,
1215 false,
1216 false,
1217 )
1218 .map(|(_, child)| (Pass::ExpandAndVisit, child)),
1219 );
1220 }
1221 }
1222 }
1223 }
1224
1225 Ok(())
1226 }
1227
1228 pub fn traverse_edges_bfs(
1239 &self,
1240 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1241 mut visitor: impl FnMut(
1242 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1243 ResolvedVc<Box<dyn Module>>,
1244 ) -> Result<GraphTraversalAction>,
1245 ) -> Result<()> {
1246 let mut queue = VecDeque::from(
1247 entries
1248 .into_iter()
1249 .map(|e| self.get_entry(e))
1250 .collect::<Result<Vec<_>>>()?,
1251 );
1252 let mut visited = FxHashSet::default();
1253 for entry_node in &queue {
1254 visitor(None, self.get_node(*entry_node)?.module())?;
1255 }
1256 while let Some(node) = queue.pop_front() {
1257 if visited.insert(node) {
1258 let node_weight = self.get_node(node)?;
1259 for (edge, succ) in
1260 self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1261 {
1262 let succ_weight = self.get_node(succ)?;
1263 let action = visitor(
1264 Some((node_weight.module(), self.get_edge(edge)?)),
1265 succ_weight.module(),
1266 )?;
1267 if !self.should_visit_node(succ_weight, Direction::Outgoing) {
1268 continue;
1269 }
1270 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1271 if !visited.contains(&succ) && action == GraphTraversalAction::Continue {
1272 queue.push_back(succ);
1273 }
1274 }
1275 }
1276 }
1277
1278 Ok(())
1279 }
1280
1281 pub fn traverse_edges_unordered(
1290 &self,
1291 mut visitor: impl FnMut(
1292 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1293 ResolvedVc<Box<dyn Module>>,
1294 ) -> Result<()>,
1295 ) -> Result<()> {
1296 self.traverse_edges_dfs(
1299 self.all_chunk_group_entry_modules(),
1300 &mut (),
1301 |parent, target, _| {
1302 visitor(parent, target)?;
1303 Ok(GraphTraversalAction::Continue)
1304 },
1305 |_, _, _| Ok(()),
1306 false,
1307 false,
1308 )
1309 }
1310
1311 pub fn traverse_edges_dfs<S>(
1332 &self,
1333 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1334 state: &mut S,
1335 visit_preorder: impl FnMut(
1336 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1337 ResolvedVc<Box<dyn Module>>,
1338 &mut S,
1339 ) -> Result<GraphTraversalAction>,
1340 visit_postorder: impl FnMut(
1341 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1342 ResolvedVc<Box<dyn Module>>,
1343 &mut S,
1344 ) -> Result<()>,
1345 include_traced: bool,
1346 include_unused: bool,
1347 ) -> Result<()> {
1348 self.traverse_edges_dfs_impl::<S>(
1349 entries,
1350 state,
1351 visit_preorder,
1352 visit_postorder,
1353 Direction::Outgoing,
1354 include_traced,
1355 include_unused,
1356 )
1357 }
1358
1359 pub fn traverse_edges_reverse_dfs<S>(
1376 &self,
1377 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1378 state: &mut S,
1379 visit_preorder: impl FnMut(
1380 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1381 ResolvedVc<Box<dyn Module>>,
1382 &mut S,
1383 ) -> Result<GraphTraversalAction>,
1384 visit_postorder: impl FnMut(
1385 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1386 ResolvedVc<Box<dyn Module>>,
1387 &mut S,
1388 ) -> Result<()>,
1389 ) -> Result<()> {
1390 self.traverse_edges_dfs_impl::<S>(
1391 entries,
1392 state,
1393 visit_preorder,
1394 visit_postorder,
1395 Direction::Incoming,
1396 false,
1397 false,
1398 )
1399 }
1400
1401 fn traverse_edges_dfs_impl<S>(
1402 &self,
1403 entries: impl IntoIterator<Item = ResolvedVc<Box<dyn Module>>>,
1404 state: &mut S,
1405 mut visit_preorder: impl FnMut(
1406 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1407 ResolvedVc<Box<dyn Module>>,
1408 &mut S,
1409 ) -> Result<GraphTraversalAction>,
1410 mut visit_postorder: impl FnMut(
1411 Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData)>,
1412 ResolvedVc<Box<dyn Module>>,
1413 &mut S,
1414 ) -> Result<()>,
1415 direction: Direction,
1416 include_traced: bool,
1417 include_unused: bool,
1418 ) -> Result<()> {
1419 if direction == Direction::Incoming {
1420 debug_assert!(
1421 self.skip_visited_module_children,
1422 "Can only trace reverse edges in a single layer graph. We do not model cross \
1423 graph reverse edges"
1424 );
1425 }
1426 let entries = entries.into_iter().collect::<Vec<_>>();
1427
1428 enum Pass {
1429 Visit,
1430 ExpandAndVisit,
1431 }
1432 #[allow(clippy::type_complexity)] let mut stack: Vec<(
1434 Pass,
1435 Option<(GraphNodeIndex, GraphEdgeIndex)>,
1436 GraphNodeIndex,
1437 )> = Vec::with_capacity(entries.len());
1438 for entry in entries.into_iter().rev() {
1439 stack.push((Pass::ExpandAndVisit, None, self.get_entry(entry)?));
1440 }
1441 let mut expanded = FxHashSet::default();
1442 while let Some((pass, parent, current)) = stack.pop() {
1443 let parent_arg = match parent {
1444 Some((parent_node, parent_edge)) => Some((
1445 self.get_node(parent_node)?.module(),
1446 self.get_edge(parent_edge)?,
1447 )),
1448 None => None,
1449 };
1450 let current_node = self.get_node(current)?;
1451 match pass {
1452 Pass::Visit => {
1453 visit_postorder(parent_arg, current_node.module(), state)?;
1454 }
1455 Pass::ExpandAndVisit => {
1456 let action = visit_preorder(parent_arg, current_node.module(), state)?;
1457 if action == GraphTraversalAction::Exclude {
1458 continue;
1459 }
1460 stack.push((Pass::Visit, parent, current));
1461 if action == GraphTraversalAction::Continue
1462 && expanded.insert(current)
1463 && self.should_visit_node(current_node, direction)
1464 {
1465 let current = current_node.target_idx(direction).unwrap_or(current);
1466 stack.extend(
1467 self.iter_graphs_neighbors_rev(
1468 current,
1469 direction,
1470 include_traced,
1471 include_unused,
1472 )
1473 .map(|(edge, child)| {
1474 (Pass::ExpandAndVisit, Some((current, edge)), child)
1475 }),
1476 );
1477 }
1478 }
1479 }
1480 }
1481
1482 Ok(())
1483 }
1484
1485 pub fn traverse_cycles(
1489 &self,
1490 edge_filter: impl Fn(&RefData) -> bool,
1491 mut visit_cycle: impl FnMut(&[&ResolvedVc<Box<dyn Module>>]) -> Result<()>,
1492 ) -> Result<()> {
1493 for (graph_idx, graph) in self.graphs.iter().enumerate() {
1494 graph.traverse_cycles(
1495 &edge_filter,
1496 &mut visit_cycle,
1497 graph_idx as u32,
1498 &self.binding_usage,
1499 )?;
1500 }
1501 Ok(())
1502 }
1503
1504 pub fn traverse_edges_fixed_point_with_priority<'graph, S, P: Ord>(
1526 &'graph self,
1527 entries: impl IntoIterator<Item = (ResolvedVc<Box<dyn Module>>, P)>,
1528 state: &mut S,
1529 mut visit: impl FnMut(
1530 Option<(ResolvedVc<Box<dyn Module>>, &'graph RefData, GraphEdgeIndex)>,
1531 ResolvedVc<Box<dyn Module>>,
1532 GraphNodeIndex,
1533 &mut S,
1534 ) -> Result<GraphTraversalAction>,
1535 priority: impl Fn(ResolvedVc<Box<dyn Module>>, &mut S) -> Result<P>,
1536 ) -> Result<usize> {
1537 if self.skip_visited_module_children {
1538 panic!(
1539 "traverse_edges_fixed_point_with_priority musn't be called on individual graphs"
1540 );
1541 }
1542
1543 let mut visit_order = 0usize;
1544 let mut order = || {
1545 let order = visit_order;
1546 visit_order += 1;
1547 order
1548 };
1549 #[derive(PartialEq, Eq)]
1550 struct NodeWithPriority<T: Ord> {
1551 node: GraphNodeIndex,
1552 priority: T,
1553 visit_order: usize,
1554 }
1555 impl<T: Ord> PartialOrd for NodeWithPriority<T> {
1556 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1557 Some(self.cmp(other))
1558 }
1559 }
1560 impl<T: Ord> Ord for NodeWithPriority<T> {
1561 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1562 self.priority
1565 .cmp(&other.priority)
1566 .then(self.visit_order.cmp(&other.visit_order))
1569 }
1570 }
1571
1572 let mut queue_set = FxHashSet::default();
1573 let mut queue = BinaryHeap::from_iter(
1574 entries
1575 .into_iter()
1576 .map(|(m, priority)| {
1577 Ok(NodeWithPriority {
1578 node: self.get_entry(m)?,
1579 priority,
1580 visit_order: order(),
1581 })
1582 })
1583 .collect::<Result<Vec<_>>>()?,
1584 );
1585
1586 for entry_node in &queue {
1587 visit(
1588 None,
1589 self.get_node(entry_node.node)?.module(),
1590 entry_node.node,
1591 state,
1592 )?;
1593 }
1594
1595 let mut visit_count = 0usize;
1596 while let Some(NodeWithPriority { node, .. }) = queue.pop() {
1597 queue_set.remove(&node);
1598 let node_weight = self.get_node(node)?;
1599 let node = node_weight.target_idx(Direction::Outgoing).unwrap_or(node);
1600
1601 visit_count += 1;
1602
1603 for (edge, succ) in
1604 self.iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1605 {
1606 let succ_weight = self.get_node(succ)?;
1607
1608 let action = visit(
1609 Some((node_weight.module(), self.get_edge(edge)?, edge)),
1610 succ_weight.module(),
1611 succ,
1612 state,
1613 )?;
1614
1615 let succ = succ_weight.target_idx(Direction::Outgoing).unwrap_or(succ);
1616 if action == GraphTraversalAction::Continue && queue_set.insert(succ) {
1617 queue.push(NodeWithPriority {
1618 node: succ,
1619 priority: priority(succ_weight.module(), state)?,
1620 visit_order: order(),
1621 });
1622 }
1623 }
1624 }
1625
1626 Ok(visit_count)
1627 }
1628
1629 pub fn iter_reachable_modules(
1631 &self,
1632 ) -> Result<impl Iterator<Item = ResolvedVc<Box<dyn Module>>>> {
1633 Ok(self.iter_reachable_nodes()?.filter_map(|n| match n {
1634 SingleModuleGraphNode::Module(m) => Some(*m),
1635 SingleModuleGraphNode::VisitedModule { .. } => None,
1636 }))
1637 }
1638
1639 pub fn iter_reachable_nodes<'a>(
1642 &'a self,
1643 ) -> Result<impl Iterator<Item = &'a SingleModuleGraphNode> + 'a> {
1644 ModuleGraphSnapshotNodeIterator::new(self)
1645 }
1646}
1647
1648struct ModuleGraphSnapshotNodeIterator<'a> {
1649 graph: &'a ModuleGraphSnapshot,
1650 visited: FxHashSet<GraphNodeIndex>,
1651 visit_queue: VecDeque<GraphNodeIndex>,
1652}
1653
1654impl<'a> ModuleGraphSnapshotNodeIterator<'a> {
1655 fn new(graph: &'a ModuleGraphSnapshot) -> Result<Self> {
1656 let entries = graph
1657 .graphs
1658 .iter()
1659 .flat_map(|g| g.chunk_group_modules())
1660 .map(|e| graph.get_entry(e))
1661 .collect::<Result<VecDeque<_>>>()?;
1662
1663 Ok(Self {
1664 graph,
1665 visited: FxHashSet::default(),
1666 visit_queue: entries,
1667 })
1668 }
1669}
1670impl<'a> Iterator for ModuleGraphSnapshotNodeIterator<'a> {
1671 type Item = &'a SingleModuleGraphNode;
1672
1673 fn next(&mut self) -> Option<Self::Item> {
1674 while let Some(node_idx) = self.visit_queue.pop_front() {
1675 if self.visited.insert(node_idx) {
1676 let node_weight = self.graph.get_node(node_idx).unwrap();
1677 if self
1678 .graph
1679 .should_visit_node(node_weight, Direction::Outgoing)
1680 {
1681 let node = node_weight
1682 .target_idx(Direction::Outgoing)
1683 .unwrap_or(node_idx);
1684 self.visit_queue.extend(
1685 self.graph
1686 .iter_graphs_neighbors_rev(node, Direction::Outgoing, false, false)
1687 .map(|(_, succ)| succ)
1688 .filter(|succ| !self.visited.contains(succ)),
1689 );
1690 }
1691 return Some(node_weight);
1692 }
1693 }
1694 None
1695 }
1696}
1697impl FusedIterator for ModuleGraphSnapshotNodeIterator<'_> {}
1698
1699#[turbo_tasks::value_impl]
1700impl SingleModuleGraph {
1701 #[turbo_tasks::function(operation)]
1702 pub async fn new_with_entry(
1703 entry: ChunkGroupEntry,
1704 include_traced: bool,
1705 include_binding_usage: bool,
1706 ) -> Result<Vc<Self>> {
1707 SingleModuleGraph::new_inner(
1708 &GraphEntries::from_chunk_groups(vec![entry]),
1709 &Default::default(),
1710 include_traced,
1711 include_binding_usage,
1712 )
1713 .await
1714 }
1715
1716 #[turbo_tasks::function(operation)]
1717 pub async fn new_with_entries(
1718 entries: ResolvedVc<GraphEntries>,
1719 include_traced: bool,
1720 include_binding_usage: bool,
1721 ) -> Result<Vc<Self>> {
1722 SingleModuleGraph::new_inner(
1723 &*entries.await?,
1724 &Default::default(),
1725 include_traced,
1726 include_binding_usage,
1727 )
1728 .await
1729 }
1730
1731 #[turbo_tasks::function(operation)]
1732 pub async fn new_with_entries_visited(
1733 entries: ResolvedVc<GraphEntries>,
1734 visited_modules: OperationVc<VisitedModules>,
1735 include_traced: bool,
1736 include_binding_usage: bool,
1737 ) -> Result<Vc<Self>> {
1738 SingleModuleGraph::new_inner(
1739 &*entries.await?,
1740 &visited_modules.connect().await?.modules,
1741 include_traced,
1742 include_binding_usage,
1743 )
1744 .await
1745 }
1746
1747 #[turbo_tasks::function(operation)]
1748 pub async fn new_with_entries_visited_intern(
1749 entries: GraphEntries,
1751 visited_modules: OperationVc<VisitedModules>,
1752 include_traced: bool,
1753 include_binding_usage: bool,
1754 ) -> Result<Vc<Self>> {
1755 SingleModuleGraph::new_inner(
1756 &entries,
1757 &visited_modules.connect().await?.modules,
1758 include_traced,
1759 include_binding_usage,
1760 )
1761 .await
1762 }
1763
1764 #[turbo_tasks::function]
1765 pub async fn module_count(&self) -> Vc<u64> {
1766 Vc::cell(self.number_of_modules as u64)
1767 }
1768
1769 #[turbo_tasks::function]
1770 pub async fn edge_count(&self) -> Vc<u64> {
1771 Vc::cell(self.graph.edge_count() as u64)
1772 }
1773}
1774
1775#[derive(Clone, Debug, Serialize, Deserialize, NonLocalValue)]
1776pub enum SingleModuleGraphNode {
1777 Module(ResolvedVc<Box<dyn Module>>),
1778 VisitedModule {
1780 idx: GraphNodeIndex,
1781 module: ResolvedVc<Box<dyn Module>>,
1782 },
1783}
1784
1785impl SingleModuleGraphNode {
1786 pub fn module(&self) -> ResolvedVc<Box<dyn Module>> {
1787 match self {
1788 SingleModuleGraphNode::Module(module) => *module,
1789 SingleModuleGraphNode::VisitedModule { module, .. } => *module,
1790 }
1791 }
1792 pub fn target_idx(&self, direction: Direction) -> Option<GraphNodeIndex> {
1793 match self {
1794 SingleModuleGraphNode::VisitedModule { idx, .. } => match direction {
1795 Direction::Outgoing => Some(*idx),
1796 Direction::Incoming => None,
1797 },
1798 SingleModuleGraphNode::Module(_) => None,
1799 }
1800 }
1801}
1802
1803#[derive(PartialEq, Eq, Debug)]
1804pub enum GraphTraversalAction {
1805 Continue,
1807 Skip,
1809 Exclude,
1811}
1812
1813#[derive(Clone, Hash, PartialEq, Eq)]
1816enum SingleModuleGraphBuilderNode {
1817 Module {
1819 module: ResolvedVc<Box<dyn Module>>,
1820 ident: Option<ReadRef<RcStr>>,
1822 is_traced: bool,
1824 },
1825 VisitedModule {
1827 module: ResolvedVc<Box<dyn Module>>,
1828 idx: GraphNodeIndex,
1829 },
1830}
1831
1832impl SingleModuleGraphBuilderNode {
1833 async fn new_module(
1834 emit_spans: bool,
1835 module: ResolvedVc<Box<dyn Module>>,
1836 is_traced: bool,
1837 ) -> Result<Self> {
1838 Ok(Self::Module {
1839 module,
1840 ident: if emit_spans {
1841 Some(module.ident_string().untracked().await?)
1843 } else {
1844 None
1845 },
1846 is_traced,
1847 })
1848 }
1849 fn new_visited_module(module: ResolvedVc<Box<dyn Module>>, idx: GraphNodeIndex) -> Self {
1850 Self::VisitedModule { module, idx }
1851 }
1852}
1853
1854struct SingleModuleGraphBuilder<'a> {
1855 visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
1856
1857 emit_spans: bool,
1858
1859 include_traced: bool,
1861
1862 include_binding_usage: bool,
1864}
1865impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
1866 type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;
1867 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
1868
1869 fn visit(
1870 &mut self,
1871 node: &SingleModuleGraphBuilderNode,
1872 _edge: Option<&RefData>,
1873 ) -> VisitControlFlow {
1874 match node {
1875 SingleModuleGraphBuilderNode::Module { .. } => VisitControlFlow::Continue,
1876 SingleModuleGraphBuilderNode::VisitedModule { .. } => VisitControlFlow::Skip,
1878 }
1879 }
1880
1881 fn edges(&mut self, node: &SingleModuleGraphBuilderNode) -> Self::EdgesFuture {
1882 let &SingleModuleGraphBuilderNode::Module {
1884 module, is_traced, ..
1885 } = node
1886 else {
1887 unreachable!()
1889 };
1890 let visited_modules = self.visited_modules;
1891 let emit_spans = self.emit_spans;
1892 let include_traced = self.include_traced;
1893 let include_binding_usage = self.include_binding_usage;
1894 async move {
1895 let refs_cell = if !is_traced {
1896 primary_chunkable_referenced_modules(*module, include_traced, include_binding_usage)
1897 } else {
1898 referenced_modules_and_affecting_sources(*module, false)
1900 };
1901 let refs = match refs_cell.await {
1902 Ok(refs) => refs,
1903 Err(e) => {
1904 return Err(e.context(module.ident().to_string().await?));
1905 }
1906 };
1907
1908 refs.iter()
1909 .flat_map(|(reference, resolved)| {
1910 resolved.modules.iter().map(|m| {
1911 (
1912 *reference,
1913 resolved.chunking_type.clone(),
1914 resolved.binding_usage.clone(),
1915 *m,
1916 )
1917 })
1918 })
1919 .filter(|(_, ty, _, _)| {
1920 !matches!(
1929 ty,
1930 ChunkingType::Traced {
1931 mode: TracedMode::Transitive
1932 }
1933 ) || is_traced
1934 })
1935 .map(async |(reference, ty, binding_usage, target)| {
1936 let to = if let Some(idx) = visited_modules.get(&target) {
1937 SingleModuleGraphBuilderNode::new_visited_module(target, *idx)
1938 } else {
1939 SingleModuleGraphBuilderNode::new_module(
1940 emit_spans,
1941 target,
1942 is_traced || ty.is_traced(),
1943 )
1944 .await?
1945 };
1946 Ok((
1947 to,
1948 RefData {
1949 chunking_type: ty,
1950 binding_usage,
1951 reference,
1952 },
1953 ))
1954 })
1955 .try_join()
1956 .await
1957 }
1958 }
1959
1960 fn span(
1961 &mut self,
1962 node: &SingleModuleGraphBuilderNode,
1963 edge: Option<&RefData>,
1964 ) -> tracing::Span {
1965 if !self.emit_spans {
1966 return Span::none();
1967 }
1968
1969 let mut span = match node {
1970 SingleModuleGraphBuilderNode::Module {
1971 ident: Some(ident), ..
1972 } => {
1973 tracing::info_span!("module", name = display(ident))
1974 }
1975 SingleModuleGraphBuilderNode::VisitedModule { .. } => {
1976 tracing::info_span!("visited module")
1977 }
1978 _ => unreachable!(),
1979 };
1980
1981 if let Some(edge) = edge {
1982 match &edge.chunking_type {
1983 ChunkingType::Parallel {
1984 inherit_async: _,
1985 hoisted: _,
1986 } => {}
1987 ChunkingType::Traced { .. } => {
1988 let _span = span.entered();
1989 span = tracing::info_span!("traced reference");
1990 }
1991 ChunkingType::Async => {
1992 let _span = span.entered();
1993 span = tracing::info_span!("async reference");
1994 }
1995 ChunkingType::PerEntry => {
1996 let _span = span.entered();
1997 span = tracing::info_span!("per-entry reference");
1998 }
1999 ChunkingType::Emitted { namespace, .. } => {
2000 let _span = span.entered();
2001 span = tracing::info_span!("emitted reference", namespace = debug(&namespace));
2002 }
2003 ChunkingType::Collected { namespace, .. } => {
2004 let _span = span.entered();
2005 span =
2006 tracing::info_span!("collected reference", namespace = debug(&namespace));
2007 }
2008 ChunkingType::Isolated { _ty: ty, merge_tag } => {
2009 let _span = span.entered();
2010 span = tracing::info_span!(
2011 "isolated reference",
2012 ty = debug(&ty),
2013 merge_tag = debug(&merge_tag)
2014 );
2015 }
2016 ChunkingType::Shared {
2017 inherit_async: _,
2018 merge_tag,
2019 } => {
2020 let _span = span.entered();
2021 span = tracing::info_span!("shared reference", merge_tag = debug(&merge_tag));
2022 }
2023 };
2024 }
2025
2026 span
2027 }
2028}
2029
2030#[cfg(test)]
2031pub mod tests {
2032 use anyhow::Result;
2033 use rustc_hash::FxHashMap;
2034 use turbo_rcstr::{RcStr, rcstr};
2035 use turbo_tasks::{ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc};
2036 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
2037 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
2038
2039 use super::*;
2040 use crate::{
2041 asset::{Asset, AssetContent},
2042 ident::AssetIdent,
2043 issue::{CollectibleIssuesExt, IssueSeverity},
2044 module::{Module, ModuleSideEffects},
2045 module_graph::chunk_group_info::EntryHeuristics,
2046 reference::{ModuleReference, ModuleReferences},
2047 resolve::ModuleResolveResult,
2048 };
2049
2050 #[turbo_tasks::value(shared)]
2051 struct ImportTraceTestResult {
2052 has_entry: bool,
2053 traces: Vec<Vec<RcStr>>,
2054 missing_traces: Vec<Vec<RcStr>>,
2055 }
2056
2057 #[turbo_tasks::function(operation, root)]
2058 async fn import_trace_test_operation(rootless: bool) -> Result<Vc<ImportTraceTestResult>> {
2059 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2060 let root = fs.root().await?;
2061 let repo = TestRepo::new(
2062 &root,
2063 [
2064 ("entry.js", vec!["dependency.js"]),
2065 ("dependency.js", vec!["entry.js"]),
2066 ],
2067 );
2068 let entry = Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join("entry.js")?, repo))
2069 .to_resolved()
2070 .await?;
2071 let graph = SingleModuleGraph::new_with_entries(
2072 GraphEntries::resolved_cell(GraphEntries::new(
2073 vec![ChunkGroupEntry::Entry {
2074 modules: vec![entry],
2075 heuristics: EntryHeuristics::default(),
2076 }],
2077 vec![],
2078 )),
2079 false,
2080 false,
2081 )
2082 .connect()
2083 .to_resolved()
2084 .await?;
2085 let graph = if rootless {
2086 let _ = graph.await?.entry_nodes();
2089 let mut graph = (*graph.await?).clone();
2090 graph.entries = GraphEntries::default();
2091 graph.resolved_cell()
2092 } else {
2093 graph
2094 };
2095
2096 let has_entry = graph.await?.has_entry_module(entry);
2097 let tracer = ModuleGraphImportTracer::new(*graph);
2098 let traces = tracer
2099 .get_traces(root.join("dependency.js")?)
2100 .await?
2101 .0
2102 .iter()
2103 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2104 .collect();
2105 let missing_traces = tracer
2106 .get_traces(root.join("missing.js")?)
2107 .await?
2108 .0
2109 .iter()
2110 .map(|trace| trace.iter().map(|ident| ident.path.path.clone()).collect())
2111 .collect();
2112
2113 Ok(ImportTraceTestResult {
2114 has_entry,
2115 traces,
2116 missing_traces,
2117 }
2118 .cell())
2119 }
2120
2121 #[turbo_tasks::value(shared)]
2122 struct ImportTraceIssues {
2123 issues: Vec<(IssueSeverity, RcStr)>,
2124 }
2125
2126 #[turbo_tasks::function(operation, root)]
2127 async fn import_trace_issues_operation(
2128 trace_operation: OperationVc<ImportTraceTestResult>,
2129 ) -> Result<Vc<ImportTraceIssues>> {
2130 let _ = trace_operation.connect().await?;
2131 let issues = trace_operation
2132 .peek_issues()
2133 .iter()
2134 .map(async |issue| {
2135 let issue = issue.into_trait_ref().await?;
2136 Ok((
2137 issue.severity(),
2138 issue.title().await?.to_unstyled_string().into(),
2139 ))
2140 })
2141 .try_join()
2142 .await?;
2143 Ok(ImportTraceIssues { issues }.cell())
2144 }
2145
2146 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2147 async fn test_import_trace_uses_explicit_entry_as_cycle_root() {
2148 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2149 BackendOptions::default(),
2150 noop_backing_storage(),
2151 ));
2152 tt.run_once(async {
2153 let result = import_trace_test_operation(false)
2154 .read_strongly_consistent()
2155 .await?;
2156 assert!(result.has_entry);
2157 assert_eq!(
2158 result.traces,
2159 vec![vec![rcstr!("dependency.js"), rcstr!("entry.js")]]
2160 );
2161 assert!(result.missing_traces.is_empty());
2162 Ok(())
2163 })
2164 .await
2165 .unwrap();
2166 }
2167
2168 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2169 async fn test_cloned_rootless_import_trace_resets_cache_and_emits_bug_issue() {
2170 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2171 BackendOptions::default(),
2172 noop_backing_storage(),
2173 ));
2174 tt.run_once(async {
2175 let trace_operation = import_trace_test_operation(true);
2176 let result = trace_operation.read_strongly_consistent().await?;
2177 assert!(!result.has_entry);
2178 assert_eq!(result.traces, vec![vec![rcstr!("dependency.js")]]);
2179 assert!(result.missing_traces.is_empty());
2180
2181 let issues = import_trace_issues_operation(trace_operation)
2182 .read_strongly_consistent()
2183 .await?;
2184 assert_eq!(
2185 issues.issues,
2186 vec![(
2187 IssueSeverity::Bug,
2188 rcstr!("Module graph is missing an entry point")
2189 )]
2190 );
2191 Ok(())
2192 })
2193 .await
2194 .unwrap();
2195 }
2196
2197 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2198 async fn test_traverse_dfs_from_entries_diamond() {
2199 run_graph_test(
2200 vec![rcstr!("a.js")],
2201 {
2202 let mut deps = FxHashMap::default();
2203 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2205 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js")]);
2206 deps.insert(rcstr!("c.js"), vec![rcstr!("d.js")]);
2207 deps
2208 },
2209 |graph, entry_modules, module_to_name| {
2210 let mut preorder_visits = Vec::new();
2211 let mut postorder_visits = Vec::new();
2212
2213 graph.traverse_edges_dfs(
2214 entry_modules,
2215 &mut (),
2216 |parent, target, _| {
2217 preorder_visits.push((
2218 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2219 module_to_name.get(&target).unwrap().clone(),
2220 ));
2221 Ok(GraphTraversalAction::Continue)
2222 },
2223 |parent, target, _| {
2224 postorder_visits.push((
2225 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2226 module_to_name.get(&target).unwrap().clone(),
2227 ));
2228 Ok(())
2229 },
2230 false,
2231 false,
2232 )?;
2233 assert_eq!(
2234 vec![
2235 (None, rcstr!("a.js")),
2236 (Some(rcstr!("a.js")), rcstr!("b.js")),
2237 (Some(rcstr!("b.js")), rcstr!("d.js")),
2238 (Some(rcstr!("a.js")), rcstr!("c.js")),
2239 (Some(rcstr!("c.js")), rcstr!("d.js"))
2240 ],
2241 preorder_visits
2242 );
2243 assert_eq!(
2244 vec![
2245 (Some(rcstr!("b.js")), rcstr!("d.js")),
2246 (Some(rcstr!("a.js")), rcstr!("b.js")),
2247 (Some(rcstr!("c.js")), rcstr!("d.js")),
2248 (Some(rcstr!("a.js")), rcstr!("c.js")),
2249 (None, rcstr!("a.js"))
2250 ],
2251 postorder_visits
2252 );
2253 Ok(())
2254 },
2255 )
2256 .await;
2257 }
2258
2259 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2260 async fn test_traverse_dfs_from_entries_cycle() {
2261 run_graph_test(
2262 vec![rcstr!("a.js")],
2263 {
2264 let mut deps = FxHashMap::default();
2265 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2267 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2268 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2269 deps
2270 },
2271 |graph, entry_modules, module_to_name| {
2272 let mut preorder_visits = Vec::new();
2273 let mut postorder_visits = Vec::new();
2274
2275 graph.traverse_edges_dfs(
2276 entry_modules,
2277 &mut (),
2278 |parent, target, _| {
2279 preorder_visits.push((
2280 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2281 module_to_name.get(&target).unwrap().clone(),
2282 ));
2283 Ok(GraphTraversalAction::Continue)
2284 },
2285 |parent, target, _| {
2286 postorder_visits.push((
2287 parent.map(|(node, _)| module_to_name.get(&node).unwrap().clone()),
2288 module_to_name.get(&target).unwrap().clone(),
2289 ));
2290 Ok(())
2291 },
2292 false,
2293 false,
2294 )?;
2295 assert_eq!(
2296 vec![
2297 (None, rcstr!("a.js")),
2298 (Some(rcstr!("a.js")), rcstr!("b.js")),
2299 (Some(rcstr!("b.js")), rcstr!("c.js")),
2300 (Some(rcstr!("c.js")), rcstr!("a.js")),
2301 ],
2302 preorder_visits
2303 );
2304 assert_eq!(
2305 vec![
2306 (Some(rcstr!("c.js")), rcstr!("a.js")),
2307 (Some(rcstr!("b.js")), rcstr!("c.js")),
2308 (Some(rcstr!("a.js")), rcstr!("b.js")),
2309 (None, rcstr!("a.js"))
2310 ],
2311 postorder_visits
2312 );
2313 Ok(())
2314 },
2315 )
2316 .await;
2317 }
2318
2319 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2320 async fn test_traverse_edges_fixed_point_with_priority_cycle() {
2321 run_graph_test(
2322 vec![rcstr!("a.js")],
2323 {
2324 let mut deps = FxHashMap::default();
2325 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js")]);
2327 deps.insert(rcstr!("b.js"), vec![rcstr!("c.js")]);
2328 deps.insert(rcstr!("c.js"), vec![rcstr!("a.js")]);
2329 deps
2330 },
2331 |graph, entry_modules, module_to_name| {
2332 let mut visits = Vec::new();
2333 let mut count = 0;
2334
2335 graph.traverse_edges_fixed_point_with_priority(
2336 entry_modules.into_iter().map(|m| (m, 0)),
2337 &mut (),
2338 |parent, target, _, _| {
2339 visits.push((
2340 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2341 module_to_name.get(&target).unwrap().clone(),
2342 ));
2343 count += 1;
2344
2345 Ok(if count < 6 {
2347 GraphTraversalAction::Continue
2348 } else {
2349 GraphTraversalAction::Skip
2350 })
2351 },
2352 |_, _| Ok(0),
2353 )?;
2354 assert_eq!(
2355 vec![
2356 (None, rcstr!("a.js")),
2357 (Some(rcstr!("a.js")), rcstr!("b.js")),
2358 (Some(rcstr!("b.js")), rcstr!("c.js")),
2359 (Some(rcstr!("c.js")), rcstr!("a.js")),
2360 (Some(rcstr!("a.js")), rcstr!("b.js")),
2362 (Some(rcstr!("b.js")), rcstr!("c.js")),
2363 ],
2364 visits
2365 );
2366
2367 Ok(())
2368 },
2369 )
2370 .await;
2371 }
2372
2373 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2374 async fn test_traverse_edges_fixed_point_no_priority_is_bfs() {
2375 run_graph_test(
2376 vec![rcstr!("a.js")],
2377 {
2378 let mut deps = FxHashMap::default();
2379 deps.insert(rcstr!("a.js"), vec![rcstr!("b.js"), rcstr!("c.js")]);
2384 deps.insert(rcstr!("b.js"), vec![rcstr!("d.js"), rcstr!("e.js")]);
2385 deps.insert(rcstr!("c.js"), vec![rcstr!("e.js"), rcstr!("f.js")]);
2386 deps
2387 },
2388 |graph, entry_modules, module_to_name| {
2389 let mut visits = Vec::new();
2390 let mut count = 0;
2391
2392 graph.traverse_edges_fixed_point_with_priority(
2393 entry_modules.into_iter().map(|m| (m, 0)),
2394 &mut (),
2395 |parent, target, _, _| {
2396 visits.push((
2397 parent.map(|(node, _, _)| module_to_name.get(&node).unwrap().clone()),
2398 module_to_name.get(&target).unwrap().clone(),
2399 ));
2400 count += 1;
2401
2402 Ok(if count < 6 {
2404 GraphTraversalAction::Continue
2405 } else {
2406 GraphTraversalAction::Skip
2407 })
2408 },
2409 |_, _| Ok(0),
2410 )?;
2411
2412 assert_eq!(
2413 vec![
2414 (None, rcstr!("a.js")),
2415 (Some(rcstr!("a.js")), rcstr!("c.js")),
2416 (Some(rcstr!("a.js")), rcstr!("b.js")),
2417 (Some(rcstr!("b.js")), rcstr!("e.js")),
2418 (Some(rcstr!("b.js")), rcstr!("d.js")),
2419 (Some(rcstr!("c.js")), rcstr!("f.js")),
2420 (Some(rcstr!("c.js")), rcstr!("e.js")),
2421 ],
2422 visits
2423 );
2424
2425 Ok(())
2426 },
2427 )
2428 .await;
2429 }
2430
2431 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2432 async fn test_traverse_cycles() {
2433 run_graph_test(
2434 vec![rcstr!("a.js")],
2435 {
2436 let mut deps = FxHashMap::default();
2437 deps.insert(
2444 rcstr!("a.js"),
2445 vec![rcstr!("i.js"), rcstr!("s.js"), rcstr!("x.js")],
2446 );
2447 deps.insert(rcstr!("i.js"), vec![rcstr!("j.js")]);
2448 deps.insert(rcstr!("j.js"), vec![rcstr!("k.js")]);
2449 deps.insert(rcstr!("k.js"), vec![rcstr!("i.js")]);
2450 deps.insert(rcstr!("s.js"), vec![rcstr!("s.js")]);
2451 deps
2452 },
2453 |graph, _, module_to_name| {
2454 let mut cycles = vec![];
2455
2456 graph.traverse_cycles(
2457 |_| true,
2458 |cycle| {
2459 cycles.push(
2460 cycle
2461 .iter()
2462 .map(|n| module_to_name.get(*n).unwrap().clone())
2463 .collect::<Vec<_>>(),
2464 );
2465 Ok(())
2466 },
2467 )?;
2468
2469 assert_eq!(
2470 cycles,
2471 vec![
2472 vec![rcstr!("k.js"), rcstr!("j.js"), rcstr!("i.js")],
2473 vec![rcstr!("s.js")]
2474 ],
2475 );
2476
2477 Ok(())
2478 },
2479 )
2480 .await;
2481 }
2482
2483 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2484 async fn test_reverse_edges_through_layered_graph() {
2485 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2486 BackendOptions::default(),
2487 noop_backing_storage(),
2488 ));
2489 tt.run_once(async move {
2490 #[turbo_tasks::value]
2491 struct ReverseTraversalResults {
2492 forward: Vec<RcStr>,
2493 reverse_from_d: Vec<RcStr>,
2494 reverse_from_b: Vec<RcStr>,
2495 }
2496
2497 #[turbo_tasks::function(operation, root)]
2498 async fn reverse_traversal_results_operation() -> Result<Vc<ReverseTraversalResults>> {
2499 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2500 let root = fs.root().await?;
2501
2502 let repo = TestRepo::new(
2505 &root,
2506 [("a.js", vec!["b.js", "d.js"]), ("b.js", vec!["c.js"])],
2507 );
2508 let make_module = |name| {
2509 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2510 .to_resolved()
2511 };
2512 let a_module = make_module("a.js").await?;
2513 let b_module = make_module("b.js").await?;
2514
2515 let parent_graph = SingleModuleGraph::new_with_entries(
2516 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2517 modules: vec![b_module],
2518 heuristics: EntryHeuristics::default(),
2519 }])
2520 .resolved_cell(),
2521 false,
2522 false,
2523 );
2524
2525 let module_graph = ModuleGraph::from_graphs(
2526 vec![
2527 parent_graph,
2528 SingleModuleGraph::new_with_entries_visited(
2529 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2530 modules: vec![a_module],
2531 heuristics: EntryHeuristics::default(),
2532 }])
2533 .resolved_cell(),
2534 VisitedModules::from_graph(parent_graph),
2535 false,
2536 false,
2537 ),
2538 ],
2539 None,
2540 )
2541 .connect();
2542 let child_graph = module_graph
2543 .iter_graphs()
2544 .await?
2545 .get(1)
2546 .unwrap()
2547 .connect()
2548 .await?;
2549
2550 let mut visited_forward = Vec::new();
2552 child_graph.traverse_edges_dfs(
2553 vec![a_module],
2554 &mut (),
2555 |_parent, child, _state_| {
2556 visited_forward.push(child);
2557 Ok(GraphTraversalAction::Continue)
2558 },
2559 |_, _, _| Ok(()),
2560 false,
2561 false,
2562 )?;
2563 let forward = visited_forward
2564 .iter()
2565 .map(|m| m.ident().to_string().owned())
2566 .try_join()
2567 .await?;
2568
2569 let d_module = child_graph
2571 .enumerate_nodes()
2572 .map(async |(_index, module)| {
2573 Ok(match module {
2574 crate::module_graph::SingleModuleGraphNode::Module(module) => {
2575 if module.ident().to_string().owned().await? == "[test]/d.js" {
2576 Some(*module)
2577 } else {
2578 None
2579 }
2580 }
2581 crate::module_graph::SingleModuleGraphNode::VisitedModule {
2582 ..
2583 } => None,
2584 })
2585 })
2586 .try_flat_join()
2587 .await?
2588 .into_iter()
2589 .next()
2590 .unwrap();
2591
2592 async fn get_reverse_from(
2593 graph: &ModuleGraphLayer,
2594 module: ResolvedVc<Box<dyn Module>>,
2595 ) -> Result<Vec<RcStr>> {
2596 let mut visited = Vec::new();
2597 graph.traverse_edges_reverse_dfs(
2598 vec![module],
2599 &mut (),
2600 |_parent, child, _state_| {
2601 visited.push(child);
2602 Ok(GraphTraversalAction::Continue)
2603 },
2604 |_, _, _| Ok(()),
2605 )?;
2606 visited
2607 .iter()
2608 .map(|m| m.ident().to_string().owned())
2609 .try_join()
2610 .await
2611 }
2612
2613 Ok(ReverseTraversalResults {
2614 forward,
2615 reverse_from_d: get_reverse_from(&child_graph, d_module).await?,
2616 reverse_from_b: get_reverse_from(&child_graph, b_module).await?,
2617 }
2618 .cell())
2619 }
2620
2621 let traversal_results = reverse_traversal_results_operation()
2622 .read_strongly_consistent()
2623 .await?;
2624
2625 assert_eq!(
2626 traversal_results.forward,
2627 vec![
2628 rcstr!("[test]/a.js"),
2629 rcstr!("[test]/b.js"),
2630 rcstr!("[test]/d.js")
2631 ]
2632 );
2633
2634 assert_eq!(
2635 traversal_results.reverse_from_d,
2636 vec![rcstr!("[test]/d.js"), rcstr!("[test]/a.js")]
2637 );
2638
2639 assert_eq!(
2640 traversal_results.reverse_from_b,
2641 vec![rcstr!("[test]/b.js"), rcstr!("[test]/a.js")]
2642 );
2643
2644 Ok(())
2645 })
2646 .await
2647 .unwrap();
2648 }
2649
2650 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2651 async fn test_iter_nodes_modules_through_layered_graph() {
2652 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2653 BackendOptions::default(),
2654 noop_backing_storage(),
2655 ));
2656 tt.run_once(async move {
2657 #[turbo_tasks::value]
2658 struct Results {
2659 iter_nodes: Vec<RcStr>,
2660 iter_modules: Vec<RcStr>,
2661 iter_nodes_single: Vec<Vec<RcStr>>,
2662 iter_modules_single: Vec<Vec<RcStr>>,
2663 }
2664
2665 #[turbo_tasks::function(operation, root)]
2666 async fn reverse_traversal_results_operation() -> Result<Vc<Results>> {
2667 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
2668 let root = fs.root().await?;
2669
2670 let repo = TestRepo::new_with_chunking_types(
2674 &root,
2675 [
2676 ("a.js", vec!["b.js"]),
2677 ("b.js", vec!["c.js"]),
2678 ("c.js", vec!["x.js"]),
2679 ("x.js", vec!["y.js", "traced.js"]),
2680 ("y.js", vec!["z.js"]),
2681 ],
2682 [(
2683 "x.js",
2684 "traced.js",
2685 ChunkingType::Traced {
2686 mode: TracedMode::Entry,
2687 },
2688 )],
2689 );
2690 let make_module = |name| {
2691 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(name).unwrap(), repo))
2692 .to_resolved()
2693 };
2694 let x_module = make_module("x.js").await?;
2695 let a_module = make_module("a.js").await?;
2696
2697 let parent_graph = SingleModuleGraph::new_with_entries(
2698 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2699 modules: vec![x_module],
2700 heuristics: EntryHeuristics::default(),
2701 }])
2702 .resolved_cell(),
2703 true,
2704 false,
2705 );
2706
2707 let module_graph = ModuleGraph::from_graphs(
2708 vec![
2709 parent_graph,
2710 SingleModuleGraph::new_with_entries_visited(
2711 GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
2712 modules: vec![a_module],
2713 heuristics: EntryHeuristics::default(),
2714 }])
2715 .resolved_cell(),
2716 VisitedModules::from_graph(parent_graph),
2717 true,
2718 false,
2719 ),
2720 ],
2721 None,
2722 )
2723 .connect();
2724 let graph_layers = module_graph.iter_graphs().await?;
2725
2726 Ok(Results {
2727 iter_nodes: module_graph
2728 .await?
2729 .iter_reachable_nodes()?
2730 .map(async |node| {
2731 Ok(match node {
2732 SingleModuleGraphNode::Module(module) => {
2733 module.ident_string().owned().await?
2734 }
2735 SingleModuleGraphNode::VisitedModule { module, .. } => {
2736 format!("visited {}", module.ident_string().owned().await?)
2737 .into()
2738 }
2739 })
2740 })
2741 .try_join()
2742 .await?,
2743 iter_modules: module_graph
2744 .await?
2745 .iter_reachable_modules()?
2746 .map(|m| m.ident_string().owned())
2747 .try_join()
2748 .await?,
2749 iter_nodes_single: graph_layers
2750 .iter()
2751 .map(async |layer| {
2752 layer
2753 .connect()
2754 .await?
2755 .iter_reachable_nodes()?
2756 .map(async |node| {
2757 Ok(match node {
2758 SingleModuleGraphNode::Module(module) => {
2759 module.ident_string().owned().await?
2760 }
2761 SingleModuleGraphNode::VisitedModule { module, .. } => {
2762 format!(
2763 "visited {}",
2764 module.ident_string().owned().await?
2765 )
2766 .into()
2767 }
2768 })
2769 })
2770 .try_join()
2771 .await
2772 })
2773 .try_join()
2774 .await?,
2775 iter_modules_single: graph_layers
2776 .iter()
2777 .map(async |layer| {
2778 layer
2779 .connect()
2780 .await?
2781 .iter_reachable_modules()?
2782 .map(|m| m.ident_string().owned())
2783 .try_join()
2784 .await
2785 })
2786 .try_join()
2787 .await?,
2788 }
2789 .cell())
2790 }
2791
2792 let traversal_results = reverse_traversal_results_operation()
2793 .read_strongly_consistent()
2794 .await?;
2795
2796 assert_eq!(
2797 traversal_results.iter_nodes,
2798 vec![
2799 rcstr!("[test]/x.js"),
2800 rcstr!("[test]/a.js"),
2801 rcstr!("[test]/y.js"),
2802 rcstr!("[test]/b.js"),
2803 rcstr!("[test]/z.js"),
2804 rcstr!("[test]/c.js"),
2805 rcstr!("visited [test]/x.js")
2806 ]
2807 );
2808 assert_eq!(
2809 traversal_results.iter_modules,
2810 vec![
2811 rcstr!("[test]/x.js"),
2812 rcstr!("[test]/a.js"),
2813 rcstr!("[test]/y.js"),
2814 rcstr!("[test]/b.js"),
2815 rcstr!("[test]/z.js"),
2816 rcstr!("[test]/c.js")
2817 ]
2818 );
2819 assert_eq!(
2820 traversal_results.iter_nodes_single,
2821 vec![
2822 vec![
2823 rcstr!("[test]/x.js"),
2824 rcstr!("[test]/y.js"),
2825 rcstr!("[test]/z.js")
2826 ],
2827 vec![
2828 rcstr!("[test]/a.js"),
2829 rcstr!("[test]/b.js"),
2830 rcstr!("[test]/c.js"),
2831 rcstr!("visited [test]/x.js")
2832 ]
2833 ]
2834 );
2835 assert_eq!(
2836 traversal_results.iter_modules_single,
2837 vec![
2838 vec![
2839 rcstr!("[test]/x.js"),
2840 rcstr!("[test]/y.js"),
2841 rcstr!("[test]/z.js")
2842 ],
2843 vec![
2844 rcstr!("[test]/a.js"),
2845 rcstr!("[test]/b.js"),
2846 rcstr!("[test]/c.js")
2847 ]
2848 ]
2849 );
2850
2851 Ok(())
2852 })
2853 .await
2854 .unwrap();
2855 }
2856
2857 #[turbo_tasks::value(shared)]
2858 struct TestRepo {
2859 repo: FxHashMap<FileSystemPath, Vec<FileSystemPath>>,
2860 chunking_types: FxHashMap<(FileSystemPath, FileSystemPath), ChunkingType>,
2861 }
2862
2863 impl TestRepo {
2864 fn new(
2865 root: &FileSystemPath,
2866 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2867 ) -> Vc<Self> {
2868 Self::new_with_chunking_types(
2869 root,
2870 dependencies,
2871 std::iter::empty::<(RcStr, RcStr, ChunkingType)>(),
2872 )
2873 }
2874
2875 fn new_with_chunking_types(
2876 root: &FileSystemPath,
2877 dependencies: impl IntoIterator<Item = (impl AsRef<str>, Vec<impl AsRef<str>>)>,
2878 chunking_types: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, ChunkingType)>,
2879 ) -> Vc<Self> {
2880 let chunking_types = chunking_types
2881 .into_iter()
2882 .map(|(from, to, ty)| {
2883 (
2884 (
2885 root.join(from.as_ref()).unwrap(),
2886 root.join(to.as_ref()).unwrap(),
2887 ),
2888 ty,
2889 )
2890 })
2891 .collect::<FxHashMap<_, _>>();
2892 Self {
2893 repo: dependencies
2894 .into_iter()
2895 .map(|(k, v)| {
2896 (
2897 root.join(k.as_ref()).unwrap(),
2898 v.iter().map(|f| root.join(f.as_ref()).unwrap()).collect(),
2899 )
2900 })
2901 .collect(),
2902 chunking_types,
2903 }
2904 .cell()
2905 }
2906 }
2907
2908 #[turbo_tasks::value]
2909 struct MockModule {
2910 path: FileSystemPath,
2911 repo: ResolvedVc<TestRepo>,
2912 }
2913 #[turbo_tasks::value_impl]
2914 impl MockModule {
2915 #[turbo_tasks::function]
2916 fn new(path: FileSystemPath, repo: ResolvedVc<TestRepo>) -> Vc<Self> {
2917 Self { path, repo }.cell()
2918 }
2919 }
2920
2921 #[turbo_tasks::value_impl]
2922 impl Asset for MockModule {
2923 #[turbo_tasks::function]
2924 fn content(&self) -> Vc<AssetContent> {
2925 panic!("MockModule::content shouldn't be called")
2926 }
2927 }
2928
2929 #[turbo_tasks::value_impl]
2930 impl Module for MockModule {
2931 #[turbo_tasks::function]
2932 fn ident(&self) -> Vc<AssetIdent> {
2933 AssetIdent::from_path(self.path.clone()).into_vc()
2934 }
2935
2936 #[turbo_tasks::function]
2937 fn source(&self) -> Vc<crate::source::OptionSource> {
2938 Vc::cell(None)
2939 }
2940
2941 #[turbo_tasks::function]
2942 async fn references(&self) -> Result<Vc<ModuleReferences>> {
2943 let repo = self.repo.await?;
2944 let references = match repo.repo.get(&self.path) {
2945 Some(deps) => {
2946 deps.iter()
2947 .map(async |p| {
2948 Vc::upcast::<Box<dyn ModuleReference>>(MockModuleReference::new(
2949 ResolvedVc::upcast(
2950 MockModule::new(p.clone(), *self.repo).to_resolved().await?,
2951 ),
2952 rcstr!("normal-dep"),
2953 repo.chunking_types
2954 .get(&(self.path.clone(), p.clone()))
2955 .cloned()
2956 .unwrap_or(ChunkingType::Parallel {
2957 inherit_async: true,
2958 hoisted: false,
2959 }),
2960 ))
2961 .to_resolved()
2962 .await
2963 })
2964 .try_join()
2965 .await?
2966 }
2967 None => vec![],
2968 };
2969
2970 Ok(Vc::cell(references))
2971 }
2972 #[turbo_tasks::function]
2973 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
2974 ModuleSideEffects::SideEffectful.cell()
2975 }
2976 }
2977
2978 #[turbo_tasks::value]
2979 #[derive(ValueToString)]
2980 #[value_to_string(self.description)]
2981 struct MockModuleReference {
2982 asset: ResolvedVc<Box<dyn Module>>,
2983 description: RcStr,
2984 chunking_type: ChunkingType,
2985 }
2986
2987 impl MockModuleReference {
2988 pub fn new(
2989 asset: ResolvedVc<Box<dyn Module>>,
2990 description: RcStr,
2991 chunking_type: ChunkingType,
2992 ) -> Vc<Self> {
2993 MockModuleReference {
2994 asset,
2995 description,
2996 chunking_type,
2997 }
2998 .cell()
2999 }
3000 }
3001
3002 #[turbo_tasks::value_impl]
3003 impl ModuleReference for MockModuleReference {
3004 #[turbo_tasks::function]
3005 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
3006 *ModuleResolveResult::module(self.asset)
3007 }
3008
3009 fn chunking_type(&self) -> Option<ChunkingType> {
3010 Some(self.chunking_type.clone())
3011 }
3012 }
3013
3014 async fn run_graph_test(
3028 entries: Vec<RcStr>,
3029 graph: FxHashMap<RcStr, Vec<RcStr>>,
3030 test_fn: impl FnOnce(
3031 &ModuleGraph,
3032 Vec<ResolvedVc<Box<dyn Module>>>,
3033 FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3034 ) -> Result<()>
3035 + Send
3036 + 'static,
3037 ) {
3038 #[turbo_tasks::value(serialization = "skip", eq = "manual", cell = "new")]
3039 struct SetupGraph {
3040 module_graph: ReadRef<ModuleGraph>,
3041 entry_modules: Vec<ResolvedVc<Box<dyn Module>>>,
3042 module_to_name: FxHashMap<ResolvedVc<Box<dyn Module>>, RcStr>,
3043 }
3044
3045 #[turbo_tasks::function(operation, root)]
3046 async fn setup_graph(
3047 entries: Vec<RcStr>,
3048 graph_entries: Vec<(RcStr, Vec<RcStr>)>,
3049 ) -> Result<Vc<SetupGraph>> {
3050 let fs = VirtualFileSystem::new_with_name(rcstr!("test"));
3051 let root = fs.root().await?;
3052
3053 let repo = TestRepo::new(&root, graph_entries);
3054 let entry_modules = entries
3055 .iter()
3056 .map(|e| {
3057 Vc::upcast::<Box<dyn Module>>(MockModule::new(root.join(e).unwrap(), repo))
3058 .to_resolved()
3059 })
3060 .try_join()
3061 .await?;
3062 let graph = SingleModuleGraph::new_with_entries(
3063 GraphEntries::resolved_cell(GraphEntries::new(
3064 vec![ChunkGroupEntry::Entry {
3065 modules: entry_modules.clone(),
3066 heuristics: EntryHeuristics::default(),
3067 }],
3068 vec![],
3069 )),
3070 false,
3071 false,
3072 );
3073
3074 let module_to_name = graph
3078 .connect()
3079 .await?
3080 .modules
3081 .keys()
3082 .map(async |m| Ok((*m, m.ident().await?.path.path.clone())))
3083 .try_join()
3084 .await?
3085 .into_iter()
3086 .collect();
3087 let module_graph = ModuleGraph::from_graphs(vec![graph], None)
3088 .connect()
3089 .await?;
3090
3091 Ok(SetupGraph {
3092 module_graph,
3093 entry_modules,
3094 module_to_name,
3095 }
3096 .cell())
3097 }
3098
3099 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
3100 BackendOptions::default(),
3101 noop_backing_storage(),
3102 ));
3103 let graph_entries = graph.into_iter().collect::<Vec<_>>();
3104 tt.run_once(async move {
3105 let setup = setup_graph(entries, graph_entries)
3106 .read_strongly_consistent()
3107 .await?;
3108
3109 test_fn(
3110 &setup.module_graph,
3111 setup.entry_modules.clone(),
3112 setup.module_to_name.clone(),
3113 )
3114 })
3115 .await
3116 .unwrap();
3117 }
3118}