feat(minifier): track boolean state across branches for self-value DCE#22753
Closed
fazba wants to merge 10 commits into
Closed
feat(minifier): track boolean state across branches for self-value DCE#22753fazba wants to merge 10 commits into
fazba wants to merge 10 commits into
Conversation
Teach statement minimization to carry boolean symbol state across statements and fold `if (id)`/`if (!id)` when values are known. Make function-call invalidation precise by killing only symbols written by reachable function-declaration calls, and add regression tests for reachable, unreachable, and nested declaration scenarios. Co-authored-by: Cursor <cursoragent@cursor.com>
Conclude the in-progress merge from main into this feature branch and keep both upstream updates and local minifier-related changes. Co-authored-by: Cursor <cursoragent@cursor.com>
Drop snapshot temp files generated during test runs so the branch only keeps stable snapshot outputs. Co-authored-by: Cursor <cursoragent@cursor.com>
Merging this PR will degrade performance by 12.37%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Finalize the statement-side boolean update call cleanup and commit the updated minifier allocation snapshot required by CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid repeated reference scans when updating branch boolean state around call expressions. Gate boolean tracking work when statements cannot seed tracked symbols to reduce hot-path overhead. Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: fazba <32932149+fazba@users.noreply.github.com>
…lidation Replace two-phase mutation collection/removal with in-place retain when invalidating known boolean symbols for direct function-declaration calls, and add an early return for empty tracking maps to reduce hot-path overhead. Co-authored-by: Cursor <cursoragent@cursor.com>
This was referenced Jun 18, 2026
graphite-app Bot
pushed a commit
that referenced
this pull request
Jun 23, 2026
…#23540) ## What A `var` initialized to a falsy constant and never reassigned is always falsy. oxc already folds such a `let`/`const` everywhere, but a `var` is held back by a hoisting check (a read before the declarator runs sees the hoisted `undefined`), so a multi-read `var` flag past a non-trivial declarative prelude was never folded. In **boolean context** that check is unnecessary: a pre-init `var` read yields `undefined`, which is indistinguishable from the falsy init inside `if (x)` / `x ? … : …` / `!x`. So fold such reads to `false` there; the existing `if (false)` dead-code pass removes the branch. This is the bundled-flag shape behind this issue. Svelte's `var hydrating = false` is read by `if (hydrating)` throughout the runtime; once a client-only bundle tree-shakes the `set_hydrating` setter, `hydrating` is a write-once falsy `var` and the whole hydration path becomes dead and is eliminated. ## Why it is sound - `SymbolValue::boolean_falsy` is set only for a write-once binding with a falsy constant initializer. - It folds to `false` **only** in boolean context; value-context reads keep their hoisting-correct behavior (never folded here). - Gated off for a script's top-level `var` (a global another script can reassign, so an in-module write count of 0 doesn't prove write-once) and for `eval` scopes. ## Verification - `oxc_minifier` tests pass; idempotent. - On a real client-only Svelte (Vite/Rollup) bundle, every `if (hydrating)` check is eliminated. - `minsize`: react 23.14 → 23.12 kB, lodash 70.96 → 70.92 kB (gzip down too). `allocs_minifier.snap`: +20 sys allocs on antd (from evaluating `var` initializers; arena allocs unchanged). - Cross-validated for soundness: on the Svelte bundle, terser `--module` and esbuild `--format=esm` independently fold the same write-once flag (16 → 0), agreeing with oxc. On a Vue 3 client bundle the runtime's flags are multi-write (e.g. `isInSSRComponentSetup` has two assignments, so it can be truthy) and oxc correctly leaves them untouched — the write-once gate is the right line. ## Scope (partial fix) This handles the real-world **bundled** shape — a write-once falsy `var` flag read in boolean context (the Svelte/Vue hydration case, where the bundler has already tree-shaken the setter). It does **not** fold the issue's literal source snippet: ```js let foo = false; function bar(val) { foo = val; } if (foo) { bar(true) } console.log(foo); ``` There `foo` is still reassigned via `bar` (so it isn't write-once), and proving the write dead requires self-referential / flow-sensitive analysis — out of scope here (see #22753 for that direction). So this is a partial step. Refs #14001. Prepared with AI assistance.
Member
|
Thank you for contributing! Closing this as we have adopted #23540 to solve that issue. |
camc314
pushed a commit
that referenced
this pull request
Jul 3, 2026
…#23540) ## What A `var` initialized to a falsy constant and never reassigned is always falsy. oxc already folds such a `let`/`const` everywhere, but a `var` is held back by a hoisting check (a read before the declarator runs sees the hoisted `undefined`), so a multi-read `var` flag past a non-trivial declarative prelude was never folded. In **boolean context** that check is unnecessary: a pre-init `var` read yields `undefined`, which is indistinguishable from the falsy init inside `if (x)` / `x ? … : …` / `!x`. So fold such reads to `false` there; the existing `if (false)` dead-code pass removes the branch. This is the bundled-flag shape behind this issue. Svelte's `var hydrating = false` is read by `if (hydrating)` throughout the runtime; once a client-only bundle tree-shakes the `set_hydrating` setter, `hydrating` is a write-once falsy `var` and the whole hydration path becomes dead and is eliminated. ## Why it is sound - `SymbolValue::boolean_falsy` is set only for a write-once binding with a falsy constant initializer. - It folds to `false` **only** in boolean context; value-context reads keep their hoisting-correct behavior (never folded here). - Gated off for a script's top-level `var` (a global another script can reassign, so an in-module write count of 0 doesn't prove write-once) and for `eval` scopes. ## Verification - `oxc_minifier` tests pass; idempotent. - On a real client-only Svelte (Vite/Rollup) bundle, every `if (hydrating)` check is eliminated. - `minsize`: react 23.14 → 23.12 kB, lodash 70.96 → 70.92 kB (gzip down too). `allocs_minifier.snap`: +20 sys allocs on antd (from evaluating `var` initializers; arena allocs unchanged). - Cross-validated for soundness: on the Svelte bundle, terser `--module` and esbuild `--format=esm` independently fold the same write-once flag (16 → 0), agreeing with oxc. On a Vue 3 client bundle the runtime's flags are multi-write (e.g. `isInSSRComponentSetup` has two assignments, so it can be truthy) and oxc correctly leaves them untouched — the write-once gate is the right line. ## Scope (partial fix) This handles the real-world **bundled** shape — a write-once falsy `var` flag read in boolean context (the Svelte/Vue hydration case, where the bundler has already tree-shaken the setter). It does **not** fold the issue's literal source snippet: ```js let foo = false; function bar(val) { foo = val; } if (foo) { bar(true) } console.log(foo); ``` There `foo` is still reassigned via `bar` (so it isn't write-once), and proving the write dead requires self-referential / flow-sensitive analysis — out of scope here (see #22753 for that direction). So this is a partial step. Refs #14001. Prepared with AI assistance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR improves minifier DCE for cases where code behavior depends on a symbol's own tracked value across statements/branches.
if (id)/if (!id)when possible.Closes #14001.
Test plan
cargo test -p oxc_minifier dce_if_statement_symbol_tracking_with_function_callscargo test -p oxc_minifier dce_if_statementcargo test -p oxc_minifier remove_constant_value