Skip to content

fix(allocator): fixed-size allocators use VirtualAlloc on Windows#22124

Merged
graphite-app[bot] merged 1 commit into
mainfrom
om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows
May 14, 2026
Merged

fix(allocator): fixed-size allocators use VirtualAlloc on Windows#22124
graphite-app[bot] merged 1 commit into
mainfrom
om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows

Conversation

@overlookmotel

@overlookmotel overlookmotel commented May 5, 2026

Copy link
Copy Markdown
Member

Fixes #19395 - OOM errors in Oxlint JS plugins on Windows.

The problem

Previously, on all platforms each fixed-size (raw transfer-enabled) allocator obtained a large chunk of memory from the System allocator (6 GiB in the case of Windows). This is fine on MacOS and most Linux distros, as they use overcommit allocation accounting, so these allocations consume only virtual memory (address space), not actual physical memory. The available address space is enormous (~128 TiB), so it's hard to exhaust it. Physical memory is only consumed when pages of the allocation are actually touched, which the vast majority never are.

Windows behaves differently. Allocations obtained from System allocator immediately count towards the memory limit. So, on memory-constrained machines, allocating 6 GiB can immediately fail - OOM just from linting the first file.

This PR

#22088, #22122, and #22123 introduced platform-specific code for handling allocation and deallocation of fixed-size Arenas, which are used in raw transfer/Oxlint JS plugins.

This PR switches to a different allocation method on Windows for fixed-size Arenas.

It switches to using VirtualAlloc. This Windows-only API allows reserving address space and committing pages of that reservation as separate operations (similar to mmap).

It works like this:

  • Fixed-size allocators now still reserve 6 GiB, but that only consumes address space, and does not count towards max memory limit.
  • Of that 6 GiB reservation, a 2 GiB "container", aligned on a 4 GiB boundary, will be used as the active part of the memory for storing the AST and other data.
  • Initially only the last 16 KiB of the container is committed (made into usable memory, with physical pages backing it).
  • If AST does not fit in the committed region, the region grows downwards, committing further pages to accomodate allocation requests, up to the maximum of 2 GiB.

Essentially, this replicates something similar to Linux-style allocation, but on Windows. The only difference is that on Windows it's necessary to explicitly commit pages before touching them, whereas Linux/MacOS commit on demand automatically.

Implementation details and testing

This PR uses the battle-tested windows-sys crate for VirtualAlloc and VirtualFree. The allocation implementation is written from scratch, but while I was researching to validate that I'd not missed any edge cases, I discovered that it's almost identical to wasmtime's implementation which uses the same APIs. Comments in the code go over in some detail considerations of Rust's memory model and pointer provenance. As far as I can see, the implementation is fairly watertight.

There are tests for the essential functionality and edge cases, and #22121 made these tests run under Miri on Windows.

However, I don't have access to a Windows machine to fully test this by running Oxlint on a massive repo. I am hoping members of the community who do can put this through its paces a bit before we merge.

What's missing

In enormous projects, when linting with both JS plugins and import plugin, it's possible to end up with a huge number of ASTs in memory simultaneously. With each AST consuming 6 GiB of address space, it's possible to exhaust the entire 128 TiB usable address space, and get OOM on virtual memory.

At present we have a workaround to prevent too many fixed-size allocators existing concurrently, so avoiding OOM, but the workaround is a drag on performance. Future work will aim to address this.

overlookmotel commented May 5, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent changes, fast-track this PR to the front of 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.

@codspeed-hq

codspeed-hq Bot commented May 5, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 48 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows (4ab57eb) with main (6d42395)2

Open in CodSpeed

Footnotes

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

  2. No successful run was found on main (e216a84) during the generation of this report, so 6d42395 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@overlookmotel overlookmotel self-assigned this May 5, 2026
@overlookmotel overlookmotel marked this pull request as ready for review May 5, 2026 02:01
Copilot AI review requested due to automatic review settings May 5, 2026 02:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates oxc_allocator’s Windows fixed-size arena implementation to avoid immediate OOMs by switching from System allocations to a VirtualAlloc-backed reserve/commit strategy, allowing address-space reservation without eagerly committing physical memory.

