Skip to content

refactor(ecma_ast): tighten allocator access to enforce Sync invariant#9278

Merged
graphite-app[bot] merged 1 commit into
mainfrom
04-30-ecmaast_safety_guard
May 5, 2026
Merged

refactor(ecma_ast): tighten allocator access to enforce Sync invariant#9278
graphite-app[bot] merged 1 commit into
mainfrom
04-30-ecmaast_safety_guard

Conversation

@IWANABETHATGUY

@IWANABETHATGUY IWANABETHATGUY commented Apr 30, 2026

Copy link
Copy Markdown
Member

Stacked on #9276.

Closed #9242

Replaces EcmaAst::allocator(&self) -> &Allocator with with_fields(&mut self, |fields| ...), a closure-based accessor that bundles read-only access to source/source_type/allocator/comments/program. The &mut self receiver makes the soundness invariant of unsafe impl Sync for EcmaAst locally checkable.

Why

oxc::allocator::Allocator is !Sync (it holds Cell<NonNull<u8>> and Cell<Option<NonNull<ChunkFooter>>> for bumpalo''s bump pointer). The unsafe impl Sync for EcmaAst {} is sound only by convention: &EcmaAst is treated as read-only at runtime. But pub fn allocator(&self) -> &Allocator punched a hole in that — anyone with a shared &EcmaAst (e.g., a rayon par_iter worker) could call ast.allocator() and then Allocator::alloc (which mutates internal Cells through &self), racing with another worker.

Sync cannot be removed — it''s required by parallel chunk rendering in render_chunk_to_assets.rs and indirectly by anyhow::Error::new(SendError<ModuleLoaderMsg>) in file_emitter.rs. So the goal is to make the unsoundness statically harder to reach.

Changes

  • EcmaAst::with_fields(&mut self, |fields| ...) is the only public way to obtain &Allocator. Built on the existing ProgramCell::with_mut, reborrowed &mut Program → &Program.
  • pub fn allocator(&self) removed. The 3 callers all migrate cleanly:
    • clone_with_another_arena reads self.program.borrow_owner().allocator.used_bytes() directly.
    • ecma_module_view_factory::create_ecma_view wraps the AstScanner::new + scanner.scan block in ast.with_fields(...).
    • runtime_module_task::make_ecma_ast does the same.
  • unsafe impl Send for EcmaAst {} and unsafe impl Sync for EcmaAst {} now have // SAFETY: blocks spelling out the invariant and naming the call sites that depend on Sync.

Verification

  • cargo check --workspace --all-targets clean.
  • cargo clippy --workspace --all-targets -- --deny warnings clean.
  • cargo test -p rolldown --test integration -- --skip ''hmr'' — all 1666 non-HMR integration tests pass.
  • rg -n ''ast\.allocator\(\)'' crates/ — zero results.

@IWANABETHATGUY
IWANABETHATGUY marked this pull request as draft April 30, 2026 09:13
@codspeed-hq

codspeed-hq Bot commented Apr 30, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing 04-30-ecmaast_safety_guard (b16882e) with main (45adf2b)

Open in CodSpeed

Footnotes

  1. 10 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.

IWANABETHATGUY commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add the label graphite: merge-when-ready to this PR to add it to 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.

@IWANABETHATGUY
IWANABETHATGUY force-pushed the 04-30-remove_stmt_infos_from_ecmaview branch from 5f96fd2 to b01d243 Compare April 30, 2026 09:20
@IWANABETHATGUY
IWANABETHATGUY force-pushed the 04-30-ecmaast_safety_guard branch 2 times, most recently from 4b699e1 to f8421ea Compare April 30, 2026 10:24
@IWANABETHATGUY
IWANABETHATGUY marked this pull request as ready for review April 30, 2026 10:43
@graphite-app
graphite-app Bot changed the base branch from 04-30-remove_stmt_infos_from_ecmaview to graphite-base/9278 May 5, 2026 06:00
@graphite-app
graphite-app Bot force-pushed the graphite-base/9278 branch from b01d243 to 45adf2b Compare May 5, 2026 06:04
@graphite-app
graphite-app Bot force-pushed the 04-30-ecmaast_safety_guard branch from f8421ea to ac7c73a Compare May 5, 2026 06:04
@graphite-app
graphite-app Bot changed the base branch from graphite-base/9278 to main May 5, 2026 06:05
@graphite-app
graphite-app Bot force-pushed the 04-30-ecmaast_safety_guard branch from ac7c73a to b16882e Compare May 5, 2026 06:05
@netlify

