perf(semantic, allocator): branchless clone_in for semantic IDs#24564
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
337913f to
4ec41d3
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR optimizes CloneIn behavior around semantic IDs (NodeId, ScopeId, SymbolId, ReferenceId) by replacing the previous runtime bool with a CloneInSemanticIds flag whose discriminants enable cheap, branchless arithmetic when preserving vs resetting IDs during arena cloning.
Changes:
- Introduces
CloneInSemanticIdsand updatesCloneIn::clone_in_impl(and all implementors/callers) to use it instead ofbool. - Adds
oxc_syntax::semantic_id::SemanticIdto provide branchless cloning helpers for semantic ID types andCell<Option<Id>>fields. - Updates the
CloneInderive generator to support#[clone_in(semantic_id)]and to special-caseCell<Option<Id>>semantic ID fields viaSemanticId::clone_cell_option_id, then regenerates affectedderive_clone_in.rsoutputs.
Reviewed changes
Copilot reviewed 19 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tasks/ast_tools/src/schema/extensions/clone_in.rs | Extends schema metadata to mark semantic ID types for codegen decisions. |
| tasks/ast_tools/src/derives/clone_in.rs | Teaches the derive to parse #[clone_in(semantic_id)], threads CloneInSemanticIds, and emits specialized cloning for Cell<Option<Id>>. |
| crates/oxc_allocator/src/clone_in.rs | Defines CloneInSemanticIds, updates CloneIn API, and updates core impls (e.g., Option, Cell, primitives). |
| crates/oxc_allocator/src/lib.rs | Re-exports CloneInSemanticIds from oxc_allocator. |
| crates/oxc_allocator/src/bitset.rs | Updates BitSet’s CloneIn impl to accept CloneInSemanticIds. |
| crates/oxc_syntax/src/semantic_id.rs | Adds SemanticId trait with branchless clone_id and clone_cell_option_id helpers + tests. |
| crates/oxc_syntax/src/lib.rs | Exposes the new semantic_id module. |
| crates/oxc_syntax/src/node.rs | Marks NodeId as #[clone_in(semantic_id)] and delegates CloneIn to SemanticId::clone_id. |
| crates/oxc_syntax/src/scope.rs | Marks ScopeId as #[clone_in(semantic_id)] and delegates CloneIn to SemanticId::clone_id. |
| crates/oxc_syntax/src/symbol.rs | Marks SymbolId as #[clone_in(semantic_id)] and delegates CloneIn to SemanticId::clone_id. |
| crates/oxc_syntax/src/reference.rs | Marks ReferenceId as #[clone_in(semantic_id)] and delegates CloneIn to SemanticId::clone_id; updates other CloneIn impl signatures. |
| crates/oxc_syntax/src/generated/derive_clone_in.rs | Regenerated clone impls to accept CloneInSemanticIds. |
| crates/oxc_ast/src/generated/derive_clone_in.rs | Regenerated clone impls to accept CloneInSemanticIds and use SemanticId::clone_cell_option_id for semantic Cell<Option<Id>> fields. |
| crates/oxc_ast/src/ast/comment.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_ast/src/ast_impl/literal.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_span/src/span.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_span/src/source_type.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_str/src/str.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_str/src/ident.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
| crates/oxc_semantic/src/scoping.rs | Updates CloneIn impl signature and adapts logic to the new CloneInSemanticIds flag. |
| crates/oxc_regular_expression/src/generated/derive_clone_in.rs | Regenerated clone impls to accept CloneInSemanticIds. |
| crates/oxc_regular_expression/src/ast_impl/allocator.rs | Updates CloneIn impl signature to accept CloneInSemanticIds. |
6c3cc38 to
9bd7649
Compare
Merge activity
|
…4564) Follow-on after #24422. Make cloning semantic IDs (`NodeId`, `ScopeId` etc) use branchless arithmetic. * Cloning `NodeId` now just uses a single OR operation to take into account whether to preserve the existing ID (`clone_in_with_semantic_ids`) or substitute a dummy ID (`clone_in`). * Cloning `Cell<Option<ScopeId>>` is 2 instructions (NOT + AND) on x86_64, or just 1 on aarch64 (NOT-AND). https://godbolt.org/z/1KWdc5hnb The mechanism is a type `CloneInSemanticIds` which is used as `with_semantic_ids` instead of a `bool`: ```rs #[repr(u32)] pub enum CloneInSemanticIds { With = 0, Without = u32::MAX, } ``` This is 4 bytes, rather than `bool`'s 1 byte, but that makes no difference - either way it takes 1 register to pass between functions. The choice of discriminants is what enables the cheap arithmetic. This is even smaller in terms of binary size than the previous solution of marking `Cell::clone_in_impl` as `#[inline(never)]` (OR instruction is smaller than function call), and it's more performant as it loses the function call overhead. It also keeps `Cell`'s blanket implementation of `CloneIn` generalized. Between #24422 and this PR, merging `CloneIn` with/without semantic IDs into a single path has gained us a large binary size reduction, and lost us nothing - or may even have gained us a marginal perf improvement. No visible change on our benchmarks, as we don't use `CloneIn` much, but it may well be a measurable improvement for Rolldown, which clones whole ASTs.
9bd7649 to
ba65790
Compare
### 💥 BREAKING CHANGES - 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and `NewTarget` (#24557) (camc314) ### 🚀 Features - 4c71560 parser: More friendly error for spread element in dynamic imports (#24705) (sapphi-red) - 7b045cd minfier: Drop last break from last switch case (#24673) (Armano) - 7d3c178 minifier: Remove unreachable recursive functions (#24125) (Dunqing) - 94f99b3 ast: Allow `NONE` to be passed to AST builder methods where `Option<ArenaVec>` is expected (#24629) (overlookmotel) - 77230c5 ast: Accept arrays for `ArenaVec` params of AST builder methods (#24621) (overlookmotel) - f08b152 allocator: Implement `FromIn` for array to `Vec` conversion (#24620) (overlookmotel) - 2338c13 track-memory-allocations: Track heap deallocs, alloc bytes, and peak growth (#24619) (Boshen) - 7aa4739 syntax,transformer: Move JSX entity decoder to `oxc_syntax` (#24617) (camc314) - 2b097c4 str: Export `Str` as `ArenaStr` (#24604) (overlookmotel) - 3acf4c1 minifier: Expand switch optimiation to remove empty cases (#24520) (Armano) - 129b759 parser: Improve diagnostics for unparenthesized LHS on exponential expr (#24569) (camc314) - 4d0c601 minifier: Fold arithmetic over undefined and null operands (#24485) (Dunqing) - 91541dd minifier: Drop empty switch statements (#24527) (Armano) - d05224d ast_visit: Generate VisitJs visitor that skips TypeScript type-space (#24499) (Boshen) - 3d22307 parser: Add `ParseOptions::enable_ident_hashes` (#24491) (Boshen) ### 🐛 Bug Fixes - 64c2241 minifier: Align class heritage removal with assumptions (#24533) (Dunqing) - 48b59f4 parser: Span ambient generator diagnostics (#24711) (camc314) - e750a82 ecmascript: Fix false negative for may_have_side_effects on dynamic property access (#24709) (sapphi-red) - f145d73 minifier: Guard reordered identifier reads (#24698) (Dunqing) - a2ef382 isolated-declarations: Reject `window.Symbol` as global symbol reference (#24689) (camc314) - b1bcf72 minifier: Invalidate facts for redeclared bindings (#24658) (Dunqing) - 921b834 minifier: Don't treat a conditionally-assigned var as write-once (#24650) (Dunqing) - 061af1f minifier: Avoid stale pure function summaries (#24636) (Dunqing) - 40c2f43 allocator: `Vec::from_array_in` do not allocate zero-length array (#24628) (overlookmotel) - 70994ae codegen: Preserve comments before expression operands (#24510) (Dunqing) - 7b4baff parser: Reject new import member access (#23459) (camc314) - 128b385 minifier: Clippy warning with no-debug-assertions (#24547) (camc314) - 8421feb parser: Use first `as` span for imported name (#24537) (leaysgur) - c517aa0 parser: Reject invalid accessor assertions (#24504) (camc314) ### ⚡ Performance - 884d9eb parser: Pre-size cover-grammar assignment target buffers (#24683) (Boshen) - d3f07a0 diagnostics: Box OxcDiagnosticInner to reduce binary size (#24665) (Boshen) - bcc9de0 parser: Defer diagnostic creation until parse exit (#24663) (Boshen) - c35d8ab allocator: Mark `ReplaceWith` panic path cold (#24515) (camc314) - ba65790 semantic, allocator: Branchless `clone_in` for semantic IDs (#24564) (overlookmotel) - 747feec parser: Build AST nodes with the AST builder instead of cloning (#24540) (Boshen) - ba35a0d react_compiler: Use IndexVec for dense id-keyed maps (#24549) (Boshen) - b685062 react_compiler: Keep small hot-path collections inline (#24514) (Marius Schulz) - a149e95 transformer: Outline rare expression exits (#24512) (camc314) - 7808a6e react_compiler: Make aliasing effects cheap to intern and clone (#24506) (Marius Schulz) - 3a36f2a react_compiler: Store AbstractValue reasons as a u16 bitmask (#24480) (Boshen) - 1c96753 react_compiler: Use FxHashMap for the lookup-only aliasing node map (#24490) (Boshen) Co-authored-by: Cameron <[email protected]>

