diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b59e9ef..7691c50e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -100,8 +100,12 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, + "editor.rulers": [ + 150 + ] }, "rust-analyzer.cargo.cfgs": [ "!miri" ], + "rust-analyzer.check.command": "clippy" } diff --git a/Cargo.lock b/Cargo.lock index b9a7033c..6ea2d488 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,10 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "containers" +version = "0.1.0" + [[package]] name = "log" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index eaf4500b..34c94b2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,15 @@ resolver = "2" # Split to default members without tests and examples. # Used when executing cargo from project root. -default-members = ["src/log"] +default-members = [ + "src/containers", + "src/log" +] # Include tests and examples as a member for IDE support and Bazel builds. -members = ["src/log"] +members = [ + "src/containers", + "src/log" +] [workspace.package] diff --git a/rustfmt.toml b/rustfmt.toml index d1f04540..a8cd55ec 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,5 +2,7 @@ # check configuration fields here: https://rust-lang.github.io/rustfmt/?version=v1.6.0&search= -tab_spaces = 4 +match_block_trailing_comma = true max_width = 150 +tab_spaces = 4 +use_field_init_shorthand = true diff --git a/src/containers/BUILD b/src/containers/BUILD new file mode 100644 index 00000000..17b83c55 --- /dev/null +++ b/src/containers/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "containers", + srcs = glob(["**/*.rs"]), + edition = "2024", + visibility = ["//visibility:public"], +) + +rust_test( + name = "tests", + crate = "containers", + tags = [ + "unit_tests", + "ut", + ], +) diff --git a/src/containers/Cargo.toml b/src/containers/Cargo.toml new file mode 100644 index 00000000..88bab7ab --- /dev/null +++ b/src/containers/Cargo.toml @@ -0,0 +1,23 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +[package] +name = "containers" +description = "Fixed-capacity and inline-storage containers" +version = "0.1.0" +authors = ["Contributors to the Eclipse Foundation"] +edition = "2024" +license-file = "../../LICENSE.md" + +[lib] +path = "lib.rs" diff --git a/src/containers/fixed_capacity/mod.rs b/src/containers/fixed_capacity/mod.rs new file mode 100644 index 00000000..5447aa2a --- /dev/null +++ b/src/containers/fixed_capacity/mod.rs @@ -0,0 +1,16 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +mod vec; + +pub use self::vec::FixedCapacityVec; diff --git a/src/containers/fixed_capacity/vec.rs b/src/containers/fixed_capacity/vec.rs new file mode 100644 index 00000000..1ed7036a --- /dev/null +++ b/src/containers/fixed_capacity/vec.rs @@ -0,0 +1,136 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +use core::fmt; +use core::ops; + +use crate::generic::vec::GenericVec; +use crate::storage::Heap; + +/// A fixed-capacity vector. +/// +/// The vector can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `Vec`, +/// except that it allocates memory immediately on construction, and can't shrink or grow. +pub struct FixedCapacityVec { + inner: GenericVec>, +} + +impl FixedCapacityVec { + /// Creates an empty vector and allocates memory for up to `capacity` elements, where `capacity <= u32::MAX`. + /// + /// # Panics + /// + /// - Panics if `capacity > u32::MAX`. + /// - Panics if the memory allocation fails. + #[must_use] + pub fn new(capacity: usize) -> Self { + assert!(capacity <= u32::MAX as usize, "FixedCapacityVec can hold at most u32::MAX elements"); + Self { + inner: GenericVec::new(capacity as u32), + } + } +} + +impl Drop for FixedCapacityVec { + fn drop(&mut self) { + self.inner.clear(); + } +} + +impl ops::Deref for FixedCapacityVec { + type Target = GenericVec>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ops::DerefMut for FixedCapacityVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl fmt::Debug for FixedCapacityVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_slice(), f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_and_pop() { + fn run_test(n: usize) { + let mut vector = FixedCapacityVec::::new(n); + let mut control = vec![]; + + let result = vector.pop(); + assert_eq!(result, None); + + for i in 0..n { + let value = i as i64 * 123 + 456; + let result = vector.push(value); + assert_eq!(*result.unwrap(), value); + control.push(value); + assert_eq!(vector.as_slice(), control.as_slice()); + } + + let result = vector.push(123456); + assert!(result.is_err()); + + for _ in 0..n { + let expected = control.pop().unwrap(); + let actual = vector.pop(); + assert_eq!(actual, Some(expected)); + } + + let result = vector.pop(); + assert_eq!(result, None); + } + + for i in 0..6 { + run_test(i); + } + } + + #[test] + fn is_full_and_is_empty() { + fn run_test(n: usize) { + let mut vector = FixedCapacityVec::::new(n); + assert!(vector.is_empty()); + + for i in 0..n { + assert!(!vector.is_full()); + vector.push(i as i64 * 123 + 456).unwrap(); + assert!(!vector.is_empty()); + } + + assert!(vector.is_full()); + + for _ in 0..n { + assert!(!vector.is_empty()); + vector.pop(); + assert!(!vector.is_full()); + } + + assert!(vector.is_empty()); + } + + for i in 0..6 { + run_test(i); + } + } +} diff --git a/src/containers/generic/mod.rs b/src/containers/generic/mod.rs new file mode 100644 index 00000000..3c2ac35f --- /dev/null +++ b/src/containers/generic/mod.rs @@ -0,0 +1,14 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +pub(crate) mod vec; diff --git a/src/containers/generic/vec.rs b/src/containers/generic/vec.rs new file mode 100644 index 00000000..eba64c0a --- /dev/null +++ b/src/containers/generic/vec.rs @@ -0,0 +1,143 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +use core::fmt; +use core::marker::PhantomData; +use core::mem::needs_drop; +use core::ops; +use core::ptr; + +use crate::storage::Storage; + +#[repr(C)] +pub struct GenericVec> { + len: u32, + storage: S, + _marker: PhantomData, +} + +impl> GenericVec { + /// Creates an empty vector. + pub fn new(capacity: u32) -> Self { + Self { + len: 0, + storage: S::new(capacity), + _marker: PhantomData, + } + } + + /// Extracts a slice containing the entire vector. + /// + /// Equivalent to `&v[..]`. + pub fn as_slice(&self) -> &[T] { + unsafe { &*self.storage.subslice(0, self.len) } + } + + /// Extracts a mutable slice of the entire vector. + /// + /// Equivalent to `&mut v[..]`. + pub fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { &mut *self.storage.subslice_mut(0, self.len) } + } + + /// Returns the maximum number of elements the vector can hold. + pub fn capacity(&self) -> usize { + self.storage.capacity() as usize + } + + /// Returns the current number of elements in the vector. + pub fn len(&self) -> usize { + self.len as usize + } + + /// Returns `true` if and only if the vector doesn't contain any elements. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns `true` if and only if the vector has reached its capacity. + pub fn is_full(&self) -> bool { + self.len() == self.capacity() + } + + /// Tries to push an element to the back of the vector. + /// + /// If the vector has spare capacity, the push succeeds and a reference to that element + /// is returned; otherwise, `Err(VectorFull)` is returned. + pub fn push(&mut self, value: T) -> Result<&mut T, VectorFull> { + if self.len < self.storage.capacity() { + let element = unsafe { self.storage.element_mut(self.len) }.write(value); + self.len += 1; + Ok(element) + } else { + Err(VectorFull) + } + } + + /// Tries to pop an element from the back of the vector. + /// + /// If the vector has at least one element, the pop succeeds; otherwise, `None` is returned. + pub fn pop(&mut self) -> Option { + if self.len > 0 { + let element = unsafe { self.storage.element(self.len - 1).assume_init_read() }; + self.len -= 1; + Some(element) + } else { + None + } + } + + /// Clears the vector, removing all values. + pub fn clear(&mut self) { + let len = self.len; + // Mark vector as empty before dropping elements, to prevent double-drop in case there's a panic in drop_in_place + self.len = 0; + if needs_drop::() { + unsafe { + ptr::drop_in_place(self.storage.subslice_mut(0, len)); + } + } + } +} + +impl> ops::Deref for GenericVec { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl> ops::DerefMut for GenericVec { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl> fmt::Debug for GenericVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_slice(), f) + } +} + +/// Indicates that an operation failed because the vector would exceed its maximum capacity. +#[derive(Clone, Copy, Default, Debug)] +pub struct VectorFull; + +impl fmt::Display for VectorFull { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "vector is full") + } +} + +impl core::error::Error for VectorFull {} diff --git a/src/containers/inline/mod.rs b/src/containers/inline/mod.rs new file mode 100644 index 00000000..996fab67 --- /dev/null +++ b/src/containers/inline/mod.rs @@ -0,0 +1,16 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +mod vec; + +pub use self::vec::InlineVec; diff --git a/src/containers/inline/vec.rs b/src/containers/inline/vec.rs new file mode 100644 index 00000000..2c8244f0 --- /dev/null +++ b/src/containers/inline/vec.rs @@ -0,0 +1,140 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +use core::fmt; +use core::ops; + +use crate::generic::vec::GenericVec; +use crate::storage::Inline; + +/// A fixed-capacity vector with inline storage. +/// +/// The vector can hold between 0 and `CAPACITY` elements, and behaves similarly to Rust's `Vec`, +/// except that it stores the elements inline and doesn't allocate. +/// +/// `CAPACITY` must be `>= 1` and `<= u32::MAX`. +#[repr(transparent)] +pub struct InlineVec { + inner: GenericVec>, +} + +impl InlineVec { + const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= u32::MAX as usize); + + /// Creates an empty vector. + pub fn new() -> Self { + let () = Self::CHECK_CAPACITY; + + Self { + inner: GenericVec::new(CAPACITY as u32), + } + } +} + +impl Default for InlineVec { + fn default() -> Self { + Self::new() + } +} + +impl ops::Deref for InlineVec { + type Target = GenericVec>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ops::DerefMut for InlineVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl fmt::Debug for InlineVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_slice(), f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_and_pop() { + fn run_test() { + let mut vector = InlineVec::::new(); + let mut control = vec![]; + + let result = vector.pop(); + assert_eq!(result, None); + + for i in 0..N { + let value = i as i64 * 123 + 456; + let result = vector.push(value); + assert_eq!(*result.unwrap(), value); + control.push(value); + assert_eq!(vector.as_slice(), control.as_slice()); + } + + let result = vector.push(123456); + assert!(result.is_err()); + + for _ in 0..N { + let expected = control.pop().unwrap(); + let actual = vector.pop(); + assert_eq!(actual, Some(expected)); + } + + let result = vector.pop(); + assert_eq!(result, None); + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn is_full_and_is_empty() { + fn run_test() { + let mut vector = InlineVec::::new(); + assert!(vector.is_empty()); + + for i in 0..N { + assert!(!vector.is_full()); + vector.push(i as i64 * 123 + 456).unwrap(); + assert!(!vector.is_empty()); + } + + assert!(vector.is_full()); + + for _ in 0..N { + assert!(!vector.is_empty()); + vector.pop(); + assert!(!vector.is_full()); + } + + assert!(vector.is_empty()); + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } +} diff --git a/src/containers/lib.rs b/src/containers/lib.rs new file mode 100644 index 00000000..4e106ab0 --- /dev/null +++ b/src/containers/lib.rs @@ -0,0 +1,21 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +pub mod fixed_capacity; +pub(crate) mod generic; +pub mod inline; +pub(crate) mod storage; diff --git a/src/containers/storage/heap.rs b/src/containers/storage/heap.rs new file mode 100644 index 00000000..bc355910 --- /dev/null +++ b/src/containers/storage/heap.rs @@ -0,0 +1,245 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +use alloc::alloc::Layout; +use alloc::alloc::alloc; +use alloc::alloc::dealloc; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ptr; +use core::ptr::NonNull; + +use super::Storage; + +/// Fixed-capacity, heap-allocated storage. +pub struct Heap { + /// Allocated capacity, in number of elements. + capacity: u32, + /// Pointer to the allocated memory. + /// + /// If `self.capacity > 0`, this points to an allocated memory area of size `self.capacity * size_of` and alignment `align_of`. + elements: NonNull, + _marker: PhantomData, +} + +impl Heap { + fn layout(capacity: u32) -> Option { + (capacity as usize) + .checked_mul(size_of::()) + .and_then(|size| Layout::from_size_align(size, align_of::()).ok()) + } +} + +impl Storage for Heap { + fn new(capacity: u32) -> Self { + let storage = if capacity > 0 { + let layout = Self::layout(capacity).unwrap_or_else(|| { + panic!( + "{capacity} elements of {typ} would overflow isize::MAX", + typ = core::any::type_name::(), + ) + }); + // SAFETY: `layout` has a non-zero size (because `capacity` is > 0) + NonNull::new(unsafe { alloc(layout) }) + .unwrap_or_else(|| panic!("failed to allocate {capacity} elements of {typ}", typ = core::any::type_name::())) + } else { + NonNull::dangling() + }; + Self { + capacity, + elements: storage.cast::(), + _marker: PhantomData, + } + } + + fn capacity(&self) -> u32 { + self.capacity + } + + unsafe fn element(&self, index: u32) -> &MaybeUninit { + debug_assert!(index < self.capacity); + let index = index as usize; + // SAFETY: + // - `index` is in-bounds of the memory allocation, as per the pre-condition on the trait method + // - `MaybeUninit` has the same memory layout as `T`, so the cast is valid + unsafe { self.elements.add(index).cast::>().as_ref() } + } + + unsafe fn element_mut(&mut self, index: u32) -> &mut MaybeUninit { + debug_assert!(index < self.capacity); + let index = index as usize; + // SAFETY: + // - `index` is in-bounds of the memory allocation, as per the pre-condition on the trait method + // - `MaybeUninit` has the same memory layout as `T`, so the cast is valid + unsafe { self.elements.add(index).cast::>().as_mut() } + } + + unsafe fn subslice(&self, start: u32, end: u32) -> *const [T] { + let start = start as usize; + let end = end as usize; + debug_assert!(start <= end); + debug_assert!(end <= self.capacity as usize); + // SAFETY: `start` is in-bounds of the memory allocation, as per the pre-condition on the trait method. + let ptr = unsafe { self.elements.as_ptr().add(start) }; + ptr::slice_from_raw_parts(ptr, end - start) + } + + unsafe fn subslice_mut(&mut self, start: u32, end: u32) -> *mut [T] { + let start = start as usize; + let end = end as usize; + debug_assert!(start <= end); + debug_assert!(end <= self.capacity as usize); + // SAFETY: `start` is in-bounds of the memory allocation, as per the pre-condition on the trait method. + let ptr = unsafe { self.elements.as_ptr().add(start) }; + ptr::slice_from_raw_parts_mut(ptr, end - start) + } +} + +impl Drop for Heap { + fn drop(&mut self) { + if self.capacity > 0 { + let layout = Self::layout(self.capacity).unwrap(); + // SAFETY: + // - `self.elements` has previously been allocated with `alloc` + // - `layout` is the same as the one used for the allocation + unsafe { + dealloc(self.elements.as_ptr().cast::(), layout); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn subslice() { + type T = u64; + + fn run_test(capacity: u32) { + let instance = Heap::::new(capacity); + + let empty_slice = unsafe { instance.subslice(0, 0) }; + assert_eq!(empty_slice.len(), 0); + assert_eq!(empty_slice as *const T, instance.elements.as_ptr()); + + let full_slice = unsafe { instance.subslice(0, capacity) }; + assert_eq!(full_slice.len(), capacity as usize); + assert_eq!(full_slice as *const T, instance.elements.as_ptr()); + + if capacity > 2 { + let partial_slice = unsafe { instance.subslice(1, 2) }; + assert_eq!(partial_slice.len(), 1); + assert_eq!(partial_slice as *const T, instance.elements.as_ptr().wrapping_add(1)); + + let end_slice = unsafe { instance.subslice(capacity - 1, capacity) }; + assert_eq!(end_slice.len(), 1); + assert_eq!(end_slice as *const T, instance.elements.as_ptr().wrapping_add(capacity as usize - 1)); + } + } + + for cap in [0, 1, 2, 3, 4, 5, i32::MAX as u32 / size_of::() as u32] { + run_test(cap); + } + } + + #[test] + fn subslice_mut() { + type T = u64; + + fn run_test(capacity: u32) { + let mut instance = Heap::::new(capacity); + + let empty_slice = unsafe { instance.subslice_mut(0, 0) }; + assert_eq!(empty_slice.len(), 0); + assert_eq!(empty_slice as *mut T, instance.elements.as_ptr()); + + let full_slice = unsafe { instance.subslice_mut(0, capacity) }; + assert_eq!(full_slice.len(), capacity as usize); + assert_eq!(full_slice as *mut T, instance.elements.as_ptr()); + + if capacity >= 2 { + let partial_slice = unsafe { instance.subslice_mut(1, 2) }; + assert_eq!(partial_slice.len(), 1); + assert_eq!(partial_slice as *mut T, instance.elements.as_ptr().wrapping_add(1)); + + let end_slice = unsafe { instance.subslice_mut(capacity - 1, capacity) }; + assert_eq!(end_slice.len(), 1); + assert_eq!(end_slice as *mut T, instance.elements.as_ptr().wrapping_add(capacity as usize - 1)); + } + } + + for cap in [0, 1, 2, 3, 4, 5, i32::MAX as u32 / size_of::() as u32] { + run_test(cap); + } + } + + #[test] + fn element() { + type T = u64; + + fn run_test(capacity: u32) { + let instance = Heap::::new(capacity); + + if capacity >= 1 { + let first_element = unsafe { instance.element(0) }; + assert_eq!(first_element.as_ptr(), instance.elements.as_ptr()); + + let last_element = unsafe { instance.element(capacity - 1) }; + assert_eq!(last_element.as_ptr(), instance.elements.as_ptr().wrapping_add(capacity as usize - 1)); + } + + if capacity >= 2 { + let second_element = unsafe { instance.element(1) }; + assert_eq!(second_element.as_ptr(), instance.elements.as_ptr().wrapping_add(1)); + + let last_element = unsafe { instance.element(capacity - 2) }; + assert_eq!(last_element.as_ptr(), instance.elements.as_ptr().wrapping_add(capacity as usize - 2)); + } + } + + for cap in [0, 1, 2, 3, 4, 5, i32::MAX as u32 / size_of::() as u32] { + run_test(cap); + } + } + + #[test] + fn element_mut() { + type T = u64; + + fn run_test(capacity: u32) { + let mut instance = Heap::::new(capacity); + + if capacity >= 1 { + let first_element = unsafe { instance.element_mut(0) }; + assert_eq!(first_element.as_ptr(), instance.elements.as_ptr()); + + let last_element = unsafe { instance.element_mut(capacity - 1) }; + assert_eq!(last_element.as_ptr(), instance.elements.as_ptr().wrapping_add(capacity as usize - 1)); + } + + if capacity >= 2 { + let second_element = unsafe { instance.element_mut(1) }; + assert_eq!(second_element.as_ptr(), instance.elements.as_ptr().wrapping_add(1)); + + let last_element = unsafe { instance.element_mut(capacity - 2) }; + assert_eq!(last_element.as_ptr(), instance.elements.as_ptr().wrapping_add(capacity as usize - 2)); + } + } + + for cap in [0, 1, 2, 3, 4, 5, i32::MAX as u32 / size_of::() as u32] { + run_test(cap); + } + } +} diff --git a/src/containers/storage/inline.rs b/src/containers/storage/inline.rs new file mode 100644 index 00000000..68c67ffb --- /dev/null +++ b/src/containers/storage/inline.rs @@ -0,0 +1,245 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +use core::mem::MaybeUninit; +use core::ptr; + +use super::Storage; + +/// Fixed-capacity, inline storage, suitable for ABI compatible containers. +/// +/// `CAPACITY` is in number of elements, not bytes. +/// It must not be zero (for compatibility with C++), and it must be `<= u32::MAX`. +pub struct Inline { + elements: [MaybeUninit; CAPACITY], +} + +impl Inline { + // Compile-time check. This condition _must_ be referenced in every function that depends on it, + // otherwise it will be removed during monomorphization. + const CHECK_CAPACITY: () = assert!(0 < CAPACITY && CAPACITY <= (u32::MAX as usize)); +} + +impl Storage for Inline { + fn new(capacity: u32) -> Self { + let () = Self::CHECK_CAPACITY; + + assert_eq!(capacity as usize, CAPACITY); + Self { + elements: [const { MaybeUninit::uninit() }; CAPACITY], + } + } + + fn capacity(&self) -> u32 { + let () = Self::CHECK_CAPACITY; + + CAPACITY as u32 + } + + unsafe fn element(&self, index: u32) -> &MaybeUninit { + let () = Self::CHECK_CAPACITY; + + let index = index as usize; + debug_assert!(index < CAPACITY); + // SAFETY: `index` is in-bounds of the array, as per the pre-condition on the trait method. + unsafe { self.elements.get_unchecked(index) } + } + + unsafe fn element_mut(&mut self, index: u32) -> &mut MaybeUninit { + let () = Self::CHECK_CAPACITY; + + let index = index as usize; + debug_assert!(index < CAPACITY); + // SAFETY: `index` is in-bounds of the array, as per the pre-condition on the trait method. + unsafe { self.elements.get_unchecked_mut(index) } + } + + unsafe fn subslice(&self, start: u32, end: u32) -> *const [T] { + let () = Self::CHECK_CAPACITY; + + let start = start as usize; + let end = end as usize; + debug_assert!(start <= end); + debug_assert!(end <= CAPACITY); + // SAFETY: `start` is in-bounds of the array, as per the pre-condition on the trait method. + let ptr = unsafe { self.elements.as_ptr().add(start) }; + ptr::slice_from_raw_parts(ptr.cast::(), end - start) + } + + unsafe fn subslice_mut(&mut self, start: u32, end: u32) -> *mut [T] { + let () = Self::CHECK_CAPACITY; + + let start = start as usize; + let end = end as usize; + debug_assert!(start <= end); + debug_assert!(end <= CAPACITY); + // SAFETY: `start` is in-bounds of the array, as per the pre-condition on the trait method. + let ptr = unsafe { self.elements.as_mut_ptr().add(start) }; + ptr::slice_from_raw_parts_mut(ptr.cast::(), end - start) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn subslice() { + type T = u64; + + fn run_test() { + let capacity = N as u32; + let instance = Inline::::new(capacity); + + let empty_slice = unsafe { instance.subslice(0, 0) }; + assert_eq!(empty_slice.len(), 0); + assert_eq!(empty_slice as *const T, instance.elements.as_ptr() as *const T); + + let full_slice = unsafe { instance.subslice(0, capacity) }; + assert_eq!(full_slice.len(), capacity as usize); + assert_eq!(full_slice as *const T, instance.elements.as_ptr() as *const T); + + if capacity > 2 { + let partial_slice = unsafe { instance.subslice(1, 2) }; + assert_eq!(partial_slice.len(), 1); + assert_eq!(partial_slice as *const T, instance.elements.as_ptr().wrapping_add(1) as *const T); + + let end_slice = unsafe { instance.subslice(capacity - 1, capacity) }; + assert_eq!(end_slice.len(), 1); + assert_eq!( + end_slice as *const T, + instance.elements.as_ptr().wrapping_add(capacity as usize - 1) as *const T + ); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn subslice_mut() { + type T = u64; + + fn run_test() { + let capacity = N as u32; + let mut instance = Inline::::new(capacity); + + let empty_slice = unsafe { instance.subslice_mut(0, 0) }; + assert_eq!(empty_slice.len(), 0); + assert_eq!(empty_slice as *mut T, instance.elements.as_ptr() as *mut T); + + let full_slice = unsafe { instance.subslice_mut(0, capacity) }; + assert_eq!(full_slice.len(), capacity as usize); + assert_eq!(full_slice as *mut T, instance.elements.as_ptr() as *mut T); + + if capacity >= 2 { + let partial_slice = unsafe { instance.subslice_mut(1, 2) }; + assert_eq!(partial_slice.len(), 1); + assert_eq!(partial_slice as *mut T, instance.elements.as_ptr().wrapping_add(1) as *mut T); + + let end_slice = unsafe { instance.subslice_mut(capacity - 1, capacity) }; + assert_eq!(end_slice.len(), 1); + assert_eq!( + end_slice as *mut T, + instance.elements.as_ptr().wrapping_add(capacity as usize - 1) as *mut T + ); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn element() { + type T = u64; + + fn run_test() { + let capacity = N as u32; + let instance = Inline::::new(capacity); + + if capacity >= 1 { + let first_element = unsafe { instance.element(0) }; + assert_eq!(first_element.as_ptr(), instance.elements.as_ptr() as *const T); + + let last_element = unsafe { instance.element(capacity - 1) }; + assert_eq!( + last_element.as_ptr(), + instance.elements.as_ptr().wrapping_add(capacity as usize - 1) as *const T, + ); + } + + if capacity >= 2 { + let second_element = unsafe { instance.element(1) }; + assert_eq!(second_element.as_ptr(), instance.elements.as_ptr().wrapping_add(1) as *const T); + + let last_element = unsafe { instance.element(capacity - 2) }; + assert_eq!( + last_element.as_ptr(), + instance.elements.as_ptr().wrapping_add(capacity as usize - 2) as *const T, + ); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } + + #[test] + fn element_mut() { + type T = u64; + + fn run_test() { + let capacity = N as u32; + let mut instance = Inline::::new(capacity); + + if capacity >= 1 { + let first_element = unsafe { instance.element_mut(0) }; + assert_eq!(first_element.as_ptr(), instance.elements.as_ptr() as *mut T); + + let last_element = unsafe { instance.element_mut(capacity - 1) }; + assert_eq!( + last_element.as_ptr(), + instance.elements.as_ptr().wrapping_add(capacity as usize - 1) as *mut T, + ); + } + + if capacity >= 2 { + let second_element = unsafe { instance.element_mut(1) }; + assert_eq!(second_element.as_ptr(), instance.elements.as_ptr().wrapping_add(1) as *mut T); + + let last_element = unsafe { instance.element_mut(capacity - 2) }; + assert_eq!( + last_element.as_ptr(), + instance.elements.as_ptr().wrapping_add(capacity as usize - 2) as *mut T, + ); + } + } + + run_test::<1>(); + run_test::<2>(); + run_test::<3>(); + run_test::<4>(); + run_test::<5>(); + } +} diff --git a/src/containers/storage/mod.rs b/src/containers/storage/mod.rs new file mode 100644 index 00000000..031a0d69 --- /dev/null +++ b/src/containers/storage/mod.rs @@ -0,0 +1,57 @@ +// ******************************************************************************* +// Copyright (c) 2025 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 +// ******************************************************************************* + +mod heap; +mod inline; + +pub use self::heap::Heap; +pub use self::inline::Inline; + +use core::mem::MaybeUninit; + +/// Interface to abstract over element storage kinds. +pub trait Storage { + /// Creates a new instance with enough capacity for the given number of elements. + fn new(capacity: u32) -> Self; + + /// Returns the allocated capacity. + fn capacity(&self) -> u32; + + /// Returns a `const` pointer to a specific element, which isn't necessarily initialized. + /// + /// # Safety + /// + /// `index < self.capacity()` must hold. + unsafe fn element(&self, index: u32) -> &MaybeUninit; + + /// Returns a `mut` pointer to a specific element, which isn't necessarily initialized. + /// + /// # Safety + /// + /// `index < self.capacity()` must hold. + unsafe fn element_mut(&mut self, index: u32) -> &mut MaybeUninit; + + /// Returns a pointer to a subslice of elements, which aren't necessarily initialized. + /// + /// # Safety + /// + /// `start <= end <= self.capacity()` must hold. + unsafe fn subslice(&self, start: u32, end: u32) -> *const [T]; + + /// Returns a pointer to a mutable subslice of elements, which aren't necessarily initialized. + /// + /// # Safety + /// + /// `start <= end <= self.capacity()` must hold. + unsafe fn subslice_mut(&mut self, start: u32, end: u32) -> *mut [T]; +}