fix(minifier): avoid stale pure function summaries#24636
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. |
Merging this PR will not alter performance
Comparing Footnotes
|
Monitor OxcCommit:
|
beaa818 to
303638a
Compare
Merge activity
|
Pure-function summaries trusted resolved read and write references, but redeclarations, direct `eval`, and classic Script globals can replace a binding without producing such a reference. A later call could therefore be folded according to an earlier empty declaration and lose runtime side effects.
## Example
```js
function foo() {}
eval("foo = () => side_effect()")
foo()
```
Bindings with redeclarations, bindings visible to direct eval, and classic Script-root bindings do not receive a reusable summary; the eval restriction is intentionally non-sticky so removing the final eval can enable the optimization in a later pass. Module and CommonJS bindings retain the existing fold.
The PR tip passed the full `oxc_minifier` suite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, and `just ready`.
## Stack
1. **#24636 — function-summary correctness**
2. #24637 — explicit symbol effect types
3. #24638 — grouped reference counts
4. #24639 — merged persistent metadata
5. #24640 — SymbolState facade
Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.
303638a to
061af1f
Compare
Pure-call summaries and member-write hazards encoded semantic states through nested `Option` values and independent bitflags. Reviewers had to reconstruct which combinations were valid and which implications producers must maintain. This introduces `FunctionSummary` for reusable call proofs and an ordered `MemberWriteEffect` for `None < Hazard < MayMutatePrototype`. Prototype mutation is therefore hazardous by construction, the removed bitflag dependency is no longer needed, and producers and consumers share named contracts. Behavior is unchanged relative to #24636. The PR tip passed the full `oxc_minifier` suite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, and `just ready`. ## Stack 1. #24636 — function-summary correctness 2. **#24637 — explicit symbol effect types** 3. #24638 — grouped reference counts 4. #24639 — merged persistent metadata 5. #24640 — SymbolState facade Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.
`SymbolValue` exposed three related counters directly, so every consumer had to coordinate read, write, and member-write-target invariants itself. This groups them in `ReferenceCounts` and exposes factual queries such as `has_single_read` and `has_only_member_write_target_reads`. The existing 48-byte `SymbolValue` layout is preserved, and the refactor does not change minified output. The PR tip passed the full `oxc_minifier` suite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, and `just ready`. ## Stack 1. #24636 — function-summary correctness 2. #24637 — explicit symbol effect types 3. **#24638 — grouped reference counts** 4. #24639 — merged persistent metadata 5. #24640 — SymbolState facade Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.
Pure-call summaries and member-write effects used separate sparse hash maps even though both are keyed by `SymbolId` and remain valid across peephole iterations. This stores both values in one `PersistentSymbolMetadata` entry. Summary replacement and monotone member-effect strengthening remain independent, and clearing a dynamic-binding summary updates an existing entry without allocating or discarding its member effect. The regenerated allocation snapshot records fewer system allocations: checker 155→152, pdf 2469→2467, antd 1044→1038, and kitchen-sink 1872→1867. `minsize.snap` remains byte-identical. The PR tip passed the full `oxc_minifier` suite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, and `just ready`. ## Stack 1. #24636 — function-summary correctness 2. #24637 — explicit symbol effect types 3. #24638 — grouped reference counts 4. **#24639 — merged persistent metadata** 5. #24640 — SymbolState facade Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.
`MinifierState` separately exposed dense per-pass values, sparse persistent metadata, and optional reachability state even though all three are indexed by semantic symbols. Its private-member stack also used symbol terminology despite storing names rather than `SymbolId`s. This introduces a `SymbolState` facade that documents the three storage lifecycles and forwards their operations while leaving the reachability algorithm in its dedicated module. The class-local tracker is renamed around private-member usage so it cannot be confused with semantic symbol state. The PR tip passed the full `oxc_minifier` suite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, and `just ready`. ## Stack 1. #24636 — function-summary correctness 2. #24637 — explicit symbol effect types 3. #24638 — grouped reference counts 4. #24639 — merged persistent metadata 5. **#24640 — SymbolState facade** Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.
### 💥 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]>

Pure-function summaries trusted resolved read and write references, but redeclarations, direct
eval, and classic Script globals can replace a binding without producing such a reference. A later call could therefore be folded according to an earlier empty declaration and lose runtime side effects.Example
Bindings with redeclarations, bindings visible to direct eval, and classic Script-root bindings do not receive a reusable summary; the eval restriction is intentionally non-sticky so removing the final eval can enable the optimization in a later pass. Module and CommonJS bindings retain the existing fold.
The PR tip passed the full
oxc_minifiersuite independently. The completed stack also passed clippy with warnings denied, formatting, minsize and allocation regeneration, andjust ready.Stack
Implemented with AI assistance (OpenAI Codex). The contributor remains responsible for reviewing, understanding, and submitting the changes under the repository AI usage policy.