diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 73ed957..d272c04 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,21 +2,27 @@ name: Rust on: push: - branches: [ "trunk" ] pull_request: - branches: [ "trunk" ] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v3 + - run: rustup update nightly && rustup default nightly + - name: Build default + run: cargo build --verbose + - name: Build no_std + run: cargo build --verbose --no-default-features --features no_std + - name: Build no_std with error_in_core + run: cargo build --verbose --no-default-features --features no_std,error_in_core + - name: Build with ptr_metadata + run: cargo build --verbose --features ptr_metadata + - name: Build restrictive + run: cargo build --verbose --no-default-features --features std + - name: Run tests + run: cargo test --verbose --features ptr_metadata diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0341177 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,64 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'contiguous-mem'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=contiguous-mem" + ], + "filter": { + "name": "contiguous-mem", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'ptr_metadata'", + "cargo": { + "args": [ + "build", + "--example=ptr_metadata", + "--package=contiguous-mem" + ], + "filter": { + "name": "ptr_metadata", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'ptr_metadata'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=ptr_metadata", + "--package=contiguous-mem" + ], + "filter": { + "name": "ptr_metadata", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e84cdc0..94cbf0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,20 @@ categories = [ ] repository = "https://github.com/Caellian/contiguous_mem" +[[example]] +name = "ptr_metadata" +path = "examples/ptr_metadata.rs" +required-features = ["ptr_metadata"] + [dependencies] portable-atomic = { version = "1", default-features = false } spin = { version = "0.9", optional = true } [features] -default = ["std"] +default = ["std", "leak_data"] std = ["portable-atomic/std"] no_std = ["dep:spin"] debug = [] +leak_data = [] ptr_metadata = [] error_in_core = [] diff --git a/README.md b/README.md index d01e45f..375b2e9 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,34 @@ contiguous_mem implements a contiguous memory storage container for arbitrary data types. +[![CI](https://github.com/Caellian/contiguous_mem/actions/workflows/rust.yml/badge.svg)](https://github.com/Caellian/contiguous_mem/actions/workflows/rust.yml) [![Crates.io](https://img.shields.io/crates/v/contiguous_mem)](https://crates.io/crates/contiguous_mem) [![Documentation](https://docs.rs/contiguous_mem/badge.svg)](https://docs.rs/contiguous_mem) -Designed for both standard and no_std environments, this library ensures efficient memory allocation while being simple and (somewhat) safe to use. +Designed for both standard and no_std environments, this library ensures efficient memory +allocation while being simple and (somewhat) safe to use. + +## Tradeoffs + +- Works without nightly but leaks data, enable `ptr_metadata` or disable default `leak_data` + feature flag if memory leaks are an issue: + + - `ptr_metadata` requires nightly, + - disabling `leak_data` imposes `Copy` requirement on stored types. + +- References returned by `store` function follow the same borrow restrictions as the + language, `Deref` is implemented for `ContiguousMemoryRef` but it will panic on + dereference if it's been already mutably borrowed somewhere else. + Use `ContiguousMemoryRef::try_get` if you'd like to handle that properly. ## Key Features - Type Agnostic: Support for various data types, including mixed types within the same container. - Multiple Implementations: Choose from specialized strategies to match your requirements: - - SyncContiguousMemory (ThreadSafeImpl): Enables asynchronous data access, ensuring safety in concurrent scenarios. - - GrowableContiguousMemory (NotThreadSafeImpl): Synchronous, mutex-free implementation for speed and dynamic resizing. - - FixedContiguousMemory (FixedSizeImpl): Highly optimized but unsafe for precise sizing and long-lived references. + - SyncContiguousMemory (ImplConcurrent): Enables asynchronous data access, ensuring safety in concurrent scenarios. + - GrowableContiguousMemory (ImplDefault): Synchronous, mutex-free implementation for speed and dynamic resizing. + - FixedContiguousMemory (ImplFixed): Highly optimized but unsafe for precise sizing and long-lived references. ## Getting Started @@ -22,7 +37,7 @@ Add the crate to your dependencies: ```toml [dependencies] -contiguous_mem = "0.2.*" +contiguous_mem = { version = "0.2.*" } ``` Optionally disable the `std` feature and enable `no_std` feature to use in `no_std` environment: @@ -32,7 +47,19 @@ Optionally disable the `std` feature and enable `no_std` feature to use in `no_s contiguous_mem = { version = "0.2.*", default-features = false, features = ["no_std"] } ``` -### Example usage +### Features + +- `std` (**default**) - enables support for `std` environment +- `no_std` - enables support for `no_std` environment +- `leak_data` (**default**) - disables `Copy` requirement for stored types, but any + references in stored data will be leaked when the memory container is dropped +- `debug` - enables `derive(Debug)` on structures unrelated to error handling +- `ptr_metadata` <_nightly_> - enables support for casting returned references + into `dyn Trait` types as well as cleaning up any types that implement `Drop` + or generate drop glue +- `error_in_core` <_nightly_> - enables support for `core::error::Error` in `no_std` environment + +### Usage ```rust use contiguous_mem::GrowableContiguousMemory; @@ -42,30 +69,20 @@ struct Data { } fn main() { - // Create a ContiguousMemory instance with a capacity of 1024 bytes and 8-byte alignment - let mut memory = GrowableContiguousMemory::new(1024, 8).unwrap(); + // Create a ContiguousMemory instance with a capacity of 1024 bytes and 1-byte alignment + let mut memory = GrowableContiguousMemory::new(1024); // Store data in the memory container let data = Data { value: 42 }; - let stored_number = memory.store(22u64).unwrap(); - let stored_data = memory.store(data).unwrap(); + let stored_number = memory.store(22u64); + let stored_data = memory.store(data); // Retrieve and use the stored data - let retrieved_data = stored_data.get().unwrap(); - println!("Retrieved data: {}", retrieved_data.value); - let retrieved_number = stored_number.get().unwrap(); - println!("Retrieved number: {}", retrieved_number); + println!("Retrieved data: {}", *stored_data); + println!("Retrieved number: {}", *stored_number); } ``` -### Features - -- `std` (default) - enables support for `std` environment -- `no_std` - enables support for `no_std` environment -- `debug` - enables `derive(Debug)` on structures -- `ptr_metadata` - enables support for casting returned references into `dyn Trait` types -- `error_in_core` - enables support for `core::error::Error` in `no_std` environment - ## Contributions Contributions are welcome, feel free to create an issue or a pull request. diff --git a/examples/ptr_metadata.rs b/examples/ptr_metadata.rs new file mode 100644 index 0000000..a8affda --- /dev/null +++ b/examples/ptr_metadata.rs @@ -0,0 +1,40 @@ +#![feature(ptr_metadata)] + +use contiguous_mem::{ + ptr_metadata_ext::static_metadata, ContiguousMemoryRef, GrowableContiguousMemory, +}; + +trait Greetable { + fn print_hello(&self); +} + +struct Person(String); +impl Greetable for Person { + fn print_hello(&self) { + println!("Saying hello to person: {}", self.0); + } +} + +struct Dog(String); +impl Greetable for Dog { + fn print_hello(&self) { + println!("Saying hello to dog: {}", self.0); + } +} + +fn main() { + let mut storage = GrowableContiguousMemory::new(4096); + let person1 = storage.store(Person("Joe".to_string())); + + let person2: ContiguousMemoryRef = storage + .store(Person("Craig".to_string())) + .as_dyn(static_metadata::()); + + let dog: ContiguousMemoryRef = storage + .store(Dog("Rover".to_string())) + .as_dyn(static_metadata::()); + + person1.print_hello(); + person2.print_hello(); + dog.print_hello(); +} diff --git a/examples/traits.rs b/examples/traits.rs deleted file mode 100644 index 14ceb9e..0000000 --- a/examples/traits.rs +++ /dev/null @@ -1,38 +0,0 @@ -use contiguous_mem::{vtable, ContiguousMemoryRef, GrowableContiguousMemory}; - -trait Greetable { - fn print_hello(&self); -} - -struct Person(String); -impl Greetable for Person { - fn print_hello(&self) { - println!("Saying hello to person: {}", self.0); - } -} - -struct Dog(String); -impl Greetable for Dog { - fn print_hello(&self) { - println!("Saying hello to dog: {}", self.0); - } -} - -fn main() { - let mut storage = GrowableContiguousMemory::new(4096); - - let person1: ContiguousMemoryRef = unsafe { - storage - .store(Person("Joe".to_string())) - .expect("unable to store person1") - .as_dyn(vtable!(Person as Greetable)) - }; - - let person2 = storage - .store(Person("Craig".to_string())) - .expect("unable to store person2"); - - let dog = storage - .store(Dog("Rover".to_string())) - .expect("unable to store dog"); -} diff --git a/src/details.rs b/src/details.rs index df3b036..578ba62 100644 --- a/src/details.rs +++ b/src/details.rs @@ -3,10 +3,11 @@ use core::{ alloc::{Layout, LayoutError}, cell::{Cell, RefCell}, - marker::PhantomData, ptr::null_mut, }; +use core::marker::PhantomData; + use portable_atomic::AtomicUsize; use crate::{ @@ -17,6 +18,9 @@ use crate::{ ContiguousMemoryRef, ContiguousMemoryState, ReferenceState, SyncContiguousMemoryRef, }; +#[allow(unused_imports)] +use crate::ContiguousMemory; + /// Implementation details of [`ContiguousMemory`]. pub trait MemoryImpl: Sized { /// The type representing reference to internal state @@ -31,6 +35,9 @@ pub trait MemoryImpl: Sized { /// The type representing [`Layout`] entries with inner mutability. type SizeType; + /// The type representing result of storing data. + type StoreResult; + /// The type representing result of accessing data that is locked in async /// context type LockResult; @@ -68,6 +75,10 @@ pub trait MemoryImpl: Sized { new_capacity: usize, ) -> Result<(), ContiguousMemoryError>; + /// Shrinks tracked area of the allocation tracker to smallest that can fit + /// currently stored data. + fn shrink_tracker(state: &mut Self::State) -> Result, LockingError>; + /// Finds the next free memory region for given layout in the tracker. fn next_free( state: &mut Self::State, @@ -76,7 +87,7 @@ pub trait MemoryImpl: Sized { } /// A marker struct representing the behavior specialization for thread-safe -/// operations within [`ContiguousMemory`](crate::ContiguousMemory). This +/// operations within [`ContiguousMemory`]. This /// implementation ensures that the container's operations can be used safely in /// asynchronous contexts, utilizing mutexes to prevent data races. pub struct ImplConcurrent; @@ -85,6 +96,7 @@ impl MemoryImpl for ImplConcurrent { type Base = Mutex<*mut u8>; type AllocationTracker = Mutex; type SizeType = AtomicUsize; + type StoreResult = Result, LockingError>; type LockResult = Result; const USE_LOCKS: bool = true; @@ -130,7 +142,7 @@ impl MemoryImpl for ImplConcurrent { ) -> Result<*mut u8, ContiguousMemoryError> { let layout = Layout::from_size_align(Self::get_capacity(state), state.alignment)?; let mut lock = state.base.lock_named(MutexKind::BaseAddress)?; - *lock = unsafe { alloc::realloc(*lock, layout, new_capacity) }; + *lock = unsafe { allocator::realloc(*lock, layout, new_capacity) }; state .size .store(new_capacity, portable_atomic::Ordering::AcqRel); @@ -140,7 +152,7 @@ impl MemoryImpl for ImplConcurrent { #[inline(always)] fn deallocate(base: &Self::Base, layout: Layout) { if let Ok(mut lock) = base.lock_named(MutexKind::BaseAddress) { - unsafe { alloc::dealloc(*lock, layout) }; + unsafe { allocator::dealloc(*lock, layout) }; *lock = null_mut(); } } @@ -155,6 +167,12 @@ impl MemoryImpl for ImplConcurrent { Ok(()) } + #[inline(always)] + fn shrink_tracker(state: &mut Self::State) -> Result, LockingError> { + let mut lock = state.tracker.lock_named(MutexKind::AllocationTracker)?; + Ok(lock.shrink_to_fit()) + } + #[inline(always)] fn next_free( state: &mut Self::State, @@ -166,7 +184,7 @@ impl MemoryImpl for ImplConcurrent { } /// A marker struct representing the behavior specialization for operations -/// within [`ContiguousMemory`](crate::ContiguousMemory) that do not require +/// within [`ContiguousMemory`] that do not require /// thread-safety. This implementation skips mutexes, making it faster but /// unsuitable for concurrent usage. pub struct ImplDefault; @@ -175,6 +193,7 @@ impl MemoryImpl for ImplDefault { type Base = Cell<*mut u8>; type AllocationTracker = RefCell; type SizeType = Cell; + type StoreResult = ContiguousMemoryRef; type LockResult = T; #[inline(always)] @@ -214,7 +233,7 @@ impl MemoryImpl for ImplDefault { new_capacity: usize, ) -> Result<*mut u8, ContiguousMemoryError> { let layout = Layout::from_size_align(state.size.get(), state.alignment)?; - let value = unsafe { alloc::realloc(state.base.get(), layout, new_capacity) }; + let value = unsafe { allocator::realloc(state.base.get(), layout, new_capacity) }; state.base.set(value); state.size.set(new_capacity); Ok(value) @@ -222,7 +241,7 @@ impl MemoryImpl for ImplDefault { #[inline(always)] fn deallocate(base: &Self::Base, layout: Layout) { - unsafe { alloc::dealloc(base.get(), layout) }; + unsafe { allocator::dealloc(base.get(), layout) }; base.set(null_mut()) } @@ -234,6 +253,11 @@ impl MemoryImpl for ImplDefault { state.tracker.borrow_mut().resize(new_capacity) } + #[inline(always)] + fn shrink_tracker(state: &mut Self::State) -> Result, LockingError> { + Ok(state.tracker.borrow_mut().shrink_to_fit()) + } + #[inline(always)] fn next_free( state: &mut Self::State, @@ -249,7 +273,7 @@ impl MemoryImpl for ImplDefault { /// A marker struct representing the behavior specialization for a highly /// performance-optimized, yet unsafe implementation within -/// [`ContiguousMemory`](crate::ContiguousMemory). This trait is used when the +/// [`ContiguousMemory`]. This trait is used when the /// exact required size is known during construction, and when the container is /// guaranteed to outlive any pointers to data contained in the memory block. pub struct ImplFixed; @@ -258,8 +282,10 @@ impl MemoryImpl for ImplFixed { type Base = *mut u8; type AllocationTracker = AllocationTracker; type SizeType = usize; + type StoreResult = Result<*mut T, ContiguousMemoryError>; type LockResult = T; + #[inline(always)] fn build_state( base: *mut u8, capacity: usize, @@ -294,13 +320,13 @@ impl MemoryImpl for ImplFixed { _state: &mut Self::State, _new_capacity: usize, ) -> Result<*mut u8, ContiguousMemoryError> { - unimplemented!("can't reallocate ContiguousMemory with FixedSizeImpl"); + unimplemented!("can't reallocate ContiguousMemory with ImplFixed"); } #[inline(always)] fn deallocate(base: &Self::Base, layout: Layout) { unsafe { - alloc::dealloc(*base, layout); + allocator::dealloc(*base, layout); } } @@ -312,6 +338,11 @@ impl MemoryImpl for ImplFixed { Err(ContiguousMemoryError::NoStorageLeft) } + #[inline(always)] + fn shrink_tracker(state: &mut Self::State) -> Result, LockingError> { + Ok(state.tracker.shrink_to_fit()) + } + #[inline(always)] fn next_free( state: &mut Self::State, @@ -337,16 +368,18 @@ pub trait ReferenceImpl: Sized { type Type: Clone; - /// Releases the specified memory range back to the allocation tracker. - fn release_reference( - state: &mut Self::MemoryState, - range: ByteRange, - ) -> Result<(), ContiguousMemoryError>; + /// Releases the specified memory region back to the allocation tracker. + fn free_region(state: &mut Self::MemoryState, range: ByteRange) -> Option<*mut ()>; /// Builds a reference for the stored data. - fn build_ref(state: &Self::MemoryState, addr: *mut T, range: &ByteRange) -> Self::Type; + fn build_ref( + state: &Self::MemoryState, + addr: *mut T, + range: &ByteRange, + ) -> Self::Type; /// Marks reference state as no longer being borrowed. + #[inline(always)] fn unborrow_ref(_state: &Self::RefState) {} unsafe fn cast(from: Self::Type) -> Self::Type; @@ -354,22 +387,28 @@ pub trait ReferenceImpl: Sized { impl ReferenceImpl for ImplConcurrent { type MemoryState = ::State; - type RefState = Arc>; + type RefState = Arc>; type RefMutLock = Mutex<()>; type RefMutGuard<'a> = MutexGuard<'a, ()>; type Type = SyncContiguousMemoryRef; #[inline(always)] - fn release_reference( - state: &mut ::State, - range: ByteRange, - ) -> Result<(), ContiguousMemoryError> { - let mut lock = state.tracker.lock_named(MutexKind::AllocationTracker)?; - lock.release(range) + fn free_region(state: &mut ::State, range: ByteRange) -> Option<*mut ()> { + if let Ok(mut lock) = state.tracker.lock_named(MutexKind::AllocationTracker) { + let _ = lock.release(range); + + if let Ok(base) = state.base.lock_named(MutexKind::BaseAddress) { + unsafe { Some(base.add(range.0) as *mut ()) } + } else { + None + } + } else { + None + } } #[inline(always)] - fn build_ref( + fn build_ref( state: &::State, _addr: *mut T, range: &ByteRange, @@ -378,7 +417,10 @@ impl ReferenceImpl for ImplConcurrent { inner: Arc::new(ReferenceState { state: state.clone(), range: range.clone(), - mutable_access: Mutex::new(()), + already_borrowed: Mutex::new(()), + #[cfg(feature = "ptr_metadata")] + drop_metadata: >::new(), + _phantom: PhantomData, }), #[cfg(feature = "ptr_metadata")] metadata: (), @@ -387,9 +429,10 @@ impl ReferenceImpl for ImplConcurrent { } } + #[inline(always)] unsafe fn cast(from: Self::Type) -> Self::Type { SyncContiguousMemoryRef { - inner: from.inner, + inner: core::mem::transmute(from.inner), #[cfg(feature = "ptr_metadata")] metadata: (), #[cfg(not(feature = "ptr_metadata"))] @@ -400,25 +443,25 @@ impl ReferenceImpl for ImplConcurrent { impl ReferenceImpl for ImplDefault { type MemoryState = ::State; - type RefState = Rc>; + type RefState = Rc>; type RefMutGuard<'a> = (); type RefMutLock = Cell; type Type = ContiguousMemoryRef; #[inline(always)] - fn release_reference( - state: &mut ::State, - range: ByteRange, - ) -> Result<(), ContiguousMemoryError> { - state - .tracker - .try_borrow_mut() - .map_err(|_| ContiguousMemoryError::TrackerInUse)? - .release(range) + fn free_region(state: &mut ::State, range: ByteRange) -> Option<*mut ()> { + if let Ok(mut tracker) = state.tracker.try_borrow_mut() { + let _ = tracker.release(range); + + let base = state.base.get(); + unsafe { Some(base.add(range.0) as *mut ()) } + } else { + None + } } #[inline(always)] - fn build_ref( + fn build_ref( state: &::State, _addr: *mut T, range: &ByteRange, @@ -427,22 +470,29 @@ impl ReferenceImpl for ImplDefault { inner: Rc::new(ReferenceState { state: state.clone(), range: range.clone(), - mutable_access: Cell::new(false), + already_borrowed: Cell::new(false), + #[cfg(feature = "ptr_metadata")] + drop_metadata: >::new(), + _phantom: PhantomData, }), - metadata: None, + #[cfg(feature = "ptr_metadata")] + metadata: (), #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } } + #[inline(always)] fn unborrow_ref(state: &Self::RefState) { - state.mutable_access.set(false) + state.already_borrowed.set(false) } + #[inline(always)] unsafe fn cast(from: Self::Type) -> Self::Type { ContiguousMemoryRef { - inner: from.inner, - metadata: None, + inner: core::mem::transmute(from.inner), + #[cfg(feature = "ptr_metadata")] + metadata: (), #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } @@ -457,11 +507,10 @@ impl ReferenceImpl for ImplFixed { type Type = *mut T; #[inline(always)] - fn release_reference( - state: &mut ::State, - range: ByteRange, - ) -> Result<(), ContiguousMemoryError> { - state.tracker.release(range) + fn free_region(state: &mut ::State, range: ByteRange) -> Option<*mut ()> { + let _ = state.tracker.release(range); + + unsafe { Some(state.base.add(range.0) as *mut ()) } } #[inline(always)] @@ -473,10 +522,8 @@ impl ReferenceImpl for ImplFixed { addr } + #[inline(always)] unsafe fn cast(from: Self::Type) -> Self::Type { from as *mut R } } - -pub trait ImplDetails: MemoryImpl + ReferenceImpl {} -impl ImplDetails for T {} diff --git a/src/error.rs b/src/error.rs index 4ec70ca..1883ee6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,9 @@ use std::sync::MutexGuard; use std::sync::PoisonError; use core::alloc::LayoutError; -use core::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use core::fmt::Debug; +#[cfg(any(feature = "std", feature = "error_in_core"))] +use core::fmt::{Display, Formatter, Result as FmtResult}; use crate::range::ByteRange; @@ -27,6 +29,7 @@ pub enum LockingError { WouldBlock, } +#[cfg(any(feature = "std", feature = "error_in_core"))] impl Display for LockingError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { @@ -89,23 +92,24 @@ where /// Error returned when multiple concurrent mutable access' are attempted to /// the same memory region. #[derive(Debug)] -pub struct BorrowMutError { +pub struct MutablyBorrowed { /// [`ByteRange`] that was attempted to be borrowed. pub range: ByteRange, } -impl Display for BorrowMutError { +#[cfg(any(feature = "std", feature = "error_in_core"))] +impl Display for MutablyBorrowed { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!( f, - "attempted to mutably borrow already borrowed memory region: {}", + "attempted to borrow already mutably borrowed memory region: {}", self.range ) } } #[cfg(any(feature = "std", feature = "error_in_core"))] -impl Error for BorrowMutError { +impl Error for MutablyBorrowed { fn source(&self) -> Option<&(dyn Error + 'static)> { None } @@ -121,21 +125,22 @@ pub enum ContiguousMemoryError { /// Attempted to occupy a memory region that is already marked as taken. AlreadyUsed, /// Attempted to operate on a memory region that is not contained within the - /// [`AllocationTracker`](crate::AllocationTracker). + /// [`AllocationTracker`](crate::tracker::AllocationTracker). NotContained, /// Attempted to free memory that has already been deallocated. DoubleFree, - /// The [`AllocationTracker`](crate::AllocationTracker) does not allow - /// shrinking to the expected size. + /// The [`AllocationTracker`](crate::tracker::AllocationTracker) does not + /// allow shrinking to the expected size. Unshrinkable { /// The minimum required size for shrinking the /// [`ContiguousMemory`](crate::ContiguousMemory) container. - min_required: usize, + required_size: usize, }, /// Indicates that a mutex wasn't lockable. Lock(LockingError), - /// Attempted to borrow the [`AllocationTracker`](crate::AllocationTracker) - /// which is already in use. + /// Attempted to borrow the + /// [`AllocationTracker`](crate::tracker::AllocationTracker) which is + /// already in use. TrackerInUse, /// Indicates that the provided [`Layout`](std::alloc::Layout) is invalid. Layout( @@ -144,7 +149,7 @@ pub enum ContiguousMemoryError { LayoutError, ), /// Tried mutably borrowing already borrowed region of memory - BorrowMut(BorrowMutError), + BorrowMut(MutablyBorrowed), } /// Represents possible poisoning sources for mutexes in [`LockingError`]. @@ -152,14 +157,15 @@ pub enum ContiguousMemoryError { pub enum MutexKind { /// Mutex containing the base memory offset was poisoned. BaseAddress, - /// [`AllocationTracker`](crate::AllocationTracker) mutex was poisoned. + /// [`AllocationTracker`](crate::tracker::AllocationTracker) mutex was + /// poisoned. AllocationTracker, /// Concurrent mutable access exclusion flag mutex in /// [`ReferenceState`](crate::ReferenceState) was poisoned. Reference, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "error_in_core"))] impl Display for ContiguousMemoryError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { @@ -177,7 +183,9 @@ impl Display for ContiguousMemoryError { f, "Attempted to free a memory region that is already marked as free" ), - ContiguousMemoryError::Unshrinkable { min_required } => write!( + ContiguousMemoryError::Unshrinkable { + required_size: min_required, + } => write!( f, "Cannot shrink memory regions; minimum required space: {} bytes", min_required diff --git a/src/lib.rs b/src/lib.rs index 208d2c1..89256e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ +#![allow(incomplete_features)] #![cfg_attr(feature = "no_std", no_std)] -#![cfg_attr(feature = "ptr_metadata", feature(ptr_metadata))] +#![cfg_attr( + feature = "ptr_metadata", + feature(ptr_metadata, unsize, specialization) +)] #![cfg_attr(feature = "error_in_core", feature(error_in_core))] #[cfg(feature = "no_std")] @@ -10,7 +14,7 @@ extern crate alloc; all(feature = "std", feature = "no_std") ))] compile_error!( - "contiguous_mem requires either 'std' or 'no_std' feature to be enabled, not both or neither" + "contiguous_mem: either 'std' or 'no_std' feature must be enabled, not both or neither" ); pub mod details; @@ -18,25 +22,22 @@ pub mod error; pub mod range; pub mod tracker; mod types; -mod util; use details::*; use range::ByteRange; use types::*; +#[cfg(feature = "ptr_metadata")] +pub use types::pointer as ptr_metadata_ext; + use core::{ alloc::{Layout, LayoutError}, - mem::size_of, + marker::PhantomData, + mem::{size_of, ManuallyDrop}, ops::DerefMut, }; -#[cfg(not(feature = "ptr_metadata"))] -use core::marker::PhantomData; - -#[cfg(feature = "ptr_metadata")] -use core::ptr::Pointee; - -use error::{BorrowMutError, ContiguousMemoryError, LockingError}; +use error::{ContiguousMemoryError, LockingError, MutablyBorrowed}; /// A memory container for efficient allocation and storage of contiguous data. /// @@ -57,12 +58,13 @@ use error::{BorrowMutError, ContiguousMemoryError, LockingError}; /// number of gaps between previously stored items, making it an effective /// choice for maintaining data locality. /// -/// [`store`]: crate::details::ImplDetails::store +/// [`store`]: ContiguousMemory::store #[cfg_attr(feature = "debug", derive(Debug))] pub struct ContiguousMemory { inner: S::State, } +/// Internal state of [`ContiguousMemory`]. pub struct ContiguousMemoryState { base: S::Base, size: S::SizeType, @@ -90,6 +92,7 @@ impl core::ops::Deref for ContiguousMemory { } impl ContiguousMemoryState { + /// Returns the layout of the memory managed by the [`ContiguousMemory`] pub fn layout(&self) -> Layout { unsafe { let capacity = S::get_capacity(core::mem::transmute(self)); @@ -98,7 +101,7 @@ impl ContiguousMemoryState { } } -impl ContiguousMemory { +impl ContiguousMemory { /// Creates a new `ContiguousMemory` instance with the specified capacity. /// /// # Arguments @@ -128,7 +131,7 @@ impl ContiguousMemory { /// success, or a `LayoutError` if the memory layout cannot be satisfied. pub fn new_aligned(capacity: usize, alignment: usize) -> Result { let layout = Layout::from_size_align(capacity, alignment)?; - let base = unsafe { alloc::alloc(layout) }; + let base = unsafe { allocator::alloc(layout) }; Ok(ContiguousMemory { inner: S::build_state(base, capacity, alignment)?, }) @@ -144,7 +147,7 @@ impl ContiguousMemory { /// # Returns /// /// - If the implementation details type `S` is - /// [`ThreadSafeImpl`](crate::ThreadSafeImpl), the result will be a + /// [`ImplConcurrent`], the result will be a /// `Result<*mut u8, [LockingError](crate::LockingError::Poisoned)>` which /// only errors if the mutex holding the base address fails. /// @@ -180,13 +183,14 @@ impl ContiguousMemory { /// /// This function can return the following errors: /// - /// - [`ContiguousMemoryError::Lock`]: This error can occur if the mutex - /// holding the base address or the - /// [`AllocationTracker`](crate::AllocationTracker) is poisoned. - /// /// - [`ContiguousMemoryError::Unshrinkable`]: This error occurs when /// attempting to shrink the memory container, but the stored data /// prevents the container from being shrunk to the desired capacity. + /// + /// - [`ContiguousMemoryError::Lock`]: This error can occur if the mutex + /// holding the base address or the [`AllocationTracker`] is poisoned. + /// + /// [`AllocationTracker`]: crate::tracker::AllocationTracker pub fn resize(&mut self, new_capacity: usize) -> Result<(), ContiguousMemoryError> { if new_capacity == S::get_capacity(&self.inner) { return Ok(()); @@ -206,6 +210,13 @@ impl ContiguousMemory { Ok(()) } + pub fn shrink_to_fit(&mut self) -> Result<(), ContiguousMemoryError> { + if let Some(shrunk) = S::shrink_tracker(&mut self.inner)? { + self.resize(shrunk)?; + } + Ok(()) + } + /// Stores a value of type `T` in the memory container. /// /// This operation allocates memory for the provided value and stores it in @@ -217,123 +228,152 @@ impl ContiguousMemory { /// /// # Returns /// + /// - If the implementation details are [`ImplDefault`] the result will be + /// a [`ContiguousMemoryRef`] pointing to the stored value. + /// + /// /// A `Result` that encapsulates the result of the storage operation: /// - /// - If the implementation details type `S` is - /// [`NotThreadSafeImpl`](crate::NotThreadSafeImpl) or - /// [`ThreadSafeImpl`](crate::ThreadSafeImpl), the result will be a - /// [`crate::CMRef`] pointing to the stored value. This reference provides - /// a convenient and safe way to access and manipulate the stored data - /// within the memory block. + /// - If the implementation details are [`ImplConcurrent`], the result will + /// be a [`SyncContiguousMemoryRef`] pointing to the stored value. /// - /// - If the implementation details type `S` is - /// [`FixedSizeImpl`](crate::FixedSizeImpl), the result will be a raw - /// pointer (`*mut T`) to the stored value. This is due to the fact that - /// fixed-size container won't move which means the pointer will not be - /// invalidated. + /// + /// - If the implementation details are [`ImplFixed`], the result will be a + /// raw pointer (`*mut T`) to the stored value. /// /// The returned [`Result`] indicates success or an error if the storage /// operation encounters any issues. /// - /// # Errors + /// ## Errors /// - /// This function can return the following errors: + /// When the implementation details are [`ImplConcurrent`]: + /// + /// - [`LockingError::Poisoned`]: This error can occur when the + /// [`AllocationTracker`] associated with the memory container is + /// poisoned. /// - /// - [`ContiguousMemoryError::NoStorageLeft`]: Only returned when the - /// implementation details type `S` is - /// [`FixedSizeImpl`](crate::FixedSizeImpl) and indicates that the - /// container couldn't accommodate the provided data due to size + /// When the implementation details are [`ImplFixed`]: + /// + /// - [`ContiguousMemoryError::NoStorageLeft`]: Indicates that + /// the container couldn't accommodate the provided data due to size /// limitations. Other implementation details grow the container instead. /// - /// - [`ContiguousMemoryError::Poisoned`]: This error can occur when the - /// [`AllocationTracker`](crate::AllocationTracker) associated with the - /// memory container is poisoned. - pub fn store(&mut self, mut value: T) -> Result, ContiguousMemoryError> + /// [`AllocationTracker`]: crate::tracker::AllocationTracker + pub fn store(&mut self, value: T) -> S::StoreResult where Self: StoreData, { - let layout = Layout::for_value(&value); - let pos: *mut T = &mut value; - unsafe { - self.store_data(pos as *mut u8, layout) - .map(|it| S::cast(it)) - } + let mut data = ManuallyDrop::new(value); + let layout = Layout::for_value(&data); + let pos = &mut *data as *mut T; + let result = unsafe { self.store_data(pos, layout) }; + result } } /// Trait for specializing store function across implementations -pub trait StoreData { - /// Works same as [`store`](CanStore::store) but takes a pointer and layout - unsafe fn store_data( +pub trait StoreData { + /// Works same as [`store`](ContiguousMemory::store) but takes a pointer and layout + unsafe fn store_data( &mut self, - data: *mut u8, + data: *mut T, layout: Layout, - ) -> Result, ContiguousMemoryError>; + ) -> S::StoreResult; } impl StoreData for ContiguousMemory { - unsafe fn store_data( + /// # Errors + /// + /// [`LockingError::Poisoned`]: This error can occur when the + /// [`AllocationTracker`] associated with the memory container is poisoned. + /// + /// [`AllocationTracker`]: crate::tracker::AllocationTracker + unsafe fn store_data( &mut self, - data: *mut u8, + data: *mut T, layout: Layout, - ) -> Result, ContiguousMemoryError> { + ) -> Result, LockingError> { let (addr, range) = loop { match ImplConcurrent::next_free(&mut self.inner, layout) { Ok(taken) => { let found = (taken.0 + ImplConcurrent::get_base(&self.inner)? as usize) as *mut u8; - unsafe { core::ptr::copy_nonoverlapping(data, found, layout.size()) } + unsafe { core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()) } break (found, taken); } Err(ContiguousMemoryError::NoStorageLeft) => { - self.resize(ImplConcurrent::get_capacity(&self.inner) * 2)?; + match self.resize(ImplConcurrent::get_capacity(&self.inner) * 2) { + Ok(()) => {} + Err(ContiguousMemoryError::Lock(locking_err)) => return Err(locking_err), + Err(other) => unreachable!( + "reached unexpected error while growing the container to store data: {:?}", + other + ), + }; } - Err(other) => return Err(other), + Err(ContiguousMemoryError::Lock(locking_err)) => return Err(locking_err), + Err(other) => unreachable!( + "reached unexpected error while looking for next region to store data: {:?}", + other + ), } }; - Ok(ImplConcurrent::build_ref(&self.inner, addr, &range)) + Ok(ImplConcurrent::build_ref( + &self.inner, + addr as *mut T, + &range, + )) } } impl StoreData for ContiguousMemory { - unsafe fn store_data( + unsafe fn store_data( &mut self, - data: *mut u8, + data: *mut T, layout: Layout, - ) -> Result, ContiguousMemoryError> { + ) -> ContiguousMemoryRef { let (addr, range) = loop { match ImplDefault::next_free(&mut self.inner, layout) { Ok(taken) => { let found = (taken.0 + self.base.get() as usize) as *mut u8; unsafe { - core::ptr::copy_nonoverlapping(data, found, layout.size()); + core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()); } break (found, taken); } Err(ContiguousMemoryError::NoStorageLeft) => { - self.resize(ImplDefault::get_capacity(&self.inner) * 2)?; + match self.resize(ImplDefault::get_capacity(&self.inner) * 2) { + Ok(()) => {}, + Err(err) => unreachable!( + "reached unexpected error while growing the container to store data: {:?}", + err + ), + } } - Err(other) => return Err(other), + Err(other) => unreachable!( + "reached unexpected error while looking for next region to store data: {:?}", + other + ), } }; - Ok(ImplDefault::build_ref(&self.inner, addr, &range)) + ImplDefault::build_ref(&self.inner, addr as *mut T, &range) } } impl StoreData for ContiguousMemory { - unsafe fn store_data( + unsafe fn store_data( &mut self, - data: *mut u8, + data: *mut T, layout: Layout, - ) -> Result<*mut u8, ContiguousMemoryError> { + ) -> Result<*mut T, ContiguousMemoryError> { let (addr, range) = loop { match ImplFixed::next_free(&mut self.inner, layout) { Ok(taken) => { let found = (taken.0 + self.base as usize) as *mut u8; unsafe { - core::ptr::copy_nonoverlapping(data, found, layout.size()); + core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()); } break (found, taken); } @@ -341,39 +381,35 @@ impl StoreData for ContiguousMemory { } }; - Ok(ImplFixed::build_ref(&self.inner, addr, &range)) + Ok(ImplFixed::build_ref(&self.inner, addr as *mut T, &range)) } } impl ContiguousMemory { #[inline(always)] - pub unsafe fn free_typed(&mut self, value: *mut T) -> Result<(), ContiguousMemoryError> { + pub unsafe fn free_typed(&mut self, value: *mut T) { Self::free(self, value, size_of::()) } - pub unsafe fn free( - &mut self, - value: *mut T, - size: usize, - ) -> Result<(), ContiguousMemoryError> { - ImplFixed::release_reference( - &mut self.inner, - ByteRange(value as usize, value as usize + size), - ) + pub unsafe fn free(&mut self, value: *mut T, size: usize) { + let pos: usize = value.sub(self.get_base() as usize) as usize; + if let Some(freed) = ImplFixed::free_region(&mut self.inner, ByteRange(pos, pos + size)) { + core::ptr::drop_in_place(freed as *mut T); + } } } -/// A type alias for [`ContiguousMemory`](crate::ContiguousMemory) that enables +/// A type alias for [`ContiguousMemory`] that enables /// references to data stored within it to be used safely across multiple /// threads. pub type SyncContiguousMemory = ContiguousMemory; -/// A type alias for [`ContiguousMemory`](crate::ContiguousMemory) that offers +/// A type alias for [`ContiguousMemory`] that offers /// a synchronous implementation without using internal mutexes making it /// faster but not thread safe. pub type GrowableContiguousMemory = ContiguousMemory; -/// A type alias for [`ContiguousMemory`](crate::ContiguousMemory) that provides +/// A type alias for [`ContiguousMemory`] that provides /// a highly performance-optimized (unsafe) implementation. Suitable when the /// required size is known upfront and the container is guaranteed to outlive /// any returned pointers. @@ -388,15 +424,15 @@ impl Drop for ContiguousMemory { /// A synchronized (thread-safe) reference to `T` data stored in a /// [`ContiguousMemory`] structure. pub struct SyncContiguousMemoryRef { - inner: Arc>, + inner: Arc>, #[cfg(feature = "ptr_metadata")] metadata: ::Metadata, #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } -/// A shorter type name for [`AsyncContiguousMemoryRef`]. -pub type ACMRef = SyncContiguousMemoryRef; +/// A shorter type name for [`SyncContiguousMemoryRef`]. +pub type SCMRef = SyncContiguousMemoryRef; impl SyncContiguousMemoryRef { /// Tries accessing referenced data at its current location. @@ -408,12 +444,20 @@ impl SyncContiguousMemoryRef { /// if the Mutex holding the `base` address pointer has been poisoned. pub fn get(&self) -> Result<&T, LockingError> where - T: Sized, + T: RefSizeReq, { unsafe { let base = ImplConcurrent::get_base(&self.inner.state)?; - let pos = base.offset(self.inner.range.0 as isize); - Ok(&*(pos as *mut T)) + let pos = base.add(self.inner.range.0); + + #[cfg(not(feature = "ptr_metadata"))] + { + Ok(&*(pos as *mut T)) + } + #[cfg(feature = "ptr_metadata")] + { + Ok(&*core::ptr::from_raw_parts(pos as *const (), self.metadata)) + } } } @@ -429,19 +473,22 @@ impl SyncContiguousMemoryRef { /// poisoned. pub fn get_mut<'a>(&'a self) -> Result, LockingError> where - T: Sized, + T: RefSizeReq, { let read = self .inner - .mutable_access + .already_borrowed .lock_named(error::MutexKind::Reference)?; unsafe { let base = ImplConcurrent::get_base(&self.inner.state)?; - let pos = base.offset(self.inner.range.0 as isize); + let pos = base.add(self.inner.range.0); Ok(MemWriteGuard { state: self.inner.clone(), _guard: read, + #[cfg(not(feature = "ptr_metadata"))] value: &mut *(pos as *mut T), + #[cfg(feature = "ptr_metadata")] + value: &mut *core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata), }) } } @@ -460,27 +507,36 @@ impl SyncContiguousMemoryRef { /// would be blocking. pub fn try_get_mut<'a>(&'a self) -> Result, LockingError> where - T: Sized, + T: RefSizeReq, { let read = self .inner - .mutable_access + .already_borrowed .try_lock_named(error::MutexKind::Reference)?; unsafe { let base = ImplConcurrent::get_base(&self.inner.state)?; - let pos = base.offset(self.inner.range.0 as isize); + let pos = base.add(self.inner.range.0); Ok(MemWriteGuard { state: self.inner.clone(), _guard: read, + #[cfg(not(feature = "ptr_metadata"))] value: &mut *(pos as *mut T), + #[cfg(feature = "ptr_metadata")] + value: &mut *core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata), }) } } - pub unsafe fn cast(self) -> SyncContiguousMemoryRef { - SyncContiguousMemoryRef { - inner: self.inner, - _phantom: PhantomData, + #[cfg(feature = "ptr_metadata")] + pub fn as_dyn(self, metadata: ::Metadata) -> SyncContiguousMemoryRef + where + T: Unsize, + { + unsafe { + SyncContiguousMemoryRef { + inner: core::mem::transmute(self.inner), + metadata, + } } } } @@ -491,6 +547,7 @@ impl Clone for SyncContiguousMemoryRef { inner: self.inner.clone(), #[cfg(feature = "ptr_metadata")] metadata: self.metadata.clone(), + #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } } @@ -499,86 +556,120 @@ impl Clone for SyncContiguousMemoryRef { /// A thread-unsafe reference to `T` data stored in [`ContiguousMemory`] /// structure. pub struct ContiguousMemoryRef { - inner: Rc>, - metadata: Option, + inner: Rc>, + #[cfg(feature = "ptr_metadata")] + metadata: ::Metadata, + #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } /// A shorter type name for [`ContiguousMemoryRef`]. pub type CMRef = ContiguousMemoryRef; -impl ContiguousMemoryRef { +impl ContiguousMemoryRef { + /// Returns a reference to data at its current location. + pub fn get(&self) -> &T + where + T: RefSizeReq, + { + ContiguousMemoryRef::::try_get(self).expect("mutably borrowed") + } + /// Returns a reference to data at its current location. - pub fn get(&self) -> &T { + pub fn try_get(&self) -> Result<&T, MutablyBorrowed> + where + T: RefSizeReq, + { + if self.inner.already_borrowed.get() { + return Err(MutablyBorrowed { + range: self.inner.range, + }); + } + unsafe { let base = ImplDefault::get_base(&self.inner.state); - let pos = base.offset(self.inner.range.0 as isize); - &*(pos as *mut T) + let pos = base.add(self.inner.range.0); + + #[cfg(not(feature = "ptr_metadata"))] + { + Ok(&*(pos as *mut T)) + } + #[cfg(feature = "ptr_metadata")] + { + Ok(&*core::ptr::from_raw_parts(pos as *const (), self.metadata)) + } } } /// Returns a mutable reference to data at its current location or the - /// [`BorrowMutError`] error if the represented memory region is already + /// [`MutablyBorrowed`] error if the represented memory region is already /// mutably borrowed. - pub fn get_mut<'a>(&'a mut self) -> MemWriteGuard<'a, T, ImplDefault> { - ContiguousMemoryRef::::try_get_mut(self).expect("already borrowed") + pub fn get_mut<'a>(&'a mut self) -> MemWriteGuard<'a, T, ImplDefault> + where + T: RefSizeReq, + { + ContiguousMemoryRef::::try_get_mut(self).expect("mutably borrowed") } /// Returns a mutable reference to data at its current location or the - /// [`BorrowMutError`] error if the represented memory region is already + /// [`MutablyBorrowed`] error if the represented memory region is already /// mutably borrowed. pub fn try_get_mut<'a>( &'a mut self, - ) -> Result, BorrowMutError> { - if !self.inner.mutable_access.get() { - return Err(BorrowMutError { + ) -> Result, MutablyBorrowed> + where + T: RefSizeReq, + { + if self.inner.already_borrowed.get() { + return Err(MutablyBorrowed { range: self.inner.range, }); } + unsafe { let base = ImplDefault::get_base(&self.inner.state); - let pos = base.offset(self.inner.range.0 as isize); + let pos = base.add(self.inner.range.0); + Ok(MemWriteGuard { state: self.inner.clone(), _guard: (), + #[cfg(not(feature = "ptr_metadata"))] value: &mut *(pos as *mut T), + #[cfg(feature = "ptr_metadata")] + value: &mut *core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata), }) } } - pub unsafe fn as_dyn(self, vtable: VTableAddr) -> ContiguousMemoryRef { - ContiguousMemoryRef { - inner: self.inner, - metadata: Some(vtable), - _phantom: PhantomData, - } - } -} - -/* -impl ContiguousMemoryRef { - pub fn get_dyn(&self) -> &T { + #[cfg(feature = "ptr_metadata")] + pub fn as_dyn(self, metadata: ::Metadata) -> ContiguousMemoryRef + where + T: Unsize, + { unsafe { - let base = ImplDefault::get_base(&self.inner.state); - let pos = base.offset(self.inner.range.0 as isize) as *const (); - let metadata = self.metadata.expect("missing metadata"); - let dynamic: *const T = core::mem::transmute((pos, metadata)); - &*dynamic + ContiguousMemoryRef { + inner: core::mem::transmute(self.inner), + metadata, + } } } } -*/ impl Clone for ContiguousMemoryRef { fn clone(&self) -> Self { ContiguousMemoryRef { inner: self.inner.clone(), + #[cfg(feature = "ptr_metadata")] metadata: self.metadata.clone(), + #[cfg(not(feature = "ptr_metadata"))] _phantom: PhantomData, } } } -impl core::ops::Deref for ContiguousMemoryRef { +impl core::ops::Deref for ContiguousMemoryRef +where + T: RefSizeReq, +{ type Target = T; fn deref(&self) -> &Self::Target { @@ -586,20 +677,33 @@ impl core::ops::Deref for ContiguousMemoryRef { } } -/// Internal state of [`ContiguousMemoryRef`] and [`AsyncContiguousMemoryRef`]. +/// Internal state of [`ContiguousMemoryRef`] and [`SyncContiguousMemoryRef`]. #[cfg_attr(feature = "debug", derive(Debug))] -pub struct ReferenceState { +pub struct ReferenceState { state: S::MemoryState, range: ByteRange, - mutable_access: S::RefMutLock, + already_borrowed: S::RefMutLock, + #[cfg(feature = "ptr_metadata")] + drop_metadata: DynMetadata, + _phantom: PhantomData, } -impl Drop for ReferenceState { +impl Drop for ReferenceState { fn drop(&mut self) { - let _ = S::release_reference(&mut self.state, self.range); + #[allow(unused_variables)] + if let Some(it) = S::free_region(&mut self.state, self.range) { + #[cfg(feature = "ptr_metadata")] + unsafe { + let drop: *mut dyn HandleDrop = + core::ptr::from_raw_parts_mut::(it, self.drop_metadata); + (&*drop).do_drop(); + } + }; } } +/// A smart reference wrapper responsible for tracking and managing a flag +/// that indicates whether the memory segment is actively being written to. pub struct MemWriteGuard<'a, T: ?Sized, S: ReferenceImpl> { state: S::RefState, _guard: S::RefMutGuard<'a>, @@ -626,91 +730,105 @@ impl<'a, T: ?Sized, S: ReferenceImpl> Drop for MemWriteGuard<'a, T, S> { } } -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct VTableAddr(usize); - -impl VTableAddr { - pub unsafe fn new(value: usize) -> Self { - VTableAddr(value) - } -} - -#[macro_export] -macro_rules! vtable { - ($struct: ty as $trait: tt) => { - unsafe { - let value: *const $struct = core::ptr::null(); - let dynamic: *const dyn $trait = value as *const dyn $trait; - let (_, addr) = core::mem::transmute::<_, (*const (), usize)>(dynamic); - ::contiguous_mem::VTableAddr::new(addr) - } - }; -} +#[cfg(all(test, feature = "std"))] +mod test { + use super::*; -/* TODO: Reference getters -#[cfg(not(feature = "ptr_metadata"))] -impl ReferenceState { - pub fn get(&self) -> Result<&T, ContiguousMemoryError> { - unsafe { - let base = S::get_base(&self.base)?.offset(self.range.0 as isize); - Ok(&*(base as *mut T)) - } + #[derive(Debug, Clone, PartialEq, Eq)] + #[repr(C)] + struct Person { + name: String, + last_name: String, } -} -#[cfg(feature = "ptr_metadata")] -impl ReferenceState { - pub fn get(&self) -> Result<&T, ContiguousMemoryError> { - unsafe { - let base = S::get_base(&self.base)?.offset(self.range.0 as isize); - let fat: *const T = core::ptr::from_raw_parts::(base as *const (), self.metadata); - Ok(&*fat) - } + #[derive(Debug, Clone, PartialEq, Eq)] + #[repr(C)] + struct Car { + owner: Person, + driver: Option, + cost: u32, + miles: u32, } -} - */ - -#[cfg(test)] -mod test { - use super::*; #[test] fn test_new_contiguous_memory() { - let memory = ContiguousMemory::::new_aligned(1024, 8).unwrap(); + let memory = GrowableContiguousMemory::new(1024); assert_eq!(memory.get_capacity(), 1024); } #[test] fn test_store_and_get_contiguous_memory() { - let mut memory = ContiguousMemory::::new_aligned(1024, 8).unwrap(); + let mut memory = GrowableContiguousMemory::new(1024); - let value = 42u32; - let stored_ref = memory.store(value).unwrap(); - assert_eq!(*stored_ref, value); + let person_a = Person { + name: "Jerry".to_string(), + last_name: "Taylor".to_string(), + }; + + let person_b = Person { + name: "Larry".to_string(), + last_name: "Taylor".to_string(), + }; + + let car_a = Car { + owner: person_a.clone(), + driver: Some(person_b.clone()), + cost: 20_000, + miles: 30123, + }; + + let car_b = Car { + owner: person_b.clone(), + driver: None, + cost: 30_000, + miles: 3780123, + }; + + let value_number = 248169u64; + let value_string = "This is a test string".to_string(); + let value_byte = 0x41u8; + + let stored_ref_number = memory.store(value_number); + let stored_ref_car_a = memory.store(car_a.clone()); + let stored_ref_string = memory.store(value_string.clone()); + let stored_ref_byte = memory.store(value_byte); + let stored_ref_car_b = memory.store(car_b.clone()); + + assert_eq!(*stored_ref_number, value_number); + assert_eq!(*stored_ref_car_a, car_a); + assert_eq!(*stored_ref_string, value_string); + assert_eq!(*stored_ref_car_b, car_b); + assert_eq!(*stored_ref_byte, value_byte); } #[test] fn test_resize_contiguous_memory() { - let mut memory = ContiguousMemory::::new_aligned(1024, 8).unwrap(); + let mut memory = GrowableContiguousMemory::new(512); - memory.resize(512).unwrap(); - assert_eq!(memory.get_capacity(), 512); + let person_a = Person { + name: "Larry".to_string(), + last_name: "Taylor".to_string(), + }; - memory.resize(2048).unwrap(); - assert_eq!(memory.get_capacity(), 2048); - } + let car_a = Car { + owner: person_a.clone(), + driver: Some(person_a), + cost: 20_000, + miles: 30123, + }; - #[test] - fn test_growable_contiguous_memory() { - let mut memory = GrowableContiguousMemory::new_aligned(1024, 8).unwrap(); + let stored_car = memory.store(car_a.clone()); - let value = 42u32; - let stored_ref = memory.store(value).unwrap(); - assert_eq!(*stored_ref, value); + assert!(memory.resize(32).is_err()); + memory.resize(1024).unwrap(); + assert_eq!(memory.get_capacity(), 1024); + + assert_eq!(*stored_car, car_a); - memory.resize(2048).unwrap(); - assert_eq!(memory.get_capacity(), 2048); + memory.resize(128).unwrap(); + assert_eq!(memory.get_capacity(), 128); + + assert_eq!(*stored_car, car_a); } #[test] @@ -726,40 +844,4 @@ mod test { // No resize allowed for FixedContiguousMemory assert!(memory.resize(2048).is_err()); } - - struct TestStruct1 { - field1: u32, - field2: u64, - } - - struct TestStruct2 { - field1: u16, - field2: f32, - field3: i32, - } - - #[test] - fn test_store_structs_with_different_layouts() { - let mut memory = ContiguousMemory::::new_aligned(1024, 8).unwrap(); - - let struct1 = TestStruct1 { - field1: 42, - field2: 1234567890, - }; - let struct2 = TestStruct2 { - field1: 123, - field2: 3.14, - field3: -42, - }; - - let stored_struct1 = memory.store(struct1).unwrap(); - let stored_struct2 = memory.store(struct2).unwrap(); - - assert_eq!(stored_struct1.field1, 42); - assert_eq!(stored_struct1.field2, 1234567890); - - assert_eq!(stored_struct2.field1, 123); - assert_eq!(stored_struct2.field2, 3.14); - assert_eq!(stored_struct2.field3, -42); - } } diff --git a/src/tracker.rs b/src/tracker.rs index 164753e..36018ad 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -2,6 +2,8 @@ use core::alloc::Layout; +#[cfg(any(feature = "no_std"))] +use crate::types::*; use crate::{error::ContiguousMemoryError, range::ByteRange}; /// A structure that keeps track of unused regions of memory within provided @@ -68,13 +70,13 @@ impl AllocationTracker { .unused .last_mut() .ok_or(ContiguousMemoryError::Unshrinkable { - min_required: self.size, + required_size: self.size, })?; let reduction = self.size - new_size; if last.len() < reduction { return Err(ContiguousMemoryError::Unshrinkable { - min_required: self.size - last.len(), + required_size: self.size - last.len(), }); } last.1 -= reduction; @@ -103,6 +105,21 @@ impl AllocationTracker { Ok(()) } + /// Removes tailing area of tracked memory bounds if it is marked as free + /// and returns the new (reduced) size. + /// + /// If the tailing area was marked as occupied `None` is returned instead. + pub fn shrink_to_fit(&mut self) -> Option { + match self.unused.last() { + Some(it) if it.1 == self.size => { + let last = self.unused.pop().expect("free byte ranges isn't empty"); + self.size -= last.len(); + Some(self.size) + } + _ => None, + } + } + /// Returns the next free memory region that can accommodate the given type /// [`Layout`]. /// diff --git a/src/types.rs b/src/types.rs index 69334eb..3cb2e44 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -//! Module re-exporting used types to help with `no_std` support. +//! Module re-exporting used types any polyfill to help with feature support. #[cfg(feature = "std")] mod std_imports { @@ -7,8 +7,9 @@ mod std_imports { pub use std::sync::Mutex; pub use std::sync::MutexGuard; - pub use std::alloc; + pub use std::alloc as allocator; } + #[cfg(feature = "std")] pub use std_imports::*; @@ -17,7 +18,7 @@ mod nostd_imports { pub use spin::Mutex; pub use spin::MutexGuard; - pub use alloc::alloc; + pub use alloc::alloc as allocator; pub use ::alloc::vec::Vec; @@ -65,7 +66,7 @@ impl LockTypesafe for Mutex { } fn try_lock_named( &self, - which: MutexKind, + _which: MutexKind, ) -> Result, crate::error::LockingError> { match self.try_lock() { Some(it) => Ok(it), @@ -73,3 +74,96 @@ impl LockTypesafe for Mutex { } } } + +/// Size requirements for types pointed to by references +#[cfg(feature = "ptr_metadata")] +pub trait RefSizeReq {} +#[cfg(feature = "ptr_metadata")] +impl RefSizeReq for T {} + +/// Size requirements for types pointed to by references +#[cfg(not(feature = "ptr_metadata"))] +pub trait RefSizeReq: Sized {} +#[cfg(not(feature = "ptr_metadata"))] +impl RefSizeReq for T {} + +/// Type requirements for values that can be stored. +#[cfg(any(feature = "ptr_metadata", feature = "leak_data"))] +pub trait StoreRequirements: 'static {} +#[cfg(any(feature = "ptr_metadata", feature = "leak_data"))] +impl StoreRequirements for T {} + +/// Type requirements for values that can be stored. +#[cfg(all(not(feature = "ptr_metadata"), not(feature = "leak_data")))] +pub trait StoreRequirements: Copy {} +#[cfg(all(not(feature = "ptr_metadata"), not(feature = "leak_data")))] +impl StoreRequirements for T {} + +#[cfg(feature = "ptr_metadata")] +pub use core::marker::Unsize; +#[cfg(feature = "ptr_metadata")] +pub use core::ptr::{DynMetadata, Pointee}; + +/// Provides some extensions for `ptr_metadata`. +#[cfg(feature = "ptr_metadata")] +pub mod pointer { + use super::*; + use core::{any::type_name, ptr::NonNull}; + + /// Statically constructs fat pointer metadata. + pub const fn static_metadata() -> ::Metadata + where + S: Unsize, + { + let (_, metadata) = (NonNull::::dangling().as_ptr() as *const T).to_raw_parts(); + metadata + } + + /// Trait that provides static pointer metadata based on type arguments. + pub trait TryMetadata { + /// Returns [`::Metadata`](Pointee::Metadata) if `Self` + /// implements `T` trait, otherwise `None` is returned. + fn try_new() -> Option<::Metadata>; + /// Returns [`::Metadata`](Pointee::Metadata) if `Self` + /// implements `T` trait and panics if it doesn't. + fn new() -> ::Metadata { + match Self::try_new() { + Some(it) => it, + None => panic!( + "{} not implemented for {}", + type_name::(), + type_name::() + ), + } + } + } + impl TryMetadata for S { + default fn try_new() -> Option<::Metadata> { + None + } + } + impl TryMetadata for S + where + S: Unsize, + { + fn try_new() -> Option<::Metadata> { + Some(static_metadata::()) + } + } + + /// Allows dynamically dropping arbitrary types. + /// + /// This is a workaround for invoking [`Drop::drop`] as well as calling + /// compiler generated drop glue dynamically. + pub trait HandleDrop { + fn do_drop(&mut self); + } + impl HandleDrop for T { + #[inline(never)] + fn do_drop(&mut self) { + unsafe { core::ptr::drop_in_place(self as *mut Self) } + } + } +} +#[cfg(feature = "ptr_metadata")] +pub use pointer::*; diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index e69de29..0000000