Follow-on after #24422.
Make cloning semantic IDs (
NodeId,ScopeIdetc) use branchless arithmetic.NodeIdnow just uses a single OR operation to take into account whether to preserve the existing ID (clone_in_with_semantic_ids) or substitute a dummy ID (clone_in).Cell<Option<ScopeId>>is 2 instructions (NOT + AND) on x86_64, or just 1 on aarch64 (NOT-AND).https://godbolt.org/z/1KWdc5hnb
The mechanism is a type
CloneInSemanticIdswhich is used aswith_semantic_idsinstead of abool:This is 4 bytes, rather than
bool's 1 byte, but that makes no difference - either way it takes 1 register to pass between functions. The choice of discriminants is what enables the cheap arithmetic.This is even smaller in terms of binary size than the previous solution of marking
Cell::clone_in_implas#[inline(never)](OR instruction is smaller than function call), and it's more performant as it loses the function call overhead. It also keepsCell's blanket implementation ofCloneIngeneralized.Between #24422 and this PR, merging
CloneInwith/without semantic IDs into a single path has gained us a large binary size reduction, and lost us nothing - or may even have gained us a marginal perf improvement.No visible change on our benchmarks, as we don't use
CloneInmuch, but it may well be a measurable improvement for Rolldown, which clones whole ASTs.