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