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, 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, 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, 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(unsafe_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 false,
359 )?;
360 Ok(state.items)
361 }
362}
363
364pub async fn compute_module_batches(
365 module_graph: Vc<ModuleGraph>,
366 _config: &BatchingConfig,
367) -> Result<Vc<ModuleBatchesGraph>> {
368 let outer_span = tracing::info_span!(
369 "compute module batches",
370 initial_pre_batch_items = tracing::field::Empty,
371 initial_pre_batches = tracing::field::Empty,
372 extracted_shared_items = tracing::field::Empty,
373 batches = tracing::field::Empty,
374 modules = tracing::field::Empty,
375 edges = tracing::field::Empty
376 );
377 let span = outer_span.clone();
378 async move {
379 let chunk_group_info = module_graph.chunk_group_info().await?;
380 let module_chunk_groups = chunk_group_info.module_chunk_groups.await?;
381 let collected_modules = module_graph.collected_modules().await?;
382 let module_graph = module_graph.await?;
383
384 let mut pre_batches = PreBatches::new();
385
386 module_graph.traverse_edges_unordered(|parent, node| {
389 if let Some((parent, ty)) = parent {
390 let std::collections::hash_set::Entry::Vacant(entry) =
391 pre_batches.boundary_modules.entry(node)
392 else {
393 return Ok(());
395 };
396 if matches!(ty.chunking_type, ChunkingType::Emitted { .. }) {
397 return Ok(());
399 } else if ty.chunking_type.is_parallel() {
400 let parent_chunk_groups = module_chunk_groups
401 .get(&parent)
402 .context("all modules need to have chunk group info")?;
403 let chunk_groups = module_chunk_groups
404 .get(&node)
405 .context("all modules need to have chunk group info")?;
406 if parent_chunk_groups != chunk_groups {
407 entry.insert();
409 }
410 } else {
411 entry.insert();
412 }
413 }
414 Ok(())
415 })?;
416
417 for chunk_group in &chunk_group_info.chunk_groups {
419 pre_batches.boundary_modules.extend(chunk_group.entries());
420 }
421
422 pre_batches.boundary_modules.extend(
424 collected_modules
425 .values()
426 .flatten()
427 .flat_map(|(_, ms)| ms.iter().map(|(_, m)| *m)),
428 );
429
430 module_graph.traverse_cycles(
433 |ref_data| ref_data.chunking_type.is_parallel(),
434 |cycle| {
435 if cycle.len() > 1
436 && cycle
437 .iter()
438 .any(|node| pre_batches.boundary_modules.contains(node))
439 {
440 pre_batches
441 .boundary_modules
442 .extend(cycle.iter().map(|node| **node));
443 }
444 Ok(())
445 },
446 )?;
447
448 let mut queue: VecDeque<(ResolvedVc<Box<dyn Module>>, PreBatchIndex)> = VecDeque::new();
449
450 let mut chunk_group_indices_with_merged_children = FxHashSet::default();
451
452 for chunk_group in &chunk_group_info.chunk_groups {
454 for entry in chunk_group.entries() {
455 pre_batches.ensure_pre_batch_for_module(entry, &module_chunk_groups, &mut queue)?;
456 }
457 if let Some(parent) = chunk_group.get_merged_parent() {
458 chunk_group_indices_with_merged_children.insert(parent);
459 }
460 }
461 for entry in collected_modules
463 .values()
464 .flatten()
465 .flat_map(|(_, ms)| ms.iter().map(|(_, m)| *m))
466 {
467 pre_batches.ensure_pre_batch_for_module(entry, &module_chunk_groups, &mut queue)?;
468 }
469
470 let mut initial_pre_batch_items = 0;
471 while let Some((chunkable_module, idx)) = queue.pop_front() {
473 let items = pre_batches
474 .get_pre_batch_items(
475 chunkable_module,
476 &module_chunk_groups,
477 &module_graph,
478 &mut queue,
479 )
480 .await?;
481 initial_pre_batch_items += items.len();
482 let batch = &mut pre_batches.batches[idx];
483 batch.items.extend(items);
484 }
485 span.record("initial_pre_batch_items", initial_pre_batch_items);
486 span.record("initial_pre_batches", pre_batches.batches.len());
487
488 let mut ordered_entries: Vec<Option<EntriesList>> =
490 vec![None; chunk_group_info.chunk_groups.len()];
491 for (i, chunk_group) in chunk_group_info.chunk_groups.iter().enumerate() {
492 if !chunk_group_indices_with_merged_children.contains(&i) {
493 continue;
494 }
495 let mut merged_modules: FxHashMap<ChunkingType, FxIndexSet<_>> = FxHashMap::default();
496 let mut stack = ordered_entries[i]
497 .as_ref()
498 .map_or_else(
499 || Either::Left(chunk_group.entries()),
500 |v| Either::Right(v.0.iter().copied()),
501 )
502 .map(|module| {
503 let idx = *pre_batches
504 .entries
505 .get(&module)
506 .context("could not prebatch for module")?;
507 Ok((idx, 0))
508 })
509 .collect::<Result<Vec<_>>>()?;
510 stack.reverse();
511 let mut visited = FxHashSet::default();
512 while let Some((idx, mut pos)) = stack.pop() {
513 let batch = &pre_batches.batches[idx];
514 while let Some(item) = batch.items.get_index(pos) {
515 match item {
516 PreBatchItem::ParallelModule(_) => {}
517 PreBatchItem::ParallelReference(other_idx) => {
518 if visited.insert(*other_idx) {
519 stack.push((idx, pos + 1));
520 stack.push((*other_idx, 0));
521 break;
522 }
523 }
524 PreBatchItem::NonParallelEdge(chunking_type, module) => {
525 if chunking_type.is_merged() {
526 merged_modules
527 .entry(chunking_type.clone())
528 .or_default()
529 .insert(*module);
530 }
531 }
532 }
533 pos += 1;
534 }
535 }
536 if !merged_modules.is_empty() {
537 for (ty, merged_modules) in merged_modules {
538 let chunk_group_key = match ty {
539 ChunkingType::Isolated {
540 merge_tag: Some(merge_tag),
541 ..
542 } => ChunkGroupKey::IsolatedMerged {
543 parent: i.into(),
544 merge_tag: merge_tag.clone(),
545 },
546 ChunkingType::Shared {
547 merge_tag: Some(merge_tag),
548 ..
549 } => ChunkGroupKey::SharedMerged {
550 parent: i.into(),
551 merge_tag: merge_tag.clone(),
552 },
553 _ => unreachable!(),
554 };
555 let idx = chunk_group_info
556 .chunk_group_keys
557 .get_index_of(&chunk_group_key)
558 .context("could not find chunk group key for merged chunk group")?;
559 ordered_entries[idx] = Some(EntriesList(merged_modules));
560 }
561 }
562 }
563
564 let mut parallel_module_to_pre_batch: FxIndexMap<_, Vec<PreBatchIndex>> =
566 FxIndexMap::default();
567
568 let mut per_entry_modules: FxHashSet<ResolvedVc<Box<dyn Module>>> = FxHashSet::default();
575
576 for (idx, pre_batch) in pre_batches.batches.iter().enumerate() {
578 for item in &pre_batch.items {
579 match item {
580 PreBatchItem::ParallelModule(module) => {
581 parallel_module_to_pre_batch
582 .entry(*module)
583 .or_default()
584 .push(idx);
585 }
586 PreBatchItem::NonParallelEdge(ty, module) => {
587 if !pre_batches.entries.contains_key(module) {
588 pre_batches.single_module_entries.insert(*module);
589 }
590 if matches!(ty, ChunkingType::PerEntry) {
591 per_entry_modules.insert(*module);
592 }
593 }
594 PreBatchItem::ParallelReference(_) => {}
595 }
596 }
597 }
598
599 let mut extracted_shared_items = 0;
602 for i in 0..parallel_module_to_pre_batch.len() {
604 let (&module, batches) = parallel_module_to_pre_batch
605 .get_index(i)
606 .context("could not find parallel module to pre batch index")?;
607 if batches.len() > 1 {
608 let batches_with_item_index = batches
610 .iter()
611 .map(|&idx| {
612 let batch_items = &pre_batches.batches[idx].items;
613 let item_idx = batch_items
614 .get_index_of(&PreBatchItem::ParallelModule(module))
615 .context("could not find batch item index for parallel module")?;
616 Ok((idx, item_idx))
617 })
618 .collect::<Result<Vec<_>>>()?;
619 let mut selected_items = 1;
620 fn get_item_at(
621 pre_batches: &PreBatches,
622 batch_idx: PreBatchIndex,
623 item_idx: usize,
624 ) -> Option<&PreBatchItem> {
625 pre_batches.batches[batch_idx].items.get_index(item_idx)
626 }
627 loop {
630 if let Some(PreBatchItem::ParallelModule(next_module)) = get_item_at(
631 &pre_batches,
632 batches_with_item_index[0].0,
633 batches_with_item_index[0].1 + selected_items,
634 ) && parallel_module_to_pre_batch
635 .get(next_module)
636 .context("could not find pre batch for parallel module")?
637 .len()
638 == batches.len()
639 && batches_with_item_index[1..]
640 .iter()
641 .all(|&(batch_idx, item_idx)| {
642 get_item_at(&pre_batches, batch_idx, item_idx + selected_items)
643 == Some(&PreBatchItem::ParallelModule(*next_module))
644 })
645 {
646 selected_items += 1;
647 continue;
648 }
649 break;
650 }
651 extracted_shared_items += selected_items;
652
653 let exact_match = batches_with_item_index
656 .iter()
657 .find(|&&(batch_idx, item_idx)| {
658 item_idx == 0
659 && pre_batches.batches[batch_idx].items.len() == selected_items
660 });
661 if let Some(&(exact_match, _)) = exact_match {
662 for &(batch_index, item_start) in batches_with_item_index.iter() {
664 if batch_index != exact_match {
665 pre_batches.batches[batch_index].items.splice(
666 item_start..item_start + selected_items,
667 std::iter::once(PreBatchItem::ParallelReference(exact_match)),
668 );
669 }
670 }
671 for item in pre_batches.batches[exact_match].items.iter() {
672 if let PreBatchItem::ParallelModule(module) = item {
673 parallel_module_to_pre_batch
674 .get_mut(module)
675 .context("could not find pre batch for parallel module")?
676 .clear();
677 }
678 }
679 } else {
680 let first_batch_index = batches_with_item_index[0].0;
683 let first_batch_item_index = batches_with_item_index[0].1;
684 let new_batch_index = pre_batches.batches.len();
685 let mut new_batch =
686 PreBatch::new(pre_batches.batches[first_batch_index].chunk_groups.clone());
687 new_batch
688 .items
689 .extend(pre_batches.batches[first_batch_index].items.splice(
690 first_batch_item_index..first_batch_item_index + selected_items,
691 std::iter::once(PreBatchItem::ParallelReference(new_batch_index)),
692 ));
693 for item in new_batch.items.iter() {
694 if let PreBatchItem::ParallelModule(module) = item {
695 parallel_module_to_pre_batch
696 .get_mut(module)
697 .context("could not find pre batch for parallel module")?
698 .clear();
699 }
700 }
701 pre_batches.batches.push(new_batch);
702 for &(batch_index, item_start) in batches_with_item_index[1..].iter() {
703 pre_batches.batches[batch_index].items.splice(
704 item_start..item_start + selected_items,
705 std::iter::once(PreBatchItem::ParallelReference(new_batch_index)),
706 );
707 }
708 }
709 }
710 }
711 span.record("extracted_shared_items", extracted_shared_items);
712
713 let mut edges_count = 0;
716
717 for i in 0..pre_batches.batches.len() {
720 let items = take(&mut pre_batches.batches[i].items);
721 let mut new_items =
722 FxIndexSet::with_capacity_and_hasher(items.len(), Default::default());
723 enum Mode {
724 ParallelChunkableModule,
725 Other,
726 }
727 let mut mode = Mode::Other;
728 for item in items {
729 let chunkable_module = if let PreBatchItem::ParallelModule(module) = &item {
730 ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(*module)
731 } else {
732 None
733 };
734 let item = if let PreBatchItem::ParallelModule(module) = item {
735 if chunkable_module.is_some() {
736 PreBatchItem::ParallelModule(module)
737 } else {
738 pre_batches.single_module_entries.insert(module);
739 PreBatchItem::NonParallelEdge(
740 ChunkingType::Parallel {
741 inherit_async: false,
742 hoisted: false,
743 },
744 module,
745 )
746 }
747 } else {
748 item
749 };
750 match (&mode, chunkable_module) {
751 (_, Some(_)) => {
752 mode = Mode::ParallelChunkableModule;
753 new_items.insert(item);
754 }
755 (Mode::Other, _) => {
756 edges_count += 1;
757 new_items.insert(item);
758 }
759 (Mode::ParallelChunkableModule, _) => {
760 let idx = pre_batches.batches.len();
762 let mut new_batch =
763 PreBatch::new(pre_batches.batches[i].chunk_groups.clone());
764 new_batch.items.extend(new_items.drain(..));
765 pre_batches.batches.push(new_batch);
766 edges_count += 1;
767 new_items.insert(PreBatchItem::ParallelReference(idx));
768 if chunkable_module.is_some() {
769 new_items.insert(item);
770 } else {
771 edges_count += 1;
772 mode = Mode::Other;
773 new_items.insert(item);
774 }
775 }
776 }
777 }
778 pre_batches.batches[i].items = new_items;
779 }
780 span.record("pre_batches", pre_batches.batches.len());
781
782 let mut graph: DiGraph<ModuleOrBatch, ModuleBatchesGraphEdge, u32> =
786 petgraph::graph::DiGraph::with_capacity(
787 pre_batches.batches.len() + pre_batches.single_module_entries.len(),
788 edges_count,
789 );
790
791 let batches = pre_batches
793 .batches
794 .iter_mut()
795 .enumerate()
796 .map(async |(i, pre_batch)| {
797 let mut modules = pre_batch.items.iter().filter_map(|item| {
798 if let PreBatchItem::ParallelModule(module) = item {
799 ResolvedVc::try_downcast(*module)
800 } else {
801 None
802 }
803 });
804 let Some(first) = modules.next() else {
805 return Ok(ModuleOrBatch::None(i));
806 };
807 if let Some(second) = modules.next() {
808 let batch = ModuleBatch::new(
809 [first, second]
810 .into_iter()
811 .chain(modules)
812 .map(|m| *m)
813 .collect::<Vec<_>>(),
814 Some(pre_batch.chunk_groups.clone()),
815 );
816 Ok(ModuleOrBatch::Batch(batch.to_resolved().await?))
817 } else {
818 Ok(ModuleOrBatch::Module(ResolvedVc::upcast(first)))
819 }
820 })
821 .try_join()
822 .await?;
823
824 let mut batch_groups: FxHashMap<_, Vec<_>> = FxHashMap::default();
826 for (i, pre_batch) in pre_batches.batches.iter().enumerate() {
827 let key = BuildHasherDefault::<FxHasher>::default().prehash(&pre_batch.chunk_groups);
828 let batch = batches[i];
829 batch_groups.entry(key).or_default().push(batch);
830 }
831 for &module in &pre_batches.single_module_entries {
832 if per_entry_modules.contains(&module) {
836 continue;
837 }
838 let chunk_groups = module_chunk_groups
839 .get(&module)
840 .context("all modules need to have chunk group info")?;
841 let key = BuildHasherDefault::<FxHasher>::default().prehash(chunk_groups);
842 batch_groups
843 .entry(key)
844 .or_default()
845 .push(ModuleOrBatch::Module(module));
846 }
847
848 let batch_groups = batch_groups
850 .into_iter()
851 .map(async |(key, items)| {
852 if items.len() == 1 {
853 anyhow::Ok(Either::Left(std::iter::empty()))
854 } else {
855 let batch_group = ModuleBatchGroup::new(items.clone(), (*key).clone())
856 .to_resolved()
857 .await?;
858 Ok(Either::Right(
859 items.into_iter().map(move |item| (item, batch_group)),
860 ))
861 }
862 })
863 .join()
864 .await
865 .into_iter()
866 .flatten_ok()
867 .collect::<Result<FxHashMap<_, _>>>()?;
868
869 let mut batches_count = 0;
871 let mut modules_count = 0;
872 let batch_indices = batches
873 .into_iter()
874 .map(|batch| {
875 match &batch {
876 ModuleOrBatch::Batch(_) => batches_count += 1,
877 ModuleOrBatch::Module(_) => modules_count += 1,
878 ModuleOrBatch::None(_) => {}
879 }
880 graph.add_node(batch)
881 })
882 .collect::<Vec<_>>();
883
884 let single_module_indices = pre_batches
886 .single_module_entries
887 .iter()
888 .map(|module| graph.add_node(ModuleOrBatch::Module(*module)))
889 .collect::<Vec<_>>();
890
891 span.record("batches", batches_count);
892 modules_count += pre_batches.single_module_entries.len();
893 span.record("modules", modules_count);
894 span.record("edges", edges_count);
895
896 let mut module_to_node: FxHashMap<ResolvedVc<Box<dyn Module>>, NodeIndex> =
898 FxHashMap::default();
899 for (module, batches) in ¶llel_module_to_pre_batch {
900 if let Some(&first_batch) = batches.first() {
903 module_to_node.insert(*module, batch_indices[first_batch]);
904 }
905 }
906 for (idx, module) in pre_batches.single_module_entries.iter().enumerate() {
907 module_to_node.insert(*module, single_module_indices[idx]);
908 }
909
910 for (i, pre_batch) in pre_batches.batches.into_iter().enumerate() {
912 let index = batch_indices[i];
913 let items = pre_batch.items;
914 for item in items {
915 match item {
916 PreBatchItem::ParallelReference(idx) => {
917 graph.add_edge(
918 index,
919 batch_indices[idx],
920 ModuleBatchesGraphEdge {
921 ty: ChunkingType::Parallel {
922 inherit_async: false,
923 hoisted: false,
924 },
925 module: None,
926 active_for_page_entry: None,
927 },
928 );
929 }
930 PreBatchItem::NonParallelEdge(ty, module) => {
931 if matches!(ty, ChunkingType::Emitted { .. }) {
932 continue;
934 }
935 if let Some(batch) = pre_batches.entries.get(&module).copied() {
936 graph.add_edge(
937 index,
938 batch_indices[batch],
939 ModuleBatchesGraphEdge {
940 ty,
941 module: Some(module),
942 active_for_page_entry: None,
943 },
944 );
945 continue;
946 }
947 let idx = pre_batches
948 .single_module_entries
949 .get_index_of(&module)
950 .context("could not find single module entry index")?;
951 let idx = single_module_indices[idx];
952 graph.add_edge(
953 index,
954 idx,
955 ModuleBatchesGraphEdge {
956 ty,
957 module: Some(module),
958 active_for_page_entry: None,
959 },
960 );
961 }
962 PreBatchItem::ParallelModule(_) => {}
963 }
964 }
965 }
966
967 debug_assert_eq!(graph.capacity().0, graph.node_count());
968 debug_assert_eq!(graph.capacity().1, graph.edge_count());
969
970 for (collecting_module, refs) in &collected_modules {
972 for (entry_modules, refs) in refs {
973 let source_node = *module_to_node
974 .get(collecting_module)
975 .context("could not find single module entry index")?;
976 for (ref_data, target_module) in refs {
977 if let Some(batch) = pre_batches.entries.get(target_module).copied() {
978 for entry_module in entry_modules {
979 graph.add_edge(
980 source_node,
981 batch_indices[batch],
982 ModuleBatchesGraphEdge {
983 ty: ref_data.chunking_type.clone(),
984 module: Some(*target_module),
985 active_for_page_entry: Some(*entry_module),
986 },
987 );
988 }
989 continue;
990 }
991 let idx = pre_batches
992 .single_module_entries
993 .get_index_of(target_module)
994 .context("could not find single module entry index")?;
995 let idx = single_module_indices[idx];
996 for entry_module in entry_modules {
997 graph.add_edge(
998 source_node,
999 idx,
1000 ModuleBatchesGraphEdge {
1001 ty: ref_data.chunking_type.clone(),
1002 module: Some(*target_module),
1003 active_for_page_entry: Some(*entry_module),
1004 },
1005 );
1006 }
1007 }
1008 }
1009 }
1010
1011 let mut entries = FxHashMap::default();
1013 for chunk_group in &chunk_group_info.chunk_groups {
1014 for module in chunk_group.entries() {
1015 if let Some(batch) = pre_batches.entries.get(&module).copied() {
1016 entries.insert(module, batch_indices[batch]);
1017 continue;
1018 }
1019 let idx = pre_batches
1020 .single_module_entries
1021 .get_index_of(&module)
1022 .context("could not find single module entry index")?;
1023 let idx = single_module_indices[idx];
1024 entries.insert(module, idx);
1025 }
1026 }
1027
1028 Ok(ModuleBatchesGraph {
1029 graph: TracedDiGraph(graph),
1030 entries,
1031 batch_groups,
1032 ordered_entries,
1033 }
1034 .cell())
1035 }
1036 .instrument(outer_span)
1037 .await
1038}