Changes:

  • Implement Windows fixed-size Arena::new_fixed_size using VirtualAlloc(MEM_RESERVE) + incremental MEM_COMMIT growth.
  • Add Windows-only dependency on windows-sys and adjust internal visibility/utilities to support the new implementation.
  • Modify CI workflow to run Windows NAPI jobs/steps unconditionally by commenting out if: guards.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/oxc_allocator/src/from_raw_parts.rs Updates safety/docs to reflect platform-dependent deallocation behavior.
crates/oxc_allocator/src/arena/utils.rs Makes round_down_to a const fn.
crates/oxc_allocator/src/arena/from_raw_parts.rs Updates safety/docs to reflect platform-dependent deallocation behavior.
crates/oxc_allocator/src/arena/fixed_size/windows.rs New Windows VirtualAlloc reserve/commit allocator + growth + tests + VirtualFree deallocation path.
crates/oxc_allocator/src/arena/create.rs Exposes FIRST_ALLOCATION_GOAL to the module for Windows allocator use.
crates/oxc_allocator/Cargo.toml / Cargo.toml / Cargo.lock Adds windows-sys (Windows-only dep for oxc_allocator, version pinned in workspace).
.github/workflows/ci.yml Comments out if: conditions for Windows NAPI jobs/steps, changing CI execution behavior.

Comment thread crates/oxc_allocator/src/arena/fixed_size/windows.rs
Comment thread crates/oxc_allocator/src/arena/fixed_size/windows.rs
Comment thread .github/workflows/ci.yml
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 8b384b5 to 31e4778 Compare May 5, 2026 02:25
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from 91be1c5 to 2098199 Compare May 5, 2026 02:25
@overlookmotel overlookmotel marked this pull request as draft May 5, 2026 02:28
@overlookmotel overlookmotel added C-bug Category - Bug A-linter-plugins Area - Linter JS plugins A-allocator Area - Allocator labels May 5, 2026
@Sysix

Sysix commented May 8, 2026

Copy link
Copy Markdown
Member

@overlookmotel
I tested it today with a big project, where the current oxlint panicked after 5 minutes of opening and modifying heavy jsPlugins files.
I tried to be as much RAM consuming, as possible. Started a 10+ docker container setup, file watcher, HD videos and more.

With this version, I could not produce a panic after 20 minutes. Almost removed jQuery from an 800 line file. Modified other heavy files too, and it worked like a charm.

I can test it on Monday with a longer session. But it looks like it fixes the issue in windows for me 👍

@overlookmotel

Copy link
Copy Markdown
Member Author

I tried to be as much RAM consuming, as possible. Started a 10+ docker container setup, file watcher, HD videos and more.

Hahaha brilliant! You really went to town! Would love to have seen your machine rocking under that load. "HD videos and more" is the crowning touch!

Thanks loads for putting it through its paces.

I got cold feet about merging it before today's release. I also want to put it through its paces on Linux/Mac to make sure the changes don't accidentally "bleed" into other platforms. Please excuse my excess caution - destabilizing the allocator could destabilize everything. I intend to look at it again later this week, do more testing, and hopefully get it into next Monday's release.

@Diggsey

Diggsey commented May 12, 2026

Copy link
Copy Markdown

This has taken the "Private Bytes" for oxlint from 88GB to ~100MB, a 99.9% reduction in committed memory 🎉 .

@Sysix

Sysix commented May 12, 2026

Copy link
Copy Markdown
Member

@overlookmotel could you sync the graphite stack?
I would like to retry it. I could only check on Monday with a small 2~ hour session. Next days I will have more frontend tasks, where oxlint --lsp is relevant. Hope for a 4+ hour session :)

@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 31e4778 to cd1cde5 Compare May 12, 2026 22:57
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from 2098199 to 01d8cdd Compare May 12, 2026 22:57
@overlookmotel

Copy link
Copy Markdown
Member Author

Stack now rebased on main.

However, I'm going to do a fix for #22339 tomorrow, and then will have to alter this stack too to go on top of that fix.

@Sysix

Sysix commented May 13, 2026

Copy link
Copy Markdown
Member

Could not crash the LSP server today 🥳

