Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions score/allocator/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ rust_library(
srcs = glob(["**/*.rs"]),
edition = "2021",
visibility = ["//visibility:public"],
deps = [
"//score/log_rust/score_log",
"//score/pal",
],
)

rust_test(
Expand Down
4 changes: 4 additions & 0 deletions score/allocator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ license-file.workspace = true
[lib]
path = "lib.rs"

[dependencies]
pal.workspace = true
score_log.workspace = true

[lints]
workspace = true
3 changes: 2 additions & 1 deletion score/allocator/allocator_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion score/allocator/heap_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ 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};

/// Global allocator.
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 {
Expand Down
2 changes: 2 additions & 0 deletions score/allocator/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
284 changes: 284 additions & 0 deletions score/allocator/mmap_arena_allocator.rs
Original file line number Diff line number Diff line change
@@ -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
// <https://www.apache.org/licenses/LICENSE-2.0>
//
// 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<Self, AllocationError> {
// 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 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove?

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)]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replace with implementation aligned with ScoreDebug?

pub struct MmapArenaAllocator {
memory_region: MemoryRegion,
offset: Cell<usize>,
}

impl MmapArenaAllocator {
/// Create new allocator.
/// Capacity is rounded up to the nearest multiplication of a page size.
pub fn new(capacity: usize) -> Result<Self, AllocationError> {
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<NonNull<u8>, 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<u8>, _layout: Layout) {
panic!("deallocation is not supported");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This must be no-op.

}
}

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) };
}
}
1 change: 0 additions & 1 deletion score/pal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ rust_library(
edition = "2021",
visibility = ["//score:__subpackages__"],
deps = [
"//score/containers_rust:containers",
"//score/log_rust/score_log",
],
)
Expand Down
1 change: 0 additions & 1 deletion score/pal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ license-file.workspace = true
path = "lib.rs"

[dependencies]
containers.workspace = true
score_log.workspace = true

[lints]
Expand Down
Loading
Loading