mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(mangler): exclude non-manglable symbols from slot assignment (#25539)
Slots were assigned to non-manglable symbols as well. This made the slot re-use non-optimal. This PR fixes that. I used AI to find out this bug and to generate the code and then reviewed the code.
This commit is contained in:
3 files changed
+39
-29
No files matched your search
@@ -557,6 +557,19 @@ impl<'a, 's> Constraints<'a, 's> {
|
||||
fn is_kept_name(&self, name: &str) -> bool {
|
||||
is_special_name(name) || (!self.reserved.is_empty() && self.reserved.contains(name))
|
||||
}
|
||||
|
||||
/// Whether `symbol_id` will receive a mangled name.
|
||||
#[inline]
|
||||
fn is_mangle_candidate(&self, symbol_id: SymbolId, scoping: &Scoping) -> bool {
|
||||
let scope_id = scoping.symbol_scope_id(symbol_id);
|
||||
|
||||
!(scope_id == scoping.root_scope_id()
|
||||
&& (!self.top_level
|
||||
|| self.exported_symbols.as_ref().is_some_and(|e| e.has_bit(symbol_id.index())))
|
||||
|| scoping.scope_flags(scope_id).contains_direct_eval()
|
||||
|| self.is_kept_name(scoping.symbol_name(symbol_id))
|
||||
|| self.keep_name_symbols.as_ref().is_some_and(|keep| keep.has_bit(symbol_id.index())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 's> SlotAssignment<'a, 's> {
|
||||
@@ -574,7 +587,6 @@ impl<'a, 's> SlotAssignment<'a, 's> {
|
||||
ast_nodes: &AstNodes,
|
||||
constraints: &Constraints,
|
||||
) -> Self {
|
||||
let keep_name_symbols = constraints.keep_name_symbols.as_ref();
|
||||
// Names of bindings in direct-`eval` scopes — collected here, reserved in Phase 4.
|
||||
// TODO: eval reservation is conservative — ideally we'd reserve names per-slot.
|
||||
let mut eval_reserved_names: FxHashSet<&'s str> = FxHashSet::default();
|
||||
@@ -610,9 +622,12 @@ impl<'a, 's> SlotAssignment<'a, 's> {
|
||||
|
||||
// Sort `bindings` in declaration order.
|
||||
tmp_bindings.clear();
|
||||
tmp_bindings.extend(bindings.values().copied().filter(|binding| {
|
||||
!keep_name_symbols.is_some_and(|keep| keep.has_bit(binding.index()))
|
||||
}));
|
||||
tmp_bindings.extend(
|
||||
bindings
|
||||
.values()
|
||||
.copied()
|
||||
.filter(|&binding| constraints.is_mangle_candidate(binding, scoping)),
|
||||
);
|
||||
if tmp_bindings.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@@ -715,6 +730,7 @@ impl<'a, 's> SlotAssignment<'a, 's> {
|
||||
&& let Some(id) = &func.id
|
||||
&& let Some(&shadower) = bindings.get(&id.name)
|
||||
&& shadower != id.symbol_id()
|
||||
&& constraints.is_mangle_candidate(id.symbol_id(), scoping)
|
||||
&& slots[shadower.index()] != SLOT_UNASSIGNED
|
||||
{
|
||||
slots[id.symbol_id().index()] = slots[shadower.index()];
|
||||
@@ -727,17 +743,13 @@ impl<'a, 's> SlotAssignment<'a, 's> {
|
||||
}
|
||||
|
||||
impl<'a> SlotRanking<'a> {
|
||||
/// Phase 3: count references per slot and sort hottest-first, skipping slots whose only
|
||||
/// symbols are kept, exported (at top level), eval-visible, or special (`arguments`).
|
||||
/// Phase 3: count references per candidate slot and sort hottest-first.
|
||||
fn tally(
|
||||
allocator: &'a Allocator,
|
||||
scoping: &Scoping,
|
||||
constraints: &Constraints,
|
||||
slots: &SlotAssignment,
|
||||
) -> Self {
|
||||
let exported_symbols = constraints.exported_symbols.as_ref();
|
||||
let keep_name_symbols = constraints.keep_name_symbols.as_ref();
|
||||
let root_scope_id = scoping.root_scope_id();
|
||||
let mut frequencies = ArenaVec::from_iter_in(
|
||||
repeat_with(|| SlotFrequency::new(allocator)).take(slots.total_slots),
|
||||
&allocator,
|
||||
@@ -748,20 +760,7 @@ impl<'a> SlotRanking<'a> {
|
||||
continue;
|
||||
}
|
||||
let symbol_id = SymbolId::from_usize(symbol_id);
|
||||
let symbol_scope_id = scoping.symbol_scope_id(symbol_id);
|
||||
if symbol_scope_id == root_scope_id
|
||||
&& (!constraints.top_level
|
||||
|| exported_symbols.is_some_and(|e| e.has_bit(symbol_id.index())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if scoping.scope_flags(symbol_scope_id).contains_direct_eval() {
|
||||
continue;
|
||||
}
|
||||
if constraints.is_kept_name(scoping.symbol_name(symbol_id)) {
|
||||
continue;
|
||||
}
|
||||
if keep_name_symbols.is_some_and(|keep| keep.has_bit(symbol_id.index())) {
|
||||
if !constraints.is_mangle_candidate(symbol_id, scoping) {
|
||||
continue;
|
||||
}
|
||||
let index = slot as usize;
|
||||
|
||||
@@ -339,8 +339,8 @@ fn reserved_names() {
|
||||
? t(exports)
|
||||
: t((e.lib = {}));
|
||||
})(this, function (exports) {
|
||||
function t(e) { return e; }
|
||||
exports.queue = t;
|
||||
function e(e) { return e; }
|
||||
exports.queue = e;
|
||||
});",
|
||||
&options,
|
||||
);
|
||||
@@ -353,9 +353,9 @@ fn reserved_names() {
|
||||
exports.other = helper;
|
||||
})(m, m.exports);",
|
||||
"(function (module, exports) {
|
||||
function t(e) { return e; }
|
||||
module.exports.helper = t;
|
||||
exports.other = t;
|
||||
function e(e) { return e; }
|
||||
module.exports.helper = e;
|
||||
exports.other = e;
|
||||
})(m, m.exports);",
|
||||
&options,
|
||||
);
|
||||
@@ -375,6 +375,17 @@ fn reserved_names() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_mangleable_symbols_do_not_consume_slots() {
|
||||
let options = MangleOptions { debug: true, ..MangleOptions::default() };
|
||||
|
||||
test(
|
||||
"var A, B; { use(A); let x; } { use(B); let y; }",
|
||||
"var A, B; { use(A); let slot_0; } { use(B); let slot_0; }",
|
||||
&options,
|
||||
);
|
||||
}
|
||||
|
||||
/// Annex B.3.2.1: In sloppy mode, function declarations inside blocks have var-like hoisting.
|
||||
/// The mangler must not assign the same name to such a function and an outer `var` binding.
|
||||
#[test]
|
||||
|
||||
@@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
6.69 MB | 2.12 MB | 2.31 MB | 453.79 kB | 488.28 kB | 5 | antd.js
|
||||
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 852.64 kB | 915.50 kB | 4 | typescript.js
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 852.09 kB | 915.50 kB | 4 | typescript.js
|
||||
|
||||
Reference in new issue
Block a user