1use std::{
2 collections::{VecDeque, hash_map::Entry},
3 hash::BuildHasherDefault,
4 mem::take,
5};
6
7use anyhow::{Context, Result, bail};
8use bincode::{Decode, Encode};
9use either::Either;
10use itertools::Itertools;
11use petgraph::graph::{DiGraph, EdgeIndex, NodeIndex};
12use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
13use serde::{Deserialize, Serialize};
14use tracing::Instrument;
15use turbo_prehash::BuildHasherExt;
16use turbo_tasks::{
17 FxIndexMap, FxIndexSet, JoinIterExt, NonLocalValue, ResolvedVc, TryJoinIterExt, ValueToString,
18 Vc, trace::TraceRawVcs, turbobail,
19};
20
21use crate::{
22 chunk::{ChunkableModule, ChunkingType},
23 module::Module,
24 module_graph::{
25 GraphTraversalAction, ModuleGraph,
26 chunk_group_info::{ChunkGroupInfo, ChunkGroupKey, RoaringBitmapWrapper},
27 module_batch::{ModuleBatch, ModuleBatchGroup, ModuleOrBatch},
28 traced_di_graph::{TracedDiGraph, iter_neighbors_rev},
29 },
30};
31#[turbo_tasks::value(task_input)]
32#[derive(Debug, Clone, Default, Hash)]
33pub struct BatchingConfig {
34 pub use_heuristic: bool,
37}
38
39#[turbo_tasks::value_impl]
40impl BatchingConfig {
41 #[turbo_tasks::function]
42 pub fn new(config: BatchingConfig) -> Vc<Self> {
43 config.cell()
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
48pub struct ModuleBatchesGraphEdge {
49 pub ty: ChunkingType,
50 pub module: Option<ResolvedVc<Box<dyn Module>>>,
51 pub active_for_page_entry: Option<ResolvedVc<Box<dyn Module>>>,
53}
54
55#[derive(Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
56struct EntriesList(
57 #[bincode(with = "turbo_bincode::indexset")] pub FxIndexSet<ResolvedVc<Box<dyn Module>>>,
58);
59
60#[turbo_tasks::value(cell = "new", eq = "manual")]
61pub struct ModuleBatchesGraph {
62 graph: TracedDiGraph<ModuleOrBatch, ModuleBatchesGraphEdge>,
63
64 #[turbo_tasks(trace_ignore)]
71 #[bincode(with_serde)]
72 entries: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex>,
73 batch_groups: FxHashMap<ModuleOrBatch, ResolvedVc<ModuleBatchGroup>>,
74
75 ordered_entries: Vec<Option<EntriesList>>,
80}
81
82impl ModuleBatchesGraph {
83 pub async fn get_entry_index(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<NodeIndex> {
84 let Some(entry) = self.entries.get(&entry) else {
85 if cfg!(debug_assertions) {
86 let possible_entries = format!(
87 "{:#?}",
88 self.entries
89 .keys()
90 .map(|e| e.ident().to_string())
91 .try_join()
92 .await?
93 );
94 turbobail!(
95 "Entry {} is not in graph (possible entries: {})",
96 entry.ident(),
97 possible_entries
98 );
99 } else {
100 bail!("Entry is not in graph");
101 }
102 };
103 Ok(*entry)
104 }
105
106 pub fn get_ordered_entries<'l>(
107 &'l self,
108 chunk_group_info: &'l ChunkGroupInfo,
109 idx: usize,
110 ) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + 'l {
111 if let Some(EntriesList(ordered_entries)) = self
112 .ordered_entries
113 .get(idx)
114 .as_ref()
115 .and_then(|o| o.as_ref())
116 {
117 if let Some(chunk_group) = chunk_group_info.chunk_groups.get_index(idx) {
118 debug_assert_eq!(ordered_entries.len(), chunk_group.entries_count());
119 }
120 Either::Left(Either::Left(ordered_entries.iter().copied()))
121 } else if let Some(chunk_group) = chunk_group_info.chunk_groups.get_index(idx) {
122 Either::Right(chunk_group.entries())
123 } else {
124 Either::Left(Either::Right(std::iter::empty()))
125 }
126 }
127
128 pub fn get_batch_group(
129 &self,
130 module_or_batch: &ModuleOrBatch,
131 ) -> Option<ResolvedVc<ModuleBatchGroup>> {
132 self.batch_groups.get(module_or_batch).copied()
133 }
134
135 pub async fn get_entry(&self, entry: ResolvedVc<Box<dyn Module>>) -> Result<ModuleOrBatch> {
136 let entry = self.get_entry_index(entry).await?;
137 Ok(*self.graph.node_weight(entry).unwrap())
138 }
139
140 #[allow(clippy::implied_bounds_in_impls)]
142 pub fn traverse_edges_from_entries_dfs<'a, S>(
160 &'a self,
161 entries: impl IntoIterator<
162 Item = NodeIndex,
163 IntoIter = impl Iterator<Item = NodeIndex> + DoubleEndedIterator,
164 >,
165 active_page_entries: Option<&FxHashSet<ResolvedVc<Box<dyn Module>>>>,
166 state: &mut S,
167 mut visit_preorder: impl FnMut(
168 Option<(&'a ModuleOrBatch, &'a ModuleBatchesGraphEdge)>,
169 &'a ModuleOrBatch,
170 &mut S,
171 ) -> Result<GraphTraversalAction>,
172 mut visit_postorder: impl FnMut(
173 Option<(&'a ModuleOrBatch, &'a ModuleBatchesGraphEdge)>,
174 &'a ModuleOrBatch,
175 &mut S,
176 ),
177 ) -> Result<()> {
178 let graph = &self.graph;
179
180 enum ReverseDFSPass {
181 Visit,
182 ExpandAndVisit,
183 }
184
185 let entries = entries.into_iter();
186 #[allow(clippy::type_complexity)] let mut stack: Vec<(ReverseDFSPass, Option<(NodeIndex, EdgeIndex)>, NodeIndex)> = entries
188 .rev()
189 .map(|e| (ReverseDFSPass::ExpandAndVisit, None, e))
190 .collect();
191 let mut expanded = FxHashSet::default();
192 while let Some((pass, parent, current)) = stack.pop() {
193 let parent_arg = parent.map(|(node, edge)| {
194 (
195 graph.node_weight(node).unwrap(),
196 graph.edge_weight(edge).unwrap(),
197 )
198 });
199 match pass {
200 ReverseDFSPass::Visit => {
201 let current_node = graph.node_weight(current).unwrap();
202 visit_postorder(parent_arg, current_node, state);
203 }
204 ReverseDFSPass::ExpandAndVisit => {
205 let current_node = graph.node_weight(current).unwrap();
206 let action = visit_preorder(parent_arg, current_node, state)?;
207 if action == GraphTraversalAction::Exclude {
208 continue;
209 }
210 stack.push((ReverseDFSPass::Visit, parent, current));
211 if action == GraphTraversalAction::Continue && expanded.insert(current) {
212 stack.extend(
213 iter_neighbors_rev(graph, current)
214 .filter(|&(edge_idx, _)| {
215 let edge = graph.edge_weight(edge_idx).unwrap();
216 match &edge.active_for_page_entry {
217 None => true,
218 Some(entry) => active_page_entries
219 .is_some_and(|entries| entries.contains(entry)),
220 }
221 })
222 .map(|(edge, child)| {
223 (ReverseDFSPass::ExpandAndVisit, Some((current, edge)), child)
224 }),
225 );
226 }
227 }
228 }
229 }
230
231 Ok(())
232 }
233}
234
235type PreBatchIndex = usize;
236
237#[derive(Hash, PartialEq, Eq, Clone, Debug)]
238enum PreBatchItem {
239 ParallelModule(ResolvedVc<Box<dyn Module>>),
240 ParallelReference(PreBatchIndex),
241 NonParallelEdge(ChunkingType, ResolvedVc<Box<dyn Module>>),
242}
243
244struct PreBatch {
245 items: FxIndexSet<PreBatchItem>,
246 chunk_groups: RoaringBitmapWrapper,
247}
248
249impl PreBatch {
250 fn new(chunk_groups: RoaringBitmapWrapper) -> Self {
251 Self {
252 items: FxIndexSet::default(),
253 chunk_groups,
254 }
255 }
256}
257
258struct TraversalState<'l> {
259 items: Vec<PreBatchItem>,
260 this: &'l mut PreBatches,
261}
262
263struct PreBatches {
264 boundary_modules: FxHashSet<ResolvedVc<Box<dyn Module>>>,
265 batches: Vec<PreBatch>,
266 entries: FxHashMap<ResolvedVc<Box<dyn Module>>, PreBatchIndex>,
267 single_module_entries: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
268}
269
270impl PreBatches {
271 fn new() -> Self {
272 Self {
273 boundary_modules: FxHashSet::default(),
274 batches: Vec::new(),
275 entries: FxHashMap::default(),
276 single_module_entries: FxIndexSet::default(),
277 }
278 }
279
280 fn ensure_pre_batch_for_module(
281 &mut self,
282 module: ResolvedVc<Box<dyn Module>>,
283 module_chunk_groups: &FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper>,
284 queue: &mut VecDeque<(ResolvedVc<Box<dyn Module>>, PreBatchIndex)>,
285 ) -> Result<PreBatchIndex> {
286 Ok(match self.entries.entry(module) {
287 Entry::Vacant(e) => {
288 let index = self.batches.len();
289 queue.push_back((module, index));
290 let chunk_groups = module_chunk_groups
291 .get(&module)
292 .context("all modules need to have chunk group info")?;
293 let batch = PreBatch::new((*chunk_groups).clone());
294 self.batches.push(batch);
295 e.insert(index);
296 index
297 }
298 Entry::Occupied(e) => *e.get(),
299 })
300 }
301
302 async fn get_pre_batch_items(
303 &mut self,
304 entry: ResolvedVc<Box<dyn Module>>,
305 module_chunk_groups: &FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper>,
306 module_graph: &ModuleGraph,
307 queue: &mut VecDeque<(ResolvedVc<Box<dyn Module>>, PreBatchIndex)>,
308 ) -> Result<Vec<PreBatchItem>> {
309 let mut state = TraversalState {
310 items: Vec::new(),
311 this: self,
312 };
313 let mut visited = FxHashSet::default();
314 module_graph.traverse_edges_dfs(
315 std::iter::once(entry),
316 &mut state,
317 |parent_info, node, state| {
318 let ty = parent_info.map_or(
319 &ChunkingType::Parallel {
320 inherit_async: false,
321 hoisted: false,
322 },
323 |(_, ty)| &ty.chunking_type,
324 );
325 let module = node;
326 if matches!(ty, ChunkingType::Emitted { .. }) {
327 return Ok(GraphTraversalAction::Exclude);
329 }
330 if !ty.is_parallel() {
331 state.items.push(PreBatchItem::NonParallelEdge(
332 ty.without_inherit_async(),
333 module,
334 ));
335 return Ok(GraphTraversalAction::Exclude);
336 }
337 if visited.insert(module) {
338 if parent_info.is_some() && state.this.boundary_modules.contains(&module) {
339 let idx = state.this.ensure_pre_batch_for_module(
340 module,
341 module_chunk_groups,
342 queue,
343 )?;
344 state.items.push(PreBatchItem::ParallelReference(idx));
345 return Ok(GraphTraversalAction::Exclude);
346 }
347 Ok(GraphTraversalAction::Continue)
348 } else {
349 Ok(GraphTraversalAction::Exclude)
350 }
351 },
352 |_, node, state| {
353 let item = PreBatchItem::ParallelModule(node);
354 state.items.push(item);
355 Ok(())
356 },
357 false,
358 )?;
359 Ok(state.items)
360 }
361}
362
363pub async fn compute_module_batches(
364 module_graph: Vc<ModuleGraph>,
365 _config: &BatchingConfig,
366) -> Result<Vc<ModuleBatchesGraph>> {
367 let outer_span = tracing::info_span!(
368 "compute module batches",
369 initial_pre_batch_items = tracing::field::Empty,
370 initial_pre_batches = tracing::field::Empty,
371 extracted_shared_items = tracing::field::Empty,
372 batches = tracing::field::Empty,
373 modules = tracing::field::Empty,
374 edges = tracing::field::Empty
375 );
376 let span = outer_span.clone();
377 async move {
378 let chunk_group_info = module_graph.chunk_group_info().await?;
379 let module_chunk_groups = chunk_group_info.module_chunk_groups.await?;
380 let collected_modules = module_graph.collected_modules().await?;
381 let module_graph = module_graph.await?;
382
383 let mut pre_batches = PreBatches::new();
384
385 module_graph.traverse_edges_unordered(|parent, node| {
388 if let Some((parent, ty)) = parent {
389 let std::collections::hash_set::Entry::Vacant(entry) =
390 pre_batches.boundary_modules.entry(node)
391 else {
392 return Ok(());
394 };
395 if matches!(ty.chunking_type, ChunkingType::Emitted { .. }) {
396 return Ok(());
398 } else if ty.chunking_type.is_parallel() {
399 let parent_chunk_groups = module_chunk_groups
400 .get(&parent)
401 .context("all modules need to have chunk group info")?;
402 let chunk_groups = module_chunk_groups
403 .get(&node)
404 .context("all modules need to have chunk group info")?;
405 if parent_chunk_groups != chunk_groups {
406 entry.insert();
408 }
409 } else {
410 entry.insert();
411 }
412 }
413 Ok(())
414 })?;
415
416 for chunk_group in &chunk_group_info.chunk_groups {
418 pre_batches.boundary_modules.extend(chunk_group.entries());
419 }
420
421 pre_batches.boundary_modules.extend(
423 collected_modules
424 .values()
425 .flatten()
426 .flat_map(|(_, ms)| ms.iter().map(|(_, m)| *m)),
427 );
428
429 module_graph.traverse_cycles(
432 |ref_data| ref_data.chunking_type.is_parallel(),
433 |cycle| {
434 if cycle.len() > 1
435 && cycle
436 .iter()
437 .any(|node| pre_batches.boundary_modules.contains(node))
438 {
439 pre_batches
440 .boundary_modules
441 .extend(cycle.iter().map(|node| **node));
442 }
443 Ok(())
444 },
445 )?;
446
447 let mut queue: VecDeque<(ResolvedVc<Box<dyn Module>>, PreBatchIndex)> = VecDeque::new();
448
449 let mut chunk_group_indices_with_merged_children = FxHashSet::default();
450
451 for chunk_group in &chunk_group_info.chunk_groups {
453 for entry in chunk_group.entries() {
454 pre_batches.ensure_pre_batch_for_module(entry, &module_chunk_groups, &mut queue)?;
455 }
456 if let Some(parent) = chunk_group.get_merged_parent() {
457 chunk_group_indices_with_merged_children.insert(parent);
458 }
459 }
460 for entry in collected_modules
462 .values()
463 .flatten()
464 .flat_map(|(_, ms)| ms.iter().map(|(_, m)| *m))
465 {
466 pre_batches.ensure_pre_batch_for_module(entry, &module_chunk_groups, &mut queue)?;
467 }
468
469 let mut initial_pre_batch_items = 0;
470 while let Some((chunkable_module, idx)) = queue.pop_front() {
472 let items = pre_batches
473 .get_pre_batch_items(
474 chunkable_module,
475 &module_chunk_groups,
476 &module_graph,
477 &mut queue,
478 )
479 .await?;
480 initial_pre_batch_items += items.len();
481 let batch = &mut pre_batches.batches[idx];
482 batch.items.extend(items);
483 }
484 span.record("initial_pre_batch_items", initial_pre_batch_items);
485 span.record("initial_pre_batches", pre_batches.batches.len());
486
487 let mut ordered_entries: Vec<Option<EntriesList>> =
489 vec![None; chunk_group_info.chunk_groups.len()];
490 for (i, chunk_group) in chunk_group_info.chunk_groups.iter().enumerate() {
491 if !chunk_group_indices_with_merged_children.contains(&i) {
492 continue;
493 }
494 let mut merged_modules: FxHashMap<ChunkingType, FxIndexSet<_>> = FxHashMap::default();
495 let mut stack = ordered_entries[i]
496 .as_ref()
497 .map_or_else(
498 || Either::Left(chunk_group.entries()),
499 |v| Either::Right(v.0.iter().copied()),
500 )
501 .map(|module| {
502 let idx = *pre_batches
503 .entries
504 .get(&module)
505 .context("could not prebatch for module")?;
506 Ok((idx, 0))
507 })
508 .collect::<Result<Vec<_>>>()?;
509 stack.reverse();
510 let mut visited = FxHashSet::default();
511 while let Some((idx, mut pos)) = stack.pop() {
512 let batch = &pre_batches.batches[idx];
513 while let Some(item) = batch.items.get_index(pos) {
514 match item {
515 PreBatchItem::ParallelModule(_) => {}
516 PreBatchItem::ParallelReference(other_idx) => {
517 if visited.insert(*other_idx) {
518 stack.push((idx, pos + 1));
519 stack.push((*other_idx, 0));
520 break;
521 }
522 }
523 PreBatchItem::NonParallelEdge(chunking_type, module) => {
524 if chunking_type.is_merged() {
525 merged_modules
526 .entry(chunking_type.clone())
527 .or_default()
528 .insert(*module);
529 }
530 }
531 }
532 pos += 1;
533 }
534 }
535 if !merged_modules.is_empty() {
536 for (ty, merged_modules) in merged_modules {
537 let chunk_group_key = match ty {
538 ChunkingType::Isolated {
539 merge_tag: Some(merge_tag),
540 ..
541 } => ChunkGroupKey::IsolatedMerged {
542 parent: i.into(),
543 merge_tag: merge_tag.clone(),
544 },
545 ChunkingType::Shared {
546 merge_tag: Some(merge_tag),
547 ..
548 } => ChunkGroupKey::SharedMerged {
549 parent: i.into(),
550 merge_tag: merge_tag.clone(),
551 },
552 _ => unreachable!(),
553 };
554 let idx = chunk_group_info
555 .chunk_group_keys
556 .get_index_of(&chunk_group_key)
557 .context("could not find chunk group key for merged chunk group")?;
558 ordered_entries[idx] = Some(EntriesList(merged_modules));
559 }
560 }
561 }
562
563 let mut parallel_module_to_pre_batch: FxIndexMap<_, Vec<PreBatchIndex>> =
565 FxIndexMap::default();
566
567 let mut per_entry_modules: FxHashSet<ResolvedVc<Box<dyn Module>>> = FxHashSet::default();
574
575 for (idx, pre_batch) in pre_batches.batches.iter().enumerate() {
577 for item in &pre_batch.items {
578 match item {
579 PreBatchItem::ParallelModule(module) => {
580 parallel_module_to_pre_batch
581 .entry(*module)
582 .or_default()
583 .push(idx);
584 }
585 PreBatchItem::NonParallelEdge(ty, module) => {
586 if !pre_batches.entries.contains_key(module) {
587 pre_batches.single_module_entries.insert(*module);
588 }
589 if matches!(ty, ChunkingType::PerEntry) {
590 per_entry_modules.insert(*module);
591 }
592 }
593 PreBatchItem::ParallelReference(_) => {}
594 }
595 }
596 }
597
598 let mut extracted_shared_items = 0;
601 for i in 0..parallel_module_to_pre_batch.len() {
603 let (&module, batches) = parallel_module_to_pre_batch
604 .get_index(i)
605 .context("could not find parallel module to pre batch index")?;
606 if batches.len() > 1 {
607 let batches_with_item_index = batches
609 .iter()
610 .map(|&idx| {
611 let batch_items = &pre_batches.batches[idx].items;
612 let item_idx = batch_items
613 .get_index_of(&PreBatchItem::ParallelModule(module))
614 .context("could not find batch item index for parallel module")?;
615 Ok((idx, item_idx))
616 })
617 .collect::<Result<Vec<_>>>()?;
618 let mut selected_items = 1;
619 fn get_item_at(
620 pre_batches: &PreBatches,
621 batch_idx: PreBatchIndex,
622 item_idx: usize,
623 ) -> Option<&PreBatchItem> {
624 pre_batches.batches[batch_idx].items.get_index(item_idx)
625 }
626 loop {
629 if let Some(PreBatchItem::ParallelModule(next_module)) = get_item_at(
630 &pre_batches,
631 batches_with_item_index[0].0,
632 batches_with_item_index[0].1 + selected_items,
633 ) && parallel_module_to_pre_batch
634 .get(next_module)
635 .context("could not find pre batch for parallel module")?
636 .len()
637 == batches.len()
638 && batches_with_item_index[1..]
639 .iter()
640 .all(|&(batch_idx, item_idx)| {
641 get_item_at(&pre_batches, batch_idx, item_idx + selected_items)
642 == Some(&PreBatchItem::ParallelModule(*next_module))
643 })
644 {
645 selected_items += 1;
646 continue;
647 }
648 break;
649 }
650 extracted_shared_items += selected_items;
651
652 let exact_match = batches_with_item_index
655 .iter()
656 .find(|&&(batch_idx, item_idx)| {
657 item_idx == 0
658 && pre_batches.batches[batch_idx].items.len() == selected_items
659 });
660 if let Some(&(exact_match, _)) = exact_match {
661 for &(batch_index, item_start) in batches_with_item_index.iter() {
663 if batch_index != exact_match {
664 pre_batches.batches[batch_index].items.splice(
665 item_start..item_start + selected_items,
666 std::iter::once(PreBatchItem::ParallelReference(exact_match)),
667 );
668 }
669 }
670 for item in pre_batches.batches[exact_match].items.iter() {
671 if let PreBatchItem::ParallelModule(module) = item {
672 parallel_module_to_pre_batch
673 .get_mut(module)
674 .context("could not find pre batch for parallel module")?
675 .clear();
676 }
677 }
678 } else {
679 let first_batch_index = batches_with_item_index[0].0;
682 let first_batch_item_index = batches_with_item_index[0].1;
683 let new_batch_index = pre_batches.batches.len();
684 let mut new_batch =
685 PreBatch::new(pre_batches.batches[first_batch_index].chunk_groups.clone());
686 new_batch
687 .items
688 .extend(pre_batches.batches[first_batch_index].items.splice(
689 first_batch_item_index..first_batch_item_index + selected_items,
690 std::iter::once(PreBatchItem::ParallelReference(new_batch_index)),
691 ));
692 for item in new_batch.items.iter() {
693 if let PreBatchItem::ParallelModule(module) = item {
694 parallel_module_to_pre_batch
695 .get_mut(module)
696 .context("could not find pre batch for parallel module")?
697 .clear();
698 }
699 }
700 pre_batches.batches.push(new_batch);
701 for &(batch_index, item_start) in batches_with_item_index[1..].iter() {
702 pre_batches.batches[batch_index].items.splice(
703 item_start..item_start + selected_items,
704 std::iter::once(PreBatchItem::ParallelReference(new_batch_index)),
705 );
706 }
707 }
708 }
709 }
710 span.record("extracted_shared_items", extracted_shared_items);
711
712 let mut edges_count = 0;
715
716 for i in 0..pre_batches.batches.len() {
719 let items = take(&mut pre_batches.batches[i].items);
720 let mut new_items =
721 FxIndexSet::with_capacity_and_hasher(items.len(), Default::default());
722 enum Mode {
723 ParallelChunkableModule,
724 Other,
725 }
726 let mut mode = Mode::Other;
727 for item in items {
728 let chunkable_module = if let PreBatchItem::ParallelModule(module) = &item {
729 ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(*module)
730 } else {
731 None
732 };
733 let item = if let PreBatchItem::ParallelModule(module) = item {
734 if chunkable_module.is_some() {
735 PreBatchItem::ParallelModule(module)
736 } else {
737 pre_batches.single_module_entries.insert(module);
738 PreBatchItem::NonParallelEdge(
739 ChunkingType::Parallel {
740 inherit_async: false,
741 hoisted: false,
742 },
743 module,
744 )
745 }
746 } else {
747 item
748 };
749 match (&mode, chunkable_module) {
750 (_, Some(_)) => {
751 mode = Mode::ParallelChunkableModule;
752 new_items.insert(item);
753 }
754 (Mode::Other, _) => {
755 edges_count += 1;
756 new_items.insert(item);
757 }
758 (Mode::ParallelChunkableModule, _) => {
759 let idx = pre_batches.batches.len();
761 let mut new_batch =
762 PreBatch::new(pre_batches.batches[i].chunk_groups.clone());
763 new_batch.items.extend(new_items.drain(..));
764 pre_batches.batches.push(new_batch);
765 edges_count += 1;
766 new_items.insert(PreBatchItem::ParallelReference(idx));
767 if chunkable_module.is_some() {
768 new_items.insert(item);
769 } else {
770 edges_count += 1;
771 mode = Mode::Other;
772 new_items.insert(item);
773 }
774 }
775 }
776 }
777 pre_batches.batches[i].items = new_items;
778 }
779 span.record("pre_batches", pre_batches.batches.len());
780
781 let mut graph: DiGraph<ModuleOrBatch, ModuleBatchesGraphEdge, u32> =
785 petgraph::graph::DiGraph::with_capacity(
786 pre_batches.batches.len() + pre_batches.single_module_entries.len(),
787 edges_count,
788 );
789
790 let batches = pre_batches
792 .batches
793 .iter_mut()
794 .enumerate()
795 .map(async |(i, pre_batch)| {
796 let mut modules = pre_batch.items.iter().filter_map(|item| {
797 if let PreBatchItem::ParallelModule(module) = item {
798 ResolvedVc::try_downcast(*module)
799 } else {
800 None
801 }
802 });
803 let Some(first) = modules.next() else {
804 return Ok(ModuleOrBatch::None(i));
805 };
806 if let Some(second) = modules.next() {
807 let batch = ModuleBatch::new(
808 [first, second]
809 .into_iter()
810 .chain(modules)
811 .map(|m| *m)
812 .collect::<Vec<_>>(),
813 Some(pre_batch.chunk_groups.clone()),
814 );
815 Ok(ModuleOrBatch::Batch(batch.to_resolved().await?))
816 } else {
817 Ok(ModuleOrBatch::Module(ResolvedVc::upcast(first)))
818 }
819 })
820 .try_join()
821 .await?;
822
823 let mut batch_groups: FxHashMap<_, Vec<_>> = FxHashMap::default();
825 for (i, pre_batch) in pre_batches.batches.iter().enumerate() {
826 let key = BuildHasherDefault::<FxHasher>::default().prehash(&pre_batch.chunk_groups);
827 let batch = batches[i];
828 batch_groups.entry(key).or_default().push(batch);
829 }
830 for &module in &pre_batches.single_module_entries {
831 if per_entry_modules.contains(&module) {
835 continue;
836 }
837 let chunk_groups = module_chunk_groups
838 .get(&module)
839 .context("all modules need to have chunk group info")?;
840 let key = BuildHasherDefault::<FxHasher>::default().prehash(chunk_groups);
841 batch_groups
842 .entry(key)
843 .or_default()
844 .push(ModuleOrBatch::Module(module));
845 }
846
847 let batch_groups = batch_groups
849 .into_iter()
850 .map(async |(key, items)| {
851 if items.len() == 1 {
852 anyhow::Ok(Either::Left(std::iter::empty()))
853 } else {
854 let batch_group = ModuleBatchGroup::new(items.clone(), (*key).clone())
855 .to_resolved()
856 .await?;
857 Ok(Either::Right(
858 items.into_iter().map(move |item| (item, batch_group)),
859 ))
860 }
861 })
862 .join()
863 .await
864 .into_iter()
865 .flatten_ok()
866 .collect::<Result<FxHashMap<_, _>>>()?;
867
868 let mut batches_count = 0;
870 let mut modules_count = 0;
871 let batch_indices = batches
872 .into_iter()
873 .map(|batch| {
874 match &batch {
875 ModuleOrBatch::Batch(_) => batches_count += 1,
876 ModuleOrBatch::Module(_) => modules_count += 1,
877 ModuleOrBatch::None(_) => {}
878 }
879 graph.add_node(batch)
880 })
881 .collect::<Vec<_>>();
882
883 let single_module_indices = pre_batches
885 .single_module_entries
886 .iter()
887 .map(|module| graph.add_node(ModuleOrBatch::Module(*module)))
888 .collect::<Vec<_>>();
889
890 span.record("batches", batches_count);
891 modules_count += pre_batches.single_module_entries.len();
892 span.record("modules", modules_count);
893 span.record("edges", edges_count);
894
895 let mut module_to_node: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
897 FxHashMap::default();
898 for (module, batches) in ¶llel_module_to_pre_batch {
899 if let Some(&first_batch) = batches.first() {
902 module_to_node.insert(*module, batch_indices[first_batch]);
903 }
904 }
905 for (idx, module) in pre_batches.single_module_entries.iter().enumerate() {
906 module_to_node.insert(*module, single_module_indices[idx]);
907 }
908
909 for (i, pre_batch) in pre_batches.batches.into_iter().enumerate() {
911 let index = batch_indices[i];
912 let items = pre_batch.items;
913 for item in items {
914 match item {
915 PreBatchItem::ParallelReference(idx) => {
916 graph.add_edge(
917 index,
918 batch_indices[idx],
919 ModuleBatchesGraphEdge {
920 ty: ChunkingType::Parallel {
921 inherit_async: false,
922 hoisted: false,
923 },
924 module: None,
925 active_for_page_entry: None,
926 },
927 );
928 }
929 PreBatchItem::NonParallelEdge(ty, module) => {
930 if matches!(ty, ChunkingType::Emitted { .. }) {
931 continue;
933 }
934 if let Some(batch) = pre_batches.entries.get(&module).copied() {
935 graph.add_edge(
936 index,
937 batch_indices[batch],
938 ModuleBatchesGraphEdge {
939 ty,
940 module: Some(module),
941 active_for_page_entry: None,
942 },
943 );
944 continue;
945 }
946 let idx = pre_batches
947 .single_module_entries
948 .get_index_of(&module)
949 .context("could not find single module entry index")?;
950 let idx = single_module_indices[idx];
951 graph.add_edge(
952 index,
953 idx,
954 ModuleBatchesGraphEdge {
955 ty,
956 module: Some(module),
957 active_for_page_entry: None,
958 },
959 );
960 }
961 PreBatchItem::ParallelModule(_) => {}
962 }
963 }
964 }
965
966 debug_assert_eq!(graph.capacity().0, graph.node_count());
967 debug_assert_eq!(graph.capacity().1, graph.edge_count());
968
969 for (collecting_module, refs) in &collected_modules {
971 for (entry_modules, refs) in refs {
972 let source_node = *module_to_node
973 .get(collecting_module)
974 .context("could not find single module entry index")?;
975 for (ref_data, target_module) in refs {
976 if let Some(batch) = pre_batches.entries.get(target_module).copied() {
977 for entry_module in entry_modules {
978 graph.add_edge(
979 source_node,
980 batch_indices[batch],
981 ModuleBatchesGraphEdge {
982 ty: ref_data.chunking_type.clone(),
983 module: Some(*target_module),
984 active_for_page_entry: Some(*entry_module),
985 },
986 );
987 }
988 continue;
989 }
990 let idx = pre_batches
991 .single_module_entries
992 .get_index_of(target_module)
993 .context("could not find single module entry index")?;
994 let idx = single_module_indices[idx];
995 for entry_module in entry_modules {
996 graph.add_edge(
997 source_node,
998 idx,
999 ModuleBatchesGraphEdge {
1000 ty: ref_data.chunking_type.clone(),
1001 module: Some(*target_module),
1002 active_for_page_entry: Some(*entry_module),
1003 },
1004 );
1005 }
1006 }
1007 }
1008 }
1009
1010 let mut entries = FxHashMap::default();
1012 for chunk_group in &chunk_group_info.chunk_groups {
1013 for module in chunk_group.entries() {
1014 if let Some(batch) = pre_batches.entries.get(&module).copied() {
1015 entries.insert(module, batch_indices[batch]);
1016 continue;
1017 }
1018 let idx = pre_batches
1019 .single_module_entries
1020 .get_index_of(&module)
1021 .context("could not find single module entry index")?;
1022 let idx = single_module_indices[idx];
1023 entries.insert(module, idx);
1024 }
1025 }
1026
1027 Ok(ModuleBatchesGraph {
1028 graph: TracedDiGraph(graph),
1029 entries,
1030 batch_groups,
1031 ordered_entries,
1032 }
1033 .cell())
1034 }
1035 .instrument(outer_span)
1036 .await
1037}