@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from cd1cde5 to fac47f6 Compare May 13, 2026 21:07
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from 01d8cdd to cdc43b7 Compare May 13, 2026 21:07
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from fac47f6 to 21e5847 Compare May 13, 2026 21:37
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from cdc43b7 to 9e53a66 Compare May 13, 2026 23:38
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 21e5847 to 98ade91 Compare May 13, 2026 23:38
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from 9e53a66 to c08e350 Compare May 14, 2026 18:15
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 98ade91 to 053c1c6 Compare May 14, 2026 18:15
@overlookmotel overlookmotel marked this pull request as ready for review May 14, 2026 18:19
@overlookmotel overlookmotel requested a review from Copilot May 14, 2026 18:23
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 053c1c6 to 74a046f Compare May 14, 2026 18:28
@overlookmotel overlookmotel force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from c08e350 to dd5ead0 Compare May 14, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Comment thread crates/oxc_allocator/src/arena/alloc_impl.rs Outdated
Comment thread crates/oxc_allocator/src/from_raw_parts.rs Outdated
Comment thread crates/oxc_allocator/src/arena/from_raw_parts.rs Outdated
Comment thread crates/oxc_allocator/Cargo.toml Outdated
Comment thread crates/oxc_allocator/src/arena/alloc_impl.rs
@overlookmotel overlookmotel force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 74a046f to 60033f6 Compare May 14, 2026 21:15
@overlookmotel

Copy link
Copy Markdown
Member Author

Have tested this branch on Mac OS linting Kibana repo with JS plugins + import plugin combined. We've found this the most demanding repo to test Oxlint on in past. It works as expected, so it looks like this change isn't causing problems on other platforms.

Finally going to merge this thing!

@graphite-app

graphite-app Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Merge activity

…22124)

Fixes #19395 - OOM errors in Oxlint JS plugins on Windows.

### The problem

Previously, on all platforms each fixed-size (raw transfer-enabled) allocator obtained a large chunk of memory from the `System` allocator (6 GiB in the case of Windows). This is fine on MacOS and most Linux distros, as they use overcommit allocation accounting, so these allocations consume only virtual memory (address space), not actual physical memory. The available address space is enormous (~128 TiB), so it's hard to exhaust it. Physical memory is only consumed when pages of the allocation are actually touched, which the vast majority never are.

Windows behaves differently. Allocations obtained from `System` allocator immediately count towards the memory limit. So, on memory-constrained machines, allocating 6 GiB can immediately fail - OOM just from linting the first file.

### This PR

