diff --git a/Cargo.lock b/Cargo.lock index f74923e0e..b2377c9f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,10 @@ version = 4 [[package]] name = "allocator" version = "0.0.1" +dependencies = [ + "pal", + "score_log", +] [[package]] name = "containers" @@ -33,7 +37,6 @@ dependencies = [ name = "pal" version = "0.0.1" dependencies = [ - "containers", "score_log", ] diff --git a/score/allocator/BUILD b/score/allocator/BUILD index 8230f754e..8066e2ee9 100644 --- a/score/allocator/BUILD +++ b/score/allocator/BUILD @@ -18,6 +18,10 @@ rust_library( srcs = glob(["**/*.rs"]), edition = "2021", visibility = ["//visibility:public"], + deps = [ + "//score/log_rust/score_log", + "//score/pal", + ], ) rust_test( diff --git a/score/allocator/Cargo.toml b/score/allocator/Cargo.toml index 05fab2447..407f9e8d5 100644 --- a/score/allocator/Cargo.toml +++ b/score/allocator/Cargo.toml @@ -22,5 +22,9 @@ license-file.workspace = true [lib] path = "lib.rs" +[dependencies] +pal.workspace = true +score_log.workspace = true + [lints] workspace = true diff --git a/score/allocator/allocator_traits.rs b/score/allocator/allocator_traits.rs index 92e40f237..46a4cfb0e 100644 --- a/score/allocator/allocator_traits.rs +++ b/score/allocator/allocator_traits.rs @@ -13,9 +13,10 @@ use core::alloc::Layout; use core::ptr::NonNull; +use score_log::ScoreDebug; /// Allocation errors. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, ScoreDebug, Clone, Copy, PartialEq, Eq)] pub enum AllocationError { /// Memory allocation failed due to insufficient memory. OutOfMemory, diff --git a/score/allocator/heap_allocator.rs b/score/allocator/heap_allocator.rs index 5276791b8..876106c65 100644 --- a/score/allocator/heap_allocator.rs +++ b/score/allocator/heap_allocator.rs @@ -16,6 +16,7 @@ extern crate alloc; use alloc::alloc::{alloc, dealloc}; use core::alloc::Layout; use core::ptr::NonNull; +use score_log::ScoreDebug; use crate::allocator_traits::{AllocationError, BasicAllocator}; @@ -23,7 +24,7 @@ use crate::allocator_traits::{AllocationError, BasicAllocator}; pub static GLOBAL_ALLOCATOR: HeapAllocator = HeapAllocator; /// Proxy to global heap allocation in Rust. -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, ScoreDebug, Default, Clone, Copy)] pub struct HeapAllocator; impl BasicAllocator for HeapAllocator { diff --git a/score/allocator/lib.rs b/score/allocator/lib.rs index 0b04b933b..3025e0a4d 100644 --- a/score/allocator/lib.rs +++ b/score/allocator/lib.rs @@ -15,6 +15,8 @@ mod allocator_traits; mod heap_allocator; +mod mmap_arena_allocator; pub use allocator_traits::{AllocationError, BasicAllocator}; pub use heap_allocator::{HeapAllocator, GLOBAL_ALLOCATOR}; +pub use mmap_arena_allocator::{page_size, MmapArenaAllocator}; diff --git a/score/allocator/mmap_arena_allocator.rs b/score/allocator/mmap_arena_allocator.rs new file mode 100644 index 000000000..2d463eef1 --- /dev/null +++ b/score/allocator/mmap_arena_allocator.rs @@ -0,0 +1,284 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +//! mmap-backed arena allocator. + +use crate::{AllocationError, BasicAllocator}; +use core::alloc::Layout; +use core::cell::Cell; +use core::ptr::{null_mut, NonNull}; +use pal::{errno, mmap, munmap, sysconf, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE, _SC_PAGESIZE}; +use score_log::fmt::{DebugStruct, FormatSpec, Result as FmtResult, ScoreDebug, Writer}; + +/// Get current page size. +pub fn page_size() -> usize { + let result = unsafe { sysconf(_SC_PAGESIZE) }; + assert!(result >= 1, "page size must not be less than 1"); + result as usize +} + +/// Get aligned value - rounded up to a multiplication of the second value. +fn align_to(value: usize, align: usize) -> usize { + let mut v_div = value / align; + if !value.is_multiple_of(align) { + v_div += 1; + } + v_div * align +} + +/// mmap-backed memory region. +#[derive(Debug)] +struct MemoryRegion { + ptr: *mut u8, + capacity: usize, +} + +impl MemoryRegion { + /// Create new memory region using mmap. + /// Capacity of the region is rounded up to the nearest multiplication of a page size. + pub fn new(capacity: usize) -> Result { + // Disallow zero size allocation. + if capacity == 0 { + return Err(AllocationError::ZeroSizeAllocation); + } + + // Determine final capacity with regard to page size. + let capacity = align_to(capacity, page_size()); + + // Create a memory mapping. + let prot = PROT_READ | PROT_WRITE; + let flags = MAP_PRIVATE | MAP_ANONYMOUS; + let fd = -1; + let ptr = unsafe { mmap(null_mut(), capacity, prot, flags, fd, 0) }; + if ptr as isize == -1 || ptr.is_null() { + // TODO: information about errno is missing. + return Err(AllocationError::Internal); + } + + Ok(Self { + ptr: ptr.cast(), + capacity, + }) + } +} + +impl ScoreDebug for MemoryRegion { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> FmtResult { + DebugStruct::new(f, spec, "MemoryRegion") + .field("capacity", &self.capacity) + .finish() + } +} + +impl Drop for MemoryRegion { + fn drop(&mut self) { + let rc = unsafe { munmap(self.ptr.cast(), self.capacity) }; + if rc != 0 { + let errno = errno(); + panic!("munmap failed, rc: {rc}, errno: {errno}"); + } + } +} + +/// mmap-backed arena allocator. +/// +/// Allocated chunks are from a continuous chunk of memory obtained using mmap. +/// Deallocation is not supported. +#[derive(Debug)] +pub struct MmapArenaAllocator { + memory_region: MemoryRegion, + offset: Cell, +} + +impl MmapArenaAllocator { + /// Create new allocator. + /// Capacity is rounded up to the nearest multiplication of a page size. + pub fn new(capacity: usize) -> Result { + let memory_region = MemoryRegion::new(capacity)?; + let offset = Cell::new(0); + Ok(Self { memory_region, offset }) + } + + /// Capacity of the allocator. + pub fn capacity(&self) -> usize { + self.memory_region.capacity + } +} + +impl BasicAllocator for MmapArenaAllocator { + fn allocate(&self, layout: Layout) -> Result, AllocationError> { + // Disallow zero size allocation. + if layout.size() == 0 { + return Err(AllocationError::ZeroSizeAllocation); + } + + // Get aligned size. + let aligned_size = align_to(layout.size(), layout.align()); + + // Get current pointer position and align. + let current_offset = align_to(self.offset.get(), layout.align()); + + // Check if allocation will not exceed capacity. + let new_offset = current_offset + aligned_size; + if new_offset >= self.capacity() { + return Err(AllocationError::OutOfMemory); + } + + // Get pointer at given offset from memory region. + let ptr_as_non_null = unsafe { + let ptr_with_offset = self.memory_region.ptr.byte_offset(current_offset as isize); + NonNull::new_unchecked(ptr_with_offset) + }; + + // Set new offset. + self.offset.set(new_offset); + + Ok(ptr_as_non_null) + } + + unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) { + panic!("deallocation is not supported"); + } +} + +impl ScoreDebug for MmapArenaAllocator { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> FmtResult { + DebugStruct::new(f, spec, "MmapArenaAllocator") + .field("capacity", &self.capacity()) + .finish() + } +} + +#[cfg(test)] +mod tests { + use crate::{page_size, AllocationError, BasicAllocator, MmapArenaAllocator}; + use core::alloc::Layout; + + #[test] + fn test_page_size_ok() { + // Check no panic, exact size is platform-specific. + let _ = page_size(); + } + + #[test] + fn test_new_ok() { + let capacity = page_size(); + let result = MmapArenaAllocator::new(capacity); + assert!(result.is_ok()); + } + + #[test] + fn test_new_zero() { + let capacity = 0; + let result = MmapArenaAllocator::new(capacity); + assert!(result.is_err_and(|e| e == AllocationError::ZeroSizeAllocation)); + } + + #[test] + fn test_capacity_aligned_to_page_size() { + let capacity = 4 * page_size(); + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + assert_eq!(allocator.capacity(), capacity); + } + + #[test] + fn test_capacity_not_aligned_to_page_size() { + let expected_capacity = 4 * page_size(); + let capacity = expected_capacity - 123; + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + assert_eq!(allocator.capacity(), expected_capacity); + } + + #[test] + fn test_allocate_single() { + let capacity = 0x1000; + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + + // Allocate. + let layout = Layout::from_size_align(253, 4).unwrap(); + let result = allocator.allocate(layout.clone()); + assert!(result.is_ok()); + + // Check offset. + assert_eq!(allocator.offset.get(), 256); + } + + #[test] + fn test_allocate_multiple() { + let capacity = 0x1000; + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + + // Cases to check, contains: + // - layout + // - allocation pointer offset from memory region start + // - internal allocator offset + let cases = vec![ + (Layout::from_size_align(253, 1).unwrap(), 0, 253), + (Layout::from_size_align(557, 2).unwrap(), 254, 812), + (Layout::from_size_align(39, 8).unwrap(), 816, 856), + ]; + + let memory_region_ptr = allocator.memory_region.ptr; + for (layout, exp_ptr_offset, exp_allocator_offset) in cases { + // Allocate. + let alloc_ptr = allocator.allocate(layout).unwrap().as_ptr(); + + // Check position relative to memory region and allocator offset. + let allocation_ptr_offset = unsafe { alloc_ptr.offset_from(memory_region_ptr) }; + assert_eq!(allocation_ptr_offset, exp_ptr_offset); + assert_eq!(allocator.offset.get(), exp_allocator_offset); + } + } + + #[test] + fn test_allocate_oom() { + let capacity = page_size(); + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + + // Allocate. + let layout = Layout::from_size_align(2 * page_size(), 4).unwrap(); + let result = allocator.allocate(layout.clone()); + assert!(result.is_err_and(|e| e == AllocationError::OutOfMemory)); + + // Check offset. + assert_eq!(allocator.offset.get(), 0); + } + + #[test] + fn test_allocate_zero() { + let capacity = page_size(); + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + + // Allocate. + let layout = Layout::from_size_align(0, 4).unwrap(); + let result = allocator.allocate(layout.clone()); + assert!(result.is_err_and(|e| e == AllocationError::ZeroSizeAllocation)); + + // Check offset. + assert_eq!(allocator.offset.get(), 0); + } + + #[test] + #[should_panic(expected = "deallocation is not supported")] + fn test_deallocate_panic() { + let capacity = page_size(); + let allocator = MmapArenaAllocator::new(capacity).unwrap(); + + // Allocate. + let layout = Layout::from_size_align(253, 4).unwrap(); + let ptr = allocator.allocate(layout.clone()).unwrap(); + + // Deallocate. + unsafe { allocator.deallocate(ptr, layout) }; + } +} diff --git a/score/pal/BUILD b/score/pal/BUILD index 32468b5f9..f5afc226f 100644 --- a/score/pal/BUILD +++ b/score/pal/BUILD @@ -19,7 +19,6 @@ rust_library( edition = "2021", visibility = ["//score:__subpackages__"], deps = [ - "//score/containers_rust:containers", "//score/log_rust/score_log", ], ) diff --git a/score/pal/Cargo.toml b/score/pal/Cargo.toml index 6b20b75ee..e910986e8 100644 --- a/score/pal/Cargo.toml +++ b/score/pal/Cargo.toml @@ -24,7 +24,6 @@ license-file.workspace = true path = "lib.rs" [dependencies] -containers.workspace = true score_log.workspace = true [lints] diff --git a/score/pal/affinity.rs b/score/pal/affinity.rs index feefd370c..eebd25a98 100644 --- a/score/pal/affinity.rs +++ b/score/pal/affinity.rs @@ -14,8 +14,9 @@ //! Affinity handling differs between Linux and QNX. //! Module ensures similar behavior between both OSes. +extern crate alloc; + use crate::{c_int, errno}; -use containers::fixed_capacity::FixedCapacityVec; use score_log::ScoreDebug; #[cfg(target_os = "linux")] @@ -78,28 +79,24 @@ impl CpuSet { mask } - /// Create list based on provided mask. - fn create_list(mask: &[u8; CPU_MASK_SIZE]) -> FixedCapacityVec { - let mut list = FixedCapacityVec::new(MAX_CPU_NUM); + pub fn set(&mut self, affinity: &[usize]) { + self.mask = Self::create_mask(affinity); + } + + pub fn get(&self) -> Box<[usize]> { + let mut cpu_id_array = [0usize; MAX_CPU_NUM]; + let mut counter = 0; for cpu_id in 0..MAX_CPU_NUM { let index = cpu_id / 8; let offset = cpu_id % 8; - if (mask[index] & (1 << offset)) != 0 { - // Error should not occur, since capacity is matching the mask size. - list.push(cpu_id).expect("failed to push CPU ID to the list"); + if (self.mask[index] & (1 << offset)) != 0 { + cpu_id_array[counter] = cpu_id; + counter += 1; } } - list - } - - pub fn set(&mut self, affinity: &[usize]) { - self.mask = Self::create_mask(affinity); - } - - pub fn get(&self) -> FixedCapacityVec { - Self::create_list(&self.mask) + Box::from(&cpu_id_array[..counter]) } } @@ -249,7 +246,7 @@ pub fn set_affinity(cpu_set: CpuSet) { } /// Get affinity of a current thread. -pub fn get_affinity() -> FixedCapacityVec { +pub fn get_affinity() -> Box<[usize]> { #[cfg(target_os = "linux")] { let mut native_cpu_set = core::mem::MaybeUninit::zeroed(); diff --git a/score/pal/lib.rs b/score/pal/lib.rs index 8397f844a..92d66360c 100644 --- a/score/pal/lib.rs +++ b/score/pal/lib.rs @@ -27,6 +27,7 @@ pub type c_long = core::ffi::c_long; pub type c_ulong = core::ffi::c_ulong; pub type size_t = usize; pub type c_void = core::ffi::c_void; +pub type off_t = i64; pub type pid_t = i32; pub type pthread_t = c_ulong; @@ -61,6 +62,8 @@ pub struct sched_param { } extern "C" { + pub fn mmap(addr: *mut c_void, len: size_t, prot: c_int, flags: c_int, fd: c_int, offset: off_t) -> *mut c_void; + pub fn munmap(addr: *mut c_void, len: size_t) -> c_int; pub fn sysconf(__name: c_int) -> c_long; pub fn pthread_self() -> pthread_t; pub fn pthread_join(native: pthread_t, value: *mut *mut c_void) -> c_int; @@ -98,3 +101,32 @@ pub const PTHREAD_INHERIT_SCHED: c_int = 0; pub const PTHREAD_EXPLICIT_SCHED: c_int = 1; #[cfg(target_os = "nto")] pub const PTHREAD_EXPLICIT_SCHED: c_int = 2; + +#[cfg(target_os = "linux")] +pub const _SC_PAGESIZE: c_int = 30; +#[cfg(target_os = "nto")] +pub const _SC_PAGESIZE: c_int = 11; + +#[cfg(target_os = "linux")] +pub const PROT_NONE: c_int = 0; +#[cfg(target_os = "linux")] +pub const PROT_READ: c_int = 1; +#[cfg(target_os = "linux")] +pub const PROT_WRITE: c_int = 2; +#[cfg(target_os = "linux")] +pub const PROT_EXEC: c_int = 4; + +#[cfg(target_os = "nto")] +pub const PROT_NONE: c_int = 0x00000000; +#[cfg(target_os = "nto")] +pub const PROT_READ: c_int = 0x00000100; +#[cfg(target_os = "nto")] +pub const PROT_WRITE: c_int = 0x00000200; +#[cfg(target_os = "nto")] +pub const PROT_EXEC: c_int = 0x00000400; + +pub const MAP_PRIVATE: c_int = 0x0002; +#[cfg(target_os = "linux")] +pub const MAP_ANONYMOUS: c_int = 0x0020; +#[cfg(target_os = "nto")] +pub const MAP_ANONYMOUS: c_int = 0x00080000;