diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 3396a2c..d587c94 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -49,13 +49,13 @@ jobs: run: rustup toolchain install nightly --no-self-update --profile minimal --component rust-src,rustfmt,clippy,miri - name: Check formatting - run: cargo +nightly fmt -- --check + run: cargo fmt -- --check - name: Run tests run: cargo test --all-features - name: Run miri tests - run: cargo +nightly miri test + run: cargo miri test - name: Run clippy run: cargo clippy --all-features --all-targets -- -D warnings -W clippy::pedantic diff --git a/Cargo.toml b/Cargo.toml index 2adb014..ab2b6f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = [".github", "rustfmt.toml", ".gitignore"] [features] lazy = [] +nightly = [] [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index f0efd40..9a66e4b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Small, fixed-capacity arena allocators for single-threaded Rust programs. -You pick the capacity up front. The arena never grows. +You pick the capacity up front. The arena capacity never grows. Addresses stay stable. When it is full, allocation returns `None`. ## Choose an arena @@ -23,6 +23,13 @@ Addresses stay stable. When it is full, allocation returns `None`. All arenas are `!Send` and `!Sync`. They are deliberately single-threaded. +## Feature flags + +- `lazy` enables `BumpArenaLazy` and `TypedArenaLazy` on Unix and Windows. +- `nightly` requires a nightly Rust toolchain and enables the unstable + standard-library `allocator_api` implementation for `BumpArena` and + `BumpArenaLazy`. + ## Use a bump arena `BumpArena` gives you uninitialized bytes. You choose the layout, initialize @@ -41,6 +48,36 @@ unsafe { ptr.write(42) }; assert_eq!(unsafe { *ptr }, 42); ``` +## Use as a standard-library allocator + +Enable `nightly` when you want an untyped arena to back standard-library +collections that use the unstable allocator API: + +```toml +[dependencies] +linalloc = { version = "1", features = ["nightly"] } +``` + +```rust +#![feature(allocator_api)] + +# #[cfg(feature = "nightly")] +# { + use linalloc::BumpArena; + + let arena = BumpArena::new(128); + let mut values = Vec::with_capacity_in(1, &arena); + + values.push(1); + values.try_reserve(1).unwrap(); + values.push(2); + + assert_eq!(&values, &[1, 2]); +# } +``` + +Use `features = ["nightly", "lazy"]` when the allocator is `BumpArenaLazy`. + ## Use a typed arena `TypedArena` stores initialized `T` values and drops the live values when @@ -137,6 +174,12 @@ values in reverse allocation order, and then reuses the storage. For untyped arenas, `reset` is unsafe. All returned slices must be dead, and any values stored in the arena must already have been dropped. +With `nightly`, `BumpArena` and `BumpArenaLazy` implement +`core::alloc::Allocator`. Per-block `deallocate` is a no-op; memory is reclaimed +only by `reset` or by dropping the arena. `grow`, `grow_zeroed`, and `shrink` +resize only the most recent allocation in place. Drop all collections and values +that use an arena allocator before calling `reset`. + ## Miri Miri covers the default eager arenas. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/bump_arena.rs b/src/bump_arena.rs index 59ca81e..393540d 100644 --- a/src/bump_arena.rs +++ b/src/bump_arena.rs @@ -10,10 +10,10 @@ use crate::UninitAllocator; /// A fixed‑capacity, single‑threaded bump allocator. /// /// The arena hands out mutable slices of [`MaybeUninit`] that -/// are logically uninitialised. The caller must initialise the -/// memory before reading from it. The backing store is a boxed +/// are logically uninitialised. The caller must initialise the +/// memory before reading from it. The backing store is a boxed /// slice whose capacity is set once at construction and **never -/// changes**, so addresses remain stable. For zero capacity, the +/// changes**, so addresses remain stable. For zero capacity, the /// boxed slice may be a dangling, non‑allocated value. /// /// # Thread safety @@ -51,7 +51,7 @@ impl BumpArena { /// Creates a bump allocator with exactly `capacity` bytes of memory. /// /// The memory is allocated from the global allocator and is - /// **uninitialised**. No zeroing or default‑initialisation is + /// **uninitialised**. No zeroing or default‑initialisation is /// performed. /// /// # Panics @@ -76,9 +76,9 @@ impl BumpArena { /// performed. /// /// The slice borrows the arena immutably (`&self`), so the arena - /// cannot be dropped or moved while the slice is alive. The + /// cannot be dropped or moved while the slice is alive. The /// backing store is never resized, so non‑zero allocations remain - /// valid until the arena is dropped or [`reset`] is called. A + /// valid until the arena is dropped or [`reset`] is called. A /// zero‑size allocation returns a well‑aligned dangling slice and /// does not advance the bump pointer. /// @@ -162,10 +162,134 @@ unsafe impl UninitAllocator for BumpArena { } } +// Safety: +// +// `BumpArena` provides correctly aligned, non‑overlapping memory that remains +// stable until the arena is dropped or reset. The `Allocator` contract is +// upheld: `allocate` hands out memory from the bump pointer, `deallocate` is a +// deliberate no‑op (the arena cannot reclaim individual blocks), and `grow` / +// `shrink` only resize the most recent allocation in place when it is the last +// block. +#[cfg(feature = "nightly")] +unsafe impl core::alloc::Allocator for BumpArena { + fn allocate(&self, layout: Layout) -> Result, core::alloc::AllocError> { + let slice = self.alloc_uninit_slice(layout).ok_or(core::alloc::AllocError)?; + // SAFETY: `slice` is guaranteed to be non-null and valid for `layout.size()` bytes. + let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + } + + unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) { + // Bump allocator memory is reclaimed only via `reset` or `Drop`. + } + + unsafe fn grow( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let offset = self.offset.get(); + let base = self.base.as_ptr().cast::>() as usize; + let old_ptr = ptr.as_ptr() as usize; + let old_offset = old_ptr.checked_sub(base).ok_or(core::alloc::AllocError)?; + let old_size = old_layout.size(); + let new_size = new_layout.size(); + let old_end = old_offset.checked_add(old_size).ok_or(core::alloc::AllocError)?; + let is_last = old_end == offset; + + if !is_last || new_size <= old_size || !old_ptr.is_multiple_of(new_layout.align()) { + return Err(core::alloc::AllocError); + } + + let required_offset = old_offset.checked_add(new_size).ok_or(core::alloc::AllocError)?; + if required_offset > self.capacity() { + return Err(core::alloc::AllocError); + } + + self.offset.set(required_offset); + let new_ptr = unsafe { + NonNull::new_unchecked( + self.base.as_ptr().cast::>().add(old_offset).cast::(), + ) + }; + Ok(NonNull::slice_from_raw_parts(new_ptr, new_size)) + } + + unsafe fn grow_zeroed( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let new_ptr = unsafe { self.grow(ptr, old_layout, new_layout)? }; + let old_size = old_layout.size(); + // Zero the newly added tail. + let new_bytes = unsafe { new_ptr.as_ptr().cast::().add(old_size) }; + unsafe { core::ptr::write_bytes(new_bytes, 0, new_layout.size() - old_size) }; + Ok(new_ptr) + } + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let offset = self.offset.get(); + let base = self.base.as_ptr().cast::>() as usize; + let old_ptr = ptr.as_ptr() as usize; + let old_offset = old_ptr.checked_sub(base).ok_or(core::alloc::AllocError)?; + let old_size = old_layout.size(); + let new_size = new_layout.size(); + let old_end = old_offset.checked_add(old_size).ok_or(core::alloc::AllocError)?; + let is_last = old_end == offset; + + if !is_last || new_size > old_size || !old_ptr.is_multiple_of(new_layout.align()) { + return Err(core::alloc::AllocError); + } + + let new_offset = old_offset.checked_add(new_size).ok_or(core::alloc::AllocError)?; + self.offset.set(new_offset); + let new_ptr = unsafe { + NonNull::new_unchecked( + self.base.as_ptr().cast::>().add(old_offset).cast::(), + ) + }; + Ok(NonNull::slice_from_raw_parts(new_ptr, new_size)) + } +} + #[cfg(test)] mod tests { + #[cfg(feature = "nightly")] + use core::alloc::Allocator; + use super::*; + #[cfg(feature = "nightly")] + fn block_ptr(block: NonNull<[u8]>) -> NonNull { + // SAFETY: `Allocator::allocate` never returns a null block pointer. + unsafe { NonNull::new_unchecked(block.as_ptr().cast::()) } + } + + #[cfg(feature = "nightly")] + fn allocate_last_block_misaligned_to(bump: &BumpArena, align: usize) -> (NonNull, Layout) { + let layout = Layout::from_size_align(8, 1).unwrap(); + let pad = Layout::from_size_align(1, 1).unwrap(); + + for _ in 0..=align { + let block = (&bump).allocate(layout).unwrap(); + let ptr = block_ptr(block); + if !(ptr.as_ptr() as usize).is_multiple_of(align) { + return (ptr, layout); + } + (&bump).allocate(pad).unwrap(); + } + + panic!("could not create a misaligned last allocation"); + } + #[test] fn alloc_alignment_and_length() { let bump = BumpArena::new(128); @@ -246,4 +370,87 @@ mod tests { assert_eq!(slice.len(), 0); assert_eq!(bump.used(), 0); } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_grow_and_shrink_resize_last_allocation() { + let bump = BumpArena::new(64); + let alloc = ≎ + let old = Layout::from_size_align(8, 4).unwrap(); + let grown = Layout::from_size_align(16, 4).unwrap(); + let shrunk = Layout::from_size_align(4, 4).unwrap(); + + let block = alloc.allocate(old).unwrap(); + let ptr = block_ptr(block); + let start = + (ptr.as_ptr() as usize) - (bump.base.as_ptr().cast::>() as usize); + unsafe { ptr.as_ptr().write_bytes(0xAB, old.size()) }; + + let grown_block = unsafe { alloc.grow(ptr, old, grown).unwrap() }; + let grown_ptr = block_ptr(grown_block); + assert_eq!(grown_ptr, ptr); + assert_eq!(bump.used(), start + grown.size()); + assert_eq!(unsafe { grown_ptr.as_ptr().read() }, 0xAB); + assert_eq!(unsafe { grown_ptr.as_ptr().add(old.size() - 1).read() }, 0xAB); + + let shrunk_block = unsafe { alloc.shrink(grown_ptr, grown, shrunk).unwrap() }; + assert_eq!(block_ptr(shrunk_block), ptr); + assert_eq!(bump.used(), start + shrunk.size()); + } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_grow_zeroed_zeroes_new_tail() { + let bump = BumpArena::new(64); + let alloc = ≎ + let old = Layout::from_size_align(4, 1).unwrap(); + let grown = Layout::from_size_align(12, 1).unwrap(); + + let block = alloc.allocate(old).unwrap(); + let ptr = block_ptr(block); + unsafe { ptr.as_ptr().write_bytes(0xAB, old.size()) }; + + let grown_block = unsafe { alloc.grow_zeroed(ptr, old, grown).unwrap() }; + let grown_ptr = block_ptr(grown_block); + assert_eq!(grown_ptr, ptr); + for index in 0..old.size() { + assert_eq!(unsafe { grown_ptr.as_ptr().add(index).read() }, 0xAB); + } + for index in old.size()..grown.size() { + assert_eq!(unsafe { grown_ptr.as_ptr().add(index).read() }, 0); + } + } + + #[cfg(feature = "nightly")] + #[test] + fn vec_try_reserve_can_grow_inside_allocator() { + let bump = BumpArena::new(64); + let mut values = Vec::with_capacity_in(1, &bump); + values.push(1); + + assert!(values.try_reserve(1).is_ok()); + values.push(2); + + assert_eq!(&values, &[1, 2]); + } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_rejects_resize_when_pointer_does_not_fit_new_alignment() { + let bump = BumpArena::new(256); + let (ptr, old) = allocate_last_block_misaligned_to(&bump, 8); + let used = bump.used(); + let grown = Layout::from_size_align(16, 8).unwrap(); + + assert!(unsafe { bump.grow(ptr, old, grown) }.is_err()); + assert_eq!(bump.used(), used); + + let bump = BumpArena::new(256); + let (ptr, old) = allocate_last_block_misaligned_to(&bump, 8); + let used = bump.used(); + let shrunk = Layout::from_size_align(4, 8).unwrap(); + + assert!(unsafe { bump.shrink(ptr, old, shrunk) }.is_err()); + assert_eq!(bump.used(), used); + } } diff --git a/src/bump_arena_lazy.rs b/src/bump_arena_lazy.rs index 202a3e3..2b4a33a 100644 --- a/src/bump_arena_lazy.rs +++ b/src/bump_arena_lazy.rs @@ -283,10 +283,132 @@ unsafe impl UninitAllocator for BumpArenaLazy { } } +// Safety: +// +// Same contract as for `&BumpArena`, with the addition that `grow` may +// trigger a virtual‑memory commit if the new size requires it. +#[cfg(feature = "nightly")] +unsafe impl core::alloc::Allocator for BumpArenaLazy { + fn allocate(&self, layout: Layout) -> Result, core::alloc::AllocError> { + let slice = self.alloc_uninit_slice(layout).ok_or(core::alloc::AllocError)?; + // SAFETY: `slice` is guaranteed to be non-null and valid for `layout.size()` bytes. + let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + } + + unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) { + // Bump allocator memory is reclaimed only via `reset` or `Drop`. + } + + unsafe fn grow( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let offset = self.offset.get(); + let base = self.base.as_ptr() as usize; + let old_ptr = ptr.as_ptr() as usize; + let old_offset = old_ptr.checked_sub(base).ok_or(core::alloc::AllocError)?; + let old_size = old_layout.size(); + let new_size = new_layout.size(); + let old_end = old_offset.checked_add(old_size).ok_or(core::alloc::AllocError)?; + let is_last = old_end == offset; + + if !is_last || new_size <= old_size || !old_ptr.is_multiple_of(new_layout.align()) { + return Err(core::alloc::AllocError); + } + + let required_offset = old_offset.checked_add(new_size).ok_or(core::alloc::AllocError)?; + if required_offset > self.capacity { + return Err(core::alloc::AllocError); + } + + if required_offset > self.commit.get() { + let slice = self + .alloc_uninit_slice_bump(old_offset, required_offset, new_size) + .ok_or(core::alloc::AllocError)?; + let ptr = unsafe { NonNull::new_unchecked(slice.as_mut_ptr().cast()) }; + return Ok(NonNull::slice_from_raw_parts(ptr, new_size)); + } + + self.offset.set(required_offset); + let new_ptr = unsafe { NonNull::new_unchecked(self.base.as_ptr().add(old_offset)) }; + Ok(NonNull::slice_from_raw_parts(new_ptr, new_size)) + } + + unsafe fn grow_zeroed( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let new_ptr = unsafe { self.grow(ptr, old_layout, new_layout)? }; + let old_size = old_layout.size(); + let new_bytes = unsafe { new_ptr.as_ptr().cast::().add(old_size) }; + unsafe { core::ptr::write_bytes(new_bytes, 0, new_layout.size() - old_size) }; + Ok(new_ptr) + } + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, core::alloc::AllocError> { + let offset = self.offset.get(); + let base = self.base.as_ptr() as usize; + let old_ptr = ptr.as_ptr() as usize; + let old_offset = old_ptr.checked_sub(base).ok_or(core::alloc::AllocError)?; + let old_size = old_layout.size(); + let new_size = new_layout.size(); + let old_end = old_offset.checked_add(old_size).ok_or(core::alloc::AllocError)?; + let is_last = old_end == offset; + + if !is_last || new_size > old_size || !old_ptr.is_multiple_of(new_layout.align()) { + return Err(core::alloc::AllocError); + } + + let new_offset = old_offset.checked_add(new_size).ok_or(core::alloc::AllocError)?; + self.offset.set(new_offset); + let new_ptr = unsafe { NonNull::new_unchecked(self.base.as_ptr().add(old_offset)) }; + Ok(NonNull::slice_from_raw_parts(new_ptr, new_size)) + } +} + #[cfg(test)] mod tests { + #[cfg(feature = "nightly")] + use core::alloc::Allocator; + use super::*; + #[cfg(feature = "nightly")] + fn block_ptr(block: NonNull<[u8]>) -> NonNull { + // SAFETY: `Allocator::allocate` never returns a null block pointer. + unsafe { NonNull::new_unchecked(block.as_ptr().cast::()) } + } + + #[cfg(feature = "nightly")] + fn allocate_last_block_misaligned_to( + bump: &BumpArenaLazy, + align: usize, + ) -> (NonNull, Layout) { + let layout = Layout::from_size_align(8, 1).unwrap(); + let pad = Layout::from_size_align(1, 1).unwrap(); + + for _ in 0..=align { + let block = (&bump).allocate(layout).unwrap(); + let ptr = block_ptr(block); + if !(ptr.as_ptr() as usize).is_multiple_of(align) { + return (ptr, layout); + } + (&bump).allocate(pad).unwrap(); + } + + panic!("could not create a misaligned last allocation"); + } + #[test] fn alloc_alignment_and_length() { let bump = BumpArenaLazy::new(128); @@ -367,4 +489,111 @@ mod tests { assert_eq!(slice.len(), 0); assert_eq!(bump.used(), 0); } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_grow_and_shrink_resize_last_allocation() { + let bump = BumpArenaLazy::new(64); + let alloc = ≎ + let old = Layout::from_size_align(8, 4).unwrap(); + let grown = Layout::from_size_align(16, 4).unwrap(); + let shrunk = Layout::from_size_align(4, 4).unwrap(); + + let block = alloc.allocate(old).unwrap(); + let ptr = block_ptr(block); + let start = (ptr.as_ptr() as usize) - (bump.base.as_ptr() as usize); + unsafe { ptr.as_ptr().write_bytes(0xAB, old.size()) }; + + let grown_block = unsafe { alloc.grow(ptr, old, grown).unwrap() }; + let grown_ptr = block_ptr(grown_block); + assert_eq!(grown_ptr, ptr); + assert_eq!(bump.used(), start + grown.size()); + assert_eq!(unsafe { grown_ptr.as_ptr().read() }, 0xAB); + assert_eq!(unsafe { grown_ptr.as_ptr().add(old.size() - 1).read() }, 0xAB); + + let shrunk_block = unsafe { alloc.shrink(grown_ptr, grown, shrunk).unwrap() }; + assert_eq!(block_ptr(shrunk_block), ptr); + assert_eq!(bump.used(), start + shrunk.size()); + } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_grow_zeroed_zeroes_new_tail() { + let bump = BumpArenaLazy::new(64); + let alloc = ≎ + let old = Layout::from_size_align(4, 1).unwrap(); + let grown = Layout::from_size_align(12, 1).unwrap(); + + let block = alloc.allocate(old).unwrap(); + let ptr = block_ptr(block); + unsafe { ptr.as_ptr().write_bytes(0xAB, old.size()) }; + + let grown_block = unsafe { alloc.grow_zeroed(ptr, old, grown).unwrap() }; + let grown_ptr = block_ptr(grown_block); + assert_eq!(grown_ptr, ptr); + for index in 0..old.size() { + assert_eq!(unsafe { grown_ptr.as_ptr().add(index).read() }, 0xAB); + } + for index in old.size()..grown.size() { + assert_eq!(unsafe { grown_ptr.as_ptr().add(index).read() }, 0); + } + } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_grow_across_commit_preserves_the_block() { + let page = sys::page_size(); + let bump = BumpArenaLazy::new(page + 64); + let alloc = ≎ + let old = Layout::from_size_align(page - 8, 1).unwrap(); + let grown = Layout::from_size_align(page + 16, 1).unwrap(); + + let block = alloc.allocate(old).unwrap(); + let ptr = block_ptr(block); + unsafe { ptr.as_ptr().write_bytes(0xAB, old.size()) }; + assert_eq!(bump.used(), old.size()); + assert_eq!(bump.commit.get(), page); + + let grown_block = unsafe { alloc.grow(ptr, old, grown).unwrap() }; + let grown_ptr = block_ptr(grown_block); + + assert_eq!(grown_ptr, ptr); + assert_eq!(bump.used(), grown.size()); + assert!(bump.commit.get() >= grown.size()); + assert_eq!(unsafe { grown_ptr.as_ptr().read() }, 0xAB); + assert_eq!(unsafe { grown_ptr.as_ptr().add(old.size() - 1).read() }, 0xAB); + } + + #[cfg(feature = "nightly")] + #[test] + fn vec_try_reserve_can_grow_inside_allocator() { + let bump = BumpArenaLazy::new(64); + let mut values = Vec::with_capacity_in(1, &bump); + values.push(1); + + assert!(values.try_reserve(1).is_ok()); + values.push(2); + + assert_eq!(&values, &[1, 2]); + } + + #[cfg(feature = "nightly")] + #[test] + fn allocator_rejects_resize_when_pointer_does_not_fit_new_alignment() { + let bump = BumpArenaLazy::new(256); + let (ptr, old) = allocate_last_block_misaligned_to(&bump, 8); + let used = bump.used(); + let grown = Layout::from_size_align(16, 8).unwrap(); + + assert!(unsafe { bump.grow(ptr, old, grown) }.is_err()); + assert_eq!(bump.used(), used); + + let bump = BumpArenaLazy::new(256); + let (ptr, old) = allocate_last_block_misaligned_to(&bump, 8); + let used = bump.used(); + let shrunk = Layout::from_size_align(4, 8).unwrap(); + + assert!(unsafe { bump.shrink(ptr, old, shrunk) }.is_err()); + assert_eq!(bump.used(), used); + } } diff --git a/src/lib.rs b/src/lib.rs index 0777176..d5374dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![warn(clippy::pedantic)] #![doc = include_str!("../README.md")] +#![cfg_attr(feature = "nightly", feature(allocator_api))] #[cfg(all(feature = "lazy", not(any(unix, windows))))] compile_error!("the `lazy` feature is currently supported only on Unix and Windows targets"); diff --git a/src/sys.rs b/src/sys.rs index a0a8836..ad75d6f 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -80,7 +80,7 @@ mod platform { fn munmap(addr: *mut c_void, length: usize) -> i32; - fn sysconf(name: i32) -> i64; + fn getpagesize() -> i32; // yanked from https://github.com/rust-lang/rust/blob/main/library/std/src/sys/io/error/unix.rs #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))] @@ -126,8 +126,6 @@ mod platform { const MAP_FAILED: *mut c_void = -1isize as *mut c_void; - const _SC_PAGESIZE: i32 = 29; - pub fn reserve(size: usize) -> Result, i32> { #[cfg(target_os = "netbsd")] // NetBSD allows an mmap(2) caller to specify what protection flags they @@ -178,12 +176,12 @@ mod platform { #[allow( clippy::cast_possible_truncation, clippy::cast_sign_loss, - reason = "sysconf returns a long, but page sizes are never that large" + reason = "getpagesize returns an int, but page sizes are never negative or very large" )] #[cold] fn page_size_store() -> usize { - let raw = unsafe { sysconf(_SC_PAGESIZE) }; - let sz = if raw < 0 { 4 * 1024 } else { raw as usize }; + let raw = unsafe { getpagesize() }; + let sz = if raw <= 0 { 4 * 1024 } else { raw as usize }; PAGE_SIZE.store(sz, Ordering::Relaxed); sz }