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