diff --git a/crates/oxc_mangler/src/lib.rs b/crates/oxc_mangler/src/lib.rs index 9a6c78b2b19b2..f8e2f9f1e52db 100644 --- a/crates/oxc_mangler/src/lib.rs +++ b/crates/oxc_mangler/src/lib.rs @@ -318,10 +318,14 @@ impl<'t> Mangler<'t> { let mut slots = Vec::from_iter_in(iter::repeat_n(0, scoping.symbols_len()), temp_allocator); // Stores the lived scope ids for each slot. Keyed by slot number. - let mut slot_liveness: Vec = Vec::new_in(temp_allocator); + // Pre-allocate capacity based on number of symbols (upper bound for slots). + let mut slot_liveness: Vec = + Vec::with_capacity_in(scoping.symbols_len(), temp_allocator); let mut tmp_bindings = Vec::with_capacity_in(100, temp_allocator); let mut reusable_slots = Vec::new_in(temp_allocator); + // Pre-computed BitSet for ancestor membership tests - reused across iterations + let mut ancestor_set = BitSet::new_in(scoping.scopes_len(), temp_allocator); // Walk down the scope tree and assign a slot number for each symbol. // It is possible to do this in a loop over the symbol list, // but walking down the scope tree seems to generate a better code. @@ -372,6 +376,14 @@ impl<'t> Mangler<'t> { ); } + // Pre-compute the set of ancestors from root to scope_id (exclusive) for O(1) membership tests. + // This avoids repeated `take_while` comparisons in the inner loop. + ancestor_set.clear(); + for ancestor_id in scoping.scope_ancestors(scope_id).skip(1) { + ancestor_set.set_bit(ancestor_id.index()); + } + + let scope_id_index = scope_id.index(); for (&symbol_id, &assigned_slot) in tmp_bindings.iter().zip(&reusable_slots) { slots[symbol_id.index()] = assigned_slot; @@ -391,16 +403,22 @@ impl<'t> Mangler<'t> { .map(|reference| ast_nodes.get_node(reference.node_id()).scope_id()); // Calculate the scope ids that this symbol is alive in. - let lived_scope_ids = referenced_scope_ids + // For each used_scope_id, we walk up the ancestor chain and collect scopes + // that are descendants of scope_id (i.e., not in ancestor_set and not scope_id itself). + let slot_liveness_bitset = &mut slot_liveness[assigned_slot as usize]; + for used_scope_id in referenced_scope_ids .chain(redeclared_scope_ids) .chain([scope_id, declared_scope_id]) - .flat_map(|used_scope_id| { - scoping.scope_ancestors(used_scope_id).take_while(|s_id| *s_id != scope_id) - }); - - // Since the slot is now assigned to this symbol, it is alive in all the scopes that this symbol is alive in. - for scope_id in lived_scope_ids { - slot_liveness[assigned_slot as usize].set_bit(scope_id.index()); + { + for ancestor_id in scoping.scope_ancestors(used_scope_id) { + let ancestor_index = ancestor_id.index(); + // Stop when we reach scope_id or any of its ancestors + if ancestor_index == scope_id_index || ancestor_set.has_bit(ancestor_index) + { + break; + } + slot_liveness_bitset.set_bit(ancestor_index); + } } } } diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index 2a6df86e8551f..94eca5ee1ee0f 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -1,8 +1,8 @@ File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes ------------------------------------------------------------------------------------------------------------------------------------------- -checker.ts | 2.92 MB || 83503 | 14179 || 152592 | 28237 +checker.ts | 2.92 MB || 83502 | 14179 || 152592 | 28237 -cal.com.tsx | 1.06 MB || 40448 | 3032 || 37138 | 4586 +cal.com.tsx | 1.06 MB || 40447 | 3032 || 37138 | 4586 RadixUIAdoptionSection.jsx | 2.52 kB || 85 | 9 || 30 | 6 @@ -10,5 +10,5 @@ pdf.mjs | 567.30 kB || 20193 | 2899 | antd.js | 6.69 MB || 99534 | 13524 || 331649 | 69358 -binder.ts | 193.08 kB || 4760 | 974 || 7075 | 824 +binder.ts | 193.08 kB || 4759 | 974 || 7075 | 824