netlify Bot commented May 5, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit dd5fe11
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69f9bae7498d6100079ca697

IWANABETHATGUY commented May 5, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • May 5, 9:35 AM UTC: The merge label 'graphite: merge-when-ready' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • May 5, 9:39 AM UTC: IWANABETHATGUY added this pull request to the Graphite merge queue.
  • May 5, 9:44 AM UTC: Merged by the Graphite merge queue.

#9278)

Stacked on #9276.

Closed #9242

Replaces `EcmaAst::allocator(&self) -> &Allocator` with `with_fields(&mut self, |fields| ...)`, a closure-based accessor that bundles read-only access to `source`/`source_type`/`allocator`/`comments`/`program`. The `&mut self` receiver makes the soundness invariant of `unsafe impl Sync for EcmaAst` locally checkable.

## Why

`oxc::allocator::Allocator` is `!Sync` (it holds `Cell<NonNull<u8>>` and `Cell<Option<NonNull<ChunkFooter>>>` for bumpalo''s bump pointer). The `unsafe impl Sync for EcmaAst {}` is sound only by convention: `&EcmaAst` is treated as read-only at runtime. But `pub fn allocator(&self) -> &Allocator` punched a hole in that — anyone with a shared `&EcmaAst` (e.g., a rayon `par_iter` worker) could call `ast.allocator()` and then `Allocator::alloc` (which mutates internal `Cell`s through `&self`), racing with another worker.

`Sync` cannot be removed — it''s required by parallel chunk rendering in `render_chunk_to_assets.rs` and indirectly by `anyhow::Error::new(SendError<ModuleLoaderMsg>)` in `file_emitter.rs`. So the goal is to make the unsoundness statically harder to reach.

## Changes

- `EcmaAst::with_fields(&mut self, |fields| ...)` is the only public way to obtain `&Allocator`. Built on the existing `ProgramCell::with_mut`, reborrowed `&mut Program → &Program`.
- `pub fn allocator(&self)` removed. The 3 callers all migrate cleanly:
  - `clone_with_another_arena` reads `self.program.borrow_owner().allocator.used_bytes()` directly.
  - `ecma_module_view_factory::create_ecma_view` wraps the `AstScanner::new` + `scanner.scan` block in `ast.with_fields(...)`.
  - `runtime_module_task::make_ecma_ast` does the same.
- `unsafe impl Send for EcmaAst {}` and `unsafe impl Sync for EcmaAst {}` now have `// SAFETY:` blocks spelling out the invariant and naming the call sites that depend on `Sync`.

## Verification

- `cargo check --workspace --all-targets` clean.
- `cargo clippy --workspace --all-targets -- --deny warnings` clean.
- `cargo test -p rolldown --test integration -- --skip ''hmr''` — all 1666 non-HMR integration tests pass.
- `rg -n ''ast\.allocator\(\)'' crates/` — zero results.
@graphite-app
graphite-app Bot force-pushed the 04-30-ecmaast_safety_guard branch from 98752a7 to dd5fe11 Compare May 5, 2026 09:39
@graphite-app
graphite-app Bot merged commit dd5fe11 into main May 5, 2026
33 checks passed
@graphite-app
graphite-app Bot deleted the 04-30-ecmaast_safety_guard branch May 5, 2026 09:44
This was referenced May 6, 2026
@shulaoda shulaoda mentioned this pull request May 7, 2026
shulaoda added a commit that referenced this pull request May 7, 2026
## [1.0.0] - 2026-05-07

### 🐛 Bug Fixes