[#22088](#22088), #22122, and #22123 introduced platform-specific code for handling allocation and deallocation of fixed-size `Arena`s, which are used in raw transfer/Oxlint JS plugins.

This PR switches to a different allocation method on Windows for fixed-size `Arena`s.

It switches to using [VirtualAlloc](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc). This Windows-only API allows reserving address space and committing pages of that reservation as separate operations (similar to `mmap`).

It works like this:

- Fixed-size allocators now still reserve 6 GiB, but that only consumes address space, and does not count towards max memory limit.
- Of that 6 GiB reservation, a 2 GiB "container", aligned on a 4 GiB boundary, will be used as the active part of the memory for storing the AST and other data.
- Initially only the last 16 KiB of the container is committed (made into usable memory, with physical pages backing it).
- If AST does not fit in the committed region, the region grows downwards, committing further pages to accomodate allocation requests, up to the maximum of 2 GiB.

Essentially, this replicates something similar to Linux-style allocation, but on Windows. The only difference is that on Windows it's necessary to explicitly commit pages before touching them, whereas Linux/MacOS commit on demand automatically.

### Implementation details and testing

This PR uses the battle-tested [windows-sys](https://crates.io/crates/windows-sys) crate for `VirtualAlloc` and `VirtualFree`. The allocation implementation is written from scratch, but while I was researching to validate that I'd not missed any edge cases, I discovered that it's almost identical to [wasmtime's implementation](https://github.com/bytecodealliance/wasmtime/blob/386a3280dee61f5c4120ce7cde621c1039e383d5/crates/wasmtime/src/runtime/vm/sys/windows/mmap.rs) which uses the same APIs. Comments in the code go over in some detail considerations of Rust's memory model and pointer provenance. As far as I can see, the implementation is fairly watertight.

There are tests for the essential functionality and edge cases, and #22121 made these tests run under Miri on Windows.

However, I don't have access to a Windows machine to fully test this by running Oxlint on a massive repo. I am hoping members of the community who do can put this through its paces a bit before we merge.

### What's missing

In enormous projects, when linting with both JS plugins and `import` plugin, it's possible to end up with a huge number of ASTs in memory simultaneously. With each AST consuming 6 GiB of address space, it's possible to exhaust the entire 128 TiB usable address space, and get OOM on _virtual_ memory.

At present we have a workaround to prevent too many fixed-size allocators existing concurrently, so avoiding OOM, but the workaround is a drag on performance. Future work will aim to address this.
graphite-app Bot pushed a commit that referenced this pull request May 14, 2026
…e chunks (#22122)

Add per-platform implementations of `dealloc_fixed_size_arena_chunk`, and delegate to them when dropping an `Arena` which has fixed-size chunks.

Currently, all the implementations are identical - they just deallocate the memory with `System` allocator. But this prepares the way for #22124, which alters the Windows impl to use `VirtualFree` instead.
graphite-app Bot pushed a commit that referenced this pull request May 14, 2026
Add a private method `Arena::grow_fixed_size_chunk` which attempts to grow a fixed-size `Arena` chunk in place.

Similar to #22122, there's multiple implementations for different platforms, but all are currently the same - all return `None` to indicate "could not grow". This is preparatory work for #22124, which adds a Windows-specific implementation.
@graphite-app graphite-app Bot force-pushed the om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method branch from dd5ead0 to e216a84 Compare May 14, 2026 22:28
@graphite-app graphite-app Bot force-pushed the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch from 60033f6 to 4ab57eb Compare May 14, 2026 22:29
Base automatically changed from om/05-03-refactor_allocator_add_arena_grow_fixed_size_chunk_method to main May 14, 2026 22:34
@graphite-app graphite-app Bot merged commit 4ab57eb into main May 14, 2026
42 checks passed
@graphite-app graphite-app Bot deleted the om/05-03-fix_allocator_fixed-size_allocators_use_virtualalloc_on_windows branch May 14, 2026 22:35
overlookmotel added a commit that referenced this pull request May 15, 2026
### 🚀 Features

- bc91a17 codegen: Expose `Codegen::with_source_type` method (#22432)
(camc314)

### 🐛 Bug Fixes

- 5ac7e79 minifier: Drop unused-var-init pure IIFEs and preserve
annotation for downstream (#22349) (Dunqing)
- 4ab57eb allocator: Fixed-size allocators use `VirtualAlloc` on Windows
(#22124) (overlookmotel)
- 66d77eb allocator: Fix segfault on Linux MUSL with fixed-size
allocators (#22388) (overlookmotel)
- b8fbc1f transformer/object-rest-spread: Correct scope id when moving
bindings (#22419) (camc314)
- 18edc2c codegen: Keep `Object.defineProperty` property name as plain
string in minify (#22400) (Dunqing)
- dda33de transformer/explicit-resource-management: Align lexical
binding scopes (#22320) (camc314)
- 8e79de8 transformer: Preserve for-await statement bodies (#22361)
(camc314)
- 0cba210 transformer/class: Replace `new.target` in static blocks
(#22360) (camc314)
- 67ab1c9 transformer/es2018/for-await: Hoist for-await generated
bindings (#22355) (camc314)
- c3ceb4a transformer/object-rest-spread: Use hoisted scope for `for-of`
temp refs (#22347) (camc314)

### ⚡ Performance

- 73a9043 allocator/bitset: Avoid temp heap `String` allocation (#22403)
(camc314)
- 8b2f4f9 transformer/object-rest-spread: Collect `Vec<SymbolId` over
`Vec<BindingIdentifier>` (#22418) (camc314)
- 83679ea parser: Split TriviaBuilder::handle_token hot/cold paths
(#22415) (Boshen)
- 2c7d781 codegen: Inline identifier-name accessors (#22411) (Boshen)
- 618bc76 diagnostics: Inline `OxcDiagnosticInner` to avoid heap
allocation (#22406) (Boshen)
- 0b4e158 parser: Reserve cap `2` for sequence expressions vec (#22374)
(camc314)
- 5f3bdd0 codegen: Add `#[inline]` to `code`, `code_len` (#22373)
(camc314)

Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-allocator Area - Allocator A-linter-plugins Area - Linter JS plugins C-bug Category - Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

linter: panic: Insufficient memory to create fixed-size allocator pool

4 participants