Skip to content

Instantly share code, notes, and snippets.

@Speykious
Created June 14, 2024 10:32
Show Gist options
  • Save Speykious/803dc4e6abdd83656decc1ce658fe2dd to your computer and use it in GitHub Desktop.
Save Speykious/803dc4e6abdd83656decc1ce658fe2dd to your computer and use it in GitHub Desktop.
Cross-platform arena allocator in Rust
#[cfg(any(target_os = "linux", target_os = "macos"))]
mod unix {
use std::{
ffi::{c_int, c_long, c_void},
ptr,
};
const PROT_NONE: c_int = 0;
const PROT_READ: c_int = 1;
const PROT_WRITE: c_int = 2;
const MAP_SHARED: c_int = 0x01;
const MAP_PRIVATE: c_int = 0x02;
const MAP_FILE: c_int = 0x00;
const MAP_FIXED: c_int = 0x10;
#[cfg(target_os = "linux")]
const MAP_ANONYMOUS: c_int = 0x20;
#[cfg(target_os = "macos")]
const MAP_ANONYMOUS: c_int = 0x1000;
#[cfg(target_os = "linux")]
const SC_PAGE_SIZE: c_int = 30;
#[cfg(target_os = "macos")]
const SC_PAGE_SIZE: c_int = 29;
extern "C" {
pub fn mmap(
addr: *mut c_void,
len: usize,
prot: c_int,
flags: c_int,
fd: c_int,
offset: isize,
) -> *mut c_void;
pub fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int;
pub fn munmap(addr: *mut c_void, len: usize) -> c_int;
pub fn sysconf(name: c_int) -> c_long;
}
pub unsafe fn vm_reserve(size_aligned: usize) -> *mut u8 {
let reserved = mmap(
ptr::null_mut(),
size_aligned,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0,
) as *mut u8;
if reserved as usize == !0 {
panic!("vm_reserve: mmap failed");
}
reserved
}
pub unsafe fn vm_release(addr: *const u8, size_aligned: usize) {
munmap(addr as _, size_aligned);
}
pub unsafe fn vm_commit(addr: *const u8, size_aligned: usize) {
mprotect(addr as _, size_aligned, PROT_READ | PROT_WRITE);
}
pub unsafe fn vm_uncommit(addr: *const u8, size_aligned: usize) {
mprotect(addr as _, size_aligned, PROT_NONE);
}
pub unsafe fn os_page_size() -> usize {
sysconf(SC_PAGE_SIZE) as usize
}
}
#[cfg(target_family = "windows")]
mod windows {
use std::{
ffi::{c_int, c_void},
ptr,
};
const MEM_COMMIT: u32 = 0x00001000;
const MEM_RESERVE: u32 = 0x00002000;
const MEM_DECOMMIT: u32 = 0x00004000;
const MEM_RELEASE: u32 = 0x00008000;
const PAGE_NOACCESS: u32 = 0x01;
const PAGE_READWRITE: u32 = 0x04;
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(non_snake_case)]
pub struct DummyStructW {
pub wProcessorArchitecture: u16,
pub wReserved: u16,
}
#[repr(C)]
#[allow(non_snake_case)]
pub union DummySystemInfoUnion {
pub dwOemId: u32,
pub dummy: DummyStructW,
}
#[repr(C)]
#[allow(non_snake_case)]
pub struct SystemInfo {
pub dummy: DummySystemInfoUnion,
pub dwPageSize: u32,
pub lpMinimumApplicationAddress: *const c_void,
pub lpMaximumApplicationAddress: *const c_void,
pub dwActiveProcessorMask: *const u32,
pub dwNumberOfProcessors: u32,
pub dwProcessorType: u32,
pub dwAllocationGranularity: u32,
pub wProcessorLevel: u16,
pub wProcessorRevision: u16,
}
extern "C" {
pub fn VirtualAlloc(
lpAddress: *mut c_void,
dwSize: usize,
flAllocationType: u32,
flProtect: u32,
) -> *mut c_void;
pub fn VirtualFree(lpAddress: *mut c_void, dwSize: usize, dwFreeType: u32) -> bool;
pub fn GetSystemInfo(lpSystemInfo: &mut SystemInfo);
}
pub unsafe fn vm_reserve(size_aligned: usize) -> *mut u8 {
VirtualAlloc(ptr::null_mut(), size_aligned, MEM_RESERVE, PAGE_NOACCESS) as _
}
pub unsafe fn vm_release(addr: *const u8, size_aligned: usize) {
VirtualFree(addr as _, size_aligned, MEM_RELEASE);
}
pub unsafe fn vm_commit(addr: *const u8, size_aligned: usize) {
VirtualAlloc(addr as _, size_aligned, MEM_COMMIT, PAGE_READWRITE);
}
pub unsafe fn vm_uncommit(addr: *const u8, size_aligned: usize) {
VirtualFree(addr as _, size_aligned, MEM_DECOMMIT);
}
pub unsafe fn os_page_size() -> usize {
let mut system_info = SystemInfo {
dummy: DummySystemInfoUnion { dwOemId: 0 },
dwPageSize: 0,
lpMinimumApplicationAddress: ptr::null(),
lpMaximumApplicationAddress: ptr::null(),
dwActiveProcessorMask: ptr::null(),
dwNumberOfProcessors: 0,
dwProcessorType: 0,
dwAllocationGranularity: 0,
wProcessorLevel: 0,
wProcessorRevision: 0,
};
GetSystemInfo(&mut system_info);
system_info.dwPageSize as usize
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_family = "windows")))]
compile_error!("Operating system not supported");
use std::{
cell::Cell,
fmt::{self, Debug},
marker::PhantomData,
mem,
ops::{Index, IndexMut},
slice,
};
#[cfg(target_family = "unix")]
use unix::*;
#[cfg(target_family = "windows")]
use windows::*;
const PAGES_PER_COMMIT: usize = 16;
pub const KIB: usize = 1024;
pub const MIB: usize = 1024 * KIB;
pub const GIB: usize = 1024 * MIB;
pub const TIB: usize = 1024 * GIB;
pub struct Arena {
base_addr: *const u8,
end_addr: *const u8,
bump_addr: Cell<*const u8>,
}
impl Arena {
pub fn new(addr_space_size: usize) -> Self {
unsafe {
let addr_space_size = ceil_align(addr_space_size, os_page_size());
let base_addr = vm_reserve(addr_space_size).cast_const();
let end_addr = base_addr.byte_add(addr_space_size);
let bump_addr = Cell::new(base_addr);
Arena {
base_addr,
end_addr,
bump_addr,
}
}
}
#[inline]
fn alloc_granularity() -> usize {
unsafe { os_page_size() * PAGES_PER_COMMIT }
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc<T>(&self, value: T) -> &mut T {
unsafe {
let ptr = self.alloc_region(mem::size_of::<T>(), mem::align_of::<T>()) as *mut T;
ptr.write(value);
&mut *ptr
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice<T>(&self, size: usize) -> &mut [T] {
unsafe {
let ptr = self.alloc_region(size * mem::size_of::<T>(), mem::align_of::<T>());
std::slice::from_raw_parts_mut(ptr as *mut T, size)
}
}
unsafe fn alloc_region(&self, size: usize, align: usize) -> *mut u8 {
let addr = ceil_align_ptr(self.bump_addr.get(), align);
let next_bump_addr = addr.byte_add(size);
if next_bump_addr > self.end_addr {
panic!("Arena is out of memory");
}
// commit pages we don't have yet
{
let alloc_granularity = Self::alloc_granularity();
let uncommitted_addr = ceil_align_ptr(self.bump_addr.get(), alloc_granularity);
if next_bump_addr >= uncommitted_addr {
let unaligned_commit_size = next_bump_addr.offset_from(uncommitted_addr) as usize;
let commit_size = ceil_align(unaligned_commit_size, alloc_granularity);
vm_commit(uncommitted_addr, commit_size);
}
}
self.bump_addr.set(next_bump_addr);
addr.cast_mut()
}
pub fn free_all(&mut self) {
unsafe {
let uncommitted_addr = ceil_align_ptr(self.bump_addr.get(), Self::alloc_granularity());
let uncommit_size = uncommitted_addr.offset_from(self.base_addr) as usize;
vm_uncommit(self.base_addr, uncommit_size);
}
self.bump_addr.set(self.base_addr);
}
}
#[inline]
unsafe fn ceil_align_ptr<T>(ptr: *const T, to: usize) -> *const T {
ceil_align(ptr as usize, to) as *const T
}
#[inline]
fn ceil_align(value: usize, to: usize) -> usize {
let aligned = value + to - 1;
aligned - aligned % to
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment