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, trace::TraceRawVcs, 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, TraceRawVcs, ValueDebugFormat, Encode, Decode)]
27#[repr(transparent)]
28pub struct RoaringBitmapWrapper(
29 #[turbo_tasks(trace_ignore)]
30 #[bincode(with_serde)]
31 pub RoaringBitmap,
32);
33
34impl TaskInput for RoaringBitmapWrapper {
35 fn is_transient(&self) -> bool {
36 false
37 }
38}
39
40impl RoaringBitmapWrapper {
41 pub fn is_proper_superset(&self, other: &Self) -> bool {
45 !self.is_subset(other)
46 }
47
48 pub fn into_inner(self) -> RoaringBitmap {
49 self.0
50 }
51}
52unsafe impl NonLocalValue for RoaringBitmapWrapper {}
53
54impl Eq for RoaringBitmapWrapper {}
58
59impl Deref for RoaringBitmapWrapper {
60 type Target = RoaringBitmap;
61 fn deref(&self) -> &Self::Target {
62 &self.0
63 }
64}
65impl DerefMut for RoaringBitmapWrapper {
66 fn deref_mut(&mut self) -> &mut Self::Target {
67 &mut self.0
68 }
69}
70impl Hash for RoaringBitmapWrapper {
71 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
72 struct HasherWriter<'a, H: std::hash::Hasher>(&'a mut H);
73 impl<H: std::hash::Hasher> std::io::Write for HasherWriter<'_, H> {
74 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
75 self.0.write(buf);
76 Ok(buf.len())
77 }
78 fn flush(&mut self) -> std::io::Result<()> {
79 Ok(())
80 }
81 }
82 self.0.serialize_into(HasherWriter(state)).unwrap();
83 }
84}
85
86#[turbo_tasks::value(transparent, cell = "keyed")]
87pub struct ModuleToChunkGroups(FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper>);
88
89#[turbo_tasks::value]
90pub struct ChunkGroupInfo {
91 pub module_chunk_groups: ResolvedVc<ModuleToChunkGroups>,
92 #[turbo_tasks(trace_ignore)]
93 #[bincode(with = "turbo_bincode::indexset")]
94 pub chunk_groups: FxIndexSet<ChunkGroup>,
95 #[turbo_tasks(trace_ignore)]
96 #[bincode(with = "turbo_bincode::indexset")]
97 pub chunk_group_keys: FxIndexSet<ChunkGroupKey>,
98 pub chunking_heuristics: ChunkingHeuristicsInfo,
99}
100
101#[derive(
105 Debug,
106 Default,
107 Clone,
108 PartialEq,
109 Eq,
110 TraceRawVcs,
111 ValueDebugFormat,
112 NonLocalValue,
113 Encode,
114 Decode,
115)]
116pub struct ChunkingHeuristicsInfo {
117 pub clusters: Vec<Vec<u16>>,
123 #[turbo_tasks(trace_ignore)]
129 pub priority_routes: RoaringBitmapWrapper,
130}
131
132#[turbo_tasks::value_impl]
133impl ChunkGroupInfo {
134 #[turbo_tasks::function]
135 pub fn module_chunk_groups(&self) -> Vc<ModuleToChunkGroups> {
136 *self.module_chunk_groups
137 }
138
139 #[turbo_tasks::function]
140 pub async fn get_index_of(&self, chunk_group: ChunkGroup) -> Result<Vc<usize>> {
141 if let Some(idx) = self.chunk_groups.get_index_of(&chunk_group) {
142 Ok(Vc::cell(idx))
143 } else {
144 if cfg!(debug_assertions) {
145 bail!(
146 "Couldn't find chunk group index for {} in {}",
147 chunk_group.debug_str(self).await?,
148 self.chunk_groups
149 .iter()
150 .map(|c| c.debug_str(self))
151 .try_join()
152 .await?
153 .join(", ")
154 );
155 } else {
156 bail!("Couldn't find chunk group index")
157 }
158 }
159 }
160}
161
162#[turbo_tasks::task_input]
164#[derive(Debug, Default, Clone, Hash, PartialEq, Eq, TraceRawVcs, Encode, Decode)]
165pub struct EntryHeuristics {
166 pub clusters: Vec<u16>,
168 pub high_priority: bool,
169}
170
171impl EntryHeuristics {
172 pub fn high_priority() -> Self {
175 Self {
176 clusters: Vec::new(),
177 high_priority: true,
178 }
179 }
180}
181
182#[turbo_tasks::task_input]
184#[derive(Debug, Clone, Hash, PartialEq, Eq, TraceRawVcs, Encode, Decode)]
185pub enum ChunkGroupEntry {
186 Entry {
187 modules: Vec<ResolvedVc<Box<dyn Module>>>,
188 heuristics: EntryHeuristics,
189 },
190 Async(ResolvedVc<Box<dyn Module>>),
191 Isolated(ResolvedVc<Box<dyn Module>>),
192 IsolatedMerged {
193 parent: Box<ChunkGroupEntry>,
194 merge_tag: RcStr,
195 entries: Vec<ResolvedVc<Box<dyn Module>>>,
196 },
197 Shared(ResolvedVc<Box<dyn Module>>),
198 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
199 SharedMerged {
200 parent: Box<ChunkGroupEntry>,
201 merge_tag: RcStr,
202 entries: Vec<ResolvedVc<Box<dyn Module>>>,
203 },
204}
205impl ChunkGroupEntry {
206 pub fn entries(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + '_ {
207 match self {
208 Self::Async(e) | Self::Isolated(e) | Self::Shared(e) => {
209 Either::Left(std::iter::once(*e))
210 }
211 Self::Entry {
212 modules: entries, ..
213 }
214 | Self::IsolatedMerged { entries, .. }
215 | Self::SharedMultiple(entries)
216 | Self::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
217 }
218 }
219}
220
221#[turbo_tasks::task_input]
222#[derive(Debug, Clone, Hash, PartialEq, Eq, TraceRawVcs, Encode, Decode)]
223pub enum ChunkGroup {
224 Entry(Vec<ResolvedVc<Box<dyn Module>>>),
227 Async(ResolvedVc<Box<dyn Module>>),
229 Isolated(ResolvedVc<Box<dyn Module>>),
232 IsolatedMerged {
235 parent: usize,
236 merge_tag: RcStr,
237 entries: Vec<ResolvedVc<Box<dyn Module>>>,
238 },
239 Shared(ResolvedVc<Box<dyn Module>>),
242 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
244 SharedMerged {
247 parent: usize,
248 merge_tag: RcStr,
249 entries: Vec<ResolvedVc<Box<dyn Module>>>,
250 },
251}
252
253impl ChunkGroup {
254 pub fn get_merged_parent(&self) -> Option<usize> {
257 match self {
258 ChunkGroup::IsolatedMerged { parent, .. } | ChunkGroup::SharedMerged { parent, .. } => {
259 Some(*parent)
260 }
261 _ => None,
262 }
263 }
264
265 pub fn entries(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn Module>>> + Clone + '_ {
268 match self {
269 ChunkGroup::Async(e) | ChunkGroup::Isolated(e) | ChunkGroup::Shared(e) => {
270 Either::Left(std::iter::once(*e))
271 }
272 ChunkGroup::Entry(entries)
273 | ChunkGroup::IsolatedMerged { entries, .. }
274 | ChunkGroup::SharedMultiple(entries)
275 | ChunkGroup::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
276 }
277 }
278
279 pub fn entries_count(&self) -> usize {
280 match self {
281 ChunkGroup::Async(_) | ChunkGroup::Isolated(_) | ChunkGroup::Shared(_) => 1,
282 ChunkGroup::Entry(entries)
283 | ChunkGroup::IsolatedMerged { entries, .. }
284 | ChunkGroup::SharedMultiple(entries)
285 | ChunkGroup::SharedMerged { entries, .. } => entries.len(),
286 }
287 }
288
289 pub async fn debug_str(&self, chunk_group_info: &ChunkGroupInfo) -> Result<String> {
290 Ok(match self {
291 ChunkGroup::Entry(entries) => format!(
292 "ChunkGroup::Entry({:?})",
293 entries
294 .iter()
295 .map(|m| m.ident().to_string())
296 .try_join()
297 .await?
298 ),
299 ChunkGroup::Async(entry) => turbofmt!("ChunkGroup::Async({:?})", entry.ident())
300 .await?
301 .to_string(),
302 ChunkGroup::Isolated(entry) => turbofmt!("ChunkGroup::Isolated({:?})", entry.ident())
303 .await?
304 .to_string(),
305 ChunkGroup::Shared(entry) => turbofmt!("ChunkGroup::Shared({:?})", entry.ident())
306 .await?
307 .to_string(),
308 ChunkGroup::SharedMultiple(entries) => format!(
309 "ChunkGroup::SharedMultiple({:?})",
310 entries
311 .iter()
312 .map(|m| m.ident().to_string())
313 .try_join()
314 .await?
315 ),
316 ChunkGroup::IsolatedMerged {
317 parent,
318 merge_tag,
319 entries,
320 } => {
321 format!(
322 "ChunkGroup::IsolatedMerged({}, {}, {:?})",
323 Box::pin(chunk_group_info.chunk_groups[*parent].debug_str(chunk_group_info))
324 .await?,
325 merge_tag,
326 entries
327 .iter()
328 .map(|m| m.ident().to_string())
329 .try_join()
330 .await?
331 )
332 }
333 ChunkGroup::SharedMerged {
334 parent,
335 merge_tag,
336 entries,
337 } => {
338 format!(
339 "ChunkGroup::SharedMerged({}, {}, {:?})",
340 Box::pin(chunk_group_info.chunk_groups[*parent].debug_str(chunk_group_info))
341 .await?,
342 merge_tag,
343 entries
344 .iter()
345 .map(|m| m.ident().to_string())
346 .try_join()
347 .await?
348 )
349 }
350 })
351 }
352}
353
354#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode, Decode)]
356pub enum ChunkGroupKey {
357 Entry(Vec<ResolvedVc<Box<dyn Module>>>),
358 Async(ResolvedVc<Box<dyn Module>>),
359 Isolated(ResolvedVc<Box<dyn Module>>),
360 IsolatedMerged {
361 parent: ChunkGroupId,
362 merge_tag: RcStr,
363 },
364 Shared(ResolvedVc<Box<dyn Module>>),
365 SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
366 SharedMerged {
367 parent: ChunkGroupId,
368 merge_tag: RcStr,
369 },
370}
371
372impl ChunkGroupKey {
373 pub async fn debug_str(
374 &self,
375 keys: impl std::ops::Index<usize, Output = Self>,
376 ) -> Result<String> {
377 Ok(match self {
378 ChunkGroupKey::Entry(entries) => format!(
379 "Entry({:?})",
380 entries
381 .iter()
382 .map(|m| m.ident().to_string())
383 .try_join()
384 .await?
385 ),
386 ChunkGroupKey::Async(module) => {
387 turbofmt!("Async({:?})", module.ident()).await?.to_string()
388 }
389 ChunkGroupKey::Isolated(module) => turbofmt!("Isolated({:?})", module.ident())
390 .await?
391 .to_string(),
392 ChunkGroupKey::IsolatedMerged { parent, merge_tag } => {
393 format!(
394 "IsolatedMerged {{ parent: {}, merge_tag: {:?} }}",
395 Box::pin(keys.index(parent.0 as usize).clone().debug_str(keys)).await?,
396 merge_tag
397 )
398 }
399 ChunkGroupKey::Shared(module) => {
400 turbofmt!("Shared({:?})", module.ident()).await?.to_string()
401 }
402 ChunkGroupKey::SharedMultiple(entries) => format!(
403 "SharedMultiple({:?})",
404 entries
405 .iter()
406 .map(|m| m.ident().to_string())
407 .try_join()
408 .await?
409 ),
410 ChunkGroupKey::SharedMerged { parent, merge_tag } => {
411 format!(
412 "SharedMerged {{ parent: {}, merge_tag: {:?} }}",
413 Box::pin(keys.index(parent.0 as usize).clone().debug_str(keys)).await?,
414 merge_tag
415 )
416 }
417 })
418 }
419}
420
421#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
422pub struct ChunkGroupId(u32);
423
424impl From<usize> for ChunkGroupId {
425 fn from(id: usize) -> Self {
426 Self(id as u32)
427 }
428}
429
430impl Deref for ChunkGroupId {
431 type Target = u32;
432 fn deref(&self) -> &Self::Target {
433 &self.0
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
438struct TraversalPriority {
439 depth: usize,
440 chunk_group_len: u64,
441}
442impl PartialOrd for TraversalPriority {
443 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
444 Some(self.cmp(other))
445 }
446}
447impl Ord for TraversalPriority {
448 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
449 let depth_order = self.depth.cmp(&other.depth).reverse();
453 let chunk_group_len_order = self.chunk_group_len.cmp(&other.chunk_group_len).reverse();
455
456 depth_order.then(chunk_group_len_order)
457 }
458}
459
460pub async fn compute_chunk_group_info(graph: &ModuleGraph) -> Result<Vc<ChunkGroupInfo>> {
461 let span_outer = tracing::info_span!(
462 "compute chunk group info",
463 module_count = tracing::field::Empty,
464 visit_count = tracing::field::Empty,
465 chunk_group_count = tracing::field::Empty
466 );
467
468 let span = span_outer.clone();
469 async move {
470 let mut chunk_groups_map: FxIndexMap<
471 ChunkGroupKey,
472 FxIndexSet<ResolvedVc<Box<dyn Module>>>,
473 > = FxIndexMap::default();
474
475 let mut module_chunk_groups: FxHashMap<ResolvedVc<Box<dyn Module>>, RoaringBitmapWrapper> =
478 FxHashMap::default();
479
480 let module_count = graph
481 .graphs
482 .iter()
483 .map(|g| g.graph.node_count())
484 .sum::<usize>();
485 span.record("module_count", module_count);
486
487 let entries = graph.all_chunk_group_entries().collect::<Vec<_>>();
489
490 let module_depth: FxHashMap<ResolvedVc<Box<dyn Module>>, usize> = {
492 let mut module_depth =
493 FxHashMap::with_capacity_and_hasher(module_count, Default::default());
494 graph.traverse_edges_bfs(
495 entries.iter().flat_map(|e| e.entries()),
496 |parent, node| {
497 if let Some((parent, _)) = parent {
498 let parent_depth = *module_depth
499 .get(&parent)
500 .context("Module depth not found")?;
501 module_depth.entry(node).or_insert(parent_depth + 1);
502 } else {
503 module_depth.insert(node, 0);
504 };
505
506 module_chunk_groups.insert(node, RoaringBitmapWrapper::default());
507
508 Ok(GraphTraversalAction::Continue)
509 },
510 )?;
511 module_depth
512 };
513
514 fn entry_to_chunk_group_id(
517 entry: ChunkGroupEntry,
518 chunk_groups_map: &mut FxIndexMap<
519 ChunkGroupKey,
520 FxIndexSet<ResolvedVc<Box<dyn Module>>>,
521 >,
522 ) -> ChunkGroupKey {
523 match entry {
524 ChunkGroupEntry::Entry { modules, .. } => ChunkGroupKey::Entry(modules),
525 ChunkGroupEntry::Async(entry) => ChunkGroupKey::Async(entry),
526 ChunkGroupEntry::Isolated(entry) => ChunkGroupKey::Isolated(entry),
527 ChunkGroupEntry::Shared(entry) => ChunkGroupKey::Shared(entry),
528 ChunkGroupEntry::SharedMultiple(entries) => ChunkGroupKey::SharedMultiple(entries),
529 ChunkGroupEntry::IsolatedMerged {
530 parent,
531 merge_tag,
532 entries: _,
533 } => {
534 let parent = entry_to_chunk_group_id(*parent, chunk_groups_map);
535 let parent_entry = chunk_groups_map.entry(parent);
536 let parent_id = parent_entry.index();
537 parent_entry.or_default();
538
539 ChunkGroupKey::IsolatedMerged {
540 parent: ChunkGroupId::from(parent_id),
541 merge_tag,
542 }
543 }
544 ChunkGroupEntry::SharedMerged {
545 parent,
546 merge_tag,
547 entries: _,
548 } => {
549 let parent = entry_to_chunk_group_id(*parent, chunk_groups_map);
550 let parent_entry = chunk_groups_map.entry(parent);
551 let parent_id = parent_entry.index();
552 parent_entry.or_default();
553
554 ChunkGroupKey::SharedMerged {
555 parent: ChunkGroupId::from(parent_id),
556 merge_tag,
557 }
558 }
559 }
560 }
561
562 let entry_chunk_group_keys = entries
563 .iter()
564 .flat_map(|&chunk_group| {
565 let chunk_group_key =
566 entry_to_chunk_group_id(chunk_group.clone(), &mut chunk_groups_map);
567 chunk_group
568 .entries()
569 .map(move |e| (e, chunk_group_key.clone()))
570 })
571 .collect::<FxHashMap<_, _>>();
572
573 let mut inherits_from: FxHashMap<u32, RoaringBitmap> = FxHashMap::default();
575
576 let visit_count = graph.traverse_edges_fixed_point_with_priority(
577 entries
578 .iter()
579 .flat_map(|e| e.entries())
580 .map(|e| {
581 Ok((
582 e,
583 TraversalPriority {
584 depth: *module_depth.get(&e).context("Module depth not found")?,
585 chunk_group_len: 0,
586 },
587 ))
588 })
589 .collect::<Result<Vec<_>>>()?,
590 &mut module_chunk_groups,
591 |parent_info: Option<(ResolvedVc<Box<dyn Module>>, &'_ RefData, _)>,
592 node: ResolvedVc<Box<dyn Module>>,
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 }
655 } else {
656 ChunkGroupInheritance::ChunkGroup(Either::Left(std::iter::once(
657 entry_chunk_group_keys
659 .get(&node)
660 .context("Module chunk group not found")?
661 .clone(),
662 )))
663 };
664
665 Ok(match chunk_groups {
666 ChunkGroupInheritance::ChunkGroup(chunk_groups) => {
667 let chunk_group_ids = chunk_groups.map(|chunk_group| {
669 let merged_parent = match &chunk_group {
671 ChunkGroupKey::IsolatedMerged { parent, .. }
672 | ChunkGroupKey::SharedMerged { parent, .. } => Some(parent.0),
673 _ => None,
674 };
675 let id = match chunk_groups_map.entry(chunk_group) {
676 Entry::Occupied(mut e) => {
677 let id = e.index() as u32;
678 if merged_parent.is_some() {
679 e.get_mut().insert(node);
680 }
681 id
682 }
683 Entry::Vacant(e) => {
684 let id = e.index() as u32;
685 let mut set = FxIndexSet::default();
686 if merged_parent.is_some() {
687 set.insert(node);
688 }
689 e.insert(set);
690 id
691 }
692 };
693 if let Some(parent) = merged_parent {
697 inherits_from.entry(parent).or_default().insert(id);
698 } else if let Some((parent_module, _, _)) = parent_info
699 && let Some(parent_groups) = module_chunk_groups.get(&parent_module)
700 {
701 for source in parent_groups.iter() {
702 inherits_from.entry(source).or_default().insert(id);
703 }
704 }
705 id
706 });
707
708 let chunk_groups =
709 RoaringBitmapWrapper(RoaringBitmap::from_iter(chunk_group_ids));
710
711 let bitset = module_chunk_groups
713 .get_mut(&node)
714 .context("Module chunk group not found")?;
715 if chunk_groups.is_proper_superset(bitset) {
716 **bitset |= chunk_groups.into_inner();
718
719 GraphTraversalAction::Continue
720 } else {
721 GraphTraversalAction::Skip
723 }
724 }
725 ChunkGroupInheritance::Inherit(parent) => {
726 if parent == node {
730 GraphTraversalAction::Skip
732 } else {
733 let [Some(parent_chunk_groups), Some(current_chunk_groups)] =
734 module_chunk_groups.get_disjoint_mut([&parent, &node])
735 else {
736 bail!("Module chunk groups not found");
740 };
741
742 if current_chunk_groups.is_empty() {
743 *current_chunk_groups = parent_chunk_groups.clone();
745 GraphTraversalAction::Continue
746 } else if parent_chunk_groups.is_proper_superset(current_chunk_groups) {
747 **current_chunk_groups |= &**parent_chunk_groups;
749 GraphTraversalAction::Continue
750 } else {
751 GraphTraversalAction::Skip
753 }
754 }
755 }
756 })
757 },
758 |successor, module_chunk_groups| {
766 Ok(TraversalPriority {
767 depth: *module_depth
768 .get(&successor)
769 .context("Module depth not found")?,
770 chunk_group_len: module_chunk_groups
771 .get(&successor)
772 .context("Module chunk group not found")?
773 .len(),
774 })
775 },
776 )?;
777
778 span.record("visit_count", visit_count);
779 span.record("chunk_group_count", chunk_groups_map.len());
780
781 static PRINT_CHUNK_GROUP_INFO: LazyLock<bool> =
782 LazyLock::new(|| match std::env::var_os("TURBOPACK_PRINT_CHUNK_GROUPS") {
783 Some(v) => v == "1",
784 None => false,
785 });
786 if *PRINT_CHUNK_GROUP_INFO {
787 use std::{
788 collections::{BTreeMap, BTreeSet},
789 path::absolute,
790 };
791
792 let mut buckets = BTreeMap::default();
793 for (module, key) in &module_chunk_groups {
794 if !key.is_empty() {
795 buckets
796 .entry(key.iter().collect::<Vec<_>>())
797 .or_insert(BTreeSet::new())
798 .insert(module.ident().to_string().await?);
799 }
800 }
801
802 let mut result = vec![];
803 result.push("Chunk Groups:".to_string());
804 for (i, (key, _)) in chunk_groups_map.iter().enumerate() {
805 result.push(format!(
806 " {:?}: {}",
807 i,
808 key.debug_str(chunk_groups_map.keys()).await?
809 ));
810 }
811 result.push("# Module buckets:".to_string());
812 for (key, modules) in buckets.iter() {
813 result.push(format!("## {:?}:", key.iter().collect::<Vec<_>>()));
814 for module in modules {
815 result.push(format!(" {module}"));
816 }
817 result.push("".to_string());
818 }
819 let f = absolute(format!("chunk_group_info_{}.log", visit_count))?;
820 println!("Wrote Chunk Group Info to {}", f.display());
821 std::fs::write(f, result.join("\n"))?;
822 }
823
824 let mut clusters: Vec<RoaringBitmap> = vec![RoaringBitmap::new(); chunk_groups_map.len()];
828 let mut priority_routes = RoaringBitmap::new();
829
830 let mut worklist: Vec<usize> = Vec::new();
831
832 for chunk_group in &entries {
833 let ChunkGroupEntry::Entry {
834 modules,
835 heuristics,
836 } = chunk_group
837 else {
838 continue;
839 };
840 if heuristics.clusters.is_empty() && !heuristics.high_priority {
841 continue;
842 }
843 if let Some(index) =
844 chunk_groups_map.get_index_of(&ChunkGroupKey::Entry(modules.clone()))
845 {
846 if clusters[index].is_empty() && !priority_routes.contains(index as u32) {
847 worklist.push(index);
848 }
849 clusters[index].extend(heuristics.clusters.iter().map(|&c| c as u32));
850 if heuristics.high_priority {
851 priority_routes.insert(index as u32);
852 }
853 }
854 }
855
856 while let Some(source) = worklist.pop() {
857 let source_priority_route = priority_routes.contains(source as u32);
858 let Some(targets) = inherits_from.get(&(source as u32)) else {
859 continue;
860 };
861 for target in targets.iter() {
862 let target = target as usize;
863 if target == source {
864 continue;
865 }
866 let [source_clusters, target_clusters] =
867 clusters.get_disjoint_mut([source, target]).unwrap();
868 let previous_target_clusters_len = target_clusters.len();
869 *target_clusters |= &*source_clusters;
870 let changed = (source_priority_route && priority_routes.insert(target as u32))
871 || previous_target_clusters_len != target_clusters.len();
872 if changed {
873 worklist.push(target);
874 }
875 }
876 }
877
878 let chunk_group_clusters: Vec<Vec<u16>> = clusters
879 .into_iter()
880 .map(|bm| bm.iter().map(|id| id as u16).collect())
881 .collect();
882 let chunk_group_priority_routes = RoaringBitmapWrapper(priority_routes);
883
884 Ok(ChunkGroupInfo {
885 module_chunk_groups: ResolvedVc::cell(module_chunk_groups),
886 chunk_group_keys: chunk_groups_map.keys().cloned().collect(),
887 chunking_heuristics: ChunkingHeuristicsInfo {
888 clusters: chunk_group_clusters,
889 priority_routes: chunk_group_priority_routes,
890 },
891 chunk_groups: chunk_groups_map
892 .into_iter()
893 .map(|(k, merged_entries)| match k {
894 ChunkGroupKey::Entry(entries) => ChunkGroup::Entry(entries),
895 ChunkGroupKey::Async(module) => ChunkGroup::Async(module),
896 ChunkGroupKey::Isolated(module) => ChunkGroup::Isolated(module),
897 ChunkGroupKey::IsolatedMerged { parent, merge_tag } => {
898 ChunkGroup::IsolatedMerged {
899 parent: parent.0 as usize,
900 merge_tag,
901 entries: merged_entries.into_iter().collect(),
902 }
903 }
904 ChunkGroupKey::Shared(module) => ChunkGroup::Shared(module),
905 ChunkGroupKey::SharedMultiple(entries) => ChunkGroup::SharedMultiple(entries),
906 ChunkGroupKey::SharedMerged { parent, merge_tag } => ChunkGroup::SharedMerged {
907 parent: parent.0 as usize,
908 merge_tag,
909 entries: merged_entries.into_iter().collect(),
910 },
911 })
912 .collect(),
913 }
914 .cell())
915 }
916 .instrument(span_outer)
917 .await
918}