- dev/lazy: lazily compiled modules should be watched (#9301) by @h-a-n-a
- implement dynamic dominator merge logic (#9270) by @TheAlexLichter
- dev: apply __toCommonJS interop when CJS requires ESM in HMR finalizer (#9261) by @h-a-n-a

### 🚜 Refactor

- ecma_ast: tighten allocator access to enforce Sync invariant (#9278) by @IWANABETHATGUY
- scan_stage: remove stmt_infos field from EcmaView (#9276) by @IWANABETHATGUY
- link_stage: detach stmt_infos from EcmaView (#9274) by @IWANABETHATGUY
- link_stage: detach depended_runtime_helper from EcmaView to remove unsafe (#9265) by @IWANABETHATGUY
- link_stage: remove unsafe in determine_module_exports_kind (#9253) by @IWANABETHATGUY

### 📚 Documentation

- getting-started: remove RC warning for 1.0.0 release (#9310) by @shulaoda
- getting-started: update version references for 1.0.0 release (#9309) by @shulaoda
- add Vite+ tab to getting-started snippets (#9285) by @shulaoda
- lazy-barrel: clarify own-exports behavior for import-then-export records (#9298) by @shulaoda
- restructure top navigation around Learn vs Reference (#9284) by @shulaoda
- builtin-plugins: add bundle analyzer plugin docs (#9292) by @shulaoda
- design doc for reference_needed_symbols (#9264) by @IWANABETHATGUY

### ⚡ Performance

- devtools: write logs on a background thread (#9219) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- mark esbuild/ts/parameter_props_use_define_for_class_fields_true as passed (#9308) by @sapphi-red
- deps: upgrade oxc to 0.129.0 (#9297) by @shulaoda
- deps: update rollup submodule for tests to v4.60.3 (#9294) by @sapphi-red
- deps: update test262 submodule for tests (#9295) by @sapphi-red
- ai: add rolldown REPL decode skill (#9245) by @Dunqing
pull Bot pushed a commit to olrtg/rolldown that referenced this pull request May 7, 2026
## [1.0.0] - 2026-05-07

### 🐛 Bug Fixes

- dev/lazy: lazily compiled modules should be watched (rolldown#9301) by @h-a-n-a
- implement dynamic dominator merge logic (rolldown#9270) by @TheAlexLichter
- dev: apply __toCommonJS interop when CJS requires ESM in HMR finalizer (rolldown#9261) by @h-a-n-a

### 🚜 Refactor

- ecma_ast: tighten allocator access to enforce Sync invariant (rolldown#9278) by @IWANABETHATGUY
- scan_stage: remove stmt_infos field from EcmaView (rolldown#9276) by @IWANABETHATGUY
- link_stage: detach stmt_infos from EcmaView (rolldown#9274) by @IWANABETHATGUY
- link_stage: detach depended_runtime_helper from EcmaView to remove unsafe (rolldown#9265) by @IWANABETHATGUY
- link_stage: remove unsafe in determine_module_exports_kind (rolldown#9253) by @IWANABETHATGUY

### 📚 Documentation

- getting-started: remove RC warning for 1.0.0 release (rolldown#9310) by @shulaoda
- getting-started: update version references for 1.0.0 release (rolldown#9309) by @shulaoda
- add Vite+ tab to getting-started snippets (rolldown#9285) by @shulaoda
- lazy-barrel: clarify own-exports behavior for import-then-export records (rolldown#9298) by @shulaoda
- restructure top navigation around Learn vs Reference (rolldown#9284) by @shulaoda
- builtin-plugins: add bundle analyzer plugin docs (rolldown#9292) by @shulaoda
- design doc for reference_needed_symbols (rolldown#9264) by @IWANABETHATGUY

### ⚡ Performance

- devtools: write logs on a background thread (rolldown#9219) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- mark esbuild/ts/parameter_props_use_define_for_class_fields_true as passed (rolldown#9308) by @sapphi-red
- deps: upgrade oxc to 0.129.0 (rolldown#9297) by @shulaoda
- deps: update rollup submodule for tests to v4.60.3 (rolldown#9294) by @sapphi-red
- deps: update test262 submodule for tests (rolldown#9295) by @sapphi-red
- ai: add rolldown REPL decode skill (rolldown#9245) by @Dunqing

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task]: Avoid unsafe that is potentially wrong

2 participants