From d6365de4f4ded7bddc3b48d52b7a55e160471dee Mon Sep 17 00:00:00 2001 From: Tin Svagelj Date: Tue, 29 Aug 2023 20:20:21 +0200 Subject: [PATCH] Finalize 0.2.0 API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed some structs, traits and variants. Fixed storage and dropping issues. Fully implemented ptr_metadata support. Cleaned up type interface to be more convenient. Added support for mutation of stored data. Separated library into more modules. Improved tests. Improved CI script and switch it to nightly Signed-off-by: Tin Å vagelj --- .github/workflows/rust.yml | 22 +- .vscode/launch.json | 64 +++++ Cargo.toml | 8 +- README.md | 61 ++-- examples/ptr_metadata.rs | 40 +++ examples/traits.rs | 38 --- src/details.rs | 147 ++++++---- src/error.rs | 38 ++- src/lib.rs | 554 +++++++++++++++++++++---------------- src/tracker.rs | 21 +- src/types.rs | 102 ++++++- src/util.rs | 0 12 files changed, 719 insertions(+), 376 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 examples/ptr_metadata.rs delete mode 100644 examples/traits.rs delete mode 100644 src/util.rs 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