1use std::{
2 hash::Hash,
3 ops::{Deref, DerefMut},
4 sync::LazyLock,
5};
6
7use anyhow::{Context, Result, bail};
8use bincode::{Decode, Encode};
9use either::Either;
10use indexmap::map::Entry;
11use roaring::RoaringBitmap;
12use rustc_hash::FxHashMap;
13use tracing::Instrument;
14use turbo_rcstr::RcStr;
15use turbo_tasks::{
16 FxIndexMap, FxIndexSet, NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, ValueToString,
17 Vc, debug::ValueDebugFormat, turbofmt,
18};
19
20use crate::{
21 chunk::ChunkingType,
22 module::Module,
23 module_graph::{GraphTraversalAction, ModuleGraph, RefData},
24};
25
26#[derive(Clone, Debug, Default, PartialEq, ValueDebugFormat, Encode, Decode)]
27#[repr(transparent)]
28pub struct RoaringBitmapWrapper(#[bincode(with_serde)] pub RoaringBitmap);
29
30impl TaskInput for RoaringBitmapWrapper {
31 fn is_transient(&self) -> bool {
32 false
33 }
34}
35
36impl RoaringBitmapWrapper {
37 pub fn is_proper_superset(&self, other: &Self) -> bool {
41 !self.is_subset(other)
42 }
43
44 pub fn into_inner(self) -> RoaringBitmap {
45 self.0
46 }
47}
48unsafe impl NonLocalValue for RoaringBitmapWrapper {}
49
50impl Eq for RoaringBitmapWrapper {}
54
55impl Deref for RoaringBitmapWrapper {
56 type Target = RoaringBitmap;
57 fn deref(&self) -> &Self::Target {
58 &self.0
59 }
60}
61impl DerefMut for RoaringBitmapWrapper {
62 fn deref_mut(&mut self) -> &mut Self::Target {
63 &mut self.0
64 }
65}
66impl Hash for RoaringBitmapWrapper {
67 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
68 struct HasherWriter<'a, H: std::hash::Hasher>(&'a mut H);
69 impl<H: std::hash::Hasher> std::io::Write for HasherWriter<'_, H> {
70 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
71 self.0.write(buf);
72 Ok(buf.len())
73 }
74 fn flush(&mut self) -> std::io::Result<()> {
75 Ok(())
76 }
77 }
78 self.0.serialize_into(HasherWriter(state)).unwrap();
79 }
80}
81
82#[turbo_tasks::value(transparent, cell = "keyed")]
83pub struct ModuleToChunkGroups(FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper>);
84
85#[turbo_tasks::value]
86pub struct ChunkGroupInfo {
87 pub module_chunk_groups: ResolvedVc<ModuleToChunkGroups>,
88 #[bincode(with = "turbo_bincode::indexset")]
89 pub chunk_groups: FxIndexSet<ChunkGroup>,
90 #[turbo_tasks(unsafe_ignore)]
91 #[bincode(with = "turbo_bincode::indexset")]
92 pub chunk_group_keys: FxIndexSet<ChunkGroupKey>,
93 pub chunking_heuristics: ChunkingHeuristicsInfo,
94}
95
96#[derive(Debug, Default, Clone, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
100pub struct ChunkingHeuristicsInfo {
101 pub clusters: Vec<Vec<u16>>,
107 pub priority_routes: RoaringBitmapWrapper,
113}
114
115#[turbo_tasks::value_impl]
116impl ChunkGroupInfo {
117 #[turbo_tasks::function]
118 pub fn module_chunk_groups(&self) -> Vc<ModuleToChunkGroups> {
119 *self.module_chunk_groups
120 }
121
122 #[turbo_tasks::function]
123 pub async fn get_index_of(&self, chunk_group: ChunkGroup) -> Result<Vc<usize>> {
124 if let Some(idx) = self.chunk_groups.get_index_of(&chunk_group) {
125 Ok(Vc::cell(idx))
126 } else {
127 if cfg!(debug_assertions) {
128 bail!(
129 "Couldn't find chunk group index for {} in {}",
130 chunk_group.debug_str(self).await?,
131 self.chunk_groups
132 .iter()
133 .map(|c| c.debug_str(self))
134 .try_join()
135 .await?
136 .join(", ")
137 );
138 } else {
139 bail!("Couldn't find chunk group index")
140 }
141 }
142 }
143}
144
145#[turbo_tasks::task_input]
147#[derive(Debug, Default, Clone, Hash, PartialEq, Eq, Encode, Decode)]
148pub struct EntryHeuristics {
149 pub clusters: Vec<u16>,
151 pub high_priority: bool,
152}
153
154impl EntryHeuristics {
155 pub fn high_priority() -> Self {
158 Self {
159 clusters: Vec::new(),
160 high_priority: true,
161 }
162 }
163}
164
165#[turbo_tasks::task_input]
167#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode, Decode)]
168pub enum ChunkGroupEntry {
169 Entry {
170 modules: Vec<ResolvedVc<Box<dyn Module>>>,
171 heuristics: EntryHeuristics,
172 },
173 Async(ResolvedVc<Box<dyn Module>>),
174 Isolated(ResolvedVc<Box<dyn Module>>),
175 IsolatedMerged {
176 parent: Box<ChunkGroupEntry>,
177 merge_tag: RcStr,
178 entries: Vec<ResolvedVc<Box<dyn Module>>>,
179 },
180 Shared(ResolvedVc<Box<dyn Module>>),
181 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
182 SharedMerged {
183 parent: Box<ChunkGroupEntry>,
184 merge_tag: RcStr,
185 entries: Vec<ResolvedVc<Box<dyn Module>>>,
186 },
187}
188impl ChunkGroupEntry {
189 pub fn entries(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
190 match self {
191 Self::Async(e) | Self::Isolated(e) | Self::Shared(e) => {
192 Either::Left(std::iter::once(*e))
193 }
194 Self::Entry {
195 modules: entries, ..
196 }
197 | Self::IsolatedMerged { entries, .. }
198 | Self::SharedMultiple(entries)
199 | Self::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
200 }
201 }
202}
203
204#[turbo_tasks::task_input]
205#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode, Decode)]
206pub enum ChunkGroup {
207 Entry(Vec<ResolvedVc<Box<dyn Module>>>),
210 Async(ResolvedVc<Box<dyn Module>>),
212 Isolated(ResolvedVc<Box<dyn Module>>),
215 IsolatedMerged {
218 parent: usize,
219 merge_tag: RcStr,
220 entries: Vec<ResolvedVc<Box<dyn Module>>>,
221 },
222 Shared(ResolvedVc<Box<dyn Module>>),
225 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
227 SharedMerged {
230 parent: usize,
231 merge_tag: RcStr,
232 entries: Vec<ResolvedVc<Box<dyn Module>>>,
233 },
234 Collected(ResolvedVc<Box<dyn Module>>),
236}
237
238impl ChunkGroup {
239 pub fn get_merged_parent(&self) -> Option<usize> {
242 match self {
243 ChunkGroup::IsolatedMerged { parent, .. } | ChunkGroup::SharedMerged { parent, .. } => {
244 Some(*parent)
245 }
246 _ => None,
247 }
248 }
249
250 pub fn entries(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Clone + '_ {
253 match self {
254 ChunkGroup::Async(e)
255 | ChunkGroup::Isolated(e)
256 | ChunkGroup::Shared(e)
257 | ChunkGroup::Collected(e) => Either::Left(std::iter::once(*e)),
258 ChunkGroup::Entry(entries)
259 | ChunkGroup::IsolatedMerged { entries, .. }
260 | ChunkGroup::SharedMultiple(entries)
261 | ChunkGroup::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
262 }
263 }
264
265 pub fn entries_count(&self) -> usize {
266 match self {
267 ChunkGroup::Async(_)
268 | ChunkGroup::Isolated(_)
269 | ChunkGroup::Shared(_)
270 | ChunkGroup::Collected(_) => 1,
271 ChunkGroup::Entry(entries)
272 | ChunkGroup::IsolatedMerged { entries, .. }
273 | ChunkGroup::SharedMultiple(entries)
274 | ChunkGroup::SharedMerged { entries, .. } => entries.len(),
275 }
276 }
277
278 pub async fn debug_str(&self, chunk_group_info: &ChunkGroupInfo) -> Result<String> {
279 Ok(match self {
280 ChunkGroup::Entry(entries) => format!(
281 "ChunkGroup::Entry({:?})",
282 entries
283 .iter()
284 .map(|m| m.ident().to_string())
285 .try_join()
286 .await?
287 ),
288 ChunkGroup::Async(entry) => turbofmt!("ChunkGroup::Async({:?})", entry.ident())
289 .await?
290 .to_string(),
291 ChunkGroup::Collected(entry) => turbofmt!("ChunkGroup::Collected({:?})", entry.ident())
292 .await?
293 .to_string(),
294 ChunkGroup::Isolated(entry) => turbofmt!("ChunkGroup::Isolated({:?})", entry.ident())
295 .await?
296 .to_string(),
297 ChunkGroup::Shared(entry) => turbofmt!("ChunkGroup::Shared({:?})", entry.ident())
298 .await?
299 .to_string(),
300 ChunkGroup::SharedMultiple(entries) => format!(
301 "ChunkGroup::SharedMultiple({:?})",
302 entries
303 .iter()
304 .map(|m| m.ident().to_string())
305 .try_join()
306 .await?
307 ),
308 ChunkGroup::IsolatedMerged {
309 parent,
310 merge_tag,
311 entries,
312 } => {
313 format!(
314 "ChunkGroup::IsolatedMerged({}, {}, {:?})",
315 Box::pin(chunk_group_info.chunk_groups[*parent].debug_str(chunk_group_info))
316 .await?,
317 merge_tag,
318 entries
319 .iter()
320 .map(|m| m.ident().to_string())
321 .try_join()
322 .await?
323 )
324 }
325 ChunkGroup::SharedMerged {
326 parent,
327 merge_tag,
328 entries,
329 } => {
330 format!(
331 "ChunkGroup::SharedMerged({}, {}, {:?})",
332 Box::pin(chunk_group_info.chunk_groups[*parent].debug_str(chunk_group_info))
333 .await?,
334 merge_tag,
335 entries
336 .iter()
337 .map(|m| m.ident().to_string())
338 .try_join()
339 .await?
340 )
341 }
342 })
343 }
344}
345
346#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode, Decode)]
348pub enum ChunkGroupKey {
349 Entry(Vec<ResolvedVc<Box<dyn Module>>>),
350 Async(ResolvedVc<Box<dyn Module>>),
351 Isolated(ResolvedVc<Box<dyn Module>>),
352 IsolatedMerged {
353 parent: ChunkGroupId,
354 merge_tag: RcStr,
355 },
356 Shared(ResolvedVc<Box<dyn Module>>),
357 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
358 SharedMerged {
359 parent: ChunkGroupId,
360 merge_tag: RcStr,
361 },
362 Collected(ResolvedVc<Box<dyn Module>>),
364}
365
366impl ChunkGroupKey {
367 pub async fn debug_str(
368 &self,
369 keys: impl std::ops::Index<usize, Output = Self>,
370 ) -> Result<String> {
371 Ok(match self {
372 ChunkGroupKey::Entry(entries) => format!(
373 "Entry({:?})",
374 entries
375 .iter()
376 .map(|m| m.ident().to_string())
377 .try_join()
378 .await?
379 ),
380 ChunkGroupKey::Async(module) => {
381 turbofmt!("Async({:?})", module.ident()).await?.to_string()
382 }
383 ChunkGroupKey::Collected(module) => turbofmt!("Collected({:?})", module.ident())
384 .await?
385 .to_string(),
386 ChunkGroupKey::Isolated(module) => turbofmt!("Isolated({:?})", module.ident())
387 .await?
388 .to_string(),
389 ChunkGroupKey::IsolatedMerged { parent, merge_tag } => {
390 format!(
391 "IsolatedMerged {{ parent: {}, merge_tag: {:?} }}",
392 Box::pin(keys.index(parent.0 as usize).clone().debug_str(keys)).await?,
393 merge_tag
394 )
395 }
396 ChunkGroupKey::Shared(module) => {
397 turbofmt!("Shared({:?})", module.ident()).await?.to_string()
398 }
399 ChunkGroupKey::SharedMultiple(entries) => format!(
400 "SharedMultiple({:?})",
401 entries
402 .iter()
403 .map(|m| m.ident().to_string())
404 .try_join()
405 .await?
406 ),
407 ChunkGroupKey::SharedMerged { parent, merge_tag } => {
408 format!(
409 "SharedMerged {{ parent: {}, merge_tag: {:?} }}",
410 Box::pin(keys.index(parent.0 as usize).clone().debug_str(keys)).await?,
411 merge_tag
412 )
413 }
414 })
415 }
416}
417
418#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
419pub struct ChunkGroupId(u32);
420
421impl From<usize> for ChunkGroupId {
422 fn from(id: usize) -> Self {
423 Self(id as u32)
424 }
425}
426
427impl Deref for ChunkGroupId {
428 type Target = u32;
429 fn deref(&self) -> &Self::Target {
430 &self.0
431 }
432}
433
434#[derive(Debug, Clone, PartialEq, Eq)]
435pub(crate) struct TraversalPriority {
436 pub depth: usize,
437 pub chunk_group_len: u64,
438}
439impl PartialOrd for TraversalPriority {
440 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
441 Some(self.cmp(other))
442 }
443}
444impl Ord for TraversalPriority {
446 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
447 let depth_order = self.depth.cmp(&other.depth).reverse();
451 let chunk_group_len_order = self.chunk_group_len.cmp(&other.chunk_group_len).reverse();
453
454 depth_order.then(chunk_group_len_order)
455 }
456}
457
458pub async fn compute_chunk_group_info(graph: &ModuleGraph) -> Result<Vc<ChunkGroupInfo>> {
459 let span_outer = tracing::info_span!(
460 "compute chunk group info",
461 module_count = tracing::field::Empty,
462 visit_count = tracing::field::Empty,
463 chunk_group_count = tracing::field::Empty
464 );
465
466 let span = span_outer.clone();
467 async move {
468 let mut chunk_groups_map: FxIndexMap<
469 ChunkGroupKey,
470 FxIndexSet<ResolvedVc<Box<dyn Module>>>,
471 > = FxIndexMap::default();
472
473 let mut module_chunk_groups: FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper> =
476 FxHashMap::default();
477
478 let module_count = graph
479 .graphs
480 .iter()
481 .map(|g| g.graph.node_count())
482 .sum::<usize>();
483 span.record("module_count", module_count);
484
485 let entries = graph.all_chunk_group_entries().collect::<Vec<_>>();
487
488 let module_depth: FxHashMap<ResolvedVc<Box<dyn Module>>, usize> = {
491 let mut module_depth =
492 FxHashMap::with_capacity_and_hasher(module_count, Default::default());
493 graph.traverse_edges_bfs(
494 entries.iter().flat_map(|e| e.entries()),
495 |parent, node| {
496 if let Some((parent, _)) = parent {
497 let parent_depth = *module_depth
498 .get(&parent)
499 .context("Module depth not found")?;
500 module_depth.entry(node).or_insert(parent_depth + 1);
501 } else {
502 module_depth.insert(node, 0);
503 };
504
505 module_chunk_groups.insert(node, RoaringBitmapWrapper::default());
506
507 Ok(GraphTraversalAction::Continue)
508 },
509 )?;
510 module_depth
511 };
512
513 fn entry_to_chunk_group_id(
516 entry: ChunkGroupEntry,
517 chunk_groups_map: &mut FxIndexMap<
518 ChunkGroupKey,
519 FxIndexSet<ResolvedVc<Box<dyn Module>>>,
520 >,
521 ) -> ChunkGroupKey {
522 match entry {
523 ChunkGroupEntry::Entry { modules, .. } => ChunkGroupKey::Entry(modules),
524 ChunkGroupEntry::Async(entry) => ChunkGroupKey::Async(entry),
525 ChunkGroupEntry::Isolated(entry) => ChunkGroupKey::Isolated(entry),
526 ChunkGroupEntry::Shared(entry) => ChunkGroupKey::Shared(entry),
527 ChunkGroupEntry::SharedMultiple(entries) => ChunkGroupKey::SharedMultiple(entries),
528 ChunkGroupEntry::IsolatedMerged {
529 parent,
530 merge_tag,
531 entries: _,
532 } => {
533 let parent = entry_to_chunk_group_id(*parent, chunk_groups_map);
534 let parent_entry = chunk_groups_map.entry(parent);
535 let parent_id = parent_entry.index();
536 parent_entry.or_default();
537
538 ChunkGroupKey::IsolatedMerged {
539 parent: ChunkGroupId::from(parent_id),
540 merge_tag,
541 }
542 }
543 ChunkGroupEntry::SharedMerged {
544 parent,
545 merge_tag,
546 entries: _,
547 } => {
548 let parent = entry_to_chunk_group_id(*parent, chunk_groups_map);
549 let parent_entry = chunk_groups_map.entry(parent);
550 let parent_id = parent_entry.index();
551 parent_entry.or_default();
552
553 ChunkGroupKey::SharedMerged {
554 parent: ChunkGroupId::from(parent_id),
555 merge_tag,
556 }
557 }
558 }
559 }
560
561 let entry_chunk_group_keys = entries
562 .iter()
563 .flat_map(|&chunk_group| {
564 let chunk_group_key =
565 entry_to_chunk_group_id(chunk_group.clone(), &mut chunk_groups_map);
566 chunk_group
567 .entries()
568 .map(move |e| (e, chunk_group_key.clone()))
569 })
570 .collect::<FxHashMap<_, _>>();
571
572 let mut inherits_from: FxHashMap<u32, RoaringBitmap> = FxHashMap::default();
574
575 let visit_count = graph.traverse_edges_fixed_point_with_priority(
576 entries
577 .iter()
578 .flat_map(|e| e.entries())
579 .map(|e| {
580 Ok((
581 e,
582 TraversalPriority {
583 depth: *module_depth.get(&e).context("Module depth not found")?,
584 chunk_group_len: 0,
585 },
586 ))
587 })
588 .collect::<Result<Vec<_>>>()?,
589 &mut module_chunk_groups,
590 |parent_info: Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, _)>,
591 node: ResolvedVc<Box<dyn Module>>,
592 _,
593 module_chunk_groups: &mut FxHashMap<
594 ResolvedVc<Box<dyn Module>>,
595 RoaringBitmapWrapper,
596 >|
597 -> Result<GraphTraversalAction> {
598 enum ChunkGroupInheritance<It: Iterator<Item = ChunkGroupKey>> {
599 Inherit(ResolvedVc<Box<dyn Module>>),
600 ChunkGroup(It),
601 }
602 let chunk_groups = if let Some((parent, ref_data, _)) = parent_info {
603 match &ref_data.chunking_type {
604 ChunkingType::Parallel { .. } => ChunkGroupInheritance::Inherit(parent),
605 ChunkingType::Async => ChunkGroupInheritance::ChunkGroup(Either::Left(
606 std::iter::once(ChunkGroupKey::Async(node)),
607 )),
608 ChunkingType::Isolated {
609 merge_tag: None, ..
610 } => ChunkGroupInheritance::ChunkGroup(Either::Left(std::iter::once(
611 ChunkGroupKey::Isolated(node),
612 ))),
613 ChunkingType::Shared {
614 merge_tag: None, ..
615 } => ChunkGroupInheritance::ChunkGroup(Either::Left(std::iter::once(
616 ChunkGroupKey::Shared(node),
617 ))),
618 ChunkingType::Isolated {
619 merge_tag: Some(merge_tag),
620 ..
621 } => {
622 let parents = module_chunk_groups
623 .get(&parent)
624 .context("Module chunk group not found")?;
625 let chunk_groups =
626 parents.iter().map(|parent| ChunkGroupKey::IsolatedMerged {
627 parent: ChunkGroupId(parent),
628 merge_tag: merge_tag.clone(),
629 });
630 ChunkGroupInheritance::ChunkGroup(Either::Right(Either::Left(
631 chunk_groups,
632 )))
633 }
634 ChunkingType::Shared {
635 merge_tag: Some(merge_tag),
636 ..
637 } => {
638 let parents = module_chunk_groups
639 .get(&parent)
640 .context("Module chunk group not found")?;
641 let chunk_groups =
642 parents.iter().map(|parent| ChunkGroupKey::SharedMerged {
643 parent: ChunkGroupId(parent),
644 merge_tag: merge_tag.clone(),
645 });
646 ChunkGroupInheritance::ChunkGroup(Either::Right(Either::Right(
647 chunk_groups,
648 )))
649 }
650 ChunkingType::Traced { .. } => {
651 return Ok(GraphTraversalAction::Skip);
653 }
654 ChunkingType::Emitted {
655 namespace: _,
656 emit_to_all_entries: _,
657 } => {
658 ChunkGroupInheritance::ChunkGroup(Either::Left(std::iter::once(
661 ChunkGroupKey::Collected(node),
662 )))
663 }
664 ChunkingType::Collected { .. } => {
665 unreachable!();
667 }
668 ChunkingType::PerEntry => {
669 ChunkGroupInheritance::Inherit(parent)
675 }
676 }
677 } else {
678 ChunkGroupInheritance::ChunkGroup(Either::Left(std::iter::once(
679 entry_chunk_group_keys
681 .get(&node)
682 .context("Module chunk group not found")?
683 .clone(),
684 )))
685 };
686
687 Ok(match chunk_groups {
688 ChunkGroupInheritance::ChunkGroup(chunk_groups) => {
689 let chunk_group_ids = chunk_groups.map(|chunk_group| {
691 let merged_parent = match &chunk_group {
693 ChunkGroupKey::IsolatedMerged { parent, .. }
694 | ChunkGroupKey::SharedMerged { parent, .. } => Some(parent.0),
695 _ => None,
696 };
697 let id = match chunk_groups_map.entry(chunk_group) {
698 Entry::Occupied(mut e) => {
699 let id = e.index() as u32;
700 if merged_parent.is_some() {
701 e.get_mut().insert(node);
702 }
703 id
704 }
705 Entry::Vacant(e) => {
706 let id = e.index() as u32;
707 let mut set = FxIndexSet::default();
708 if merged_parent.is_some() {
709 set.insert(node);
710 }
711 e.insert(set);
712 id
713 }
714 };
715 if let Some(parent) = merged_parent {
719 inherits_from.entry(parent).or_default().insert(id);
720 } else if let Some((parent_module, _, _)) = parent_info
721 && let Some(parent_groups) = module_chunk_groups.get(&parent_module)
722 {
723 for source in parent_groups.iter() {
724 inherits_from.entry(source).or_default().insert(id);
725 }
726 }
727 id
728 });
729
730 let chunk_groups =
731 RoaringBitmapWrapper(RoaringBitmap::from_iter(chunk_group_ids));
732
733 let bitset = module_chunk_groups
735 .get_mut(&node)
736 .context("Module chunk group not found")?;
737 if chunk_groups.is_proper_superset(bitset) {
738 **bitset |= chunk_groups.into_inner();
740
741 GraphTraversalAction::Continue
742 } else {
743 GraphTraversalAction::Skip
745 }
746 }
747 ChunkGroupInheritance::Inherit(parent) => {
748 if parent == node {
752 GraphTraversalAction::Skip
754 } else {
755 let [Some(parent_chunk_groups), Some(current_chunk_groups)] =
756 module_chunk_groups.get_disjoint_mut([&parent, &node])
757 else {
758 bail!("Module chunk groups not found");
762 };
763
764 if current_chunk_groups.is_empty() {
765 *current_chunk_groups = parent_chunk_groups.clone();
767 GraphTraversalAction::Continue
768 } else if parent_chunk_groups.is_proper_superset(current_chunk_groups) {
769 **current_chunk_groups |= &**parent_chunk_groups;
771 GraphTraversalAction::Continue
772 } else {
773 GraphTraversalAction::Skip
775 }
776 }
777 }
778 })
779 },
780 |successor, module_chunk_groups| {
788 Ok(TraversalPriority {
789 depth: *module_depth
790 .get(&successor)
791 .context("Module depth not found")?,
792 chunk_group_len: module_chunk_groups
793 .get(&successor)
794 .context("Module chunk group not found")?
795 .len(),
796 })
797 },
798 )?;
799
800 span.record("visit_count", visit_count);
801 span.record("chunk_group_count", chunk_groups_map.len());
802
803 static PRINT_CHUNK_GROUP_INFO: LazyLock<bool> =
804 LazyLock::new(|| match std::env::var_os("TURBOPACK_PRINT_CHUNK_GROUPS") {
805 Some(v) => v == "1",
806 None => false,
807 });
808 if *PRINT_CHUNK_GROUP_INFO {
809 use std::{
810 collections::{BTreeMap, BTreeSet},
811 path::absolute,
812 };
813
814 let mut buckets = BTreeMap::default();
815 for (module, key) in &module_chunk_groups {
816 if !key.is_empty() {
817 buckets
818 .entry(key.iter().collect::<Vec<_>>())
819 .or_insert(BTreeSet::new())
820 .insert(module.ident().to_string().await?);
821 }
822 }
823
824 let mut result = vec![];
825 result.push("Chunk Groups:".to_string());
826 for (i, (key, _)) in chunk_groups_map.iter().enumerate() {
827 result.push(format!(
828 " {:?}: {}",
829 i,
830 key.debug_str(chunk_groups_map.keys()).await?
831 ));
832 }
833 result.push("# Module buckets:".to_string());
834 for (key, modules) in buckets.iter() {
835 result.push(format!("## {:?}:", key.iter().collect::<Vec<_>>()));
836 for module in modules {
837 result.push(format!(" {module}"));
838 }
839 result.push("".to_string());
840 }
841 let f = absolute(format!("chunk_group_info_{}.log", visit_count))?;
842 println!("Wrote Chunk Group Info to {}", f.display());
843 std::fs::write(f, result.join("\n"))?;
844 }
845
846 let mut clusters: Vec<RoaringBitmap> = vec![RoaringBitmap::new(); chunk_groups_map.len()];
850 let mut priority_routes = RoaringBitmap::new();
851
852 let mut worklist: Vec<usize> = Vec::new();
853
854 for chunk_group in &entries {
855 let ChunkGroupEntry::Entry {
856 modules,
857 heuristics,
858 } = chunk_group
859 else {
860 continue;
861 };
862 if heuristics.clusters.is_empty() && !heuristics.high_priority {
863 continue;
864 }
865 if let Some(index) =
866 chunk_groups_map.get_index_of(&ChunkGroupKey::Entry(modules.clone()))
867 {
868 if clusters[index].is_empty() && !priority_routes.contains(index as u32) {
869 worklist.push(index);
870 }
871 clusters[index].extend(heuristics.clusters.iter().map(|&c| c as u32));
872 if heuristics.high_priority {
873 priority_routes.insert(index as u32);
874 }
875 }
876 }
877
878 while let Some(source) = worklist.pop() {
879 let source_priority_route = priority_routes.contains(source as u32);
880 let Some(targets) = inherits_from.get(&(source as u32)) else {
881 continue;
882 };
883 for target in targets.iter() {
884 let target = target as usize;
885 if target == source {
886 continue;
887 }
888 let [source_clusters, target_clusters] =
889 clusters.get_disjoint_mut([source, target]).unwrap();
890 let previous_target_clusters_len = target_clusters.len();
891 *target_clusters |= &*source_clusters;
892 let changed = (source_priority_route && priority_routes.insert(target as u32))
893 || previous_target_clusters_len != target_clusters.len();
894 if changed {
895 worklist.push(target);
896 }
897 }
898 }
899
900 let chunk_group_clusters: Vec<Vec<u16>> = clusters
901 .into_iter()
902 .map(|bm| bm.iter().map(|id| id as u16).collect())
903 .collect();
904 let chunk_group_priority_routes = RoaringBitmapWrapper(priority_routes);
905
906 Ok(ChunkGroupInfo {
907 module_chunk_groups: ResolvedVc::cell(module_chunk_groups),
908 chunk_group_keys: chunk_groups_map.keys().cloned().collect(),
909 chunking_heuristics: ChunkingHeuristicsInfo {
910 clusters: chunk_group_clusters,
911 priority_routes: chunk_group_priority_routes,
912 },
913 chunk_groups: chunk_groups_map
914 .into_iter()
915 .map(|(k, merged_entries)| match k {
916 ChunkGroupKey::Entry(entries) => ChunkGroup::Entry(entries),
917 ChunkGroupKey::Async(module) => ChunkGroup::Async(module),
918 ChunkGroupKey::Collected(module) => ChunkGroup::Collected(module),
919 ChunkGroupKey::Isolated(module) => ChunkGroup::Isolated(module),
920 ChunkGroupKey::IsolatedMerged { parent, merge_tag } => {
921 ChunkGroup::IsolatedMerged {
922 parent: parent.0 as usize,
923 merge_tag,
924 entries: merged_entries.into_iter().collect(),
925 }
926 }
927 ChunkGroupKey::Shared(module) => ChunkGroup::Shared(module),
928 ChunkGroupKey::SharedMultiple(entries) => ChunkGroup::SharedMultiple(entries),
929 ChunkGroupKey::SharedMerged { parent, merge_tag } => ChunkGroup::SharedMerged {
930 parent: parent.0 as usize,
931 merge_tag,
932 entries: merged_entries.into_iter().collect(),
933 },
934 })
935 .collect(),
936 }
937 .cell())
938 }
939 .instrument(span_outer)
940 .await
941}