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