perf(mangler): share allocated names across reused slots (#25605)

Avoid duplicate semantic-arena allocations when multiple symbols share one mangler slot.

Previously, Phase 5 renamed every symbol in a shared slot individually. Each call to `Scoping::set_symbol_name` cloned the same generated `Ident` into the semantic arena, so a slot shared by `N` symbols made `N` allocations and copied the name `N` times.

This adds `Scoping::set_symbol_names`, which clones the generated name once into the semantic arena and copies the immutable `Ident` handle to every symbol in that slot.

The name must still be cloned once: generated names are backed by the mangler’s temporary allocator, which may be reset after mangling. Sharing that temporary backing would be unsound.

## Before / after

For a slot containing `N` symbols and a mangled name of length `L`:

| | Arena allocations | Copied bytes |
|---|---:|---:|
| Before | `N` | `N × L` |
| After | `1` | `L` |

| Benchmark | Old: one per symbol | New: one per slot | Saved |
|---|---:|---:|---:|
| `RadixUIAdoptionSection.jsx` | 12 | 12 | 0 |
| `react.development.js` | 470 | 143 | 327 |
| `App.tsx` | 1,791 | 426 | 1,365 |
| `binder.ts` | 930 | 374 | 556 |
| `kitchen-sink.tsx` | 5,422 | 1,058 | 4,364 |
| `RadixUIAdoptionSection.jsx_keep_names` | 12 | 12 | 0 |
This commit is contained in:
camc314 committed 2026-08-14 00:11:28 +00:00
1 parent 50201a465e
commit 757f3d4113
2 files changed
+18 -3

No files matched your search

+2 -3
View File
@@ -853,9 +853,8 @@ impl<'a, const CAPACITY: usize> NameTable<'a, CAPACITY> {
symbols_renamed_in_this_batch.iter().zip(slice_of_same_len_strings.iter())
{
// A slot can be shared by several symbols (cross-scope reuse); rename them all.
for &symbol_id in &symbol_to_rename.symbol_ids {
scoping.set_symbol_name(symbol_id, Ident::from(new_name.as_str()));
}
scoping
.set_symbol_names(&symbol_to_rename.symbol_ids, Ident::from(new_name.as_str()));
}
}
}
+16
View File
@@ -374,6 +374,22 @@ impl Scoping {
});
}
/// Rename multiple symbols to the same name.
///
/// The name is allocated once in the semantic arena and shared between all symbols.
#[inline]
pub fn set_symbol_names(&mut self, symbol_ids: &[SymbolId], name: Ident<'_>) {
if symbol_ids.is_empty() {
return;
}
self.cell.with_dependent_mut(|allocator, cell| {
let name = name.clone_in(allocator);
for &symbol_id in symbol_ids {
cell.symbol_names[symbol_id.index()] = name;
}
});
}
/// Get the [`SymbolFlags`] for a symbol, which describe how the symbol is declared.
///
/// To find how a symbol is used, use [`Scoping::get_resolved_references`].