perf(allocator): per-platform Arena::new_fixed_size implementations (#22088)

Introduce separate `Arena::new_fixed_size` implementations for different platforms.

Different platforms have differing abilities to service allocation requests with high alignment, so a different allocation strategy is appropriate for each:

- Mac OS: System allocator refuses 4 GiB-aligned allocation requests. Allocate ~4 GiB with 2 GiB alignment, then use either the top or bottom half for the arena chunk.
- Windows: System allocator cannot service allocations with any alignment over 16. Allocate ~6 GiB with 16 alignment, and then find a ~2 GiB block within that allocation which is aligned on 4 GiB. This is what `std` was doing already, but implementing it ourself sidesteps an annoyance with `std`'s implementation.
- Linux: System allocator can service high-alignment requests, so just ask for exactly what we want (~2 GiB with 4 GiB alignment).

This reduces virtual memory usage on Linux, side-steps `std`'s workaround on Windows, and is unchanged for Mac OS.

This also prepares the way for using `VirtualAlloc`\-based allocation on Windows, to avoid committing large amounts of memory - the cause of OOM problems in Oxlint with JS plugins on Windows (#19395).
This commit is contained in:
overlookmotel committed 2026-05-02 23:05:04 +00:00
1 parent 94040ce45b
commit 0bf0cb938e
4 files changed
+204 -37

No files matched your search

@@ -0,0 +1,47 @@
use std::{
alloc::{GlobalAlloc, Layout, System},
ptr::NonNull,
};
use crate::generated::fixed_size_constants::{BLOCK_ALIGN, BLOCK_SIZE};
use super::super::Arena;
// Linux's system allocator supports high alignment allocations directly, so we just request what we want.
// We assume that other non-MacOS, non-Windows platforms also support high alignment allocations.
/// Layout of backing allocations.
const ALLOC_LAYOUT: Layout = match Layout::from_size_align(BLOCK_SIZE, BLOCK_ALIGN) {
Ok(layout) => layout,
Err(_) => unreachable!(),
};
const _: () = assert!(ALLOC_LAYOUT.size() > 0);
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
/// Construct a static-sized [`Arena`] backed by an allocation made via the [`System`] allocator.
///
/// The returned [`Arena`] uses a single chunk of `BLOCK_SIZE` bytes, aligned on `BLOCK_ALIGN`.
/// It cannot grow.
///
/// Returns `None` if the allocation fails.
///
/// See module-level docs for the rationale and platform-specific allocation strategy.
pub fn new_fixed_size() -> Option<Self> {
// Allocate block of memory aligned on `BLOCK_ALIGN`.
// SAFETY: `ALLOC_LAYOUT` does not have zero size.
let alloc_ptr = unsafe { System.alloc(ALLOC_LAYOUT) };
let alloc_ptr = NonNull::new(alloc_ptr)?;
debug_assert!(alloc_ptr.addr().get().is_multiple_of(BLOCK_ALIGN));
// SAFETY:
// * Region starting at `alloc_ptr` with `BLOCK_SIZE` bytes is the allocation we just made.
// * `alloc_ptr` has high alignment (`BLOCK_ALIGN`).
// * `BLOCK_SIZE` is large and a multiple of 16.
// * `alloc_ptr` has permission for writes.
let arena = unsafe { Self::from_raw_parts(alloc_ptr, BLOCK_SIZE, alloc_ptr, ALLOC_LAYOUT) };
Some(arena)
}
}
@@ -1,5 +1,3 @@
//! [`Arena::new_fixed_size`]
use std::{
alloc::{GlobalAlloc, Layout, System},
ptr::NonNull,
@@ -7,43 +5,33 @@ use std::{
use crate::generated::fixed_size_constants::{BLOCK_ALIGN, BLOCK_SIZE};
use super::{Arena, CHUNK_ALIGN, CHUNK_FOOTER_SIZE};
use super::super::Arena;
// System allocator on Mac OS refuses allocations with 4 GiB alignment, so we over-allocate
// `BLOCK_SIZE + TWO_GIB` (4 GiB - 16) bytes with 2 GiB alignment, and then use either the 1st or 2nd half
// of the allocation, one of which is guaranteed to be on a 4 GiB boundary.
// <https://github.com/rust-lang/rust/blob/556d20a834126d2d0ac20743b9792b8474d6d03c/library/std/src/sys/alloc/unix.rs#L16-L27>
// <https://github.com/rust-lang/rust/issues/30170>
const TWO_GIB: usize = 1 << 31;
const FOUR_GIB: usize = 1 << 32;
// What we ideally want is an allocation 2 GiB in size, aligned on 4 GiB.
// But system allocator on Mac OS refuses allocations with 4 GiB alignment.
// https://github.com/rust-lang/rust/blob/556d20a834126d2d0ac20743b9792b8474d6d03c/library/std/src/sys/alloc/unix.rs#L16-L27
// https://github.com/rust-lang/rust/issues/30170
//
// So we instead allocate 4 GiB with 2 GiB alignment, and then use either the 1st or 2nd half
// of the allocation, one of which is guaranteed to be on a 4 GiB boundary.
//
// TODO: We could use this workaround only on Mac OS, and just allocate what we actually want on Linux.
// Windows OS allocator also doesn't support high alignment allocations, so Rust contains a workaround
// which over-allocates (6 GiB in this case).
// https://github.com/rust-lang/rust/blob/556d20a834126d2d0ac20743b9792b8474d6d03c/library/std/src/sys/alloc/windows.rs#L120-L137
// Could just use that built-in workaround, rather than implementing our own, or allocate a 6 GiB chunk
// with alignment 16, to skip Rust's built-in workaround.
// Note: Rust's workaround will likely commit a whole page of memory, just to store the real pointer.
const ALLOC_SIZE: usize = BLOCK_SIZE + TWO_GIB;
const ALLOC_ALIGN: usize = TWO_GIB;
/// Layout of backing allocations for fixed-size allocators.
const _: () = {
// The over-alloc trick relies on the chunk fitting in either half of the allocation
assert!(BLOCK_SIZE <= TWO_GIB);
assert!(BLOCK_ALIGN == FOUR_GIB);
};
/// Layout of backing allocations.
const ALLOC_LAYOUT: Layout = match Layout::from_size_align(ALLOC_SIZE, ALLOC_ALIGN) {
Ok(layout) => layout,
Err(_) => unreachable!(),
};
const _: () = {
// `ChunkFooter` lives in the last `CHUNK_FOOTER_SIZE` bytes of the block and must be aligned on `CHUNK_ALIGN` (16).
// `BLOCK_SIZE` and `CHUNK_FOOTER_SIZE` are both multiples of `CHUNK_ALIGN`.
assert!(BLOCK_SIZE > 0);
assert!(BLOCK_SIZE.is_multiple_of(CHUNK_ALIGN));
assert!(BLOCK_SIZE >= CHUNK_FOOTER_SIZE);
assert!(CHUNK_FOOTER_SIZE.is_multiple_of(CHUNK_ALIGN));
};
const _: () = assert!(ALLOC_LAYOUT.size() > 0);
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
/// Construct a static-sized [`Arena`] backed by an allocation made via the [`System`] allocator.
@@ -51,12 +39,9 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
/// The returned [`Arena`] uses a single chunk of `BLOCK_SIZE` bytes, aligned on `BLOCK_ALIGN`.
/// It cannot grow.
///
/// Allocation is made via [`System`] allocator, bypassing any registered alternative global allocator
/// (e.g. Mimalloc in linter). Mimalloc complains that it cannot serve allocations with high alignment,
/// and presumably it's pointless to try to obtain such large allocations from a thread-local heap,
/// so better to go direct to the system allocator anyway.
///
/// Returns `None` if the allocation fails.
///
/// See module-level docs for the rationale and platform-specific allocation strategy.
pub fn new_fixed_size() -> Option<Self> {
// Allocate block of memory.
// SAFETY: `ALLOC_LAYOUT` does not have zero size.
@@ -73,21 +58,19 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
// Chunk occupies 2nd half of the allocation.
//
// Either way, `chunk_ptr` is aligned on 4 GiB.
let offset = alloc_ptr.as_ptr() as usize % FOUR_GIB;
let offset = alloc_ptr.addr().get() % FOUR_GIB;
// SAFETY: We allocated 4 GiB of memory, so adding `offset` to `alloc_ptr` is in bounds
let chunk_ptr = unsafe { alloc_ptr.add(offset) };
debug_assert!(chunk_ptr.addr().get().is_multiple_of(BLOCK_ALIGN));
// The `Arena`'s chunk fills the whole `BLOCK_SIZE` block.
// `ChunkFooter` sits in the last `CHUNK_FOOTER_SIZE` bytes of the block.
//
// SAFETY:
// * Region starting at `chunk_ptr` with `BLOCK_SIZE` bytes is within the allocation we just made.
// * `chunk_ptr` has high alignment (4 GiB).
// * `BLOCK_SIZE` is large and a multiple of 16.
// * `chunk_ptr` has permission for writes.
// * `chunk_ptr` and `alloc_ptr` have permission for writes.
let arena = unsafe { Self::from_raw_parts(chunk_ptr, BLOCK_SIZE, alloc_ptr, ALLOC_LAYOUT) };
Some(arena)
}
}
@@ -0,0 +1,52 @@
//! [`Arena::new_fixed_size`]
//!
//! Construct an [`Arena`] of fixed size, aligned on a high boundary, backed by an allocation made via
//! [`System`] allocator. Used by raw transfer.
//!
//! Allocating via [`System`] bypasses any registered alternative global allocator (e.g. Mimalloc in linter).
//! Mimalloc complains that it cannot serve allocations with high alignment, and presumably it's pointless
//! to try to obtain such large allocations from a thread-local heap, so better to go direct to the system
//! allocator anyway.
//!
//! The implementation differs between platforms, due to differences in what platforms' system allocators support:
//!
//! * Mac OS: System allocator refuses allocations with 4 GiB alignment.
//! See <https://github.com/rust-lang/rust/issues/30170>.
//! We over-allocate `BLOCK_SIZE + TWO_GIB` (4 GiB - 16) bytes with 2 GiB alignment,
//! then use whichever half of the allocation is aligned on `BLOCK_ALIGN`.
//!
//! * Windows: System allocator also doesn't support high alignment allocations, but Rust's `std` contains
//! a workaround for servicing high-alignment requests.
//! We side-step that by over-allocating `BLOCK_SIZE + BLOCK_ALIGN` (6 GiB - 16) bytes with alignment 16,
//! then aligning the returned pointer to `BLOCK_ALIGN` (4 GiB) ourselves.
//! This avoids `std`'s workaround committing a whole extra page just to store the real allocation pointer.
//!
//! * Linux: System allocator supports high alignment, so we just request what we want.
//!
//! * Other: Assume same as Linux.
//!
//! [`Arena`]: super::Arena
//! [`Arena::new_fixed_size`]: super::Arena::new_fixed_size
//! [`System`]: std::alloc::System
use crate::generated::fixed_size_constants::BLOCK_SIZE;
use super::{CHUNK_ALIGN, CHUNK_FOOTER_SIZE};
// `ChunkFooter` lives in the last `CHUNK_FOOTER_SIZE` bytes of the block and must be aligned on `CHUNK_ALIGN` (16).
// `BLOCK_SIZE` and `CHUNK_FOOTER_SIZE` are both multiples of `CHUNK_ALIGN`.
const _: () = {
assert!(BLOCK_SIZE > 0);
assert!(BLOCK_SIZE.is_multiple_of(CHUNK_ALIGN));
assert!(BLOCK_SIZE >= CHUNK_FOOTER_SIZE);
assert!(CHUNK_FOOTER_SIZE.is_multiple_of(CHUNK_ALIGN));
};
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
mod linux;
@@ -0,0 +1,85 @@
use std::{
alloc::{GlobalAlloc, Layout, System},
ptr::NonNull,
};
use crate::generated::fixed_size_constants::{BLOCK_ALIGN, BLOCK_SIZE};
use super::super::Arena;
// Windows system allocator doesn't support high alignment allocations directly.
//
// Rust's `std` implements a workaround which over-allocates `size + align` bytes, and stores the real pointer
// in a hidden slot before the returned allocation, likely committing an extra page just for the 8-byte pointer.
// <https://github.com/rust-lang/rust/blob/556d20a834126d2d0ac20743b9792b8474d6d03c/library/std/src/sys/alloc/windows.rs#L120-L137>
//
// That's unnecessary for our use case, because we already store the real allocation pointer in the `ChunkFooter`.
// So we side-step `std`'s workaround by requesting a low-alignment allocation that's large enough to find
// a `BLOCK_ALIGN`-aligned chunk of `BLOCK_SIZE` bytes inside it, and we align the chunk pointer to
// `BLOCK_ALIGN` ourselves.
const ALLOC_SIZE: usize = BLOCK_SIZE + BLOCK_ALIGN;
const ALLOC_ALIGN: usize = 16;
// For rounding up to `BLOCK_ALIGN` to make sense, `BLOCK_ALIGN` must be a multiple of `ALLOC_ALIGN`
const _: () = assert!(BLOCK_ALIGN.is_multiple_of(ALLOC_ALIGN));
/// Layout of backing allocations.
const ALLOC_LAYOUT: Layout = match Layout::from_size_align(ALLOC_SIZE, ALLOC_ALIGN) {
Ok(layout) => layout,
Err(_) => unreachable!(),
};
const _: () = assert!(ALLOC_LAYOUT.size() > 0);
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
/// Construct a static-sized [`Arena`] backed by an allocation made via the [`System`] allocator.
///
/// The returned [`Arena`] uses a single chunk of `BLOCK_SIZE` bytes, aligned on `BLOCK_ALIGN`.
/// It cannot grow.
///
/// Returns `None` if the allocation fails.
///
/// See module-level docs for the rationale and platform-specific allocation strategy.
///
/// [`System`]: std::alloc::System
pub fn new_fixed_size() -> Option<Self> {
// Allocate block of memory with low alignment, large enough to find a `BLOCK_ALIGN`-aligned chunk inside.
// SAFETY: `ALLOC_LAYOUT` does not have zero size.
let alloc_ptr = unsafe { System.alloc(ALLOC_LAYOUT) };
let alloc_ptr = NonNull::new(alloc_ptr)?;
// Get pointer to use for allocator chunk, by rounding `alloc_ptr` up to the next `BLOCK_ALIGN` boundary.
//
// `offset` is the number of bytes between `alloc_ptr` and that next-aligned address:
// `(BLOCK_ALIGN - (alloc_addr % BLOCK_ALIGN)) % BLOCK_ALIGN`
//
// The final `% BLOCK_ALIGN` is needed because if `alloc_addr` is already aligned,
// `BLOCK_ALIGN - (alloc_addr % BLOCK_ALIGN)` = `BLOCK_ALIGN - 0` = `BLOCK_ALIGN`. But we want `0`.
// (actually it doesn't matter, `BLOCK_ALIGN` would also be valid, but 0 is neater.)
//
// The expression below collapses both `%`s into a single masked unsigned negation.
// `wrapping_neg()` computes `-alloc_addr (mod 2^N)` (where `N = usize::BITS`),
// so taking it `mod BLOCK_ALIGN` gives `-alloc_addr (mod BLOCK_ALIGN)` which is exactly the formula above.
// (Compiler reduces `% BLOCK_ALIGN` to `& (BLOCK_ALIGN - 1)` because `BLOCK_ALIGN` is a power-of-2 const.)
//
// `offset` is in `0..BLOCK_ALIGN`, so `chunk_ptr + BLOCK_SIZE` is within the allocation
// (we allocated `BLOCK_SIZE + BLOCK_ALIGN` bytes).
let alloc_addr = alloc_ptr.addr().get();
let offset = alloc_addr.wrapping_neg() % BLOCK_ALIGN;
// SAFETY: `offset < BLOCK_ALIGN` and we allocated `BLOCK_SIZE + BLOCK_ALIGN` bytes,
// so `alloc_ptr + offset` is in bounds
let chunk_ptr = unsafe { alloc_ptr.add(offset) };
debug_assert!(chunk_ptr.addr().get().is_multiple_of(BLOCK_ALIGN));
// SAFETY:
// * Region starting at `chunk_ptr` with `BLOCK_SIZE` bytes is within the allocation we just made.
// * `chunk_ptr` has high alignment (`BLOCK_ALIGN`).
// * `BLOCK_SIZE` is large and a multiple of 16.
// * `chunk_ptr` and `alloc_ptr` have permission for writes.
let arena = unsafe { Self::from_raw_parts(chunk_ptr, BLOCK_SIZE, alloc_ptr, ALLOC_LAYOUT) };
Some(arena)
}
}