Skip to content

perf(diagnostics): box OxcDiagnosticInner to reduce binary size#24665

Merged
graphite-app[bot] merged 1 commit into
mainfrom
perf/diagnostics-box-inner
Jul 19, 2026
Merged

perf(diagnostics): box OxcDiagnosticInner to reduce binary size#24665
graphite-app[bot] merged 1 commit into
mainfrom
perf/diagnostics-box-inner

Conversation

@Boshen

@Boshen Boshen commented Jul 19, 2026

Copy link
Copy Markdown
Member

OxcDiagnostic is an unboxed ~300-byte struct constructed at thousands of callsites. Boxing its inner data behind an 8-byte handle means every callsite, every Result<T, OxcDiagnostic>, and every move copies 8 bytes instead of ~300, so the compiler emits far less inlined construction/copy code.

Binary size (stripped, real release builds, aarch64-apple-darwin)

Binary Before After Delta
oxlint 14,177,616 B 13,665,680 B −499.9 KiB (−3.61%)
napi transform (.node) 3,482,624 B 3,433,040 B −48.4 KiB (−1.42%)

Most of the win lands in oxc_linter, whose ~1,500 diagnostic-construction sites inline into rule bodies; oxc_react_compiler also contributes.

The error/warn constructors delegate to an outlined #[inline(never)] fn new, so the Box allocation + field initialization exist once in the binary rather than inlined at every construction site.

This reverses #22406, which unboxed the inner to save a heap allocation. That is now safe: the parser no longer constructs diagnostics on its hot path (#24663).

Supersedes the draft #24656, which additionally carried a now-redundant copy of the parser deferral that has since landed via #24663.

@Boshen
Boshen requested a review from camc314 as a code owner July 19, 2026 11:32
@github-actions github-actions Bot added A-linter Area - Linter A-cli Area - CLI labels Jul 19, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 19, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 5.31%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 6 improved benchmarks
✅ 61 untouched benchmarks
⏩ 9 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation react_compiler[RadixUIAdoptionSection.jsx] 2.6 ms 2.5 ms +7.76%
Simulation linter[RadixUIAdoptionSection.jsx] 3.2 ms 3 ms +7.11%
Simulation linter[react.development.js] 13.6 ms 12.8 ms +6.11%
Simulation linter[binder.ts] 26.9 ms 25.8 ms +4.32%
Simulation linter[App.tsx] 67.6 ms 65.3 ms +3.61%
Simulation transformer[react.development.js] 706.3 µs 685.3 µs +3.06%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing perf/diagnostics-box-inner (9132e09) with main (bcc9de0)

Open in CodSpeed

Footnotes

  1. 9 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Jul 19, 2026

Boshen commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

Merge activity

`OxcDiagnostic` is an unboxed ~300-byte struct constructed at thousands of callsites. Boxing its inner data behind an 8-byte handle means every callsite, every `Result<T, OxcDiagnostic>`, and every move copies 8 bytes instead of ~300, so the compiler emits far less inlined construction/copy code.

## Binary size (stripped, real release builds, aarch64-apple-darwin)

| Binary | Before | After | Delta |
|---|---:|---:|---:|
| `oxlint` | 14,177,616 B | 13,665,680 B | **−499.9 KiB (−3.61%)** |
| napi transform (`.node`) | 3,482,624 B | 3,433,040 B | **−48.4 KiB (−1.42%)** |

Most of the win lands in `oxc_linter`, whose ~1,500 diagnostic-construction sites inline into rule bodies; `oxc_react_compiler` also contributes.

The `error`/`warn` constructors delegate to an outlined `#[inline(never)] fn new`, so the `Box` allocation + field initialization exist once in the binary rather than inlined at every construction site.

This reverses #22406, which unboxed the inner to save a heap allocation. That is now safe: the parser no longer constructs diagnostics on its hot path (#24663).

Supersedes the draft #24656, which additionally carried a now-redundant copy of the parser deferral that has since landed via #24663.
@graphite-app
graphite-app Bot force-pushed the perf/diagnostics-box-inner branch from 9132e09 to d3f07a0 Compare July 19, 2026 11:54
@graphite-app
graphite-app Bot merged commit d3f07a0 into main Jul 19, 2026
29 checks passed
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Jul 19, 2026
@graphite-app
graphite-app Bot deleted the perf/diagnostics-box-inner branch July 19, 2026 11:58
camc314 added a commit that referenced this pull request Jul 21, 2026
### 💥 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]>
camc314 added a commit that referenced this pull request Jul 21, 2026
# Oxlint
### 💥 BREAKING CHANGES

- 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and
`NewTarget` (#24557) (camc314)

### 🚀 Features

- 7b045cd minfier: Drop last break from last switch case (#24673)
(Armano)
- dd18383 linter/node: Implement no-top-level-await rule (#24634)
(Connor Shea)
- 16a65f2 linter/react: Implement function-component-definition rule
(#24471) (Cole Ellison)
- 7f1f585 linter: Reuse `jest/padding-around-test-blocks` for
`vitest/padding-around-test-blocks` (#24519) (Mikhail Baev)
- 99978a8 linter/import/consistent-type-specifier-style: Support
`prefer-top-level-if-only-type-imports` option (#24502) (camc314)

### 🐛 Bug Fixes

- 0184ad6 linter/unicorn/no-useless-undefined: Preserve valid parameter
defaults (#24686) (camc314)
- 8694167 linter/eslint/prefer-destructuring: Handle typed declarations
(#24616) (camc314)
- 477cf0f linter/eslint/no-throw-literal: Handle assigned errors
(#24561) (Cole Ellison)
- ac9200a linter: Detect React components from returned JSX (#24521)
(camc314)
- c0a6522 linter/eslint/no-useless-computed-key: Allow TS syntax in
computed keys (#24524) (Cole Ellison)

### ⚡ Performance

- 346eed1 linter/unicorn/prefer-event-target: Only run on `Class` and
`NewExpression` nodes (#24685) (Mikhail Baev)
- 7be5cf0 oxlint/lsp: Only invoke lint on code actions when document is
not opened (#24676) (Sysix)
- d3f07a0 diagnostics: Box OxcDiagnosticInner to reduce binary size
(#24665) (Boshen)
- 90ae040 linter/reporter/stylish: Compute diagnostic Info once per
diagnostic (#24525) (connorshea)

### 📚 Documentation

- e6f7174 linter/valid-expect: Fix correct example being identical to
incorrect one (#24468) (mkan0141)
# Oxfmt
### 💥 BREAKING CHANGES

- 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and
`NewTarget` (#24557) (camc314)

### 🚀 Features

- 3d22307 parser: Add `ParseOptions::enable_ident_hashes` (#24491)
(Boshen)

### 🐛 Bug Fixes

- 6fe866a oxfmt: Keep tailwind classes glued to template expr with
`preserveWhitespace` (#24609) (leaysgur)
- 33e32d8 formatter_css: Use `line_suffix` for EOL line comment (#24580)
(leaysgur)
- 5f76998 formatter_graphql: Keep same line comments pending across
intervening tokens (#24579) (leaysgur)

Co-authored-by: Boshen <[email protected]>
Co-authored-by: Cameron <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant