diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..82ded86 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,87 @@ +name: ci + +on: + pull_request: + push: + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: ["stable", "nightly"] + std: [true, false] + debug: [true, false] + unsafe_impl: [true, false] + ptr_metadata: [true, false] + error_in_core: [true, false] + exclude: + - rust: stable + ptr_metadata: true + - rust: stable + error_in_core: true + # no point in testing nightly with unstable features disabled + - rust: nightly + ptr_metadata: false + - rust: nightly + error_in_core: false + runs-on: ${{ matrix.os }} + name: | + test - ${{ matrix.os }}; ${{ matrix.rust }} rust; features: { + std: ${{ matrix.std }}, + debug: ${{ matrix.debug }}, + unsafe_impl: ${{ matrix.unsafe_impl }}, + ptr_metadata: ${{ matrix.ptr_metadata }}, + error_in_core: ${{ matrix.error_in_core }} + } + env: + RUST_BACKTRACE: 1 # Emit backtraces on panics. + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + profile: minimal + override: true + - if: matrix.std == true + run: echo "FEATURES=$FEATURES,std" >> $GITHUB_ENV + - if: matrix.debug == true + run: echo "FEATURES=$FEATURES,debug" >> $GITHUB_ENV + - if: matrix.unsafe_impl == true + run: echo "FEATURES=$FEATURES,unsafe_impl" >> $GITHUB_ENV + - if: matrix.ptr_metadata == true + run: echo "FEATURES=$FEATURES,ptr_metadata" >> $GITHUB_ENV + - if: matrix.error_in_core == true + run: echo "FEATURES=$FEATURES,error_in_core" >> $GITHUB_ENV + - run: cargo test --no-default-features --features=${{ env.FEATURES }} --verbose + + lint: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + components: clippy + override: true + - run: cargo clippy --all-features -- -D warnings # Deny clippy warnings + + miri: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - name: Install Miri + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + components: miri + override: true + - run: cargo miri setup + - run: cargo miri test --all-features --verbose diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 6f837d4..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Rust Clippy - -on: - push: - pull_request: - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - run: rustup update nightly && rustup default nightly - - run: rustup component add clippy - - name: Run clippy - run: cargo clippy --all-features diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 847374c..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Rust - -on: - push: - pull_request: - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - run: rustup update nightly && rustup default nightly - - name: Build default - run: cargo build --verbose - - name: Build no_std all features - run: cargo build --verbose --no-default-features --features no_std,debug,ptr_metadata,error_in_core - - name: Build all features - run: cargo build --verbose --no-default-features --features debug,ptr_metadata,error_in_core - - name: Run tests - run: cargo test --verbose --features ptr_metadata diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index daea724..783ef07 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -13,11 +13,7 @@ jobs: steps: - uses: actions/checkout@v3 - - run: rustup update nightly && rustup default nightly - - name: Install cargo-semver-check from crates.io - uses: baptiste0928/cargo-install@v2 + - name: Check semver + uses: obi1kenobi/cargo-semver-checks-action@v2 with: - crate: cargo-semver-checks - version: "^0.23" - - name: Run semver check - run: cargo semver-checks check-release + toolchain: nightly diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 028b592..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,249 +0,0 @@ -{ - // 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}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'failing'", - "cargo": { - "args": [ - "build", - "--example=failing", - "--package=contiguous-mem" - ], - "filter": { - "name": "failing", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'failing'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=failing", - "--package=contiguous-mem" - ], - "filter": { - "name": "failing", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'default_impl'", - "cargo": { - "args": [ - "build", - "--example=default_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "default_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'default_impl'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=default_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "default_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'game_loading'", - "cargo": { - "args": [ - "build", - "--example=game_loading", - "--package=contiguous-mem" - ], - "filter": { - "name": "game_loading", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'game_loading'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=game_loading", - "--package=contiguous-mem" - ], - "filter": { - "name": "game_loading", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'sync_impl'", - "cargo": { - "args": [ - "build", - "--example=sync_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "sync_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'sync_impl'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=sync_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "sync_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'unsafe_impl'", - "cargo": { - "args": [ - "build", - "--example=unsafe_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "unsafe_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'unsafe_impl'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=unsafe_impl", - "--package=contiguous-mem" - ], - "filter": { - "name": "unsafe_impl", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 55649d5..af21bdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,32 +1,48 @@ [package] name = "contiguous-mem" -version = "0.4.2" +version = "0.5.0" edition = "2021" -description = "A contiguous memory storage" +description = "A contiguous memory container" authors = ["Tin Švagelj "] license = "MIT OR Apache-2.0 OR Zlib" keywords = ["memory", "contiguous", "storage", "container", "nostd"] categories = ["data-structures", "memory-management", "no-std"] repository = "https://github.com/Caellian/contiguous_mem" +[[example]] +name = "game_loading" +path = "examples/game_loading.rs" +required-features = ["unsafe_impl"] + [[example]] name = "ptr_metadata" path = "examples/ptr_metadata.rs" required-features = ["ptr_metadata"] -[dependencies] -portable-atomic = { version = "1", default-features = false, optional = true } -spin = { version = "0.9", optional = true } +[[example]] +name = "unsafe_impl" +path = "examples/unsafe_impl.rs" +required-features = ["unsafe_impl"] [features] -default = [] -no_std = ["dep:portable-atomic", "dep:spin"] +default = ["std", "unsafe_impl", "debug"] + +std = [] debug = [] + +# Implementations +unsafe_impl = [] + +# Nightly features ptr_metadata = [] error_in_core = [] +[dependencies] +allocator-api2 = "0.3.0" +sptr = "0.3.2" + [dev-dependencies] -byteorder = "1.4" +byteorder = "1.5" [package.metadata.docs.rs] all-features = true diff --git a/LICENSE_MIT b/LICENSE_MIT index 7ff07df..e54032e 100644 --- a/LICENSE_MIT +++ b/LICENSE_MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Tin Švagelj +Copyright (c) 2024 Tin Švagelj Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE_ZLIB b/LICENSE_ZLIB index c0afaab..4f094f2 100644 --- a/LICENSE_ZLIB +++ b/LICENSE_ZLIB @@ -1,4 +1,4 @@ -Copyright (c) 2023 Tin Švagelj +Copyright (c) 2024 Tin Švagelj This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/README.md b/README.md index 268755e..e3bde2f 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,37 @@ # contiguous_mem -contiguous_mem streamlines storage and management of data stored in contiguous -blocks of memory. +contiguous_mem is space optimized a vector like collection that can store +entries of **varying layouts** **close in memory** while retaining type +information at the reference level. [![Crate](https://img.shields.io/crates/v/contiguous_mem?style=for-the-badge&logo=docs.rs)](https://crates.io/crates/contiguous_mem) [![Documentation](https://img.shields.io/docsrs/contiguous-mem?style=for-the-badge&logo=rust)](https://docs.rs/contiguous-mem) [![CI Status](https://img.shields.io/github/actions/workflow/status/Caellian/contiguous_mem/rust.yml?style=for-the-badge&logo=githubactions&logoColor=%23fff&label=CI)](https://github.com/Caellian/contiguous_mem/actions/workflows/rust.yml) [![Zlib or MIT or Apache 2.0 license](https://img.shields.io/crates/l/contiguous-mem?style=for-the-badge)](https://github.com/Caellian/contiguous_mem#license) -## Key Features +## Use Case -- `no_std` support! -- Simple and straightforward interface similar to standard containers. -- Support for dynamic resizing of allocated memory keeping the created - references functional (for safe implementations). - -### Specialized implementations - -You can pick and choose which implementation suits your use case best allowing -you to avoid runtime cost of synchronization and additionally memory cost of -safely wrapping referenced data if you don't need it. +![quick preview showing layout advantage](./doc/layout.png)
+* Both Vec and ContiguousMemory have one +level of indirection that's not shown for sake of simplicity. -Default implementation keeps relative offsets of stored data which are resolved -on access. +You need to store several different types and ensure their close proximity on +the heap to reduce cache misses, but which/how many is determined at runtime. -## Use cases +### Key Features -- Storing differently typed/sized data. ([example](./examples/default_impl.rs)) -- Ensuring stored data is placed adjacently in memory. ([example](./examples/game_loading.rs)) - - Note that returned references are **not** contiguous, only data they refer - to is. +- `no_std` support! +- Interface similar to `Vec`. +- Support for dynamic resizing of allocated memory while keeping the existing + references functional (for safe implementations). +- Exhaustively tested with Miri. +- Limited downstream dependencies (only polyfills). + - [sptr](https://crates.io/crates/sptr) is used as polyfill for + [Strict Provenance](https://doc.rust-lang.org/beta/unstable-book/language-features/strict-provenance.html) + and required for MIRI. + - [allocator-api2](https://crates.io/crates/allocator-api2) + is used as polyfill for the + [allocator API](https://doc.rust-lang.org/unstable-book/library-features/allocator-api.html). ## Getting Started @@ -37,14 +39,14 @@ Add the crate to your dependencies: ```toml [dependencies] -contiguous_mem = { version = "0.4" } +contiguous_mem = { version = "0.5" } ``` -Optionally enable `no_std` feature to use in `no_std` environment: +Disable default features (`std` feature) for use in `no_std` environments: ```toml [dependencies] -contiguous_mem = { version = "0.4", features = ["no_std"] } +contiguous_mem = { version = "0.5", default-feature = false, features = ["unsafe_impl"] } ``` ### Features @@ -56,6 +58,7 @@ contiguous_mem = { version = "0.4", features = ["no_std"] } - [`error_in_core`](https://dev-doc.rust-lang.org/stable/unstable-book/library-features/error-in-core.html) <_nightly_> - enables support for `core::error::Error` in `no_std` environment +- `unsafe_impl` (default) - enables `UnsafeContiguousMemory` ### Usage @@ -68,46 +71,43 @@ struct Data { } fn main() { - // Create a ContiguousMemory instance with a capacity of 1024 bytes and 1-byte alignment - let mut memory = ContiguousMemory::new(1024); + // Create a ContiguousMemory instance + let mut memory = ContiguousMemory::new(); // Store data in the memory container let data = Data { value: 42 }; - let stored_number: ContiguousMemoryRef = memory.push(22u64); - let stored_data: ContiguousMemoryRef = memory.push(data); + let stored_number: ContiguousEntryRef = memory.push(22u64); + let stored_data: ContiguousEntryRef = memory.push(data); // Retrieve and use the stored data assert_eq!(*stored_data.get(), data); assert_eq!(*stored_number.get(), 22); } ``` +* Note that reference types returned by store are inferred and only shown +here for demonstration purposes. -- References have a similar API as - [`RefCell`](https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html) - -Note that reference types returned by store are inferred and only shown -here for demonstration purposes. +Returned references have semantics similar to +[`RefCell`](https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html). For more usage examples see the -[`examples`](https://github.com/Caellian/contiguous_mem/tree/trunk/examples) +[examples](https://github.com/Caellian/contiguous_mem/tree/trunk/examples) directory. ## Stability -All versions prior to 1.0.0 are not considered production ready. This is my -first crate and there's still a lot of edge cases I didn't get a chance to -consider yet. +This crate has almost complete test coverage and is tested with Miri. It doesn't +rely on any weird language quirks, but it _does_ deal with memory management. -Prelimenary tests are in place but I don't consider them sufficient to guarantee -full correctness of behavior. I am however using this crate for development of -another crate which allows me to do some integration testing besides just -examples. +There's a lot of unsafe code due to the nature of the crate, but again, it's +covered by tests and Miri. ## Alternatives - manually managing memory to ensure contiguous placement of data - prone to errors and requires unsafe code -- for storing types with uniform `Layout`, when you only need to erase their +- multiple levels of indirection (`Vec>`) +- for storing types with **uniform** `Layout`, when you only need to erase their types at the container level see: - [`any_vec`](https://crates.io/crates/any_vec) - [`type_erased_vec`](https://crates.io/crates/type_erased_vec) @@ -133,4 +133,5 @@ license unless you explicitly state otherwise. ## License This project is licensed under [Zlib](./LICENSE_ZLIB), [MIT](./LICENSE_MIT), or -[Apache-2.0](./LICENSE_APACHE) license, choose whichever suits you most. +[Apache-2.0](./LICENSE_APACHE) license, choose whichever suits your use case the +best. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e79e958 --- /dev/null +++ b/build.rs @@ -0,0 +1,16 @@ +use std::process::Command; + +fn main() { + println!("cargo::rustc-check-cfg=cfg(nightly)"); + + let output = Command::new("rustc") + .args(["--version"]) + .output() + .expect("unable to get rustc version") + .stdout; + let version = String::from_utf8_lossy(&output); + + if version.contains("nightly") { + println!("cargo:rustc-cfg=nightly") + } +} diff --git a/doc/crate.md b/doc/crate.md deleted file mode 100644 index 8a5b616..0000000 --- a/doc/crate.md +++ /dev/null @@ -1,40 +0,0 @@ -contiguous_mem streamlines storage and management of data stored in contiguous -blocks of memory. - -## Implementations - -Primary interface of the crate is the [`ContiguousMemoryStorage`] structure -which is re-exported under following type aliases with specified implementation -details flag: - -- [`ContiguousMemory`] -- [`SyncContiguousMemory`] -- [`UnsafeContiguousMemory`] - -See individual items for usage examples, as well as project -[`examples`](https://github.com/Caellian/contiguous_mem/tree/trunk/examples) -directory. - -## Features - -- `no_std` - enables `no_std` dependencies for atomics, mutexes and rwlocks -- `debug` - enables `derive(Debug)` on structures unrelated to error handling -- [`ptr_metadata`](https://doc.rust-lang.org/beta/unstable-book/library-features/ptr-metadata.html) - <_nightly_> - allows casting references into `dyn Trait` -- [`error_in_core`](https://dev-doc.rust-lang.org/stable/unstable-book/library-features/error-in-core.html) - <_nightly_> - enables support for `core::error::Error` in `no_std` - environment - -## Contributions - -Contributions are welcome, feel free to -[create an issue](https://github.com/Caellian/contiguous_mem/issues) or a -[pull request](https://github.com/Caellian/contiguous_mem/pulls). - -All contributions to the project are licensed under the Zlib/MIT/Apache 2.0 -license unless you explicitly state otherwise. - -## License - -This project is licensed under [Zlib](./LICENSE_ZLIB), [MIT](./LICENSE_MIT), or -[Apache-2.0](./LICENSE_APACHE) license, choose whichever suits you most. diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 0000000..d9d899c --- /dev/null +++ b/doc/features.md @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Feature

Default

Description

stdenables std types
unsafe_implenables UnsafeContiguousMemory
debugenables derive(Debug) on structures unrelated to error handling
Nightly
ptr_metadataallows casting references into dyn Trait
error_in_coreenables support for core::error::Error in no_std environment
+ + diff --git a/doc/layout.png b/doc/layout.png new file mode 100644 index 0000000..a13b3df Binary files /dev/null and b/doc/layout.png differ diff --git a/doc/layout.typ b/doc/layout.typ new file mode 100644 index 0000000..9e94585 --- /dev/null +++ b/doc/layout.typ @@ -0,0 +1,89 @@ +#import "@preview/tablex:0.0.8": * + +#set page( + margin: 1cm, + width: auto, + height: auto, + fill: none, +) +#set text(size: 15pt, font: "Ubuntu") + +#let rounded(color, width: auto, content) = box( + radius: 2pt, + fill: color, + stroke: ( + thickness: 2pt, + paint: color.darken(30%).desaturate(50%) + ), + inset: 5pt, + width: width, + content +) + +#let container(color, name, ..items) = rounded( + color, + align(left, stack(dir: ttb, + spacing: 5pt, + box( + fill: color.lighten(80%).saturate(90%), + radius: 2pt, + inset: 2pt, + { + set text(size: 20pt) + raw(lang: "rust", name) + } + ), + stack(dir: ltr, spacing: 5pt, ..items) + )) +) + +#let byte-size = 20pt; +#let largest = byte-size * 4; + +#let vec-layout = container( + color.hsl(100.68deg, 46.46%, 75.1%), + "Vec", + rounded(color.hsl(215.49deg, 53.38%, 73.92%), width: largest, "Enum::A"), + rounded(color.hsl(276.34deg, 53.38%, 73.92%), width: largest, "Enum::B"), + rounded(color.hsl(330.42deg, 53.38%, 73.92%), width: largest, "Enum::C"), + rounded(color.hsl(64deg, 100%, 79.41%), width: largest, "Enum::D"), + rounded(color.hsl(124.92deg, 85.92%, 72.16%), width: largest, "Enum::E"), +) + +#let cmem-layout = container( + color.hsl(171.86deg, 46.46%, 75.1%), + "ContiguousMemory", + rounded(color.hsl(215.49deg, 53.38%, 73.92%), width: largest, "A"), + rounded(color.hsl(276.34deg, 53.38%, 73.92%), width: byte-size, "B"), + h(byte-size + 4pt), + rounded(color.hsl(330.42deg, 53.38%, 73.92%), width: byte-size * 2, "C"), + rounded(color.hsl(64deg, 100%, 79.41%), width: byte-size * 2, "D"), +) + +#let cmem-layout-after = container( + color.hsl(171.86deg, 46.46%, 75.1%), + "ContiguousMemory", + rounded(color.hsl(215.49deg, 53.38%, 73.92%), width: largest, "A"), + rounded(color.hsl(276.34deg, 53.38%, 73.92%), width: byte-size, "B"), + rounded(color.hsl(124.92deg, 85.92%, 72.16%), width: byte-size, "E"), + rounded(color.hsl(330.42deg, 53.38%, 73.92%), width: byte-size * 2, "C"), + rounded(color.hsl(64deg, 100%, 79.41%), width: byte-size * 2, "D"), +) + +#box( + fill: white, + outset: 1cm, + radius: 1cm, + stack( + dir: ttb, + vec-layout, + v(10pt), + align(horizon, stack(dir: ltr, spacing: 1pt, + cmem-layout, raw(lang: "rust", ".push(E)"), $ arrow.filled $, h(5pt), cmem-layout-after + )), + v(5pt), + move(dx: 100pt, text(size: 10pt)[ + #table(align: center, stroke: none, inset: 0pt, $arrow.filled.t$, v(5pt), [_alignment_], v(2pt), [_padding_]) + ]) + ) +) diff --git a/examples/default_impl.rs b/examples/default_impl.rs index a5b6234..1ec8731 100644 --- a/examples/default_impl.rs +++ b/examples/default_impl.rs @@ -1,4 +1,4 @@ -use contiguous_mem::*; +use contiguous_mem::{types::ImplDefault, *}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Data { @@ -6,15 +6,19 @@ struct Data { } fn main() { - // Create a ContiguousMemory instance with a capacity of 1024 bytes and 1-byte alignment - let mut memory = ContiguousMemory::new(1024); + // Create a ContiguousMemory instance + let mut memory = ContiguousMemory::::new(); // Store data in the memory container let data = Data { value: 42 }; - let stored_number: ContiguousEntryRef = memory.push(22u64); - let stored_data: ContiguousEntryRef = memory.push(data); + let stored_number: EntryRef = memory.push(22u64); + let stored_data: EntryRef = memory.push(data); // Retrieve and use the stored data assert_eq!(*stored_data.get(), data); assert_eq!(*stored_number.get(), 22); + + // All stored data gets cleaned up once `memory` goes out of scope, or we + // can forget it existed: + // memory.leak(); } diff --git a/examples/game_loading.rs b/examples/game_loading.rs index f6b13e4..ee60297 100644 --- a/examples/game_loading.rs +++ b/examples/game_loading.rs @@ -1,10 +1,11 @@ use std::{ + alloc::Layout, io::{Cursor, ErrorKind, Read, Write}, mem::align_of, }; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; -use contiguous_mem::*; +use contiguous_mem::{types::ImplUnsafe, *}; pub enum IndexOrPtr { Index(u32), @@ -31,6 +32,9 @@ impl IndexOrPtr { } pub trait Load { + /// # Safety + /// + /// Loading is unsafe for performance reasons. unsafe fn load(data: R) -> Self; } pub trait Save { @@ -103,7 +107,91 @@ impl Save for Level { } } -// this function emulates FS access for this example, ignore it +fn main() { + let mut data = ContiguousMemory::::with_layout( + Layout::from_size_align(112, align_of::()).unwrap(), + ); + + // Create enemy lookup list. + let enemies: &[*const Enemy] = &[ + data.push(load_game_file("enemy1.dat")), + data.push(load_game_file("enemy2.dat")), + data.push(load_game_file("enemy3.dat")), + data.push(load_game_file("enemy4.dat")), + ]; + + // Create level lookup list. + let levels: &[*mut Level] = &[ + data.push(load_game_file("level1.dat")), + data.push(load_game_file("level2.dat")), + ]; + + // Data won't go out of scope while we're using it in this example, but if + // it were loaded in some other function it would. + data.leak(); + // Now we can assume all created pointers are 'static. + + // Prepare levels for use + levels.iter().for_each(|level| { + let level = unsafe { &mut **level }; + + level.enemies = level + .enemies + .iter() + .map(|enemy| enemy.to_ref(enemies)) + .collect(); + }); + + let mut time = 0.0; + let mut current_level: usize = 0; + + // Main game loop + while current_level < levels.len() { + // Simulate the passage of time (you can replace this with your game logic) + time += 1.0; + + let mut all_enemies_killed = true; + let current_lvl = unsafe { &mut *levels[current_level] }; + + for enemy in current_lvl.enemies.iter_mut() { + let enemy_ref = enemy.unwrap_ref(); + + let health_reduction = ((5.0 + time * 0.25) as u32).min(enemy_ref.health); + enemy_ref.health -= health_reduction; + enemy_ref.age += 1.0; + + // Check if the enemy is still alive + if enemy_ref.health > 0 { + all_enemies_killed = false; + } + } + + // If all enemies in the current level are killed, reset them and progress to the next level + if all_enemies_killed { + println!( + "All enemies in level {} have been killed!", + current_level + 1 + ); + current_level += 1; + + // Reset all enemies in the next level + if current_level < levels.len() { + let next_level = unsafe { &mut *levels[current_level] }; + for enemy in next_level.enemies.iter_mut() { + enemy.unwrap_ref().reset(); + } + } + } + } + + println!( + "Congratulations! You've completed all levels in: {:.2}", + time + ); +} + +/// This function emulates filesystem access and deserialization for this +/// example, you can ignore it. fn load_game_file(file_name: &'static str) -> T { let mut data = Vec::with_capacity(24); let mut data_cursor = Cursor::new(&mut data); @@ -169,88 +257,3 @@ fn load_game_file(file_name: &'static str) -> T { unsafe { T::load(data_cursor) } } - -fn main() { - let mut data = UnsafeContiguousMemory::new_aligned(112, align_of::()).unwrap(); - - // Create enemy lookup list. - let enemies: &[*const Enemy] = unsafe { - &[ - data.push(load_game_file("enemy1.dat")).unwrap_unchecked(), - data.push(load_game_file("enemy2.dat")).unwrap_unchecked(), - data.push(load_game_file("enemy3.dat")).unwrap_unchecked(), - data.push(load_game_file("enemy4.dat")).unwrap_unchecked(), - ] - }; - - // Create level lookup list. - let levels: &[*mut Level] = unsafe { - &[ - data.push(load_game_file("level1.dat")).unwrap_unchecked(), - data.push(load_game_file("level2.dat")).unwrap_unchecked(), - ] - }; - - // data won't go out of scope while we're using it in this example, but in - // your use case it might. This is here for completeness. - data.forget(); - // now we can assume all created pointers are 'static - - // prepare levels for use - levels.iter().for_each(|level| { - let level = unsafe { &mut **level }; - - level.enemies = level - .enemies - .iter() - .map(|enemy| enemy.to_ref(enemies)) - .collect(); - }); - - let mut time = 0.0; - let mut current_level: usize = 0; - - // Main game loop - while current_level < levels.len() { - // Simulate the passage of time (you can replace this with your game logic) - time += 1.0; - - let mut all_enemies_killed = true; - let current_lvl = unsafe { &mut *levels[current_level] }; - - for enemy in current_lvl.enemies.iter_mut() { - let enemy_ref = enemy.unwrap_ref(); - - let health_reduction = ((5.0 + time * 0.25) as u32).min(enemy_ref.health); - enemy_ref.health -= health_reduction; - enemy_ref.age += 1.0; - - // Check if the enemy is still alive - if enemy_ref.health > 0 { - all_enemies_killed = false; - } - } - - // If all enemies in the current level are killed, reset them and progress to the next level - if all_enemies_killed { - println!( - "All enemies in level {} have been killed!", - current_level + 1 - ); - current_level += 1; - - // Reset all enemies in the next level - if current_level < levels.len() { - let next_level = unsafe { &mut *levels[current_level] }; - for enemy in next_level.enemies.iter_mut() { - enemy.unwrap_ref().reset(); - } - } - } - } - - println!( - "Congratulations! You've completed all levels in: {:.2}", - time - ); -} diff --git a/examples/ptr_metadata.rs b/examples/ptr_metadata.rs index f2d21e9..520e16f 100644 --- a/examples/ptr_metadata.rs +++ b/examples/ptr_metadata.rs @@ -1,6 +1,6 @@ #![feature(ptr_metadata)] -use contiguous_mem::*; +use contiguous_mem::{types::ImplDefault, *}; trait Greetable { fn print_hello(&self); @@ -21,13 +21,12 @@ impl Greetable for Dog { } fn main() { - let mut storage = ContiguousMemory::new(4096); + let mut storage = ContiguousMemory::::with_capacity(4096); let person1 = storage.push(Person("Joe".to_string())); - let person2: ContiguousEntryRef = - storage.push(Person("Craig".to_string())).into_dyn(); + let person2: EntryRef = storage.push(Person("Craig".to_string())).into_dyn(); - let dog: ContiguousEntryRef = storage.push(Dog("Rover".to_string())).into_dyn(); + let dog: EntryRef = storage.push(Dog("Rover".to_string())).into_dyn(); person1.get().print_hello(); person2.get().print_hello(); diff --git a/examples/sync_impl.rs b/examples/sync_impl.rs deleted file mode 100644 index 079853f..0000000 --- a/examples/sync_impl.rs +++ /dev/null @@ -1,43 +0,0 @@ -use contiguous_mem::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct Data { - value: u32, -} - -fn main() { - let storage = SyncContiguousMemory::new(4096); - - let mut sent_storage = storage.clone(); - let writer_one = - std::thread::spawn(move || sent_storage.push(22u64).expect("unable to store number")); - - let data = Data { value: 42 }; - - let mut sent_storage = storage.clone(); - let writer_two = std::thread::spawn(move || { - sent_storage - .push(Data { value: 42 }) - .expect("unable to store Data") - }); - - let stored_number: SyncContiguousEntryRef = - writer_one.join().expect("unable to join number thread"); - let mut stored_number_clone = stored_number.clone(); - let stored_data: SyncContiguousEntryRef = - writer_two.join().expect("unable to join Data thread"); - - let number_ref = stored_number - .get() - .expect("number ref poisoned on first use"); - let stored_data = stored_data.get().expect("Data ref poisoned on first use"); - - // note that number is still locked here - assert!( - stored_number_clone.try_get_mut().is_err(), - "number reference should not be writable as the number is currently borrowed" - ); - - assert_eq!(*number_ref, 22); - assert_eq!(*stored_data, data); -} diff --git a/examples/unsafe_impl.rs b/examples/unsafe_impl.rs index 87a43c9..fab15ea 100644 --- a/examples/unsafe_impl.rs +++ b/examples/unsafe_impl.rs @@ -1,4 +1,4 @@ -use contiguous_mem::*; +use contiguous_mem::{types::ImplUnsafe, *}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Data { @@ -6,22 +6,25 @@ struct Data { } fn main() { - // Create a ContiguousMemory instance with a capacity of 1024 bytes and 1-byte alignment - let mut memory = UnsafeContiguousMemory::new(1024); + // Create a ContiguousMemory instance with a capacity of 1024 bytes and + // 1-byte alignment + let mut memory = ContiguousMemory::::with_capacity(1024); // Store data in the memory container let data = Data { value: 42 }; - let stored_number: *mut u64 = memory - .push(22u64) - .expect("there should be enough space to store a number"); - let stored_data: *mut Data = memory - .push(data) - .expect("there should be enough space to store Data"); + let stored_number: *mut u64 = memory.push(22u64); + let stored_data: *mut Data = memory.push(data); // Retrieve and use the stored data unsafe { + assert!(!stored_data.is_null()); assert_eq!(*stored_data, data); + assert!(!stored_number.is_null()); assert_eq!(*stored_number, 22); } + + // All stored data gets cleaned up once `memory` goes out of scope, or we + // can forget it existed: + // memory.leak(); } diff --git a/src/details.rs b/src/details.rs deleted file mode 100644 index cb63999..0000000 --- a/src/details.rs +++ /dev/null @@ -1,836 +0,0 @@ -//! Implementation details for behavior specialization marker structs. -//! -//! End-users aren't meant to interact with traits defined in this module -//! directly and they exist solely to simplify implementation of -//! [`ContiguousMemoryStorage`](ContiguousMemoryStorage) by erasing -//! type details of different implementations. -//! -//! Any changes to these traits aren't considered a breaking change and won't -//! be reflected in version numbers. - -use core::{ - alloc::{Layout, LayoutError}, - cell::{Cell, RefCell, RefMut}, - mem::size_of, - ptr::null_mut, -}; - -use core::marker::PhantomData; - -#[cfg(feature = "no_std")] -use portable_atomic::{AtomicUsize, Ordering}; -#[cfg(not(feature = "no_std"))] -use std::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{ - error::{ContiguousMemoryError, LockSource, LockingError}, - range::ByteRange, - refs::{sealed::*, ContiguousEntryRef, SyncContiguousEntryRef}, - tracker::AllocationTracker, - types::*, - BaseLocation, ContiguousMemoryState, -}; - -/// Implementation details shared between [storage](StorageDetails) and -/// [`reference`](ReferenceDetails) implementations. -pub trait ImplBase: Sized { - /// The type representing reference to internal state - type StorageState: Clone; - - /// The type of reference returned by store operations. - type ReferenceType: Clone; - - /// The type representing result of accessing data that is locked in async - /// context - type LockResult; - - /// The type representing the allocation tracker reference type. - type ATGuard<'a>; - - /// Indicates whether locks are used for synchronization, allowing the - /// compiler to easily optimize away branches involving them. - const USES_LOCKS: bool = false; -} - -/// Implementation that's not thread-safe but performs faster as it avoids -/// mutexes and locks. -/// -/// For example usage of default implementation see: [`ContiguousMemory`](crate::ContiguousMemory) -#[cfg_attr(feature = "debug", derive(Debug))] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ImplDefault; -impl ImplBase for ImplDefault { - type StorageState = Rc>; - type ReferenceType = ContiguousEntryRef; - type LockResult = T; - type ATGuard<'a> = RefMut<'a, AllocationTracker>; -} - -/// Thread-safe implementation utilizing mutexes and locks to prevent data -/// races. -/// -/// For example usage of default implementation see: -/// [`SyncContiguousMemory`](crate::SyncContiguousMemory) -#[cfg_attr(feature = "debug", derive(Debug))] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ImplConcurrent; -impl ImplBase for ImplConcurrent { - type StorageState = Arc>; - type ReferenceType = SyncContiguousEntryRef; - type LockResult = Result; - type ATGuard<'a> = MutexGuard<'a, AllocationTracker>; - - const USES_LOCKS: bool = true; -} - -/// Implementation which provides direct (unsafe) access to stored entries. -/// -/// For example usage of default implementation see: -/// [`UnsafeContiguousMemory`](crate::UnsafeContiguousMemory) -#[cfg_attr(feature = "debug", derive(Debug))] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ImplUnsafe; -impl ImplBase for ImplUnsafe { - type StorageState = ContiguousMemoryState; - type ReferenceType = *mut T; - type LockResult = T; - type ATGuard<'a> = &'a mut AllocationTracker; -} - -/// Implementation details of -/// [`ContiguousMemoryStorage`](ContiguousMemoryStorage). -pub trait StorageDetails: ImplBase { - /// The type representing the base memory and allocation tracking. - type Base; - - /// The type representing the allocation tracker discrete type. - type AllocationTracker; - - /// The type representing [`Layout`] entries with inner mutability. - type SizeType; - - /// The type representing result of storing data. - type PushResult; - - /// Builds a new internal state from provided parameters - fn build_state( - base: *mut u8, - capacity: usize, - alignment: usize, - ) -> Result; - - /// Dereferences the inner state smart pointer and returns it by reference. - fn deref_state(state: &Self::StorageState) -> &ContiguousMemoryState; - - /// Retrieves the base pointer from the base instance. - fn get_base(base: &Self::Base) -> Self::LockResult<*mut u8>; - - /// Retrieves the base pointer from the base instance. Non blocking version. - fn try_get_base(base: &Self::Base) -> Self::LockResult<*mut u8>; - - /// Retrieves the capacity from the state. - fn get_capacity(capacity: &Self::SizeType) -> usize; - - /// Returns a writable reference to AllocationTracker. - fn get_allocation_tracker<'a>( - state: &'a mut Self::StorageState, - ) -> Self::LockResult>; - - /// Resizes and reallocates the base memory according to new capacity. - fn resize_container( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result, ContiguousMemoryError>; - - /// Deallocates the base memory using layout information. - fn deallocate(base: &mut Self::Base, layout: Layout); - - /// Resizes the allocation tracker to the new capacity. - fn resize_tracker( - state: &mut Self::StorageState, - 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::StorageState) -> Self::LockResult>; - - /// Finds the next free memory region for given layout in the tracker. - fn track_next( - state: &mut Self::StorageState, - layout: Layout, - ) -> Result; - - /// Returns whether a given layout can be stored or returns an error if - /// [`AllocationTracker`] can't be stored. - fn peek_next(state: &Self::StorageState, layout: Layout) - -> Self::LockResult>; -} - -impl StorageDetails for ImplConcurrent { - type Base = RwLock<*mut u8>; - type AllocationTracker = Mutex; - type SizeType = AtomicUsize; - type PushResult = Result, LockingError>; - - fn build_state( - base: *mut u8, - capacity: usize, - alignment: usize, - ) -> Result { - let layout = Layout::from_size_align(capacity, alignment)?; - - Ok(Arc::new(ContiguousMemoryState { - base: BaseLocation(RwLock::new(base)), - capacity: AtomicUsize::new(layout.size()), - alignment: layout.align(), - tracker: Mutex::new(AllocationTracker::new(capacity)), - })) - } - - #[inline] - fn deref_state(state: &Self::StorageState) -> &ContiguousMemoryState { - state - } - - #[inline] - fn get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - base.read_named(LockSource::BaseAddress) - .map(|result| *result) - } - - #[inline] - fn try_get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - base.try_read_named(LockSource::BaseAddress) - .map(|result| *result) - } - - #[inline] - fn get_capacity(capacity: &Self::SizeType) -> usize { - capacity.load(Ordering::Acquire) - } - - #[inline] - fn get_allocation_tracker<'a>( - state: &'a mut Self::StorageState, - ) -> Self::LockResult> { - state.tracker.lock_named(LockSource::AllocationTracker) - } - - fn resize_container( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result, ContiguousMemoryError> { - let layout = - Layout::from_size_align(state.capacity.load(Ordering::Acquire), state.alignment)?; - let mut base_addr = state.base.write_named(LockSource::BaseAddress)?; - let prev_addr = *base_addr; - *base_addr = unsafe { allocator::realloc(*base_addr, layout, new_capacity) }; - state.capacity.store(new_capacity, Ordering::Release); - Ok(if *base_addr != prev_addr { - Some(*base_addr) - } else { - None - }) - } - - #[inline] - fn deallocate(base: &mut Self::Base, layout: Layout) { - if let Ok(mut lock) = base.write_named(LockSource::BaseAddress) { - unsafe { allocator::dealloc(*lock, layout) }; - *lock = null_mut(); - } - } - - #[inline] - fn resize_tracker( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result<(), ContiguousMemoryError> { - let mut lock = state.tracker.lock_named(LockSource::AllocationTracker)?; - lock.resize(new_capacity)?; - Ok(()) - } - - #[inline] - fn shrink_tracker(state: &mut Self::StorageState) -> Result, LockingError> { - let mut lock = state.tracker.lock_named(LockSource::AllocationTracker)?; - Ok(lock.shrink_to_fit()) - } - - #[inline] - fn track_next( - state: &mut Self::StorageState, - layout: Layout, - ) -> Result { - let base = Self::get_base(&state.base)? as usize; - let mut lock = state.tracker.lock_named(LockSource::AllocationTracker)?; - lock.take_next(base, layout) - } - - #[inline] - fn peek_next( - state: &Self::StorageState, - layout: Layout, - ) -> Result, LockingError> { - let lock = state.tracker.lock_named(LockSource::AllocationTracker)?; - Ok(lock.peek_next(layout)) - } -} - -impl StorageDetails for ImplDefault { - type Base = Cell<*mut u8>; - type AllocationTracker = RefCell; - type SizeType = Cell; - type PushResult = ContiguousEntryRef; - - fn build_state( - base: *mut u8, - capacity: usize, - alignment: usize, - ) -> Result { - let layout: Layout = Layout::from_size_align(capacity, alignment)?; - - Ok(Rc::new(ContiguousMemoryState { - base: BaseLocation(Cell::new(base)), - capacity: Cell::new(layout.size()), - alignment: layout.align(), - tracker: RefCell::new(AllocationTracker::new(capacity)), - })) - } - - #[inline] - fn deref_state(state: &Self::StorageState) -> &ContiguousMemoryState { - state - } - - #[inline] - fn get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - base.get() - } - - #[inline] - fn try_get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - Self::get_base(base) - } - - #[inline] - fn get_capacity(capacity: &Self::SizeType) -> usize { - capacity.get() - } - - #[inline] - fn get_allocation_tracker<'a>( - state: &'a mut Self::StorageState, - ) -> Self::LockResult> { - state.tracker.borrow_mut() - } - - fn resize_container( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result, ContiguousMemoryError> { - let layout = Layout::from_size_align(state.capacity.get(), state.alignment)?; - let prev_base = state.base.get(); - let new_base = unsafe { allocator::realloc(prev_base, layout, new_capacity) }; - state.base.set(new_base); - state.capacity.set(new_capacity); - Ok(if new_base != prev_base { - Some(new_base) - } else { - None - }) - } - - #[inline] - fn deallocate(base: &mut Self::Base, layout: Layout) { - unsafe { allocator::dealloc(base.get(), layout) }; - base.set(null_mut()) - } - - #[inline] - fn resize_tracker( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result<(), ContiguousMemoryError> { - state.tracker.borrow_mut().resize(new_capacity) - } - - #[inline] - fn shrink_tracker(state: &mut Self::StorageState) -> Option { - state.tracker.borrow_mut().shrink_to_fit() - } - - #[inline] - fn track_next( - state: &mut Self::StorageState, - layout: Layout, - ) -> Result { - let base = state.base.get() as usize; - let mut tracker = state.tracker.borrow_mut(); - tracker.take_next(base, layout) - } - - #[inline] - fn peek_next(state: &Self::StorageState, layout: Layout) -> Option { - let tracker = state.tracker.borrow(); - tracker.peek_next(layout) - } -} - -impl StorageDetails for ImplUnsafe { - type Base = *mut u8; - type AllocationTracker = AllocationTracker; - type SizeType = usize; - type PushResult = Result<*mut T, ContiguousMemoryError>; - - fn build_state( - base: *mut u8, - capacity: usize, - alignment: usize, - ) -> Result { - let layout = Layout::from_size_align(capacity, alignment)?; - Ok(ContiguousMemoryState { - base: BaseLocation(base), - capacity: layout.size(), - alignment: layout.align(), - tracker: AllocationTracker::new(capacity), - }) - } - - #[inline] - fn deref_state(state: &Self::StorageState) -> &ContiguousMemoryState { - state - } - - #[inline] - fn get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - *base - } - - #[inline] - fn try_get_base(base: &Self::Base) -> Self::LockResult<*mut u8> { - Self::get_base(base) - } - - #[inline] - fn get_capacity(capacity: &Self::SizeType) -> usize { - *capacity - } - - #[inline] - fn get_allocation_tracker<'a>( - state: &'a mut Self::StorageState, - ) -> Self::LockResult> { - &mut state.tracker - } - - fn resize_container( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result, ContiguousMemoryError> { - let layout = Layout::from_size_align(state.capacity, state.alignment)?; - let prev_base = *state.base; - state.base = BaseLocation(unsafe { allocator::realloc(prev_base, layout, new_capacity) }); - state.capacity = new_capacity; - Ok(if *state.base != prev_base { - Some(*state.base) - } else { - None - }) - } - - #[inline] - fn deallocate(base: &mut Self::Base, layout: Layout) { - unsafe { - allocator::dealloc(*base, layout); - } - *base = null_mut(); - } - - #[inline] - fn resize_tracker( - state: &mut Self::StorageState, - new_capacity: usize, - ) -> Result<(), ContiguousMemoryError> { - state.tracker.resize(new_capacity) - } - - #[inline] - fn shrink_tracker(state: &mut Self::StorageState) -> Option { - state.tracker.shrink_to_fit() - } - - #[inline] - fn track_next( - state: &mut Self::StorageState, - layout: Layout, - ) -> Result { - let base = *state.base as usize; - state.tracker.take_next(base, layout) - } - - #[inline] - fn peek_next(state: &Self::StorageState, layout: Layout) -> Option { - state.tracker.peek_next(layout) - } -} - -/// Implementation details of returned [reference types](crate::refs). -pub trait ReferenceDetails: ImplBase { - /// The type representing internal state of the reference. - type RefState: Clone; - - /// The type handling concurrent mutable access exclusion. - type BorrowLock; - - /// Type of the concurrent mutable access exclusion read guard. - type ReadGuard<'a>: DebugReq; - /// Type of the concurrent mutable access exclusion write guard. - type WriteGuard<'a>: DebugReq; - - /// Releases the specified memory region back to the allocation tracker. - fn free_region( - tracker: Self::LockResult>, - base: Self::LockResult<*mut u8>, - range: ByteRange, - ) -> Option<*mut ()>; - - /// Builds a reference for the stored data. - fn build_ref( - state: &Self::StorageState, - addr: *mut T, - range: ByteRange, - ) -> Self::ReferenceType; - - /// Marks reference state as no longer being borrowed. - fn unborrow_ref(_state: &Self::RefState, _kind: BorrowKind) {} -} - -impl ReferenceDetails for ImplConcurrent { - type RefState = Arc>; - type BorrowLock = RwLock<()>; - type ReadGuard<'a> = RwLockReadGuard<'a, ()>; - type WriteGuard<'a> = RwLockWriteGuard<'a, ()>; - - fn free_region( - tracker: Self::LockResult>, - base: Self::LockResult<*mut u8>, - range: ByteRange, - ) -> Option<*mut ()> { - if let Ok(mut lock) = tracker { - let _ = lock.release(range); - - if let Ok(base) = base { - unsafe { Some(base.add(range.0) as *mut ()) } - } else { - None - } - } else { - None - } - } - - fn build_ref( - state: &Self::StorageState, - _addr: *mut T, - range: ByteRange, - ) -> Self::ReferenceType { - SyncContiguousEntryRef { - inner: Arc::new(ReferenceState { - state: state.clone(), - range, - borrow_kind: RwLock::new(()), - drop_fn: drop_fn::(), - _phantom: PhantomData, - }), - #[cfg(feature = "ptr_metadata")] - metadata: (), - #[cfg(not(feature = "ptr_metadata"))] - _phantom: PhantomData, - } - } -} - -impl ReferenceDetails for ImplDefault { - type RefState = Rc>; - type BorrowLock = Cell; - type ReadGuard<'a> = (); - type WriteGuard<'a> = (); - - fn free_region( - mut tracker: Self::LockResult>, - base: Self::LockResult<*mut u8>, - range: ByteRange, - ) -> Option<*mut ()> { - let _ = tracker.release(range); - unsafe { Some(base.add(range.0) as *mut ()) } - } - - fn build_ref( - state: &Self::StorageState, - _addr: *mut T, - range: ByteRange, - ) -> Self::ReferenceType { - ContiguousEntryRef { - inner: Rc::new(ReferenceState { - state: state.clone(), - range, - borrow_kind: Cell::new(BorrowState::Read(0)), - drop_fn: drop_fn::(), - _phantom: PhantomData, - }), - #[cfg(feature = "ptr_metadata")] - metadata: (), - #[cfg(not(feature = "ptr_metadata"))] - _phantom: PhantomData, - } - } - - fn unborrow_ref(state: &Self::RefState, _kind: BorrowKind) { - let next = match state.borrow_kind.get() { - BorrowState::Read(count) => BorrowState::Read(count - 1), - BorrowState::Write => BorrowState::Read(0), - }; - state.borrow_kind.set(next) - } -} - -impl ReferenceDetails for ImplUnsafe { - type RefState = (); - type BorrowLock = (); - type ReadGuard<'a> = (); - type WriteGuard<'a> = (); - - fn free_region( - tracker: Self::LockResult>, - base: Self::LockResult<*mut u8>, - range: ByteRange, - ) -> Option<*mut ()> { - let _ = tracker.release(range); - - unsafe { Some(base.add(range.0) as *mut ()) } - } - - fn build_ref( - _base: &Self::StorageState, - addr: *mut T, - _range: ByteRange, - ) -> Self::ReferenceType { - addr - } -} - -pub trait StoreDataDetails: StorageDetails { - unsafe fn push_raw( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Self::PushResult; - - unsafe fn push_raw_persisted( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Self::PushResult; - - fn assume_stored( - state: &Self::StorageState, - position: usize, - ) -> Self::LockResult>; -} - -impl StoreDataDetails for ImplConcurrent { - unsafe fn push_raw( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Result, LockingError> { - let (addr, range) = loop { - match ImplConcurrent::track_next(state, layout) { - Ok(taken) => { - let found = (taken.0 - + *state.base.read_named(LockSource::BaseAddress)? as usize) - as *mut u8; - unsafe { core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()) } - break (found, taken); - } - Err(ContiguousMemoryError::NoStorageLeft) => { - let curr_capacity = state.capacity.load(Ordering::Acquire); - let new_capacity = curr_capacity - .saturating_mul(2) - .max(curr_capacity + layout.size()); - match ImplConcurrent::resize_container(state, new_capacity) { - Ok(_) => { - match ImplConcurrent::resize_tracker(state, new_capacity) { - Ok(_) => {}, - Err(ContiguousMemoryError::Lock(locking_err)) => return Err(locking_err), - Err(_) => unreachable!("unable to grow AllocationTracker"), - }; - } - Err(ContiguousMemoryError::Lock(locking_err)) => return Err(locking_err), - Err(other) => unreachable!( - "reached unexpected error while growing the container to store data: {:?}", - 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(state, addr as *mut T, range)) - } - - #[inline(always)] - unsafe fn push_raw_persisted( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Self::PushResult { - match Self::push_raw(state, data, layout) { - Ok(it) => { - let result = it.clone(); - core::mem::forget(it.inner); - Ok(result) - } - err => err, - } - } - - #[inline(always)] - fn assume_stored( - state: &Self::StorageState, - position: usize, - ) -> Result, LockingError> { - let addr = unsafe { - state - .base - .read_named(LockSource::BaseAddress)? - .add(position) - }; - Ok(ImplConcurrent::build_ref( - state, - addr as *mut T, - ByteRange(position, size_of::()), - )) - } -} - -impl StoreDataDetails for ImplDefault { - unsafe fn push_raw( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> ContiguousEntryRef { - let (addr, range) = loop { - match ImplDefault::track_next(state, layout) { - Ok(taken) => { - let found = (taken.0 + state.base.get() as usize) as *mut u8; - unsafe { - core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()); - } - break (found, taken); - } - Err(ContiguousMemoryError::NoStorageLeft) => { - let curr_capacity = state.capacity.get(); - let new_capacity = curr_capacity - .saturating_mul(2) - .max(curr_capacity + layout.size()); - match ImplDefault::resize_container(state, new_capacity) { - Ok(_) => { - ImplDefault::resize_tracker(state, new_capacity).expect("unable to grow AllocationTracker"); - }, - Err(err) => unreachable!( - "reached unexpected error while growing the container to store data: {:?}", - err - ), - } - } - Err(other) => unreachable!( - "reached unexpected error while looking for next region to store data: {:?}", - other - ), - } - }; - - ImplDefault::build_ref(state, addr as *mut T, range) - } - - #[inline(always)] - unsafe fn push_raw_persisted( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Self::PushResult { - let value = Self::push_raw(state, data, layout); - let result = value.clone(); - core::mem::forget(value.inner); - result - } - - #[inline(always)] - fn assume_stored( - state: &Self::StorageState, - position: usize, - ) -> ContiguousEntryRef { - let addr = unsafe { state.base.get().add(position) }; - ImplDefault::build_ref(state, addr as *mut T, ByteRange(position, size_of::())) - } -} - -impl StoreDataDetails for ImplUnsafe { - /// Returns a raw pointer (`*mut T`) to the stored value or an error if no - /// free regions remain - unsafe fn push_raw( - state: &mut Self::StorageState, - data: *const T, - layout: Layout, - ) -> Result<*mut T, ContiguousMemoryError> { - let (addr, range) = match ImplUnsafe::track_next(state, layout) { - Ok(taken) => { - let found = (taken.0 + *state.base as usize) as *mut u8; - unsafe { - core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()); - } - - (found, taken) - } - Err(other) => return Err(other), - }; - - Ok(ImplUnsafe::build_ref(state, addr as *mut T, range)) - } - - unsafe fn push_raw_persisted( - _state: &mut Self::StorageState, - _data: *const T, - _layout: Layout, - ) -> Self::PushResult { - unimplemented!() - } - - #[inline(always)] - fn assume_stored(state: &Self::StorageState, position: usize) -> *mut T { - let addr = unsafe { state.base.add(position) }; - ImplUnsafe::build_ref( - state, - addr as *mut T, - ByteRange(position, position + size_of::()), - ) - } -} - -/// Trait representing requirements for implementation details of the -/// [`ContiguousMemoryStorage`](crate::ContiguousMemoryStorage). -/// -/// This trait is implemented by: -/// - [`ImplDefault`] -/// - [`ImplConcurrent`] -/// - [`ImplUnsafe`] -pub trait ImplDetails: ImplBase + StorageDetails + ReferenceDetails + StoreDataDetails {} -impl ImplDetails for Impl {} diff --git a/src/error.rs b/src/error.rs index a6dfdd6..59132b6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,215 +1,95 @@ //! Errors produced by the crate. -#[cfg(feature = "error_in_core")] -use core::error::Error; -#[cfg(all(not(feature = "error_in_core"), not(feature = "no_std")))] -use std::error::Error; +#[cfg(any(feature = "error_in_core", feature = "std"))] +use crate::types::Error; -#[cfg(not(feature = "no_std"))] -use std::sync::MutexGuard; -#[cfg(not(feature = "no_std"))] -use std::sync::PoisonError; - -use core::alloc::LayoutError; use core::fmt::Debug; -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] +#[cfg(any(feature = "std", feature = "error_in_core"))] use core::fmt::{Display, Formatter, Result as FmtResult}; -use crate::range::ByteRange; +use crate::{range::ByteRange, reference::BorrowState}; -/// Error returned when a [`Mutex`](crate::types::Mutex) or a -/// [`RwLock`](crate::types::RwLock) isn't lockable. -#[derive(Debug)] -pub enum LockingError { - /// Not lockable because the mutex/lock was poisoned. - Poisoned { - /// Specifies source of poisoning. - source: LockSource, - }, - /// Not lockable because the lock would be blocking. - WouldBlock { - /// Specifies which mutex/lock would block. - source: LockSource, - }, +#[cfg(nightly)] +use core::alloc::AllocError; +#[cfg(not(nightly))] +use allocator_api2::alloc::AllocError; + +/// Represents a class of errors returned by invalid memory operations and +/// allocator failure. +#[derive(Debug, Clone, Copy)] +pub enum MemoryError { + /// Tried allocating memory chunk larger than [`isize::MAX`] or what is + /// currently available. + TooLarge, + /// Allocation failure caused by either resource exhaustion or invalid + /// arguments being provided to an allocator. + Allocator( + /// Cause allocator error. + AllocError, + ), +} + +impl From for MemoryError { + fn from(_: core::alloc::LayoutError) -> Self { + Self::TooLarge + } } -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Display for LockingError { +#[cfg(any(feature = "std", feature = "error_in_core"))] +impl Display for MemoryError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { - LockingError::Poisoned { source } => write!( - f, - "Cannot acquire lock: {}", - match source { - LockSource::BaseAddress => { - "base address Mutex was poisoned" - } - LockSource::AllocationTracker => "AllocationTracker Mutex was poisoned", - LockSource::Reference => - "reference concurrent mutable access exclusion flag Mutex was poisoned", - } - ), - LockingError::WouldBlock { source } => write!( + MemoryError::TooLarge => write!( f, - "Lock would block the current thread: {}", - match source { - LockSource::BaseAddress => "base address already borrowed", - LockSource::AllocationTracker => "AllocationTracker already borrowed", - LockSource::Reference => "reference already borrowed", - } + "Tried allocating container capacity larger than `isize::MAX`" ), + MemoryError::Allocator(_) => write!(f, "Allocator error"), } } } -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Error for LockingError { +#[cfg(any(feature = "std", feature = "error_in_core"))] +impl Error for MemoryError { fn source(&self) -> Option<&(dyn Error + 'static)> { - None - } -} - -#[cfg(not(feature = "no_std"))] -impl From>> for LockingError { - fn from(_: PoisonError>) -> Self { - LockingError::Poisoned { - source: LockSource::BaseAddress, + match self { + MemoryError::Allocator(inner) => Some(inner), + _ => None, } } } -#[cfg(not(feature = "no_std"))] -impl From>> for LockingError { - fn from(_: PoisonError>) -> Self { - LockingError::Poisoned { - source: LockSource::AllocationTracker, - } +impl From for MemoryError { + fn from(err: AllocError) -> Self { + MemoryError::Allocator(err) } } -/// Error returned when concurrent mutable access is attempted to the same -/// memory region. +/// Error returned when concurrent mutable access to the same memory region is +/// attempted. #[derive(Debug)] -pub struct RegionBorrowedError { - /// [`ByteRange`] that was attempted to be borrowed. +pub struct RegionBorrowError { + /// Range that was attempted to be borrowed. pub range: ByteRange, + /// State of the borrow before failiure. + pub borrow_state: BorrowState, } - -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Display for RegionBorrowedError { +#[cfg(any(feature = "std", feature = "error_in_core"))] +impl Display for RegionBorrowError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "attempted to borrow already mutably borrowed memory region: {}", - self.range - ) - } -} - -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Error for RegionBorrowedError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - None - } -} - -/// Represents errors that can occur while using the -/// [`ContiguousMemoryStorage`](crate::ContiguousMemoryStorage) container. -#[derive(Debug)] -#[non_exhaustive] -pub enum ContiguousMemoryError { - /// Tried to store data that does not fit into any of the remaining free - /// memory regions. - NoStorageLeft, - /// 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::tracker::AllocationTracker). - NotContained, - /// Attempted to free memory that has already been deallocated. - DoubleFree, - /// The [`AllocationTracker`](crate::tracker::AllocationTracker) does not - /// allow shrinking to the expected size. - Unshrinkable { - /// The minimum required size to house currently stored data. - required_size: usize, - }, - /// Indicates that a mutex wasn't lockable. - Lock(LockingError), - /// Indicates that the provided [`Layout`](core::alloc::Layout) is invalid. - Layout( - /// The underlying error that caused the [`Layout`](core::alloc::Layout) - /// to be considered invalid. - LayoutError, - ), - /// Tried mutably borrowing already borrowed region of memory - BorrowMut(RegionBorrowedError), -} - -/// Represents possible poisoning sources for mutexes and locks. -#[derive(Debug, Clone, Copy)] -#[non_exhaustive] -pub enum LockSource { - /// Mutex containing the base memory offset was poisoned. - BaseAddress, - /// `AllocationTracker` mutex was poisoned. - AllocationTracker, - /// Concurrent mutable access exclusion flag in `ReferenceState` was - /// poisoned. - Reference, -} - -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Display for ContiguousMemoryError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self { - ContiguousMemoryError::NoStorageLeft => { - write!(f, "Insufficient free storage available") - } - ContiguousMemoryError::NotContained => { - write!(f, "Attempted to mark a memory region that isn't contained") - } - ContiguousMemoryError::AlreadyUsed => write!( + match self.borrow_state { + BorrowState::Read(_) => write!( f, - "Attempted to take a memory region that is already marked as occupied" + "Attempted to mutably borrow already immuatably borrowed memory region: {}", + self.range ), - ContiguousMemoryError::DoubleFree => write!( + BorrowState::Write => write!( f, - "Attempted to free a memory region that is already marked as free" + "Attempted to immutably borrow already mutably borrowed memory region: {}", + self.range ), - ContiguousMemoryError::Unshrinkable { - required_size: min_required, - } => write!( - f, - "Cannot shrink memory regions; minimum required space: {} bytes", - min_required - ), - ContiguousMemoryError::Lock(it) => write!(f, "Poison error: {}", it), - ContiguousMemoryError::Layout(it) => write!(f, "Layout error: {}", it), - ContiguousMemoryError::BorrowMut(it) => write!(f, "Borrow mutable error: {}", it), } } } -#[cfg(any(not(feature = "no_std"), feature = "error_in_core"))] -impl Error for ContiguousMemoryError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - ContiguousMemoryError::Layout(it) => Some(it), - ContiguousMemoryError::Lock(it) => Some(it), - _ => None, - } - } -} - -impl From for ContiguousMemoryError { - fn from(layout_err: LockingError) -> Self { - ContiguousMemoryError::Lock(layout_err) - } -} - -impl From for ContiguousMemoryError { - fn from(layout_err: LayoutError) -> Self { - ContiguousMemoryError::Layout(layout_err) - } -} +#[cfg(any(feature = "std", feature = "error_in_core"))] +impl Error for RegionBorrowError {} diff --git a/src/lib.rs b/src/lib.rs index d668549..11ac3e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,44 @@ #![allow(incomplete_features)] -#![cfg_attr(feature = "no_std", no_std)] +#![allow(unstable_name_collisions)] +#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "ptr_metadata", feature(ptr_metadata, unsize))] #![cfg_attr(feature = "error_in_core", feature(error_in_core))] -#![cfg_attr(doc, feature(doc_auto_cfg))] +#![cfg_attr(nightly, feature(allocator_api))] +#![cfg_attr(all(doc, nightly), feature(doc_auto_cfg))] +#![cfg_attr(nightly, feature(strict_provenance, strict_provenance_lints))] +#![cfg_attr(nightly, warn(fuzzy_provenance_casts))] #![warn(missing_docs)] -#![doc = include_str!("../doc/crate.md")] -#[cfg(feature = "no_std")] +//!contiguous_mem is space optimized a vector like collection that can store +//!entries of varying layouts close in memory while retaining type information +//!at the reference level. +#![doc = include_str!("../doc/features.md")] + +#[cfg(not(feature = "std"))] extern crate alloc; -mod details; pub mod error; +pub mod memory; pub mod range; -pub mod refs; -pub mod tracker; -mod types; - -use details::*; -pub use details::{ImplConcurrent, ImplDefault, ImplUnsafe}; -pub use range::ByteRange; -use refs::sealed::EntryRef; -pub use refs::{CERef, ContiguousEntryRef, SCERef, SyncContiguousEntryRef}; -#[cfg(feature = "ptr_metadata")] -pub use types::static_metadata; -use types::*; +mod raw; +pub mod reference; +pub mod types; +// Re-exports +pub use error::*; +use reference::ConstructReference; +pub use reference::EntryRef; + +use core::mem::align_of; use core::{ - alloc::{Layout, LayoutError}, + alloc::Layout, mem::{size_of, ManuallyDrop}, - ops::Deref, }; -use error::{ContiguousMemoryError, LockingError}; +use memory::{ManageMemory, System}; +use range::ByteRange; +use raw::*; +use types::*; /// A memory container for efficient allocation and storage of contiguous data. /// @@ -39,688 +46,1210 @@ use error::{ContiguousMemoryError, LockingError}; /// of arbitrary data types while ensuring that stored items are placed /// adjacently and ensuring they're properly alligned. /// -/// Type argument `Impl` specifies implementation details for the behavior of -/// this struct. +/// # Examples /// -/// Note that this structure is a smart abstraction over underlying data, -/// copying it creates a copy which represents the same internal state. If you -/// need to copy the memory region into a new container see: -/// [`ContiguousMemoryStorage::copy_data`] -pub struct ContiguousMemoryStorage { - inner: Impl::StorageState, +/// ## Default Implementation +/// +/// ``` +#[doc = include_str!("../examples/default_impl.rs")] +/// ``` +#[cfg_attr(feature = "unsafe_impl", doc = "")] +#[cfg_attr(feature = "unsafe_impl", doc = "## Unsafe Implementation")] +#[cfg_attr(feature = "unsafe_impl", doc = "```")] +#[cfg_attr(feature = "unsafe_impl", doc = include_str!("../examples/unsafe_impl.rs"))] +#[cfg_attr(feature = "unsafe_impl", doc = "```")] +pub struct ContiguousMemory< + Impl: ImplDetails = ImplDefault, + A: ManageMemory = System, +> { + inner: Impl::StateRef>, } -impl ContiguousMemoryStorage { +impl> ContiguousMemory { + /// Creates a new, empty `ContiguousMemory` instance aligned with alignment + /// of `usize`. + /// + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut storage: ContiguousMemory = ContiguousMemory::new(); + /// ``` + pub fn new() -> Self { + Self { + inner: Reference::new( + MemoryState::::new(unsafe { + Layout::from_size_align_unchecked(0, align_of::()) + }) + .expect("unable to create an empty container"), + ), + } + } + /// Creates a new `ContiguousMemory` instance with the specified `capacity`, - /// aligned as platform dependant alignment of `usize`. - pub fn new(capacity: usize) -> Self { - Self::new_aligned(capacity, core::mem::align_of::()) - .expect("unable to create a ContiguousMemory with usize alignment") - } - - /// Creates a new `ContiguousMemory` instance with the specified `capacity` - /// and `alignment`. - pub fn new_aligned(capacity: usize, alignment: usize) -> Result { - let layout = Layout::from_size_align(capacity, alignment)?; - let base = unsafe { allocator::alloc(layout) }; - Ok(ContiguousMemoryStorage { - inner: Impl::build_state(base, capacity, alignment)?, + /// aligned with alignment of `usize`. + /// + /// # Panics + /// + /// Panics if capacity exceeds `isize::MAX` bytes or the allocator can't + /// provide required amount of memory. + /// + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut storage: ContiguousMemory = ContiguousMemory::with_capacity(1024); + /// # assert_eq!(storage.capacity(), 1024); + /// # assert_eq!(storage.align(), core::mem::align_of::()); + /// ``` + pub fn with_capacity(capacity: usize) -> Self { + if !is_layout_valid(capacity, align_of::()) { + panic!( + "capacity too large; max: {}", + isize::MAX as usize - (align_of::() - 1) + ) + } + Self::with_layout(unsafe { + Layout::from_size_align_unchecked(capacity, align_of::()) }) } - /// Creates a new `ContiguousMemory` instance with the provided `layout`. - pub fn new_for_layout(layout: Layout) -> Self { - let base = unsafe { allocator::alloc(layout) }; - unsafe { - // SAFETY: Impl::build_state won't return a LayoutError because - // we're constructing it from a provided layout argument. - ContiguousMemoryStorage { - inner: Impl::build_state(base, layout.size(), layout.align()).unwrap_unchecked(), - } + /// Creates a new `ContiguousMemory` instance with capacity and alignment of + /// the provided `layout`. + /// + /// # Panics + /// + /// Panics if capacity exceeds `isize::MAX` bytes or the allocator can't + /// provide required amount of memory. + /// + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// # use core::mem::align_of; + /// use core::alloc::Layout; + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut storage: ContiguousMemory = ContiguousMemory::with_layout( + /// Layout::from_size_align(512, align_of::()).unwrap() + /// ); + /// # assert_eq!(storage.capacity(), 512); + /// # assert_eq!(storage.align(), align_of::()); + /// ``` + pub fn with_layout(layout: Layout) -> Self { + Self { + inner: match MemoryState::::new(layout) { + Ok(it) => Reference::new(it), + Err(_) => unreachable!("unable to create a container with layout: {:?}", layout), + }, } } +} - /// Returns the current capacity of the memory container. +impl, A: ManageMemory> ContiguousMemory { + /// Creates a new, empty `ContiguousMemory` instance aligned with alignment + /// of `usize` that uses the specified allocator. /// - /// The capacity represents the size of the memory block that has been - /// allocated for storing data. It may be larger than the amount of data - /// currently stored within the container. - pub fn get_capacity(&self) -> usize { - Impl::get_capacity(&self.capacity) + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// # use core::mem::align_of; + /// use contiguous_mem::ContiguousMemory; + /// use contiguous_mem::memory::System; + /// + /// let mut storage: ContiguousMemory = ContiguousMemory::with_alloc(System); + /// # assert_eq!(storage.capacity(), 0); + /// # assert_eq!(storage.align(), align_of::()); + /// ``` + pub fn with_alloc(alloc: A) -> Self { + unsafe { + Self { + inner: Reference::new( + MemoryState::::new_with_alloc( + Layout::from_size_align_unchecked(0, align_of::()), + alloc, + ) + .expect("unable to create an empty container"), + ), + } + } } - /// Returns the layout of the memory region containing stored data. - pub fn get_layout(&self) -> Layout { - Impl::deref_state(&self.inner).layout() + /// Creates a new `ContiguousMemory` instance with the specified `capacity`, + /// aligned with alignment of `usize`. + /// + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// # use core::mem::align_of; + /// use contiguous_mem::ContiguousMemory; + /// use contiguous_mem::memory::System; + /// + /// let mut storage: ContiguousMemory = ContiguousMemory::with_capacity_and_alloc( + /// 256, + /// System + /// ); + /// # assert_eq!(storage.capacity(), 256); + /// # assert_eq!(storage.align(), align_of::()); + /// ``` + pub fn with_capacity_and_alloc(capacity: usize, alloc: A) -> Self { + if !is_layout_valid(capacity, align_of::()) { + panic!( + "capacity too large; max: {}", + isize::MAX as usize - (align_of::() - 1) + ) + } + unsafe { + Self::with_layout_and_alloc( + Layout::from_size_align_unchecked(capacity, align_of::()), + alloc, + ) + } } - /// Resizes the memory container to the specified `new_capacity`, optionally - /// returning the new base address of the stored items - if `None` is - /// returned the base address of the memory block is the same. + /// Creates a new `ContiguousMemory` instance with capacity and alignment of + /// the provided `layout`. /// - /// Shrinking the container is generally performed in place by freeing - /// tailing memory space, but growing it can move the data in memory to find - /// a location that can fit it. + /// # Panics /// - /// [Unsafe implementation](ImplUnsafe) should match on the returned value - /// and update any existing pointers accordingly. + /// Panics if the provided allocator fails to allocate initial `layout`. /// - /// # Errors - /// - /// [`ContiguousMemoryError::Unshrinkable`] error is returned when - /// attempting to shrink the memory container, but previously stored data - /// prevents the container from being shrunk to the desired capacity. + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// use core::mem::align_of; + /// use core::alloc::Layout; + /// use contiguous_mem::ContiguousMemory; + /// use contiguous_mem::memory::System; /// - /// In a concurrent implementation [`ContiguousMemoryError::Lock`] is - /// returned if the mutex holding the base address or the - /// [`AllocationTracker`](crate::tracker::AllocationTracker) is poisoned. - pub fn resize( - &mut self, - new_capacity: usize, - ) -> Result, ContiguousMemoryError> { - // TODO: (0.5.0) Change resize return type to *mut () - if new_capacity == Impl::get_capacity(&self.capacity) { - return Ok(None); + /// let mut storage: ContiguousMemory = ContiguousMemory::with_layout_and_alloc( + /// Layout::from_size_align(0, align_of::()).unwrap(), + /// System + /// ); + /// # assert_eq!(storage.capacity(), 0); + /// # assert_eq!(storage.align(), align_of::()); + /// ``` + pub fn with_layout_and_alloc(layout: Layout, alloc: A) -> Self { + Self { + inner: match MemoryState::::new_with_alloc(layout, alloc) { + Ok(it) => Reference::new(it), + Err(_) => panic!("unable to create a container with layout: {:?}", layout), + }, } - - let old_capacity = Impl::get_capacity(&self.capacity); - Impl::resize_tracker(&mut self.inner, new_capacity)?; - let moved = match Impl::resize_container(&mut self.inner, new_capacity) { - Ok(it) => it, - Err(ContiguousMemoryError::Lock(lock_err)) if Impl::USES_LOCKS => { - Impl::resize_tracker(&mut self.inner, old_capacity)?; - return Err(ContiguousMemoryError::Lock(lock_err)); - } - Err(other) => return Err(other), - }; - - Ok(moved) } - /// Reserves exactly `additional` bytes. - /// After calling this function, new capacity will be equal to: - /// `self.get_capacity() + additional`. + /// Returns the [`MemoryBase`] of the container. /// - /// # Errors + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert!(!s.base().is_allocated()); /// - /// See: [`ContiguousMemoryStorage::resize`] - pub fn reserve(&mut self, additional: usize) -> Result, ContiguousMemoryError> { - self.resize(self.get_capacity() + additional) - .map(|it| it.map(|ptr| ptr as *mut ())) + /// let r = s.push(6); + /// assert!(s.base().is_allocated()); + /// ``` + pub fn base(&self) -> MemoryBase { + *ReadableInner::read(&self.inner.base).expect("can't read base") } - /// Reserves exactly additional bytes required to store a value of type `V`. - /// After calling this function, new capacity will be equal to: - /// `self.get_capacity() + size_of::()`. + /// Returns a pointer to the base address of the allocated memory or `null` + /// if the container didn't allocate. /// - /// # Errors + /// # Examples + /// ``` + /// use core::ptr::null; + /// use contiguous_mem::ContiguousMemory; /// - /// See: [`ContiguousMemoryStorage::resize`] - pub fn reserve_type(&mut self) -> Result, ContiguousMemoryError> { - self.reserve(size_of::()) + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!(s.base_ptr(), null()); + /// + /// let r = s.push(3); + /// assert!(s.base_ptr() != null()); + /// ``` + #[inline] + pub fn base_ptr(&self) -> *const u8 { + self.base().as_ptr() } - /// Reserves exactly additional bytes required to store `count` number of - /// values of type `V`. - /// After calling this function, new capacity will be equal to: - /// `self.get_capacity() + size_of::() * count`. + /// Returns the current capacity (in bytes) of the memory container. /// - /// # Errors + /// The capacity represents the size of the memory block that has been + /// allocated for storing data. It may be larger than the amount of data + /// currently stored within the container. /// - /// See: [`ContiguousMemoryStorage::resize`] - pub fn reserve_type_count( - &mut self, - count: usize, - ) -> Result, ContiguousMemoryError> { - self.reserve(size_of::() * count) - } - - /// Stores a `value` of type `T` in the contiguous memory block and returns - /// a reference or a pointer pointing to it. + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; /// - /// Value type argument `T` is used to deduce type size and returned - /// reference dropping behavior. + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!(s.capacity(), 0); /// - /// Returned value is implementation specific: + /// let r1 = s.push(1u8); + /// assert_eq!(s.capacity(), 1); /// - /// | Implementation | Result | Alias | - /// |-|:-:|:-:| - /// |[Default](ImplDefault)|[`ContiguousEntryRef`](refs::ContiguousEntryRef)|[`CERef`](refs::CERef)| - /// |[Concurrent](ImplConcurrent)|[`SyncContiguousEntryRef`](refs::SyncContiguousEntryRef)|[`SCERef`](refs::SCERef)| - /// |[Unsafe](ImplUnsafe)|`*mut T`|_N/A_| + /// // will add required padding for alignment: + /// let r2 = s.push(2u32); + /// assert_eq!(s.capacity(), 8); /// - /// # Errors + /// // will fill empty region before r2: + /// let r3 = s.push(3u8); + /// let r4 = s.push(4u8); + /// assert_eq!(s.capacity(), 8); + /// ``` + #[inline] + pub fn capacity(&self) -> usize { + self.base().size() + } + + /// Returns the total size of all stored entries excluding the padding. /// - /// ## Concurrent implementation + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; /// - /// Concurrent implementation returns a - /// [`LockingError::Poisoned`](crate::error::LockingError::Poisoned) error - /// when the `AllocationTracker` associated with the memory container is - /// poisoned. + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!(s.size(), 0); /// - /// ## Unsafe implementation + /// let r1 = s.push(1u8); + /// assert_eq!(s.size(), 1); /// - /// Unsafe implementation returns a [`ContiguousMemoryError::NoStorageLeft`] - /// indicating that the container couldn't store the provided data with - /// current size. + /// // will add required padding for alignment: + /// let r2 = s.push(2u32); + /// assert_eq!(s.size(), 5); /// - /// Memory block can still be grown by calling [`ContiguousMemory::resize`], - /// but it can't be done automatically as that would invalidate all the - /// existing pointers without any indication. - pub fn push(&mut self, value: T) -> Impl::PushResult { - let mut data = ManuallyDrop::new(value); - let layout = Layout::for_value(&data); - let pos = &mut *data as *mut T; + /// // will fill empty region before r2: + /// let r3 = s.push(3u8); + /// let r4 = s.push(4u8); + /// assert_eq!(s.size(), 7); + /// ``` + pub fn size(&self) -> usize { + self.capacity() + - ReadableInner::read(&self.inner.tracker) + .unwrap() + .count_free() + } - unsafe { self.push_raw(pos, layout) } + /// Returns the alignment of the memory container. + /// + /// # Examples + /// ``` + /// # #![allow(unused_mut)] + /// use core::mem::align_of; + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!(s.align(), align_of::()); + /// ``` + #[inline] + pub fn align(&self) -> usize { + self.base().alignment() } - /// Stores a `value` of type `T` in the contiguous memory block and returns - /// a reference to it which doesn't mark the memory segment as free when - /// dropped. + /// Returns the layout of the memory region containing stored data. /// - /// See [`ContiguousMemoryStorage::push`] for details. - pub fn push_persisted(&mut self, value: T) -> Impl::PushResult - where - Impl::ReferenceType: EntryRef, - { - let mut data = ManuallyDrop::new(value); - let layout = Layout::for_value(&data); - let pos = &mut *data as *mut T; + /// # Examples + /// ``` + /// use core::alloc::Layout; + /// use core::mem::align_of; + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!( + /// s.layout(), + /// Layout::from_size_align(0, align_of::()).unwrap() + /// ); + /// let r = s.push(b"Hello world"); + /// assert_eq!( + /// s.layout(), + /// Layout::from_size_align(8, align_of::()).unwrap() + /// ); + /// ``` + pub fn layout(&self) -> Layout { + self.base().layout() + } - unsafe { self.push_raw_persisted(pos, layout) } + /// Returns `true` if provided generic type `T` can be stored without + /// growing the container. + /// + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// assert_eq!(s.can_push_t::(), false); + /// + /// let r1 = s.push(1u32); + /// assert_eq!(s.can_push_t::(), false); + /// + /// let r2 = s.push(2u32); + /// let r3 = s.push(3u32); + /// assert_eq!(s.can_push_t::(), true); + /// ``` + #[inline] + pub fn can_push_t(&self) -> bool { + self.can_push(Layout::new::()) } - /// Works same as [`push`](ContiguousMemory::push) but takes a pointer and - /// layout. + /// Returns `true` if the provided `value` can be stored without growing the + /// container. /// - /// Pointer type is used to deduce the destruction behavior for - /// implementations that return a reference, but can be disabled by casting - /// the provided pointer into `*const ()` type and then calling - /// [`transmute`](core::mem::transmute) on the returned reference: - /// ```rust - /// # use contiguous_mem::{ContiguousMemory, CERef}; - /// # use core::alloc::Layout; - /// # use core::mem; - /// # let mut storage = ContiguousMemory::new(0); - /// let value = vec!["ignore", "drop", "for", "me"]; - /// let erased = &value as *const Vec<&str> as *const (); - /// let layout = Layout::new::>(); + /// `value` can either be a [`Layout`] or a reference to a `Sized` value. /// - /// let stored: CERef> = unsafe { - /// mem::transmute(storage.push_raw(erased, layout)) - /// }; + /// # Examples /// ``` + /// use core::alloc::Layout; + /// use contiguous_mem::ContiguousMemory; /// - /// # Safety + /// let mut s: ContiguousMemory = ContiguousMemory::new(); /// - /// This function is unsafe because it clones memory from provided pointer - /// which means it could cause a segmentation fault if the pointer is - /// invalid. + /// let r1 = s.push([0u32; 4]); /// - /// Further, it also allows escaping type drop glue because it takes type - /// [`Layout`] as a separate argument. - pub unsafe fn push_raw( - &mut self, - data: *const T, - layout: Layout, - ) -> Impl::PushResult { - Impl::push_raw(&mut self.inner, data, layout) + /// let a = [1u32; 2]; + /// assert_eq!(s.can_push(&a), false); + /// let r2 = s.push(a); + /// + /// assert_eq!(s.can_push(Layout::new::()), true); + /// ``` + pub fn can_push(&self, value: impl HasLayout) -> bool { + let layout = value.as_layout(); + let tracker = ReadableInner::read(&self.inner.tracker).unwrap(); + let base = self.base(); + tracker.can_store(base, layout) } - /// Variant of [`push_raw`](ContiguousMemory::push_raw) which returns a - /// reference that doesn't mark the used memory segment as free when - /// dropped. - pub unsafe fn push_raw_persisted( - &mut self, - data: *const T, - layout: Layout, - ) -> Impl::PushResult - where - Impl::ReferenceType: EntryRef, - { - Impl::push_raw_persisted(&mut self.inner, data, layout) + /// Grows the memory container to the specified `new_capacity`. + /// + /// If the base address changed due to reallocation, new [`MemoryBase`] is + /// returned as `Ok(Some(MemoryBase))`, if base address stayed the same the + /// result is `Ok(None)`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds `isize::MAX` or the allocator + /// operation fails. + /// + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::with_capacity(4); + /// assert_eq!(s.capacity(), 4); + /// assert_eq!(s.size(), 0); + /// + /// let r = s.push(1u32); + /// assert_eq!(s.size(), 4); + /// assert_eq!(s.can_push(&2u32), false); + /// + /// s.grow_to(8); + /// assert_eq!(s.can_push(&2u32), true); + /// ``` + pub fn grow_to(&mut self, new_capacity: usize) -> Option { + match self.try_grow_to(new_capacity) { + Ok(it) => it, + Err(MemoryError::TooLarge) => panic!("new capacity exceeds `isize::MAX`"), + Err(MemoryError::Allocator(_)) => panic!("allocator error"), + } } - /// Assumes value is stored at the provided _relative_ `position` in - /// managed memory and returns a pointer or a reference to it. + /// Tries growing the memory container to the specified `new_capacity`. /// - /// # Example + /// If the base address changed due to reallocation, new [`MemoryBase`] is + /// returned as `Ok(Some(MemoryBase))`, if base address stayed the same the + /// result is `Ok(None)`. /// - /// ```rust - /// # use contiguous_mem::UnsafeContiguousMemory; - /// let mut storage = UnsafeContiguousMemory::new(128); - /// let initial_position = storage.push(278u32).unwrap(); + /// If the new capacity exceeds `isize::MAX` or the allocator couldn't + /// allocate required memory, a [`MemoryError`] is returned. /// - /// // ...other code... + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; /// - /// let base_addr = storage.get_base(); - /// storage.resize(512); + /// let mut s: ContiguousMemory = ContiguousMemory::new(); /// - /// let new_position: *mut u32 = storage.assume_stored( - /// initial_position as usize - base_addr as usize - /// ); - /// unsafe { - /// assert_eq!(*new_position, 278u32); - /// } + /// assert!(s.try_grow_to(1024).is_ok()); + /// assert_eq!(s.capacity(), 1024); /// ``` /// - /// # Safety - /// - /// This functions isn't unsafe because creating an invalid pointer isn't - /// considered unsafe. Responsibility for guaranteeing safety falls on - /// code that's dereferencing the pointer. - pub fn assume_stored( - &self, - position: usize, - ) -> Impl::LockResult> { - Impl::assume_stored(&self.inner, position) - } -} + /// The method returns an error if the system can't reserve requested + /// memory: + /// ```should_panic + /// # use contiguous_mem::ContiguousMemory; + /// # let mut s: ContiguousMemory = ContiguousMemory::new(); + /// let required_size: usize = usize::MAX; // bad read? + /// // can't allocate all addressable memory + /// assert!(s.try_grow_to(required_size).is_ok()); // PANIC! + /// ``` + pub fn try_grow_to(&mut self, new_capacity: usize) -> Result, MemoryError> { + let mut base = WritableInner::write(&self.inner.base).unwrap(); -impl ContiguousMemoryStorage { - /// Returns the base address of the allocated memory. - pub fn get_base(&self) -> *const () { - ImplDefault::get_base(&self.base) as *const () - } + let new_capacity = WritableInner::write(&self.inner.tracker) + .unwrap() + .grow(new_capacity); + if new_capacity == base.size() { + return Ok(None); + }; - /// Returns `true` if provided generic type `T` can be stored without - /// growing the container. - pub fn can_push(&self) -> bool { - let layout = Layout::new::(); - ImplDefault::peek_next(&self.inner, layout).is_some() + let new_addr = unsafe { self.inner.alloc.grow(*base, new_capacity)? }; + + Ok(if new_addr != base.address { + base.address = new_addr; + Some(*base) + } else { + None + }) } - /// Returns `true` if the provided `value` can be stored without growing the - /// container. - pub fn can_push_value(&self, value: &T) -> bool { - let layout = Layout::for_value(value); - ImplDefault::peek_next(&self.inner, layout).is_some() + /// Handles reserving capacity while ensuring appropriate padding. + #[inline] + fn ensure_free_section( + &mut self, + required: usize, + align: Option, + ) -> Result, MemoryError> { + let (capacity, last_offset, largest_free, tailing_free) = { + let tracker = ReadableInner::read(&self.inner.tracker).unwrap(); + ( + tracker.size(), + tracker.last_offset(), + tracker.largest_free_range(), + tracker.tailing_free_bytes(), + ) + }; + let base_pos = self.base_ptr() as usize; + + if let Some(largest) = largest_free { + debug_assert!(base_pos != 0); + + let largest_size = align + .map(|a| largest.offset(base_pos).aligned(a)) + .unwrap_or(largest) + .len(); + + if largest_size >= required { + return Ok(None); + } + } + + let padding = match align { + None => 0, + Some(a) => { + // we know that base + last_offset won't fall out of addressable + // range because allocator would've already failed by this point + let pos = if capacity > 0 { + base_pos + last_offset + } else { + // if capacity is 0, we didn't allocate and only need to + // ensure relative alignment padding + self.align() + }; + let extra = pos % a; + + // if already aligned padding is 0 + if extra > 0 { + a - extra + } else { + 0 + } + } + }; + + let mut additional = required + padding - tailing_free; + if !EXACT { + additional = core::cmp::max(capacity, additional); + } + + self.try_grow_to(capacity.saturating_add(additional)) } - /// Returns `true` if the provided `layout` can be stored without growing - /// the container. - pub fn can_push_layout(&self, layout: Layout) -> bool { - ImplDefault::peek_next(&self.inner, layout).is_some() + /// Like [`try_reserve`](ContiguousMemory::try_reserve), grows the + /// underlying memory to ensure container has a free segment that can store + /// `capacity`, but panics if that's not possible. + /// + /// See the [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. + /// + /// # Panics + /// + /// Panics if attempting to grow the container to a capacity larger than + /// `isize::MAX` or the allocator can't allocate required memory. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// # use core::alloc::Layout; + /// let layout = Layout::from_size_align(4, 8).unwrap(); + /// let mut s: ContiguousMemory = ContiguousMemory::with_layout(layout); + /// + /// # assert_eq!(s.size(), 0); + /// assert_eq!(s.capacity(), 4); + /// + /// let r1 = s.push(1u8); + /// assert_eq!(s.size(), 1); + /// assert_eq!(s.capacity(), 4); + /// + /// s.reserve(8); + /// assert_eq!(s.capacity(), 9); + /// ``` + #[inline] + pub fn reserve(&mut self, capacity: usize) -> Option { + match self.try_reserve(capacity) { + Ok(it) => it, + Err(MemoryError::TooLarge) => panic!("new capacity exceeds `isize::MAX`"), + Err(MemoryError::Allocator(_)) => panic!("unable to allocate more memory"), + } } - /// Shrinks the allocated memory to fit the currently stored data and - /// returns the new capacity. - pub fn shrink_to_fit(&mut self) -> usize { - if let Some(shrunk) = ImplDefault::shrink_tracker(&mut self.inner) { - self.resize(shrunk).expect("unable to shrink container"); - shrunk - } else { - self.capacity.get() + /// Tries growing the underlying memory to ensure container has a free + /// segment that can store `capacity`. + /// + /// Works like [`try_reserve_layout`](ContiguousMemory::try_reserve_layout), + /// but doesn't account for specific alignment of the reserved segment. + /// Check its documentation for more details. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// # use core::alloc::Layout; + /// let layout = Layout::from_size_align(4, 8).unwrap(); + /// let mut s: ContiguousMemory = ContiguousMemory::with_layout(layout); + /// # assert_eq!(s.size(), 0); + /// assert_eq!(s.capacity(), 4); + /// + /// let r1 = s.push(1u8); + /// assert_eq!(s.size(), 1); + /// assert_eq!(s.capacity(), 4); + /// + /// s.try_reserve(8).expect("should have enough memory"); + /// assert_eq!(s.capacity(), 9); + /// + /// assert!(s.try_reserve(usize::MAX).is_err()); + /// ``` + pub fn try_reserve(&mut self, capacity: usize) -> Result, MemoryError> { + if capacity == 0 { + return Ok(None); } + self.ensure_free_section::(capacity, None) } - /// Forgets this container without dropping it and returns its base address - /// and [`Layout`]. + /// Grows the underlying memory to ensure container has a free segment that + /// can store `capacity`, or panics. /// - /// # Safety + /// See the [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. /// - /// Calling this method will create a memory leak because the smart pointer - /// to state will not be dropped even when all of the created references go - /// out of scope. As this method takes ownership of the container, calling - /// it also ensures that dereferencing pointers created by - /// [`as_ptr`](refs::ContiguousEntryRef::as_ptr), - /// [`as_ptr_mut`](refs::ContiguousEntryRef::as_ptr_mut), - /// [`into_ptr`](refs::ContiguousEntryRef::into_ptr), and - /// [`into_ptr_mut`](refs::ContiguousEntryRef::into_ptr_mut) - /// `ContiguousEntryRef` methods is guaranteed to be safe. + /// # Panics /// - /// This method isn't unsafe as leaking data doesn't cause undefined - /// behavior. - /// ([_see details_](https://doc.rust-lang.org/nomicon/leaking.html)) - pub fn forget(self) -> (*const (), Layout) { - let base = ImplDefault::get_base(&self.base); - let layout = self.get_layout(); - core::mem::forget(self); - (base as *const (), layout) + /// Panics if attempting to grow the container to a capacity larger than + /// `isize::MAX` or the allocator can't allocate required memory. + /// + /// # Examples + /// ``` + /// use contiguous_mem::ContiguousMemory; + /// + /// let mut s: ContiguousMemory = ContiguousMemory::with_capacity(4); + /// assert_eq!(s.capacity(), 4); + /// + /// let r = s.push(1u32); + /// assert_eq!(s.capacity(), s.size()); + /// assert_eq!(s.can_push(&2u32), false); + /// + /// s.reserve_exact(4); + /// assert_eq!(s.capacity(), 8); + /// assert_eq!(s.can_push(&2u32), true); + /// ``` + #[inline] + pub fn reserve_exact(&mut self, capacity: usize) -> Option { + match self.try_reserve_exact(capacity) { + Ok(it) => it, + Err(MemoryError::TooLarge) => panic!("new capacity exceeds `isize::MAX`"), + Err(MemoryError::Allocator(_)) => panic!("unable to allocate more memory"), + } } -} -impl ContiguousMemoryStorage { - /// Returns the base address of the allocated memory or a - /// [`LockingError::Poisoned`] error if the mutex holding the base address - /// has been poisoned. + /// Tries growing the underlying memory to ensure container has a free + /// segment that can store `capacity`. + /// + /// Works much like + /// [`try_reserve_layout_exact`](ContiguousMemory::try_reserve_layout_exact), + /// but doesn't ensure a specific alignment of the reserved segment. /// - /// This function will block the current thread until base address RwLock - /// doesn't become readable. - pub fn get_base(&self) -> Result<*const (), LockingError> { - unsafe { core::mem::transmute(ImplConcurrent::get_base(&self.base)) } + /// See the [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// + /// assert!(s.try_reserve_exact(1024).is_ok()); + /// assert_eq!(s.capacity(), 1024); + /// ``` + /// + /// The method returns an error if the system can't reserve requested + /// memory: + /// ```should_panic + /// # use contiguous_mem::ContiguousMemory; + /// # let mut s: ContiguousMemory = ContiguousMemory::new(); + /// let required_size: usize = usize::MAX; // bad read? + /// // can't allocate all addressable memory + /// assert!(s.try_reserve_exact(required_size).is_ok()); // PANIC! + /// ``` + pub fn try_reserve_exact( + &mut self, + capacity: usize, + ) -> Result, MemoryError> { + if capacity == 0 { + return Ok(None); + } + self.ensure_free_section::(capacity, None) } - /// Returns `true` if provided generic type `T` can be stored without - /// growing the container or a [`LockingError::Poisoned`] error if - /// allocation tracker mutex has been poisoned. + /// Like [`try_reserve_layout`](ContiguousMemory::try_reserve_layout), grows + /// the underlying memory to ensure container has a free segment that can + /// store a value with provided `layout`, or panics if that's not possible. + /// + /// See the [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. /// - /// This function will block the current thread until internal allocation - /// tracked doesn't become available. - pub fn can_push(&self) -> Result { - let layout = Layout::new::(); - ImplConcurrent::peek_next(&self.inner, layout).map(|it| it.is_some()) + /// # Panics + /// + /// Panics if attempting to grow the container to a capacity larger than + /// `isize::MAX` or the allocator can't allocate required memory. + #[inline] + pub fn reserve_layout(&mut self, layout: impl HasLayout) -> Option { + match self.try_reserve_layout(layout) { + Ok(it) => it, + Err(MemoryError::TooLarge) => panic!("new capacity exceeds `isize::MAX`"), + Err(MemoryError::Allocator(_)) => panic!("unable to allocate more memory"), + } } - /// Returns `true` if the provided `value` can be stored without growing the - /// container or a [`LockingError::Poisoned`] error if allocation tracker - /// mutex has been poisoned. + /// Tries growing the underlying memory to ensure container has a free + /// segment that can store a value with provided `layout`. /// - /// This function will block the current thread until internal allocation - /// tracked doesn't become available. - pub fn can_push_value(&self, value: &T) -> Result { - let layout = Layout::for_value(value); - ImplConcurrent::peek_next(&self.inner, layout).map(|it| it.is_some()) + /// If the base address changed due to reallocation, new [`BasePtr`] is + /// returned as `Ok(Some(BasePtr))`, if base address remained the same + /// `Ok(None)` is returned. + /// + /// If the new capacity exceeds `isize::MAX` or the allocator couldn't + /// allocate required memory, a [`MemoryError`] is returned. If allocating + /// the required capacity is expected to succeed, use the function variant + /// without the `try_` prefix. + /// + /// `layout` argument [type](HasLayout) can either be a [`Layout`] value + /// _or_ a reference to any `Sized` type. + /// + /// After calling this function, new capacity will be greater than: + /// `self.size() + padding + layout.size()`.
+ /// `padding` is preceding blank space necessary to ensure the proper + /// alignment of the provided layout. If ensuring alignment is not needed + /// (because the data is unaligned), use variants without the `_layout` + /// suffix. + /// + /// This function might allocate more than requested amount of memory to + /// reduce number of reallocations. If exact allocation is needed instead, + /// use variants with `_exact` suffix. + /// + /// In total, this function has 8 different variants. Use the one which best + /// suits your specific requirements: + /// + /// | Variant | `Err` / panic | alignment | amortized growth | + /// |:-|:-:|:-:|:-:| + /// |[`reserve`](ContiguousMemory::reserve) |panic|✗|✓| + /// |[`try_reserve`](ContiguousMemory::try_reserve) |`Err`|✗|✓| + /// |[`reserve_exact`](ContiguousMemory::reserve_exact) |panic|✗|✗| + /// |[`try_reserve_exact`](ContiguousMemory::try_reserve_exact) |`Err`|✗|✗| + /// |[`reserve_layout`](ContiguousMemory::reserve_layout) |panic|✓|✓| + /// |[`try_reserve_layout`](ContiguousMemory::try_reserve_layout) |`Err`|✓|✓| + /// |[`reserve_layout_exact`](ContiguousMemory::reserve_layout_exact) |panic|✓|✗| + /// |[`try_reserve_layout_exact`](ContiguousMemory::try_reserve_layout_exact)|`Err`|✓|✗| + pub fn try_reserve_layout( + &mut self, + layout: impl HasLayout, + ) -> Result, MemoryError> { + let layout = layout.as_layout(); + if layout.size() == 0 { + return Ok(None); + } + self.ensure_free_section::(layout.size(), Some(layout.align())) } - /// Returns `true` if the provided `layout` can be stored without growing - /// the container or a [`LockingError::Poisoned`] error if allocation - /// tracker mutex has been poisoned. + /// Like + /// [`try_reserve_layout_exact`](ContiguousMemory::try_reserve_layout_exact), + /// tries growing the underlying memory to ensure container has a free + /// segment that can store a value with provided `layout`, but panics if + /// that's not possible instead of returning an error value. + /// + /// See the [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. + /// + /// # Panics /// - /// This function will block the current thread until internal allocation - /// tracked doesn't become available. - pub fn can_push_layout(&self, layout: Layout) -> Result { - ImplConcurrent::peek_next(&self.inner, layout).map(|it| it.is_some()) + /// Panics if attempting to grow the container to a capacity larger than + /// `isize::MAX` or the allocator can't allocate required memory. + #[inline] + pub fn reserve_layout_exact(&mut self, layout: impl HasLayout) -> Option { + match self.try_reserve_layout_exact(layout) { + Ok(it) => it, + Err(MemoryError::TooLarge) => panic!("new capacity exceeds `isize::MAX`"), + Err(MemoryError::Allocator(_)) => panic!("unable to allocate more memory"), + } } - /// Shrinks the allocated memory to fit the currently stored data and - /// returns the new capacity. + /// Tries growing the underlying memory to ensure container has a free + /// segment that can store a value with provided `layout`. /// - /// This function will block the current thread until internal allocation - /// tracked doesn't become available. - pub fn shrink_to_fit(&mut self) -> Result { - if let Some(shrunk) = ImplConcurrent::shrink_tracker(&mut self.inner)? { - self.resize(shrunk).expect("unable to shrink container"); - Ok(shrunk) - } else { - Ok(self.get_capacity()) + /// Unlike [`try_reserve_layout`](ContiguousMemory::try_reserve_layout), + /// this function will only reserve memory necessary to accommodate the + /// provided layout and not more. + /// + /// See [base implementation](ContiguousMemory::try_reserve_layout) for + /// more details. + pub fn try_reserve_layout_exact( + &mut self, + layout: impl HasLayout, + ) -> Result, MemoryError> { + let layout = layout.as_layout(); + if layout.size() == 0 { + return Ok(None); } + self.ensure_free_section::(layout.size(), Some(layout.align())) } - /// Forgets this container without dropping it and returns its base address - /// and [`Layout`], or a [`LockingError::Poisoned`] error if base address - /// `RwLock` has been poisoned. + /// Tries shrinking the capacity of the container to provided + /// `new_capacity`, or smallest larger one if provided `new_capacity` can't + /// accomodate stored data, and returns the [`MemoryBase`]. + /// + /// `MemoryBase` result will generally stay the same for shrinking + /// operations, but that depends on the used [allocator `A`](ManageMemory). /// - /// For details on safety see _Safety_ section of - /// [default implementation](ContiguousMemoryStorage::forget). - pub fn forget(self) -> Result<(*const (), Layout), LockingError> { - let base = ImplConcurrent::get_base(&self.base); - let layout = self.get_layout(); - core::mem::forget(self); - base.map(|it| (it as *const (), layout)) + /// # Panics + /// + /// Panics if the allocator wasn't able to shrink the allocated memory + /// region. This should almost never happen unless the allocator doesn't + /// support deallocation. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// let mut s: ContiguousMemory = ContiguousMemory::with_capacity(32); + /// assert_eq!(s.capacity(), 32); + /// + /// let r = s.push(1u16); + /// + /// // can't grow capacity + /// s.shrink_to(64); + /// assert_eq!(s.capacity(), 32); + /// + /// // can shrink capacity to more than is currently used + /// s.shrink_to(8); + /// assert_eq!(s.capacity(), 8); + /// + /// // but it won't shrink it past the minimum required + /// s.shrink_to(0); + /// assert_eq!(s.capacity(), 2); + /// ``` + pub fn shrink_to(&mut self, new_capacity: usize) -> MemoryBase { + let mut tracker = WritableInner::write(&self.inner.tracker).unwrap(); + let new_capacity = tracker.shrink(new_capacity); + let mut base = WritableInner::write(&self.inner.base).unwrap(); + if new_capacity == base.size() { + return *base; + } + + base.address = unsafe { self.inner.alloc.shrink(*base, new_capacity) } + .expect("unable to shrink the container"); + + *base } -} -impl ContiguousMemoryStorage { - /// Returns the base address of the allocated memory. - pub fn get_base(&self) -> *const () { - self.base.0 as *const () + /// Shrinks the capacity to fit the currently stored data and returns the + /// new [`MemoryBase`]. + /// + /// Bytes between stored objects will remain allocated to reduce + /// fragmentation. + /// + /// # Panics + /// + /// Panics if the allocator wasn't able to shrink the allocated memory + /// region. This should almost never happen unless the allocator doesn't + /// support deallocation. + /// + /// # Examples + /// + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// let mut s: ContiguousMemory = ContiguousMemory::with_capacity(1024); + /// + /// assert_eq!(s.capacity(), 1024); + /// let r = s.push(1u16); + /// + /// s.shrink_to_fit(); + /// assert_eq!(s.capacity(), 2); + /// ``` + pub fn shrink_to_fit(&mut self) -> MemoryBase { + let mut base = WritableInner::write(&self.inner.base).unwrap(); + let new_capacity = match WritableInner::write(&self.inner.tracker) + .unwrap() + .shrink_to_fit() + { + Some(it) => it, + None => return *base, + }; + base.address = unsafe { self.inner.alloc.shrink(*base, new_capacity) } + .expect("unable to shrink the container"); + + *base } - /// Returns `true` if the provided value can be stored without growing the - /// container. + /// Stores a `value` of type `T` in the contiguous memory block and returns + /// a [`reference`](EntryRef) to it. + /// + /// Value type argument `T` is used to infer type size and returned + /// reference dropping behavior. + /// + /// Use [`push_persisted`](ContiguousMemory::push_persisted) if you want to + /// push data that shouldn't be cleared once the reference is dropped. + /// + /// Use [`push_raw`](ContiguousMemory::push_raw) if you want to take full + /// control over details of push the pushed type (memory location and + /// `Layout` ). /// - /// It's usually clearer to try storing the value directly and then handle - /// the case where it wasn't stored through error matching. + /// There's also a [`push_raw_persisted`](ContiguousMemory::push_persisted) + /// variant that combines functionality of both. + /// + /// # Panics /// - /// # Example + /// Panics if: + /// - the collection needs to grow and new capacity exceeds `isize::MAX` + /// bytes, or + /// - allocation of additional memory fails. /// - /// ```rust - /// # use contiguous_mem::UnsafeContiguousMemory; - /// # use core::mem::size_of_val; - /// let mut storage = UnsafeContiguousMemory::new(0); - /// let value = [2, 4, 8, 16]; + /// # Examples /// - /// # assert_eq!(storage.can_push::>(), false); - /// if !storage.can_push::>() { - /// storage.resize(storage.get_capacity() + size_of_val(&value)); + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// + /// let r1 = s.push(1u16); + /// let mut r2 = s.push(2u32); + /// let r3 = s.push("hello"); /// - /// // ...update old pointers... + /// println!("{} world", *r3.get()); + /// assert_eq!(*r1.get() as u32 + *r2.get(), 3u32); + /// + /// let r1 = s.push(3u32); + /// r2 = s.push(4u32); + /// assert_eq!(*r1.get() + *r2.get(), 7u32); + /// ``` + pub fn push(&mut self, value: T) -> Impl::PushResult { + let mut data = ManuallyDrop::new(value); + let layout = Layout::for_value(&data); + let pos = &mut *data as *mut T; + + unsafe { self.push_raw(pos, layout) } + } + + /// Stores a `value` of type `T` in the contiguous memory block and returns + /// a reference to it which doesn't mark the memory segment as free when + /// dropped. + /// + /// This is semantically similar to: + /// ``` + /// # let value = "I will be static".to_string(); + /// let leaked = Box::leak(Box::new(value)); + /// ``` + /// + /// See [`ContiguousMemory::push`] for details. + /// + /// # Panics + /// + /// Panics if the collection needs to grow and new capacity exceeds + /// `isize::MAX` bytes or allocation of additional memory fails. + /// + /// # Examples + /// + /// ``` + /// # use contiguous_mem::ContiguousMemory; + /// # use core::sync::atomic::AtomicBool; + /// # use core::sync::atomic::Ordering; + /// let mut s: ContiguousMemory = ContiguousMemory::new(); + /// + /// static DROPPED: AtomicBool = AtomicBool::new(false); + /// + /// struct SideEffect; + /// impl Drop for SideEffect { + /// fn drop(&mut self) { + /// unsafe { + /// DROPPED.store(true, Ordering::SeqCst); + /// } + /// } /// } /// - /// let stored_value = - /// storage.push(value).expect("unable to store after growing the container"); + /// let r1 = s.push_persisted(SideEffect); // leaked on insertion + /// assert_eq!(DROPPED.load(Ordering::SeqCst), false); + /// + /// // value will stay allocated even after last reference is dropped: + /// std::mem::drop(r1); + /// assert_eq!(DROPPED.load(Ordering::SeqCst), false); + /// + /// // normal use will trigger `DROPPED`: + /// let normal = SideEffect; + /// std::mem::drop(normal); + /// assert_eq!(DROPPED.load(Ordering::SeqCst), true); /// ``` - pub fn can_push(&self) -> bool { - let layout = Layout::new::(); - ImplUnsafe::peek_next(&self.inner, layout).is_some() + pub fn push_persisted(&mut self, value: T) -> Impl::PushResult { + let mut data = ManuallyDrop::new(value); + let layout = Layout::for_value(&data); + let pos = &mut *data as *mut T; + + unsafe { self.push_raw_persisted(pos, layout) } } - /// Returns `true` if the provided `value` can be stored without growing the - /// container. - pub fn can_push_value(&self, value: &T) -> bool { - let layout = Layout::for_value(value); - ImplUnsafe::peek_next(&self.inner, layout).is_some() + /// Works same as [`push`](ContiguousMemory::push) but takes a `data` + /// pointer and `layout`. + /// + /// Pointer type `T` is used to infer the drop behavior of the returned + /// reference. + /// + /// # Panics + /// + /// Panics if the collection needs to grow and new capacity exceeds + /// `isize::MAX` bytes or allocation of additional memory fails. + /// + /// # Safety + /// + /// This function is unsafe because it clones memory from provided pointer + /// which means it could cause a segmentation fault if the pointer is + /// invalid. + /// + /// Further, it also allows escaping type drop glue because it takes type + /// [`Layout`] as a separate argument. + /// + /// # Examples + /// + /// Disabling drop handling by casting the provided pointer into `*const ()` + /// type and then calling [`transmute`](core::mem::transmute) on the + /// returned reference: + /// ``` + /// # use contiguous_mem::*; + /// # use contiguous_mem::memory::System; + /// # use core::alloc::Layout; + /// # use core::mem; + /// # let mut storage: ContiguousMemory = ContiguousMemory::new(); + /// let value = vec!["ignore", "drop", "for", "me"]; + /// let erased = &value as *const Vec<&str> as *const (); + /// let layout = Layout::new::>(); + /// + /// // Reference type arguments must be fully specified. + /// let stored: EntryRef, System> = unsafe { + /// mem::transmute(storage.push_raw(erased, layout)) + /// }; + /// ``` + pub unsafe fn push_raw(&mut self, data: *const T, layout: Layout) -> Impl::PushResult { + let range = loop { + if layout.size() == 0 { + break ByteRange::EMPTY; + } + + let base = self.base(); + let next = WritableInner::write(&self.inner.tracker) + .unwrap() + .take_next(base.pos_or_align(), layout); + + match next { + Some(it) => { + let found = it.offset_base_unwrap(base.address); + unsafe { + core::ptr::copy_nonoverlapping(data as *mut u8, found, layout.size()); + } + break it; + } + None if Impl::GROW => { + self.reserve_layout(layout); + } + _ => { + break ByteRange::EMPTY; + } + } + }; + + ConstructReference::new(&self.inner, range) } - /// Returns `true` if the provided `layout` can be stored without growing - /// the container. - pub fn can_push_layout(&self, layout: Layout) -> bool { - ImplUnsafe::peek_next(&self.inner, layout).is_some() + /// Variant of [`push_raw`](ContiguousMemory::push_raw) which returns a + /// reference that doesn't mark the used memory segment as free when + /// dropped. + /// + /// # Panics + /// + /// Panics if the collection needs to grow and new capacity exceeds + /// `isize::MAX` bytes or allocation of additional memory fails. + /// + /// # Safety + /// + /// See: [`ContiguousMemory::push_raw`] + pub unsafe fn push_raw_persisted( + &mut self, + data: *const T, + layout: Layout, + ) -> Impl::PushResult { + let value = self.push_raw(data, layout); + let result = value.clone(); + core::mem::forget(value); + result } - /// Shrinks the allocated memory to fit the currently stored data and - /// returns the new capacity. - pub fn shrink_to_fit(&mut self) -> usize { - if let Some(shrunk) = ImplUnsafe::shrink_tracker(&mut self.inner) { - self.resize(shrunk).expect("unable to shrink container"); - shrunk - } else { - self.capacity - } + /// Assumes value is stored at the provided _relative_ `position` in managed + /// memory and returns a pointer or a reference to it. + /// + /// # Safety + /// + /// This function isn't unsafe because creating an invalid pointer isn't + /// considered unsafe. Responsibility for guaranteeing safety falls on code + /// that's dereferencing the pointer. + pub fn assume_stored(&self, position: usize) -> Impl::PushResult { + ConstructReference::new(&self.inner, ByteRange(position, position + size_of::())) } - /// Clones the allocated memory region into a new ContiguousMemoryStorage. + /// Clones the allocated memory region into a new `MemoryStorage`. /// /// This function isn't unsafe, even though it ignores presence of `Copy` - /// bound on stored data, because it doesn't create any pointers. - #[must_use] - pub fn copy_data(&self) -> Self { - let current_layout = self.get_layout(); - let result = Self::new_for_layout(current_layout); - unsafe { - core::ptr::copy_nonoverlapping( - self.get_base(), - result.get_base() as *mut (), - current_layout.size(), - ); + /// bound on stored data, because it doesn't create any invalid references. + #[must_use = "unused copied collection"] + pub fn copy_data(&self) -> Self + where + A: Clone, + { + let current_layout = self.layout(); + let result = Self::with_layout_and_alloc(current_layout, self.inner.alloc.clone()); + match self.base().address { + Some(base) => unsafe { + core::ptr::copy_nonoverlapping( + base.as_ptr() as *const (), + result.base().as_ptr_mut_unchecked(), + current_layout.size(), + ); + }, + None => { + // empty structure; nothing to copy + } } + result } - /// Allows freeing a memory range stored at provided `position`. + /// Marks the entire contents of the container as free, allowing new data to + /// be stored in place of previously stored data. /// - /// Type of the position pointer `T` determines the size of the freed chunk. + /// This allows clearing persisted entries created with + /// [`ContiguousMemory::push_persisted`] and + /// [`ContiguousMemory::push_raw_persisted`] methods. /// /// # Safety /// - /// This function is considered unsafe because it can mark a memory range - /// as free while a valid reference is pointing to it from another place in - /// code. - pub unsafe fn free_typed(&mut self, position: *mut T) { - Self::free(self, position, size_of::()) + /// This method is unsafe because it doesn't invalidate any previously + /// returned references. Storing data into the container and then trying to + /// access previously stored data from any existing references will cause + /// undefined behavior. + pub unsafe fn clear(&mut self) { + WritableInner::write(&self.inner.tracker).unwrap().clear(); } - /// Allows freeing a memory range stored at provided `position` with the - /// specified `size`. + /// Marks the provided `region` of the container as free, allowing new data + /// to be stored in place of previously stored data. + /// + /// This allows clearing persisted entries created with + /// [`ContiguousMemory::push_persisted`] and + /// [`ContiguousMemory::push_raw_persisted`] methods. + /// + /// # Panics + /// + /// This function panics in debug mode if the provided region falls outside + /// of the memory tracked by the segment tracker. /// /// # Safety /// - /// This function is considered unsafe because it can mark a memory range - /// as free while a valid reference is pointing to it from another place in - /// code. - pub unsafe fn free(&mut self, position: *mut T, size: usize) { - let pos: usize = position.sub(self.get_base() as usize) as usize; - let base = ImplUnsafe::get_base(&self.base); - let tracker = ImplUnsafe::get_allocation_tracker(&mut self.inner); - if let Some(freed) = ImplUnsafe::free_region(tracker, base, ByteRange(pos, pos + size)) { - core::ptr::drop_in_place(freed as *mut T); - } + /// This method is unsafe because it doesn't invalidate any previously + /// returned references overlapping `region`. Storing data into the + /// container and then trying to access previously stored data from + /// overlapping regions will cause undefined behavior. + pub unsafe fn clear_region(&mut self, region: ByteRange) { + WritableInner::write(&self.inner.tracker) + .unwrap() + .release(region); } - /// Forgets this container without dropping it and returns its base address + /// Forgets this container without dropping it, returning its base address /// and [`Layout`]. /// - /// For details on safety see _Safety_ section of - /// [default implementation](ContiguousMemoryStorage::forget). - pub fn forget(self) -> (*const (), Layout) { - let base = ImplUnsafe::get_base(&self.base); - let layout = self.get_layout(); + /// # Safety + /// + /// Calling this method will create a memory leak because the smart pointer + /// to state will not be dropped even when all of the created references go + /// out of scope. As this method takes ownership of the container, calling + /// it also ensures that dereferencing pointers created by + /// [`as_ptr`](EntryRef::as_ptr) and related [`EntryRef`] functions is + /// guaranteed to be safe. + /// + /// This method isn't unsafe as leaking data doesn't cause undefined + /// behavior. ([_why_](https://doc.rust-lang.org/nomicon/leaking.html)) + pub fn leak(self) -> MemoryBase { + let base = self.base(); core::mem::forget(self); - (base as *const (), layout) + base + } + + /// Provides a very verbose [segment display][memory::DisplaySegments]. + #[cfg(feature = "debug")] + pub fn display_layout(&self) -> memory::DisplaySegments { + let tracker = ReadableInner::read(&self.inner.tracker).unwrap(); + memory::DisplaySegments(self.base().as_pos(), tracker.clone()) } } #[cfg(feature = "debug")] -impl core::fmt::Debug for ContiguousMemoryStorage +impl, A: ManageMemory> core::fmt::Debug for ContiguousMemory where - Impl::StorageState: core::fmt::Debug, + Impl::StateRef>: core::fmt::Debug, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ContiguousMemoryStorage") + f.debug_struct("ContiguousMemory") .field("inner", &self.inner) .finish() } } -impl Clone for ContiguousMemoryStorage { +impl, A: ManageMemory> Clone for ContiguousMemory +where + Impl::StateRef>: Clone, +{ + /// Creates a copy which represents the same memory region as this one. + /// + /// If you need to copy the memory region of this container into a new one, + /// use: [`ContiguousMemory::copy_data`] fn clone(&self) -> Self { - ContiguousMemoryStorage { + Self { inner: self.inner.clone(), } } } -impl Deref for ContiguousMemoryStorage { - type Target = ContiguousMemoryState; - - fn deref(&self) -> &Self::Target { - Impl::deref_state(&self.inner) - } -} - -pub(crate) mod sealed { - use super::*; - - #[derive(Clone, PartialEq, Eq)] - #[repr(transparent)] - pub(crate) struct BaseLocation(pub(crate) Impl::Base); - - #[cfg(feature = "debug")] - impl core::fmt::Debug for BaseLocation - where - Impl::LockResult<*mut u8>: core::fmt::Debug, - { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("BaseLocation") - .field(&Impl::get_base(&self.0)) - .finish() - } - } - - impl Deref for BaseLocation { - type Target = ::Base; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl Copy for BaseLocation {} - unsafe impl Send for BaseLocation where Impl: PartialEq {} - unsafe impl Sync for BaseLocation where Impl: PartialEq {} - - #[repr(C)] - pub struct ContiguousMemoryState { - pub(crate) base: BaseLocation, - pub(crate) capacity: Impl::SizeType, - pub(crate) alignment: usize, - pub(crate) tracker: Impl::AllocationTracker, - } - - impl core::fmt::Debug for ContiguousMemoryState - where - BaseLocation: core::fmt::Debug, - Impl::SizeType: core::fmt::Debug, - Impl::AllocationTracker: core::fmt::Debug, - { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ContiguousMemoryState") - .field("base", &self.base) - .field("capacity", &self.capacity) - .field("alignment", &self.alignment) - .field("tracker", &self.tracker) - .finish() - } - } - - impl ContiguousMemoryState { - /// Returns the layout of the managed memory. - pub fn layout(&self) -> Layout { - unsafe { - let capacity = Impl::get_capacity(core::mem::transmute(self)); - Layout::from_size_align_unchecked(capacity, self.alignment) - } - } - } - - impl Clone for ContiguousMemoryState { - fn clone(&self) -> Self { - Self { - base: self.base, - capacity: self.capacity, - alignment: self.alignment, - tracker: self.tracker.clone(), - } - } - } - - impl Drop for ContiguousMemoryState { - fn drop(&mut self) { - let layout = self.layout(); - Impl::deallocate(&mut self.base.0, layout) - } +impl, A: ManageMemory + Default> Default for ContiguousMemory { + fn default() -> Self { + ContiguousMemory::with_alloc(A::default()) } } -use sealed::*; - -/// Alias for `ContiguousMemoryStorage` that uses -/// [concurrent implementation](ImplConcurrent). -/// -/// # Example -/// -/// ```rust -#[doc = include_str!("../examples/sync_impl.rs")] -/// ``` -pub type SyncContiguousMemory = ContiguousMemoryStorage; -/// Alias for `ContiguousMemoryStorage` that uses -/// [default implementation](ImplDefault). -/// -/// # Example -/// -/// ```rust -#[doc = include_str!("../examples/default_impl.rs")] -/// ``` -pub type ContiguousMemory = ContiguousMemoryStorage; - -/// Alias for `ContiguousMemoryStorage` that uses -/// [unsafe implementation](ImplUnsafe). -/// -/// # Example -/// -/// ```rust -#[doc = include_str!("../examples/unsafe_impl.rs")] -/// ``` -pub type UnsafeContiguousMemory = ContiguousMemoryStorage; - -#[cfg(all(test, not(feature = "no_std")))] +#[cfg(test)] mod test { - use core::mem::align_of; - use super::*; + extern crate std; + #[derive(Debug, Clone, PartialEq, Eq)] #[repr(C)] struct Person { @@ -737,15 +1266,9 @@ mod test { miles: u32, } - #[test] - fn construct_contiguous_memory() { - let memory = ContiguousMemory::new(1024); - assert_eq!(memory.get_capacity(), 1024); - } - #[test] fn store_and_get() { - let mut memory = ContiguousMemory::new(1024); + let mut memory = ContiguousMemory::::with_capacity(1024); let person_a = Person { name: "Jerry".to_string(), @@ -778,6 +1301,7 @@ mod test { let stored_ref_number = memory.push(value_number); let stored_ref_car_a = memory.push(car_a.clone()); let stored_ref_string = memory.push(value_string.clone()); + let stored_ref_byte = memory.push(value_byte); let stored_ref_car_b = memory.push(car_b.clone()); @@ -788,65 +1312,9 @@ mod test { assert_eq!(*stored_ref_byte.get(), value_byte); } - #[test] - fn resize_manually() { - let mut memory = ContiguousMemory::new(512); - - let person_a = Person { - name: "Larry".to_string(), - last_name: "Taylor".to_string(), - }; - - let car_a = Car { - owner: person_a.clone(), - driver: Some(person_a), - cost: 20_000, - miles: 30123, - }; - - let stored_car = memory.push(car_a.clone()); - - assert!(memory.resize(32).is_err()); - memory.resize(1024).unwrap(); - assert_eq!(memory.get_capacity(), 1024); - - assert_eq!(*stored_car.get(), car_a); - - memory.resize(128).unwrap(); - assert_eq!(memory.get_capacity(), 128); - - assert_eq!(*stored_car.get(), car_a); - } - - #[test] - fn resize_automatically() { - let mut memory = ContiguousMemory::new_aligned(12, align_of::()).unwrap(); - - { - let _a = memory.push(1u32); - let _b = memory.push(2u32); - let _c = memory.push(3u32); - assert_eq!(memory.can_push::(), false); - let _d = memory.push(4u32); - assert_eq!(memory.get_capacity(), 24); - } - - memory.resize(4).expect("can't shrink empty storage"); - { - memory.push_persisted(1u16); - memory.push_persisted(2u16); - assert_eq!(memory.can_push::(), false); - memory.push_persisted(3u64); - // expecting 12, but due to alignment we're skipping two u16 slots - // and then double the size as remaining (aligned) 4 bytes aren't - // enough for u64 - assert_eq!(memory.get_capacity(), 24); - } - } - #[test] fn add_to_zero_sized() { - let mut memory = ContiguousMemory::new(0); + let mut memory = ContiguousMemory::::new(); let person = Person { name: "Jacky".to_string(), @@ -855,7 +1323,7 @@ mod test { let stored_person = memory.push(person.clone()); - assert_eq!(memory.get_capacity(), 48); + assert_eq!(memory.capacity(), 48); assert_eq!(*stored_person.get(), person); } } diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..fe74da5 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,672 @@ +//! Structs and code for memory management. + +use core::{cmp, fmt::Write}; +use core::{alloc::Layout, ptr::NonNull}; + +pub use crate::raw::{BaseAddress, BasePtr, MemoryBase}; +use crate::types::HasLayout; + +#[cfg(not(feature = "std"))] +use crate::types::{vec, Vec}; +use crate::{range::ByteRange, MemoryError}; + +#[cfg(nightly)] +use core::alloc::Allocator; +#[cfg(not(nightly))] +use allocator_api2::alloc::Allocator; + +#[cfg(nightly)] +pub use std::alloc::System; +#[cfg(not(nightly))] +pub use allocator_api2::alloc::System; + +/// A structure that keeps track of unoccupied regions of memory. +/// +/// This is used by [`ContiguousMemory`] to manage positions of stored items +/// while preventing overlap of assigned regions and proper alignment of stored +/// data. +/// +/// # Placement strategy +/// +/// A region provided for a given [`Layout`] is the beginning of the smallest +/// unoccupied segment with appropriate leading padding required to keep the +/// value represented by the `Layout` valid. +/// +/// Using the smallest unoccupied segment is necessary to reduce segmentation +/// that would occur if a greedier strategy (first available) were used. +/// +/// A different approach may be taken in the future and this change isn't +/// considered breaking - [`ContiguousMemory`] provides no means of directly +/// accessing the raw bytes of the stored data (i.e. `as_bytes`) and placement +/// order, positions or even alignment of stored data shouldn't be relied upon. +/// +/// [`Layout`]: core::alloc::Layout +/// [`ContiguousMemory`]: crate::ContiguousMemory +#[derive(Clone)] +pub struct SegmentTracker { + size: usize, + unoccupied: Vec, +} + +impl SegmentTracker { + /// Constructs a new empty `SegmentTracker` of the provided `size`. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::memory::SegmentTracker; + /// # use contiguous_mem::range::ByteRange; + /// let tracker = SegmentTracker::new(1024); + /// + /// assert!(!tracker.is_full()); + /// assert_eq!(tracker.size(), 1024); + /// assert_eq!(tracker.whole_range(), ByteRange(0, 1024)); + /// ``` + pub fn new(size: usize) -> Self { + SegmentTracker { + size, + unoccupied: if size > 0 { + vec![ByteRange(0, size)] + } else { + vec![] + }, + } + } + + /// Returns the total memory size being tracked. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::memory::SegmentTracker; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// assert_eq!(tracker.size(), 1024); + /// + /// tracker.grow(2048); + /// + /// assert_eq!(tracker.size(), 2048); + /// ``` + pub fn size(&self) -> usize { + self.size + } + + /// Returns the sum of unoccupied bytes of all unused memory segments. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::memory::SegmentTracker; + /// # use contiguous_mem::range::ByteRange; + /// # use core::alloc::Layout; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// assert_eq!(tracker.count_free(), 1024); + /// + /// let layout = Layout::from_size_align(512, 8).unwrap(); + /// let _ = tracker.take_next(4, layout).unwrap(); + /// + /// // both preceding 8 bytes and subsequent 504 bytes are counted towards + /// // the total: + /// assert_eq!(tracker.count_free(), 512); + /// ``` + pub fn count_free(&self) -> usize { + self.unoccupied.iter().fold(0, |acc, it| acc + it.len()) + } + + /// Returns `true` if there is no empty space left in the tracked region. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::memory::SegmentTracker; + /// # use contiguous_mem::range::ByteRange; + /// # use core::alloc::Layout; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// let layout = Layout::from_size_align(512, 8).unwrap(); + /// let _ = tracker.take_next(4, layout).unwrap(); + /// + /// assert!(!tracker.is_full()); + /// + /// let layout = Layout::from_size_align(504, 8).unwrap(); + /// let _ = tracker.take_next(4, layout).unwrap(); + /// + /// assert!(!tracker.is_full()); + /// + /// let layout = Layout::from_size_align(8, 4).unwrap(); + /// let _ = tracker.take_next(4, layout).unwrap(); + /// + /// assert!(tracker.is_full()); + /// ``` + pub fn is_full(&self) -> bool { + self.unoccupied.is_empty() + } + + /// Returns a [`ByteRange`] encompassing the entire tracked memory region. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::memory::SegmentTracker; + /// # use contiguous_mem::range::ByteRange; + /// # use core::alloc::Layout; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// assert_eq!(tracker.whole_range(), ByteRange(0, 1024)); + /// + /// let layout = Layout::from_size_align(512, 8).unwrap(); + /// let _ = tracker.take_next(4, layout).unwrap(); + /// + /// assert_eq!(tracker.whole_range(), ByteRange(0, 1024)); + /// ``` + pub fn whole_range(&self) -> ByteRange { + ByteRange(0, self.size) + } + + /// Grows the available memory range represented by this structure to + /// provided `new_size` and returns the new size. + pub fn grow(&mut self, new_size: usize) -> usize { + if new_size <= self.size { + return self.size; + } + + match self.unoccupied.last_mut() { + Some(it) if it.1 == self.size => { + // if the last free region ends at the end of tracked region + // grow it + it.1 = new_size; + } + _ => { + self.unoccupied.push(ByteRange(self.size, new_size)); + } + } + self.size = new_size; + self.size + } + + /// Tries shrinking the available memory range represented by this structure + /// to provided `new_size` and returns the new size. + pub fn shrink(&mut self, new_size: usize) -> usize { + if new_size >= self.size { + return self.size; + } + + let last = match self.unoccupied.last_mut() { + Some(it) => it, + None => return self.size, + }; + + let reduction = self.size - new_size; + let reduction = cmp::min(reduction, last.len()); + last.1 -= reduction; + if last.is_empty() { + self.unoccupied.pop(); + } + self.size -= reduction; + self.size + } + + /// 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 { + if self.unoccupied.last().map(|it| it.1) != Some(self.size) { + return None; + } + + let last = unsafe { + // SAFETY: Prev. if returned if pop is None + self.unoccupied.pop().unwrap_unchecked() + }; + self.size -= last.len(); + + Some(self.size) + } + + /// Returns `true` if the provided type `layout` can be stored within any + /// unused segments of the represented memory region. + pub fn can_store(&self, base: MemoryBase, layout: impl HasLayout) -> bool { + let layout = layout.as_layout(); + if layout.size() == 0 { + return true; + } else if layout.size() > self.size { + return false; + } + + self.unoccupied.iter().any(|it| { + it.offset(base.pos_or_align()) // absolute range + .aligned(layout.align()) // aligned to value + .len() + >= layout.size() + }) + } + + /// Returns the appropriate [`Location`] that can accommodate the given type + /// `layout`. + /// + /// If the `layout` cannot be stored within any unused segments of the + /// represented memory region, `None` is returned instead. + /// + /// This function mutably borrows because the returned `Location` is only + /// valid until this tracker gets mutated from somewhere else. The returned + /// value can also apply mutation on `self` via a call to + /// [`Location::mark_occupied`]. + pub fn peek_next(&mut self, base_pos: usize, layout: impl HasLayout) -> Option> { + let layout = layout.as_layout(); + if layout.size() == 0 { + return Some(Location::zero_sized(self)); + } else if layout.size() > self.size { + return None; + } + + // try to find the smallest free ByteRange that can hold the given + // layout while keeping it properly aligned. + let (found_position, found_range) = self + .unoccupied + .iter() + .enumerate() + .filter(|(_, it)| { + it.offset(base_pos) // absolute range + .aligned(layout.align()) // properly aligned + .len() // length of + >= layout.size() + }) + .min_by_key(|(_, it)| it.len())?; + + let available = found_range.aligned(layout.align()).cap_size(layout.size()); + + Some(Location::new(self, found_position, *found_range, available)) + } + + /// Returns either a start position of a free byte range at the end of the + /// tracker, or total size if end is occupied. + #[inline] + pub fn last_offset(&self) -> usize { + match self.unoccupied.last() { + Some(it) if it.1 == self.size => it.0, + _ => self.size, + } + } + + /// Returns a copy largest free [`ByteRange`] tracked by this tracker. + pub fn largest_free_range(&self) -> Option { + self.unoccupied.iter().max_by_key(|it| it.len()).copied() + } + + /// Returns a number of tailing free bytes in the tracker. + #[inline] + pub fn tailing_free_bytes(&self) -> usize { + match self.unoccupied.last() { + Some(it) if it.1 == self.size => it.len(), + _ => 0, + } + } + + /// Takes the next available memory region that can hold the provided + /// `layout`. + /// + /// It returns a [`ByteRange`] of the memory region that was marked as used + /// if successful, otherwise `None` + /// + /// # Examples + /// + /// ``` + /// # use contiguous_mem::range::ByteRange; + /// # use contiguous_mem::memory::SegmentTracker; + /// # use core::alloc::Layout; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// let layout = Layout::from_size_align(128, 8).unwrap(); + /// let range = tracker.take_next(8, layout).unwrap(); + /// + /// assert_eq!(range, ByteRange(0, 128)); + /// ``` + #[inline] + pub fn take_next(&mut self, base_pos: usize, layout: impl HasLayout) -> Option { + let mut location = self.peek_next(base_pos, layout)?; + location.mark_occupied(); + Some(location.usable) + } + + /// Tries marking the provided memory `region` as free. + /// + /// # Panics + /// + /// This function panics in debug mode if: + /// * the provided region falls outside of the memory tracked by the + /// `SegmentTracker`, or + /// * the provided region is in part or whole already marked as free. + /// + /// # Examples + /// ``` + /// # use contiguous_mem::range::ByteRange; + /// # use contiguous_mem::memory::SegmentTracker; + /// # use core::alloc::Layout; + /// let mut tracker = SegmentTracker::new(1024); + /// + /// let range = tracker + /// .take_next(8, Layout::from_size_align(32, 8).unwrap()) + /// .unwrap(); + /// assert_eq!(range, ByteRange(0, 32)); + /// + /// tracker.release(range); + /// assert!(!tracker.is_full()); + /// ``` + pub fn release(&mut self, region: ByteRange) { + if region.is_empty() { + return; + } + #[cfg(debug_assertions)] + if !self.whole_range().contains(region) { + panic!("{} not contained in segment tracker", region); + } + + if let Some(found) = self + .unoccupied + .iter_mut() + .find(|it| region.1 == it.0 || it.1 == region.0 || it.contains(region)) + { + #[cfg(debug_assertions)] + if found.overlaps(region) { + panic!("double free in segment tracker"); + } + found.apply_union_unchecked(region); + } else if let Some((i, _)) = self + .unoccupied + .iter() + .enumerate() + .find(|it| it.0 > region.0) + { + self.unoccupied.insert(i, region); + } else { + self.unoccupied.push(region); + } + } + + /// Clears all regions marked as occupied. + #[inline] + pub fn clear(&mut self) { + self.unoccupied.clear(); + self.unoccupied.push(self.whole_range()) + } +} + +#[cfg(feature = "debug")] +impl core::fmt::Debug for SegmentTracker { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SegmentTracker") + .field("size", &self.size) + .field("unused", &self.unoccupied) + .finish() + } +} + +/// A result of [`SegmentTracker::peek_next`] which contains information about +/// available allocation slot and wherein a certain [`Layout`] could be placed. +/// +/// `'a` is the lifetime of the [`SegmentTracker`] that produced this struct. +/// The reference is stored because it prevents any mutations from ocurring on +/// the tracker while a `Location` object is alive, which ensures it points to a +/// valid [`ByteRange`] stored in the tracker which can be acted upon without +/// incurring any additional lookup costs. +pub struct Location<'a> { + parent: &'a mut SegmentTracker, + index: usize, + whole: ByteRange, + usable: ByteRange, +} + +impl<'a> Location<'a> { + /// Creates a `Location` for a zero-sized struct in the `parent`. + pub fn zero_sized(parent: &'a mut SegmentTracker) -> Self { + Location { + parent, + index: 0, + whole: ByteRange::EMPTY, + usable: ByteRange::EMPTY, + } + } + + /// Creates a `Location` for a given `SegmentTracker` with required fields. + pub fn new( + parent: &'a mut SegmentTracker, + index: usize, + whole: ByteRange, + usable: ByteRange, + ) -> Self { + Location { + parent, + index, + whole, + usable, + } + } + + /// Returns the index of the containing byte range for the insertion + /// location. + pub fn position(&self) -> usize { + self.index + } + + /// Returns the containing byte range of the insertion location. + pub fn range(&self) -> ByteRange { + self.whole + } + + /// Returns a usable byte range of the insertion location. + pub fn usable_range(&self) -> ByteRange { + self.usable + } + + /// Returns `true` if the pointed-to location is zero-sized. + #[inline] + pub fn is_zero_sized(&self) -> bool { + self.usable.is_empty() + } + + /// Marks the pointed-to location as occupied. + pub fn mark_occupied(&mut self) { + if self.is_zero_sized() { + return; + } + + let left = ByteRange(self.whole.0, self.usable.0); + let right = ByteRange(self.usable.1, self.whole.1); + + // these are intentionally ordered by likelyhood to reduce cache misses + match (left.is_empty(), right.is_empty()) { + (true, false) => { + // left aligned + self.parent.unoccupied[self.index] = right; + } + (false, false) => { + // remaining space before and after + self.parent.unoccupied[self.index] = left; + self.parent.unoccupied.insert(self.index + 1, right); + } + (true, true) => { + // available occupies entirety of found + self.parent.unoccupied.remove(self.index); + } + (false, true) => { + // right aligned + self.parent.unoccupied[self.index] = left; + } + } + } +} + +/// Memory manager controls allocation and deallocation of underlying memory +/// used by the container. +/// +/// It also manages shrinking/growing of the container. +/// +/// [`Layout`] arguments can have the size 0 and that _shouldn't_ cause a panic, +/// implementations of the trait must ensure to return `None` as [`BaseAddress`] +/// appropriately in those cases. +/// +/// Default implementation that uses a system allocator (`malloc`) is +/// [`alloc::System`](System). Other allocators are supported as well. +pub trait ManageMemory { + /// Allocates a block of memory with size and alignment specified by + /// `layout` argument. + fn allocate(&self, layout: Layout) -> Result; + + /// Deallocates a block of memory of provided `base`. + /// + /// # Safety + /// + /// See: [alloc::Allocator::deallocate] + unsafe fn deallocate(&self, base: MemoryBase); + + /// Shrinks the provided memory slice to `new_size`. + /// + /// Generally doesn't cause a move, but an implementation can choose to do + /// so. + /// + /// # Safety + /// + /// See: [alloc::Allocator::shrink] + unsafe fn shrink(&self, base: MemoryBase, new_size: usize) -> Result; + + /// Grows the provided memory slice to `new_size`. + /// + /// # Safety + /// + /// See: [alloc::Allocator::grow] + unsafe fn grow(&self, base: MemoryBase, new_size: usize) -> Result; +} + +impl ManageMemory for A { + fn allocate(&self, layout: Layout) -> Result { + if layout.size() == 0 { + Ok(None) + } else { + Allocator::allocate(self, layout) + .map(Some) + .map_err(MemoryError::from) + } + } + + unsafe fn deallocate(&self, base: MemoryBase) { + if base.is_allocated() { + unsafe { + Allocator::deallocate( + self, + NonNull::new_unchecked(base.as_ptr_mut()), + base.layout(), + ) + } + } + } + + unsafe fn shrink(&self, base: MemoryBase, new_size: usize) -> Result { + match base.address { + Some(it) => { + if new_size > 0 { + let new_layout = Layout::from_size_align(new_size, base.alignment())?; + Allocator::shrink( + self, + NonNull::new_unchecked(it.as_ptr() as *mut u8), + base.layout(), + new_layout, + ) + .map(Some) + .map_err(MemoryError::from) + } else { + Allocator::deallocate( + self, + NonNull::new_unchecked(it.as_ptr() as *mut u8), + base.layout(), + ); + Ok(None) + } + } + None => Ok(None), + } + } + + unsafe fn grow(&self, base: MemoryBase, new_size: usize) -> Result { + match base.address { + Some(it) => { + let new_layout = Layout::from_size_align(new_size, base.alignment())?; + Allocator::grow( + self, + NonNull::new_unchecked(it.as_ptr() as *mut u8), + base.layout(), + new_layout, + ) + .map(Some) + .map_err(MemoryError::from) + } + None => { + if new_size == 0 { + Ok(None) + } else { + let new_layout = Layout::from_size_align(new_size, base.alignment())?; + Allocator::allocate(self, new_layout) + .map(Some) + .map_err(MemoryError::from) + } + } + } + } +} + +/// Provides a very verbose [`Display`][core::fmt::Display] of +/// [`SegmentTracker`] with address information. +#[cfg(feature = "debug")] +pub struct DisplaySegments(pub(crate) usize, pub(crate) SegmentTracker); +#[cfg(feature = "debug")] +impl core::fmt::Display for DisplaySegments { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "v- start: 0x{:X}", self.0)?; + let mut len = 2; + f.write_char('|')?; + let mut location = self.0; + for &u in &self.1.unoccupied { + if u.0 != location - self.0 { + let occupied = u.0 + self.0 - location; + let used = format!("#..{}B..#", occupied); + if used.len() < occupied { + f.write_str(&used)?; + len += used.len(); + } else { + f.write_str(&"#".repeat(occupied))?; + len += occupied; + } + } + let space = (u.1.saturating_sub(u.0)).max(1); + let start = format!("[0x{:X}", u.0 + self.0); + let end = format!("0x{:X}]", u.1 + self.0); + let total = format!("|{}B|", space); + if start.len() + total.len() + end.len() >= space { + write!(f, "{}{}{}", start, total, end)?; + len += start.len() + total.len() + end.len(); + } else { + write!(f, "{}|{}", start, end)?; + len += start.len() + 1 + end.len(); + } + location = self.0 + u.1; + } + if location != self.0 + self.1.size { + let occupied = self.0 + self.1.size - location; + let used = format!("#..{}B..#", occupied); + if used.len() < occupied { + f.write_str(&used)?; + len += used.len(); + } else { + f.write_str(&"#".repeat(occupied))?; + len += occupied; + } + } + f.write_char('|')?; + writeln!(f, " total: {}B", self.1.size)?; + let end = format!("end: 0x{:X} -^", self.0 + self.1.size); + if end.len() > len { + let end = format!("^- end: 0x{:X}", self.0 + self.1.size); + write!(f, "{}{}", " ".repeat(len - 1), end)?; + } else { + write!(f, "{}{}", " ".repeat(len - end.len()), end)?; + } + + Ok(()) + } +} diff --git a/src/range.rs b/src/range.rs index ea3a4e2..e0f5940 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,11 +1,19 @@ -#![doc(hidden)] +//! Contains [`ByteRange`] and related code. use core::fmt::Display; -/// Represents a range of bytes in -/// [`AllocationTracker`](crate::tracker::AllocationTracker) and -/// [`ContiguousMemoryStorage`](crate::ContiguousMemoryStorage). +#[cfg(not(nightly))] +use sptr::Strict; + +use crate::raw::BaseAddress; + +/// Represents a range of bytes. +/// +/// This type is very semantically similar to [`Range`][core::ops::Range], but +/// it's not an iterator so it implements [`Copy`], and has some additional +/// functionality. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] pub struct ByteRange( /// **Inclusive** lower bound of this byte range. pub usize, @@ -13,7 +21,11 @@ pub struct ByteRange( pub usize, ); +#[allow(unused)] impl ByteRange { + /// An empty byte range. + pub const EMPTY: ByteRange = ByteRange(0, 0); + /// Constructs a new byte range, ensuring that `from` and `to` are ordered /// correctly. pub fn new(from: usize, to: usize) -> Self { @@ -25,16 +37,37 @@ impl ByteRange { ByteRange(from, to) } - /// Aligns this byte range to the provided `alignment`. + /// Aligns the start of this byte range to the provided `alignment`. + /// + /// If the aligned start is past the range end, then end moved to the new + /// `start` value to keep the bounds ordered, i.e. avoid negative range + /// lengths. pub fn aligned(&self, alignment: usize) -> Self { let modulo = self.0 % alignment; if modulo == 0 { return *self; } + + let aligned_start = self.0 + alignment - modulo; + if aligned_start > self.1 { + // alignment shrunk the range past its length + return ByteRange(aligned_start, aligned_start) + } + + ByteRange(aligned_start, self.1) + } + + /// Aligns the start of this byte range to the provided `alignment`. + pub fn offset_aligned(&self, alignment: usize) -> Self { + let modulo = self.0 % alignment; + if modulo == 0 { + return *self; + } self.offset(alignment - modulo) } /// Caps the end address of this byte range to the provided `position`. + #[inline] pub fn cap_end(&self, position: usize) -> Self { ByteRange(self.0, position.min(self.1)) } @@ -48,6 +81,7 @@ impl ByteRange { } /// Offsets this byte range by a provided unsigned `offset`. + #[inline] pub fn offset(&self, offset: usize) -> Self { ByteRange(self.0 + offset, self.1 + offset) } @@ -61,91 +95,86 @@ impl ByteRange { } /// Returns length of this byte range. + #[inline] pub fn len(&self) -> usize { - self.1 - self.0 + debug_assert!(self.1 >= self.0, "negative byte range length"); + self.1.saturating_sub(self.0) } - /// Returns true if this byte range is zero-sized. + /// Returns `true` if this byte range is zero-sized. + #[inline] pub fn is_empty(&self) -> bool { self.0 == self.1 } - /// Returns `true` if this byte range contains another byte range `other`. + /// Returns `true` if this byte range contains `other` byte range. + #[inline] pub fn contains(&self, other: Self) -> bool { self.0 <= other.0 && other.1 <= self.1 } - /// Returns two byte ranges that remain when another `other` range is - /// removed from this one. - /// - /// It is possible for either or both of the returned byte ranges to have a - /// length of 0 if `other` is aligned with either the upper or lower bound - /// of this range, or if it is equal to this range. - pub fn difference_unchecked(&self, other: Self) -> (Self, Self) { - (ByteRange(self.0, other.0), ByteRange(other.1, self.1)) + /// Returns `true` if `other` byte range overlaps this byte range. + #[inline] + pub fn overlaps(&self, other: Self) -> bool { + self.contains(other) + || (other.0 <= self.0 && other.1 > self.0) + || (other.0 < self.1 && other.1 > self.1) } /// Merges this byte range with `other` and returns a byte range that /// contains both. - pub fn merge_unchecked(&self, other: Self) -> Self { + /// + /// # Example + /// + /// ``` + /// # use contiguous_mem::range::ByteRange; + /// let a = ByteRange::new_unchecked(0, 10); + /// let b = ByteRange::new_unchecked(10, 20); + /// + /// let added_seq = a.union_unchecked(b); + /// assert_eq!(added_seq.0, 0); + /// assert_eq!(added_seq.1, 20); + /// + /// // range union is symmetrical + /// let added_seq_rev = b.union_unchecked(a); + /// assert_eq!(added_seq_rev.0, 0); + /// assert_eq!(added_seq_rev.1, 20); + /// ``` + pub fn union_unchecked(&self, other: Self) -> Self { ByteRange(self.0.min(other.0), self.1.max(other.1)) } - /// Merges another `other` byte range into this one, resulting in a byte - /// range that contains both. - pub fn merge_in_unchecked(&mut self, other: Self) { + /// Merges `other` byte range into this one, resulting in a byte range that + /// contains both. + pub fn apply_union_unchecked(&mut self, other: Self) { self.0 = self.0.min(other.0); self.1 = self.1.max(other.1); } + + #[inline] + pub(crate) fn offset_base(&self, addr: BaseAddress) -> Option<*mut T> { + addr.map(|it| (it.as_ptr() as *const u8).map_addr(|addr| addr + self.0) as *mut T) + } + + #[inline] + pub(crate) unsafe fn offset_base_unwrap(&self, addr: BaseAddress) -> *mut T { + (addr.unwrap().as_ptr() as *mut u8).map_addr(|addr| addr + self.0) as *mut T + } } -impl Display for ByteRange { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "[{:x}, {:x})", self.0, self.1) +impl From> for ByteRange { + fn from(value: core::ops::Range) -> Self { + Self(value.start, value.end) + } +} +impl From for core::ops::Range { + fn from(value: ByteRange) -> Self { + value.0..value.1 } } -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn byterange_merging_works() { - let a = ByteRange::new_unchecked(0, 10); - let b = ByteRange::new_unchecked(10, 20); - - let added_seq = a.merge_unchecked(b); - assert_eq!(added_seq.0, 0); - assert_eq!(added_seq.1, 20); - - let added_seq_rev = b.merge_unchecked(a); - assert_eq!(added_seq_rev.0, 0); - assert_eq!(added_seq_rev.1, 20); - } - - #[test] - fn byterange_difference_works() { - let larger = ByteRange::new_unchecked(0, 500); - - let left_aligned = ByteRange::new_unchecked(0, 10); - let test_left = larger.difference_unchecked(left_aligned); - assert_eq!(test_left.0 .0, 0); - assert_eq!(test_left.0 .1, 0); - assert_eq!(test_left.1 .0, 10); - assert_eq!(test_left.1 .1, 500); - - let contained = ByteRange::new_unchecked(300, 400); - let test_contained = larger.difference_unchecked(contained); - assert_eq!(test_contained.0 .0, 0); - assert_eq!(test_contained.0 .1, 300); - assert_eq!(test_contained.1 .0, 400); - assert_eq!(test_contained.1 .1, 500); - - let right_aligned = ByteRange::new_unchecked(450, 500); - let test_right = larger.difference_unchecked(right_aligned); - assert_eq!(test_right.0 .0, 0); - assert_eq!(test_right.0 .1, 450); - assert_eq!(test_right.1 .0, 500); - assert_eq!(test_right.1 .1, 500); +impl Display for ByteRange { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[{:x}, {:x})", self.0, self.1) } } diff --git a/src/raw.rs b/src/raw.rs new file mode 100644 index 0000000..ba9416d --- /dev/null +++ b/src/raw.rs @@ -0,0 +1,173 @@ +#![allow(unused)] + +use core::{ + cell::{Cell, RefCell}, + ptr::NonNull, +}; + +use crate::{ + error::MemoryError, + memory::{ManageMemory, SegmentTracker, System}, +}; + +use super::*; + +/// Pointer to allocated slice of memory. +pub type BasePtr = NonNull<[u8]>; +/// Optional [`BasePtr`] value. +/// +/// `None` for zero-sized contiguous memory, `Some` otherwise. +pub type BaseAddress = Option; + +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct MemoryState, A: ManageMemory> { + pub base: Impl::Base, + pub tracker: Impl::Tracker, + pub alloc: A, +} + +impl> MemoryState { + pub fn new(layout: Layout) -> Result { + let alloc = System; + let ptr = alloc.allocate(layout)?; + Ok(MemoryState { + base: Impl::Base::from(MemoryBase { + address: ptr, + alignment: layout.align(), + }), + tracker: Impl::Tracker::from(SegmentTracker::new(layout.size())), + alloc, + }) + } +} +impl, A: ManageMemory> MemoryState { + pub fn new_with_alloc(layout: Layout, alloc: A) -> Result { + let ptr = alloc.allocate(layout)?; + Ok(MemoryState { + base: Impl::Base::from(MemoryBase { + address: ptr, + alignment: layout.align(), + }), + tracker: Impl::Tracker::from(SegmentTracker::new(layout.size())), + alloc, + }) + } +} + +impl, A: ManageMemory + Clone> Clone for MemoryState { + fn clone(&self) -> Self { + MemoryState { + base: Impl::Base::from(*ReadableInner::read(&self.base).unwrap()), + tracker: Impl::Tracker::from(ReadableInner::read(&self.tracker).unwrap().clone()), + alloc: self.alloc.clone(), + } + } +} + +impl, A: ManageMemory> Drop for MemoryState { + fn drop(&mut self) { + if let Ok(base) = ReadableInner::read(&self.base) { + unsafe { A::deallocate(&self.alloc, *base) } + } + } +} + +/// Memory allocation details. +/// +/// Unlike a fat pointer, this struct also stores information on expected +/// alignment the slice was allocated with, unifying [`Layout`] and pointer +/// types. +#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct MemoryBase { + pub(crate) address: BaseAddress, + alignment: usize, +} + +impl MemoryBase { + /// Returns a const raw pointer to the first byte or `null` if not + /// allocated. + #[inline] + pub fn as_ptr(&self) -> *const u8 { + self.address + .map(|it| it.as_ptr() as *const u8) + .unwrap_or_else(core::ptr::null) + } + /// Returns a const raw pointer to the first byte. + /// + /// # Safety + /// + /// This method assumes the base address exists (has been allocated). + /// Calling it is UB if the slice wasn't yet allocated. + #[inline] + pub unsafe fn as_ptr_unchecked(&self) -> *const T { + self.address.unwrap_unchecked().as_ptr() as *const T + } + + /// Returns a mutable raw pointer to the first byte or `null` if not + /// allocated. + #[inline] + pub fn as_ptr_mut(&self) -> *mut u8 { + self.address + .map(|it| it.as_ptr() as *mut u8) + .unwrap_or_else(core::ptr::null_mut) + } + + /// Returns a mutable raw pointer to the first byte. + /// + /// # Safety + /// + /// This method assumes the base address exists (slice has been allocated). + /// Calling it is UB if the slice wasn't yet allocate. + #[inline] + pub unsafe fn as_ptr_mut_unchecked(&self) -> *mut T { + self.address.unwrap_unchecked().as_ptr() as *mut T + } + + /// Returns the absolute position of the slice in memory or 0 if not + /// allocated. + #[inline] + pub fn as_pos(&self) -> usize { + self.as_ptr() as usize + } + + /// Returns the absolute position of the slice in memory or targeted + /// alignment if not allocated. + #[inline] + pub fn pos_or_align(&self) -> usize { + self.address + .map(|it| it.as_ptr() as *const u8 as usize) + .unwrap_or(self.alignment) + } + + /// Returns `true` if the slice has been allocated. + #[inline] + pub fn is_allocated(&self) -> bool { + self.address.is_some() + } + + /// Returns the size of the allocation, or 0 if the slice hasn't been + /// allocated. + #[inline] + pub fn size(&self) -> usize { + match self.address { + Some(it) => unsafe { it.as_ref().len() }, + None => 0, + } + } + + /// Returns the (tageted) alignment of the memory slice. + #[inline] + pub fn alignment(&self) -> usize { + self.alignment + } + + /// Returns the layout of the memory slice. + #[inline] + pub fn layout(&self) -> Layout { + unsafe { Layout::from_size_align_unchecked(self.size(), self.alignment) } + } +} + +unsafe impl Send for MemoryBase {} +unsafe impl Sync for MemoryBase {} diff --git a/src/reference.rs b/src/reference.rs new file mode 100644 index 0000000..b9d1bef --- /dev/null +++ b/src/reference.rs @@ -0,0 +1,491 @@ +//! Returned reference types and read/write guards. +//! +//! See [`ContiguousMemory::push`](crate::ContiguousMemory::push) for +//! information on implementation specific return values. + +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, + ptr::null_mut, +}; + +use crate::{ + error::RegionBorrowError, memory::ManageMemory, range::ByteRange, raw::MemoryState, types::*, +}; + +#[cfg(feature = "ptr_metadata")] +use core::marker::Unsize; +#[cfg(feature = "ptr_metadata")] +use core::ptr::Pointee; + +/// A reference to an entry of type `T` stored in +/// [`ContiguousMemory`](crate::ContiguousMemory). +pub struct EntryRef = ImplDefault> { + pub(crate) inner: Impl::SharedRef>, + #[cfg(feature = "ptr_metadata")] + pub(crate) metadata: ::Metadata, +} + +impl> EntryRef { + /// Returns a byte range within container memory this reference points to. + pub fn range(&self) -> ByteRange { + self.inner.range + } + + #[inline] + fn get_impl(&self) -> Result, RegionBorrowError> + where + T: RefSizeReq, + { + let mut borrow = WritableInner::write(&self.inner.borrow_kind).unwrap(); + if let BorrowState::Read(count) = *borrow { + *borrow = BorrowState::Read(count + 1); + } else { + return Err(RegionBorrowError { + range: self.inner.range, + borrow_state: *borrow, + }); + } + + let base = ReadableInner::read(&self.inner.state.base).unwrap(); + unsafe { + let pos = base.as_ptr().add(self.inner.range.0); + + Ok(MemoryReadGuard { + state: self.inner.clone(), + #[cfg(not(feature = "ptr_metadata"))] + value: &*(pos as *const T), + #[cfg(feature = "ptr_metadata")] + value: &*core::ptr::from_raw_parts::(pos as *const (), self.metadata), + }) + } + } + + /// Returns a reference to data at its current location and panics if the + /// represented memory region is mutably borrowed. + pub fn get(&self) -> MemoryReadGuard<'_, T, A, Impl> + where + T: RefSizeReq, + { + match Self::get_impl(self) { + Ok(it) => it, + Err(RegionBorrowError { range, .. }) => { + panic!("region {} already mutably borrowed", range) + } + } + } + + /// Returns a reference to data at its current location or a + /// [`RegionBorrowError`] error if the represented memory region is mutably + /// borrowed. + pub fn try_get(&self) -> Result, RegionBorrowError> + where + T: RefSizeReq, + { + Self::get_impl(self) + } + + #[inline] + fn get_mut_impl(&self) -> Result, RegionBorrowError> + where + T: RefSizeReq, + { + let mut borrow = WritableInner::write(&self.inner.borrow_kind).unwrap(); + if *borrow != BorrowState::Read(0) { + return Err(RegionBorrowError { + range: self.inner.range, + borrow_state: *borrow, + }); + } else { + *borrow = BorrowState::Write; + } + + let base = ReadableInner::read(&self.inner.state.base).unwrap(); + unsafe { + let pos = base.as_ptr().add(self.inner.range.0); + + Ok(MemoryWriteGuard { + state: self.inner.clone(), + #[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), + }) + } + } + + /// Returns a mutable reference to data at its current location and panics + /// if the reference has already been borrowed. + pub fn get_mut(&mut self) -> MemoryWriteGuard<'_, T, A, Impl> + where + T: RefSizeReq, + { + match Self::get_mut_impl(self) { + Ok(it) => it, + Err(RegionBorrowError { range, .. }) => { + panic!("region {} already immutably borrowed", range) + } + } + } + + /// Returns a mutable reference to data at its current location or a + /// [`RegionBorrowError`] error if the represented memory region is already + /// borrowed. + pub fn try_get_mut(&mut self) -> Result, RegionBorrowError> + where + T: RefSizeReq, + { + Self::get_mut_impl(self) + } + + /// Casts this reference into a dynamic type `R`. + #[cfg(feature = "ptr_metadata")] + pub fn into_dyn(self) -> EntryRef + where + T: Sized + Unsize, + { + // TODO: See if equal size can be guaranteed somehow and use transmute + // for ptr_metadata casts + + EntryRef { + inner: unsafe { + // SAFETY: Reinterpretation of T to R is safe because both + // EntryRefs are equally sized bc T is phantom. As A and Impl of + // the result are the same, and Unsize requirement is satisfied, + // this pointer cast is safe. + // + // Transform would be used, but it can't see the types are + // equally sized due to use of type arguments. + core::ptr::read( + &self.inner as *const Impl::SharedRef> + as *const Impl::SharedRef>, + ) + }, + metadata: static_metadata::(), + } + } + + /// Tries downcasting this dynamic reference into a discrete type `R`, + /// returns None if `R` drop handler doesn't match the original one. + #[cfg(feature = "ptr_metadata")] + pub fn downcast_dyn>(self) -> Option> { + if self.inner.drop_fn != drop_fn::() { + return None; + } + Some(EntryRef { + inner: unsafe { + // SAFETY: See EntryRef::into_dyn + core::ptr::read( + &self.inner as *const Impl::SharedRef> + as *const Impl::SharedRef>, + ) + }, + metadata: (), + }) + } + + /// Transmutes this reference to type `R` with provided `metadata`. + /// + /// [`static_metadata`] function may be used to statically construct + /// metadata for a struct-trait pair. + /// + /// # Safety + /// + /// This function is unsafe because it assumes any `T` to implement `R`, as + /// the original type of stored data can be erased through + /// [`into_dyn`](EntryRef::into_dyn) it's impossible to check whether the + /// initial struct actually implements `R`. + /// + /// Calling methods from an incorrect vtable will cause undefined behavior. + #[cfg(feature = "ptr_metadata")] + pub unsafe fn with_metadata( + self, + metadata: ::Metadata, + ) -> EntryRef { + EntryRef { + inner: unsafe { + // SAFETY: See EntryRef::into_dyn + core::ptr::read( + &self.inner as *const Impl::SharedRef> + as *const Impl::SharedRef>, + ) + }, + metadata, + } + } + + /// Creates an immutable pointer to underlying data. + /// + /// # Safety + /// + /// This function returns a pointer that may become invalid if the + /// container's memory is resized to a capacity which requires the memory + /// segment to be moved. + /// + /// When the reference goes out of scope, its region will be marked as free + /// which means that a subsequent call to [`ContiguousMemory::push`] + /// or friends can cause undefined behavior when dereferencing the pointer. + /// + /// [`ContiguousMemory::push`]: crate::ContiguousMemory::push + pub unsafe fn as_ptr(&self) -> *const T + where + T: RefSizeReq, + { + self.as_ptr_mut() as *const T + } + + /// Creates a mutable pointer to underlying data. + /// + /// # Safety + /// + /// In addition to concerns noted in [`EntryRef::as_ptr`], this function + /// also provides mutable access to the underlying data allowing potential + /// data races. + pub unsafe fn as_ptr_mut(&self) -> *mut T + where + T: RefSizeReq, + { + let base = ReadableInner::read(&self.inner.state.base).expect("unable to read base"); + let pos = base.as_ptr_mut().add(self.inner.range.0); + + #[cfg(not(feature = "ptr_metadata"))] + { + pos as *mut T + } + #[cfg(feature = "ptr_metadata")] + { + core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata) + } + } + + /// Creates an immutable pointer to underlying data while also preventing + /// the occupied memory region from being marked as free. + /// + /// # Safety + /// + /// This function returns a pointer that may become invalid if the + /// container's memory is resized to a capacity which requires the memory + /// segment to be moved. + pub unsafe fn into_ptr(self) -> *const T + where + T: RefSizeReq, + { + self.into_ptr_mut() as *const T + } + + /// Creates a mutable pointer to underlying data while also preventing the + /// occupied memory region from being marked as free. + /// + /// # Safety + /// + /// In addition to concerns noted in [`EntryRef::into_ptr`], this function + /// also provides mutable access to the underlying data allowing potential + /// data races. + pub unsafe fn into_ptr_mut(self) -> *mut T + where + T: RefSizeReq, + { + let result = self.as_ptr_mut(); + let inner: *mut ReferenceState = self.inner.deref() + as *const ReferenceState + as *mut ReferenceState; + core::ptr::drop_in_place(&mut (*inner).state); + core::mem::forget(self.inner); + result + } +} + +impl Clone for EntryRef { + fn clone(&self) -> Self { + EntryRef { + inner: self.inner.clone(), + #[cfg(feature = "ptr_metadata")] + metadata: self.metadata, + } + } +} + +#[cfg(feature = "debug")] +impl core::fmt::Debug for EntryRef +where + MemoryState: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EntryRef") + .field("inner", &self.inner) + .finish() + } +} + +pub(crate) mod state { + use super::*; + + /// Internal state of [`EntryRef`]. + pub struct ReferenceState, A: ManageMemory> { + pub state: Impl::StateRef>, + pub range: ByteRange, + pub borrow_kind: Impl::BorrowLock, + pub drop_fn: DropFn, + pub _phantom: PhantomData, + } + + #[cfg(feature = "debug")] + impl, A: ManageMemory> core::fmt::Debug + for ReferenceState + where + Impl::StateRef>: core::fmt::Debug, + Impl::BorrowLock: core::fmt::Debug, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EntryRef") + .field("state", &self.state) + .field("range", &self.range) + .field("borrow_kind", &self.borrow_kind) + .finish() + } + } + + impl, A: ManageMemory> Drop for ReferenceState { + fn drop(&mut self) { + if let Ok(base) = ReadableInner::read(&self.state.base) { + if let Ok(mut tracker) = WritableInner::write(&self.state.tracker) { + tracker.release(self.range); + unsafe { (self.drop_fn)(self.range.offset_base_unwrap(base.address)) }; + } + } + } + } +} +use state::*; + +/// Used for modelling XOR borrow semantics at runtime. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BorrowState { + /// The memory is being immutably accessed. + /// + /// The value of `0` represents number of immutable references crated to + /// memory location. + Read(usize), + /// The memory is being mutably accessed. + Write, +} + +/// Size requirements for types pointed to by references +/// +/// This is a sealed marker trait that allows `ptr_metadata` feature to disable +/// [`Sized`] requirement for [`EntryRef`] types. +#[cfg(feature = "ptr_metadata")] +pub trait RefSizeReq: Sealed {} +#[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 + Sealed {} +#[cfg(not(feature = "ptr_metadata"))] +impl RefSizeReq for T {} + +/// Strategy for [`EntryRef`] construction when items are pushed into the +/// container. +pub trait ConstructReference = ImplDefault>: + Sized + Clone +{ + /// Constructs a result reference type specified by the + /// [`implementation`](ImplDetails). + /// + /// Implementation of this method should be `#[inline]`d. + fn new(state: &Impl::StateRef>, range: ByteRange) -> Self; +} + +impl ConstructReference for EntryRef { + #[inline] + fn new(state: &Rc>, range: ByteRange) -> Self { + EntryRef { + inner: Reference::new(ReferenceState { + state: state.clone(), + range, + borrow_kind: core::cell::Cell::new(BorrowState::Read(0)), + drop_fn: drop_fn::(), + _phantom: PhantomData, + }), + #[cfg(feature = "ptr_metadata")] + metadata: (), + } + } +} + +#[cfg(feature = "unsafe_impl")] +impl ConstructReference for *mut T { + #[inline] + fn new(state: &Owned>, range: ByteRange) -> Self { + if range.is_empty() { + return null_mut(); + } + unsafe { + let base = ReadableInner::read(&state.base).unwrap(); + range.offset_base_unwrap(base.address) + } + } +} + +/// A smart reference wrapper responsible for tracking and managing a flag that +/// indicates whether the memory segment is actively being written to. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct MemoryWriteGuard<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing
= ImplDefault> +{ + state: Impl::SharedRef>, + #[allow(unused)] + value: &'a mut T, +} + +impl<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing> Deref + for MemoryWriteGuard<'a, T, A, Impl> +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing> DerefMut + for MemoryWriteGuard<'a, T, A, Impl> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.value + } +} + +impl<'a, T: ?Sized, Impl: ImplReferencing, A: ManageMemory> Drop + for MemoryWriteGuard<'a, T, A, Impl> +{ + fn drop(&mut self) { + Impl::unborrow_ref::(&self.state); + } +} + +/// A smart reference wrapper responsible for tracking and managing a flag that +/// indicates whether the memory segment is actively being read from. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct MemoryReadGuard<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing = ImplDefault> { + state: Impl::SharedRef>, + value: &'a T, +} + +impl<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing> Deref + for MemoryReadGuard<'a, T, A, Impl> +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'a, T: ?Sized, A: ManageMemory, Impl: ImplReferencing> Drop + for MemoryReadGuard<'a, T, A, Impl> +{ + fn drop(&mut self) { + Impl::unborrow_ref::(&self.state); + } +} diff --git a/src/refs.rs b/src/refs.rs deleted file mode 100644 index b54750a..0000000 --- a/src/refs.rs +++ /dev/null @@ -1,675 +0,0 @@ -//! Returned reference types and read/write guards. -//! -//! See [`ContiguousMemoryStorage::push`](crate::ContiguousMemoryStorage::push) -//! for information on implementation specific return values. - -use core::{ - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use crate::{ - details::{ImplConcurrent, ImplDefault, ImplDetails, StorageDetails}, - error::{LockSource, LockingError, RegionBorrowedError}, - range::ByteRange, - types::*, -}; - -/// A synchronized (thread-safe) reference to `T` data stored in a -/// [`ContiguousMemoryStorage`](crate::ContiguousMemoryStorage) structure. -pub struct SyncContiguousEntryRef { - pub(crate) inner: Arc>, - #[cfg(feature = "ptr_metadata")] - pub(crate) metadata: ::Metadata, - #[cfg(not(feature = "ptr_metadata"))] - pub(crate) _phantom: PhantomData, -} - -/// A shorter type name for [`SyncContiguousEntryRef`]. -pub type SCERef = SyncContiguousEntryRef; - -impl SyncContiguousEntryRef { - /// Returns a byte range within container memory this reference points to. - pub fn range(&self) -> ByteRange { - self.inner.range - } - - /// Returns a reference to data at its current location or returns a - /// [`LockingError::Poisoned`](crate::error::LockingError::Poisoned) error - /// if the Mutex holding the `base` address pointer has been poisoned. - /// - /// If the data is mutably accessed, this method will block the current - /// thread until it becomes available. - pub fn get(&self) -> Result, LockingError> - where - T: RefSizeReq, - { - let guard = self.inner.borrow_kind.read_named(LockSource::Reference)?; - - unsafe { - let base = ImplConcurrent::get_base(&self.inner.state.base)?; - let pos = base.add(self.inner.range.0); - - Ok(MemoryReadGuard { - state: self.inner.clone(), - guard, - #[cfg(not(feature = "ptr_metadata"))] - value: &*(pos as *mut T), - #[cfg(feature = "ptr_metadata")] - value: &*core::ptr::from_raw_parts(pos as *const (), self.metadata), - }) - } - } - - /// Returns a reference to data at its current location or returns a - /// [`LockingError::Poisoned`](crate::error::LockingError::Poisoned) error - /// if the Mutex holding the `base` address pointer has been poisoned. - /// - /// If the data is mutably accessed, this method returns a - /// [`LockingError::WouldBlock`](crate::error::LockingError::WouldBlock) - /// error. - pub fn try_get(&self) -> Result, LockingError> - where - T: RefSizeReq, - { - let guard = self - .inner - .borrow_kind - .try_read_named(LockSource::Reference)?; - - unsafe { - let base = ImplConcurrent::get_base(&self.inner.state.base)?; - let pos = base.add(self.inner.range.0); - - Ok(MemoryReadGuard { - state: self.inner.clone(), - guard, - #[cfg(not(feature = "ptr_metadata"))] - value: &*(pos as *mut T), - #[cfg(feature = "ptr_metadata")] - value: &*core::ptr::from_raw_parts(pos as *const (), self.metadata), - }) - } - } - - /// Returns or write guard to referenced data at its current location a - /// [`LockingError::Poisoned`] error if the Mutex holding the base address - /// pointer or the Mutex holding concurrent mutable access flag has been - /// poisoned. - pub fn get_mut(&mut self) -> Result, LockingError> - where - T: RefSizeReq, - { - let guard = self.inner.borrow_kind.write_named(LockSource::Reference)?; - unsafe { - let base = ImplConcurrent::get_base(&self.inner.state.base)?; - let pos = base.add(self.inner.range.0); - Ok(MemoryWriteGuard { - 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), - }) - } - } - - /// Returns a write guard to referenced data at its current location or a - /// `LockingError` if that isn't possible. - /// - /// # Errors - /// - /// This function can return the following errors: - /// - /// - [`LockingError::Poisoned`] error if the Mutex holding the base address - /// pointer or the Mutex holding mutable access exclusion flag has been - /// poisoned. - /// - /// - [`LockingError::WouldBlock`] error if accessing referenced data chunk - /// would be blocking. - pub fn try_get_mut(&mut self) -> Result, LockingError> - where - T: RefSizeReq, - { - let guard = self - .inner - .borrow_kind - .try_write_named(LockSource::Reference)?; - unsafe { - let base = ImplConcurrent::try_get_base(&self.inner.state.base)?; - let pos = base.add(self.inner.range.0); - Ok(MemoryWriteGuard { - 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), - }) - } - } - - /// Casts this reference into a dynamic type `R`. - #[cfg(feature = "ptr_metadata")] - pub fn into_dyn(self) -> SyncContiguousEntryRef - where - T: Sized + Unsize, - { - unsafe { - SyncContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata: static_metadata::(), - } - } - } - - /// Tries downcasting this dynamic reference into a discrete type `R`, - /// returns None if `R` drop handler doesn't match the original one. - #[cfg(feature = "ptr_metadata")] - pub fn downcast_dyn>(self) -> Option> { - if self.inner.drop_fn != drop_fn::() { - return None; - } - unsafe { - Some(SyncContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata: (), - }) - } - } - - /// Transmutes this reference to type `R` with provided `metadata`. - /// - /// [`static_metadata`](crate::static_metadata) function may be used to - /// statically construct metadata for a struct-trait pair. - /// - /// # Safety - /// - /// See: [`ContiguousEntryRef::with_metadata`] - #[cfg(feature = "ptr_metadata")] - pub unsafe fn with_metadata( - self, - metadata: ::Metadata, - ) -> ContiguousEntryRef { - unsafe { - ContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata, - } - } - } - - /// Creates an immutable pointer to underlying data, blocking the current - /// thread until base address can be read. - /// - /// This function can return a [`LockingError::Poisoned`] error if the Mutex - /// holding the base address pointer has been poisoned. - /// - /// # Safety - /// - /// See: [`ContiguousEntryRef::as_ptr`] - pub unsafe fn as_ptr(&self) -> Result<*const T, LockingError> - where - T: RefSizeReq, - { - self.as_ptr_mut().map(|it| it as *const T) - } - - /// Creates a mutable pointer to underlying data, blocking the current - /// thread until base address can be read. - /// - /// This function can return a [`LockingError::Poisoned`] error if the Mutex - /// holding the base address pointer has been poisoned. - /// - /// # Safety - /// - /// See: [`ContiguousEntryRef::as_ptr_mut`] - pub unsafe fn as_ptr_mut(&self) -> Result<*mut T, LockingError> - where - T: RefSizeReq, - { - let base = ImplConcurrent::get_base(&self.inner.state.base)?; - 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_mut::( - pos as *mut (), - self.metadata, - )) - } - } - - /// Creates an immutable pointer to underlying data while also preventing - /// the occupied memory region from being marked as free, blocking the - /// current thread until base address can be read - /// - /// This function can return a [`LockingError::Poisoned`] error if the Mutex - /// holding the base address pointer has been poisoned. - /// - /// # Safety - /// - /// See: [`ContiguousEntryRef::into_ptr`] - pub unsafe fn into_ptr(self) -> Result<*const T, LockingError> - where - T: RefSizeReq, - { - self.into_ptr_mut().map(|it| it as *const T) - } - - /// Creates a mutable pointer to underlying data while also preventing - /// the occupied memory region from being marked as free, blocking the - /// current thread until base address can be read - /// - /// This function can return a [`LockingError::Poisoned`] error if the Mutex - /// holding the base address pointer has been poisoned. - /// - /// # Safety - /// - /// See: [`ContiguousEntryRef::into_ptr_mut`] - pub unsafe fn into_ptr_mut(self) -> Result<*mut T, LockingError> - where - T: RefSizeReq, - { - let result = self.as_ptr_mut(); - let inner: *mut ReferenceState = self.inner.as_ref() - as *const ReferenceState - as *mut ReferenceState; - core::ptr::drop_in_place(&mut (*inner).state); - core::mem::forget(self.inner); - result - } -} - -impl EntryRef for SyncContiguousEntryRef {} - -impl Clone for SyncContiguousEntryRef { - fn clone(&self) -> Self { - SyncContiguousEntryRef { - inner: self.inner.clone(), - #[cfg(feature = "ptr_metadata")] - metadata: self.metadata, - #[cfg(not(feature = "ptr_metadata"))] - _phantom: PhantomData, - } - } -} - -#[cfg(feature = "debug")] -impl core::fmt::Debug for SyncContiguousEntryRef { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("SyncContiguousEntryRef") - .field("inner", &self.inner) - .finish() - } -} - -/// A thread-unsafe reference to `T` data stored in -/// [`ContiguousMemoryStorage`](crate::ContiguousMemoryStorage) structure. -pub struct ContiguousEntryRef { - pub(crate) inner: Rc>, - #[cfg(feature = "ptr_metadata")] - pub(crate) metadata: ::Metadata, - #[cfg(not(feature = "ptr_metadata"))] - pub(crate) _phantom: PhantomData, -} - -/// A shorter type name for [`ContiguousEntryRef`]. -pub type CERef = ContiguousEntryRef; - -impl ContiguousEntryRef { - /// Returns a byte range within container memory this reference points to. - pub fn range(&self) -> ByteRange { - self.inner.range - } - - /// Returns a reference to data at its current location and panics if the - /// represented memory region is mutably borrowed. - pub fn get(&self) -> MemoryReadGuard<'_, T, ImplDefault> - where - T: RefSizeReq, - { - ContiguousEntryRef::::try_get(self).expect("mutably borrowed") - } - - /// Returns a reference to data at its current location or a - /// [`RegionBorrowedError`] error if the represented memory region is - /// mutably borrowed. - pub fn try_get(&self) -> Result, RegionBorrowedError> - where - T: RefSizeReq, - { - let state = self.inner.borrow_kind.get(); - if let BorrowState::Read(count) = state { - self.inner.borrow_kind.set(BorrowState::Read(count + 1)); - } else { - return Err(RegionBorrowedError { - range: self.inner.range, - }); - } - - unsafe { - let base = ImplDefault::get_base(&self.inner.state.base); - let pos = base.add(self.inner.range.0); - - Ok(MemoryReadGuard { - state: self.inner.clone(), - guard: (), - #[cfg(not(feature = "ptr_metadata"))] - value: &*(pos as *mut T), - #[cfg(feature = "ptr_metadata")] - value: &*core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata), - }) - } - } - - /// Returns a mutable reference to data at its current location and panics - /// if the reference has already been borrowed. - pub fn get_mut(&mut self) -> MemoryWriteGuard<'_, T, ImplDefault> - where - T: RefSizeReq, - { - ContiguousEntryRef::::try_get_mut(self).expect("mutably borrowed") - } - - /// Returns a mutable reference to data at its current location or a - /// [`RegionBorrowedError`] error if the represented memory region is - /// already borrowed. - pub fn try_get_mut( - &mut self, - ) -> Result, RegionBorrowedError> - where - T: RefSizeReq, - { - if self.inner.borrow_kind.get() != BorrowState::Read(0) { - return Err(RegionBorrowedError { - range: self.inner.range, - }); - } else { - self.inner.borrow_kind.set(BorrowState::Write); - } - - unsafe { - let base = ImplDefault::get_base(&self.inner.state.base); - let pos = base.add(self.inner.range.0); - - Ok(MemoryWriteGuard { - 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), - }) - } - } - - /// Casts this reference into a dynamic type `R`. - #[cfg(feature = "ptr_metadata")] - pub fn into_dyn(self) -> ContiguousEntryRef - where - T: Sized + Unsize, - { - unsafe { - ContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata: static_metadata::(), - } - } - } - - /// Tries downcasting this dynamic reference into a discrete type `R`, - /// returns None if `R` drop handler doesn't match the original one. - #[cfg(feature = "ptr_metadata")] - pub fn downcast_dyn>(self) -> Option> { - if self.inner.drop_fn != drop_fn::() { - return None; - } - unsafe { - Some(ContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata: (), - }) - } - } - - /// Transmutes this reference to type `R` with provided `metadata`. - /// - /// [`static_metadata`](crate::static_metadata) function may be used to - /// statically construct metadata for a struct-trait pair. - /// - /// # Safety - /// - /// This function is unsafe because it assumes any `T` to implement `R`, - /// as the original type of stored data can be erased through - /// [`into_dyn`](ContiguousEntryRef::into_dyn) it's impossible to check - /// whether the initial struct actually implements `R`. - /// - /// Calling methods from an incorrect vtable will cause undefined behavior. - #[cfg(feature = "ptr_metadata")] - pub unsafe fn with_metadata( - self, - metadata: ::Metadata, - ) -> ContiguousEntryRef { - unsafe { - ContiguousEntryRef { - inner: core::mem::transmute(self.inner), - metadata, - } - } - } - - /// Creates an immutable pointer to underlying data. - /// - /// # Safety - /// - /// This function returns a pointer that may become invalid if the - /// container's memory is resized to a capacity which requires the memory - /// segment to be moved. - /// - /// When the reference goes out of scope, its region will be marked as free - /// which means that a subsequent call to [`ContiguousMemoryStorage::push`] - /// or friends can cause undefined behavior when dereferencing the pointer. - /// - /// [`ContiguousMemoryStorage::push`]: crate::ContiguousMemoryStorage::push - pub unsafe fn as_ptr(&self) -> *const T - where - T: RefSizeReq, - { - self.as_ptr_mut() as *const T - } - - /// Creates a mutable pointer to underlying data. - /// - /// # Safety - /// - /// In addition to concerns noted in [`ContiguousEntryRef::as_ptr`], - /// this function also provides mutable access to the underlying data - /// allowing potential data races. - pub unsafe fn as_ptr_mut(&self) -> *mut T - where - T: RefSizeReq, - { - let base = ImplDefault::get_base(&self.inner.state.base); - let pos = base.add(self.inner.range.0); - - #[cfg(not(feature = "ptr_metadata"))] - { - pos as *mut T - } - #[cfg(feature = "ptr_metadata")] - { - core::ptr::from_raw_parts_mut::(pos as *mut (), self.metadata) - } - } - - /// Creates an immutable pointer to underlying data while also preventing - /// the occupied memory region from being marked as free. - /// - /// # Safety - /// - /// This function returns a pointer that may become invalid if the - /// container's memory is resized to a capacity which requires the memory - /// segment to be moved. - pub unsafe fn into_ptr(self) -> *const T - where - T: RefSizeReq, - { - self.into_ptr_mut() as *const T - } - - /// Creates a mutable pointer to underlying data while also preventing - /// the occupied memory region from being marked as free. - /// - /// # Safety - /// - /// In addition to concerns noted in - /// [`ContiguousEntryRef::into_ptr`], this function also provides - /// mutable access to the underlying data allowing potential data races. - pub unsafe fn into_ptr_mut(self) -> *mut T - where - T: RefSizeReq, - { - let result = self.as_ptr_mut(); - let inner: *mut ReferenceState = self.inner.as_ref() - as *const ReferenceState - as *mut ReferenceState; - core::ptr::drop_in_place(&mut (*inner).state); - core::mem::forget(self.inner); - result - } -} - -impl EntryRef for ContiguousEntryRef {} - -impl Clone for ContiguousEntryRef { - fn clone(&self) -> Self { - ContiguousEntryRef { - inner: self.inner.clone(), - #[cfg(feature = "ptr_metadata")] - metadata: self.metadata, - #[cfg(not(feature = "ptr_metadata"))] - _phantom: PhantomData, - } - } -} - -#[cfg(feature = "debug")] -impl core::fmt::Debug for ContiguousEntryRef { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ContiguousEntryRef") - .field("inner", &self.inner) - .finish() - } -} - -pub(crate) mod sealed { - use super::*; - - pub trait EntryRef {} - - /// Internal state of [`ContiguousEntryRef`] and [`SyncContiguousEntryRef`]. - pub struct ReferenceState { - pub state: Impl::StorageState, - pub range: ByteRange, - pub borrow_kind: Impl::BorrowLock, - pub drop_fn: DropFn, - pub _phantom: PhantomData, - } - - #[cfg(feature = "debug")] - impl core::fmt::Debug for ReferenceState - where - Impl::StorageState: core::fmt::Debug, - Impl::BorrowLock: core::fmt::Debug, - { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ReferenceState") - .field("state", &self.state) - .field("range", &self.range) - .field("borrow_kind", &self.borrow_kind) - .finish() - } - } - - impl Drop for ReferenceState { - fn drop(&mut self) { - let base = Impl::get_base(&Impl::deref_state(&self.state).base); - let tracker = Impl::get_allocation_tracker(&mut self.state); - if let Some(it) = Impl::free_region(tracker, base, self.range) { - (self.drop_fn)(it); - }; - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum BorrowKind { - Read, - Write, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum BorrowState { - Read(usize), - Write, - } -} -use sealed::*; - -/// A smart reference wrapper responsible for tracking and managing a flag -/// that indicates whether the memory segment is actively being written to. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct MemoryWriteGuard<'a, T: ?Sized, Impl: ImplDetails> { - state: Impl::RefState, - #[allow(unused)] - guard: Impl::WriteGuard<'a>, - value: &'a mut T, -} - -impl<'a, T: ?Sized, Impl: ImplDetails> Deref for MemoryWriteGuard<'a, T, Impl> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.value - } -} - -impl<'a, T: ?Sized, Impl: ImplDetails> DerefMut for MemoryWriteGuard<'a, T, Impl> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.value - } -} - -impl<'a, T: ?Sized, Impl: ImplDetails> Drop for MemoryWriteGuard<'a, T, Impl> { - fn drop(&mut self) { - Impl::unborrow_ref::(&self.state, BorrowKind::Write); - } -} - -/// A smart reference wrapper responsible for tracking and managing a flag -/// that indicates whether the memory segment is actively being read from. -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct MemoryReadGuard<'a, T: ?Sized, Impl: ImplDetails> { - state: Impl::RefState, - #[allow(unused)] - guard: Impl::ReadGuard<'a>, - value: &'a T, -} - -impl<'a, T: ?Sized, Impl: ImplDetails> Deref for MemoryReadGuard<'a, T, Impl> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.value - } -} - -impl<'a, T: ?Sized, Impl: ImplDetails> Drop for MemoryReadGuard<'a, T, Impl> { - fn drop(&mut self) { - Impl::unborrow_ref::(&self.state, BorrowKind::Read); - } -} diff --git a/src/tracker.rs b/src/tracker.rs deleted file mode 100644 index d200e24..0000000 --- a/src/tracker.rs +++ /dev/null @@ -1,302 +0,0 @@ -#![doc(hidden)] - -use core::{alloc::Layout, cmp::Ordering}; - -#[cfg(feature = "no_std")] -use crate::types::{vec, Vec}; -use crate::{error::ContiguousMemoryError, range::ByteRange}; - -/// A structure that keeps track of unused regions of memory within provided -/// bounds. -#[derive(Clone)] -pub struct AllocationTracker { - size: usize, - unused: Vec, -} - -impl AllocationTracker { - /// Constructs a new `AllocationTracker` of the provided `size`. - pub fn new(size: usize) -> Self { - AllocationTracker { - size, - unused: vec![ByteRange(0, size)], - } - } - - /// Returns the total memory size being tracked. - pub fn len(&self) -> usize { - self.size - } - - /// Checks if there is no empty space left in the tracked region. - pub fn is_empty(&self) -> bool { - self.unused.is_empty() - } - - /// Returns a [`ByteRange`] encompassing the entire tracked memory region. - pub fn whole_range(&self) -> ByteRange { - ByteRange(0, self.size) - } - - /// Tries resizing the available memory range represented by this structure - /// to provided `new_size`, or an [`ContiguousMemoryError::Unshrinkable`] - /// error if the represented memory range cannot be shrunk enough to fit - /// the desired size. - pub fn resize(&mut self, new_size: usize) -> Result<(), ContiguousMemoryError> { - match new_size.cmp(&self.size) { - Ordering::Equal => {} - Ordering::Less => { - let last = self - .unused - .last_mut() - .ok_or(ContiguousMemoryError::Unshrinkable { - required_size: self.size, - })?; - - let reduction = self.size - new_size; - if last.len() < reduction { - return Err(ContiguousMemoryError::Unshrinkable { - required_size: self.size - last.len(), - }); - } - last.1 -= reduction; - self.size = new_size; - } - Ordering::Greater => { - match self.unused.last() { - Some(it) => { - // check whether the last free region ends at the end of - // tracked region - if it.1 == self.size { - let last = self - .unused - .last_mut() - .expect("free byte ranges isn't empty"); - last.1 = new_size; - } else { - self.unused.push(ByteRange(self.size, new_size)); - } - } - None => { - self.unused.push(ByteRange(self.size, new_size)); - } - } - self.size = new_size; - } - } - 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`. - /// - /// If the `layout` cannot be safely stored within any free segments of the - /// represented memory region, `None` is returned instead. - pub fn peek_next(&self, layout: Layout) -> Option { - if layout.size() > self.size { - return None; - } - - let available = self.unused.iter().find(|it| { - it.len() >= layout.size() && it.aligned(layout.align()).len() >= layout.size() - })?; - - let usable = available.aligned(layout.align()).cap_size(layout.size()); - - Some(usable) - } - - /// Tries marking the provided memory `region` as not free, returning one - /// of the following errors if that's not possible: - /// - /// - [`ContiguousMemoryError::NotContained`]: If the provided region falls - /// outside of the memory tracked by the `AllocationTracker`. - /// - [`ContiguousMemoryError::AlreadyUsed`]: If the provided region isn't - /// free. - pub fn take(&mut self, region: ByteRange) -> Result<(), ContiguousMemoryError> { - if self.whole_range().contains(region) { - return Err(ContiguousMemoryError::NotContained); - } - - let (i, found) = self - .unused - .iter() - .enumerate() - .find(|(_, it)| it.contains(region)) - .ok_or(ContiguousMemoryError::AlreadyUsed)?; - - let (left, right) = found.difference_unchecked(region); - - if !left.is_empty() { - self.unused[i] = left; - if !right.is_empty() { - self.unused.insert(i + 1, right); - } - } else if !right.is_empty() { - self.unused[i] = right; - } else { - self.unused.remove(i); - } - - Ok(()) - } - - /// Takes the next available memory region that can hold the provided - /// `layout`. - /// - /// On success, it returns a [`ByteRange`] of the memory region that was - /// taken, or a [`ContiguousMemoryError::NoStorageLeft`] error if the - /// requested `layout` cannot be placed within any free regions. - pub fn take_next( - &mut self, - base_address: usize, - layout: Layout, - ) -> Result { - if layout.size() > self.size { - return Err(ContiguousMemoryError::NoStorageLeft); - } - - let (i, available) = self - .unused - .iter() - .enumerate() - .find(|(_, it)| { - if it.len() < layout.size() { - return false; - } - - let aligned = it - .offset(base_address) - .aligned(layout.align()) - .cap_end(base_address + self.len()); - - aligned.len() >= layout.size() - }) - .ok_or(ContiguousMemoryError::NoStorageLeft)?; - - let taken = available.aligned(layout.align()).cap_size(layout.size()); - - let (left, right) = available.difference_unchecked(taken); - - if !left.is_empty() { - self.unused[i] = left; - if !right.is_empty() { - self.unused.insert(i + 1, right); - } - } else if !right.is_empty() { - self.unused[i] = right; - } else { - self.unused.remove(i); - } - - Ok(taken) - } - - /// Tries marking the provided memory `region` as free, returning a - /// [`ContiguousMemoryError::NotContained`] error if the provided region - /// falls outside of the memory tracked by the `AllocationTracker`. - pub fn release(&mut self, region: ByteRange) -> Result<(), ContiguousMemoryError> { - if !self.whole_range().contains(region) { - return Err(ContiguousMemoryError::NotContained); - } - - if let Some(found) = self - .unused - .iter_mut() - .find(|it| region.1 == it.0 || it.1 == region.0 || it.contains(region)) - { - if found.contains(region) { - return Err(ContiguousMemoryError::DoubleFree); - } - found.merge_in_unchecked(region); - } else if let Some((i, _)) = self.unused.iter().enumerate().find(|it| it.0 > region.0) { - self.unused.insert(i, region); - } else { - self.unused.push(region); - } - - Ok(()) - } -} - -#[cfg(feature = "debug")] -impl core::fmt::Debug for AllocationTracker { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("AllocationTracker") - .field("size", &self.size) - .field("unused", &self.unused) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new_allocation_tracker() { - let tracker = AllocationTracker::new(1024); - assert_eq!(tracker.len(), 1024); - assert_eq!(tracker.is_empty(), false); - assert_eq!(tracker.whole_range(), ByteRange(0, 1024)); - } - - #[test] - fn test_resize_allocation_tracker() { - let mut tracker = AllocationTracker::new(1024); - - tracker.resize(512).unwrap(); - assert_eq!(tracker.len(), 512); - - tracker.resize(2048).unwrap(); - assert_eq!(tracker.len(), 2048); - } - - #[test] - fn test_take_and_release_allocation_tracker() { - let mut tracker = AllocationTracker::new(1024); - - let range = tracker - .take_next(0, Layout::from_size_align(32, 8).unwrap()) - .unwrap(); - assert_eq!(range, ByteRange(0, 32)); - - tracker - .release(range) - .expect("expected AllocationTracker to have the provided range marked as taken"); - assert_eq!(tracker.is_empty(), false); - } - - #[test] - fn test_peek_next_allocation_tracker() { - let tracker = AllocationTracker::new(1024); - - let layout = Layout::from_size_align(64, 8).unwrap(); - let range = tracker.peek_next(layout).unwrap(); - assert_eq!(range, ByteRange(0, 64)); - } - - #[test] - fn test_take_next_allocation_tracker() { - let mut tracker = AllocationTracker::new(1024); - - let layout = Layout::from_size_align(128, 8).unwrap(); - let range = tracker.take_next(0, layout).unwrap(); - assert_eq!(range, ByteRange(0, 128)); - } -} diff --git a/src/types.rs b/src/types.rs index d9a93b1..063047a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,189 +1,372 @@ -//! Module re-exporting used types any polyfill to help with feature support. +//! Module re-exporting used types and polyfill to help with feature support. -#[cfg(not(feature = "no_std"))] +#[cfg(feature = "std")] mod std_imports { pub use std::rc::Rc; - pub use std::sync::Arc; - pub use std::sync::Mutex; - pub use std::sync::MutexGuard; - pub use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - - pub use std::alloc as allocator; } -#[cfg(not(feature = "no_std"))] -pub use std_imports::*; +#[cfg(feature = "std")] +pub(crate) use std_imports::*; -#[cfg(feature = "no_std")] +#[cfg(not(feature = "std"))] mod nostd_imports { - pub use spin::Mutex; - pub use spin::MutexGuard; - pub use spin::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - - pub use alloc::alloc as allocator; + pub use ::alloc::rc::Rc; pub use ::alloc::vec; pub use ::alloc::vec::Vec; +} +#[cfg(not(feature = "std"))] +pub(crate) use nostd_imports::*; - pub use ::alloc::rc::Rc; - pub use ::alloc::sync::Arc; +#[cfg(feature = "error_in_core")] +pub use core::error::Error; +use core::{ + cell::UnsafeCell, + convert::Infallible, alloc::Layout, ops::{Deref, DerefMut}, + fmt::Debug, +}; +#[cfg(all(not(feature = "error_in_core"), feature = "std"))] +pub use std::error::Error; + +use crate::{reference::{state::ReferenceState, BorrowState, EntryRef}, memory::{ManageMemory, SegmentTracker}, raw::MemoryBase, ConstructReference}; + +/// Unifies reading behavior from structs that provide inner mutability. +/// +/// All implemented functions should be `#[inline]`d to ensure that final code +/// behaves as if this abstraction didn't exist. +/// +/// This allows different [`ContigousMemory`](crate::ContiguousMemory) +/// implementation details to use the same code base while staying correct for +/// the strictest one. +pub trait ReadableInner { + /// Read guard type returned by `read` and `try_read` operations. + /// + /// In case the cell type doesn't provide a guard for operations, a + /// replacement that updates cell value on `Drop` should be used (such as + /// [`CellWriteGuard`]). + type ReadGuard<'a>: Deref + where + Self: 'a; + + /// Error returned when calling [`read`](ReadableInner::read) or + /// [`try_read`](ReadableInner::try_read) fails. + #[cfg(not(any(feature = "error_in_core", feature = "std")))] + type BorrowError: Debug; + /// Error returned when calling [`read`](ReadableInner::read) or + /// [`try_read`](ReadableInner::try_read) fails. + #[cfg(any(feature = "error_in_core", feature = "std"))] + type BorrowError: Error; + + /// Returns the [read guard](ReadableInner::ReadGuard) for `T` if the + /// wrapped readable can be read, or an [error](ReadableInner::BorrowError) + /// if that's not possible (usually due to container being poisoned). + /// + /// This method will block for implementations of concurrent containers + /// (such as `Mutex`), for non-blocking access use + /// [`try_read`](ReadableInner::try_read). + fn read(&self) -> Result, Self::BorrowError>; + /// Returns the [read guard](ReadableInner::ReadGuard) for `T` if the + /// wrapped readable can be read, or an [error](ReadableInner::BorrowError) + /// if it's being mutably accessed from somewhere else or if read isn't + /// possible (usually due to container being poisoned). + fn try_read(&self) -> Result, Self::BorrowError> { + self.read() + } } -#[cfg(feature = "no_std")] -pub use nostd_imports::*; -use crate::error::{LockSource, LockingError}; +/// Unifies writing behavior from structs that provide inner mutability. +/// +/// See [`ReadableInner`] for more details. +pub trait WritableInner: ReadableInner { + /// Write guard type returned by `write` and `try_write` operations. + /// + /// In case the cell type doesn't provide a guard for operations, a + /// replacement that updates cell value on `Drop` should be used (such as + /// [`CellWriteGuard`]). + type WriteGuard<'a>: DerefMut + where + Self: 'a; -/// Trait that adds a method which mimics std `Result::map_err` on a Lock in -/// order to unify no_std and std environments. -/// -/// This is necessary as [spin::Mutex::lock] doesn't return a Result but a -/// [MutexGuard] directly. -pub(crate) trait MutexTypesafe { - fn lock_named(&self, source: LockSource) -> Result, crate::error::LockingError>; - fn try_lock_named( - &self, - source: LockSource, - ) -> Result, crate::error::LockingError>; -} -#[cfg(not(feature = "no_std"))] -impl MutexTypesafe for Mutex { - fn lock_named(&self, source: LockSource) -> Result, crate::error::LockingError> { - match self.lock() { - Ok(it) => Ok(it), - Err(_) => Err(LockingError::Poisoned { source }), - } - } - fn try_lock_named( + /// Error returned when calling [`write`](WritableInner::write) or + /// [`try_write`](WritableInner::try_write) fails. + #[cfg(not(any(feature = "error_in_core", feature = "std")))] + type MutBorrowError: Debug; + /// Error returned when calling [`write`](WritableInner::write) or + /// [`try_write`](WritableInner::try_write) fails. + #[cfg(any(feature = "error_in_core", feature = "std"))] + type MutBorrowError: Error; + + /// Returns the [write guard](WritableInner::WriteGuard) for `T` if the + /// wrapped writable can be written to, or an + /// [error](WritableInner::MutBorrowError) if that's not possible (usually + /// due to container being poisoned). + /// + /// This method will block for implementations of concurrent containers + /// (such as `Mutex`), for non-blocking access use + /// [`try_write`](WritableInner::try_write). + fn write(&self) + -> Result, Self::MutBorrowError>; + /// Returns the [write guard](WritableInner::WriteGuard) for `T` if the + /// wrapped writable can be written to, or an + /// [error](WritableInner::MutBorrowError) if it's being mutably accessed + /// from somewhere else or if write isn't possible (usually due to container + /// being poisoned). + fn try_write( &self, - source: LockSource, - ) -> Result, crate::error::LockingError> { - match self.try_lock() { - Ok(it) => Ok(it), - Err(std::sync::TryLockError::Poisoned(_)) => Err(LockingError::Poisoned { source }), - Err(std::sync::TryLockError::WouldBlock) => Err(LockingError::WouldBlock { source }), - } + ) -> Result, Self::MutBorrowError> { + self.write() } } -#[cfg(feature = "no_std")] -impl MutexTypesafe for Mutex { - fn lock_named(&self, _source: LockSource) -> Result, LockingError> { - Ok(self.lock()) + +impl ReadableInner for core::cell::Cell { + type ReadGuard<'a> = Owned + where + Self: 'a; + type BorrowError = Infallible; + + #[inline] + fn read(&self) -> Result, Self::BorrowError> { + Ok(Owned(self.get())) + } + #[inline] + fn try_read(&self) -> Result, Self::BorrowError> { + Ok(Owned(self.get())) } - fn try_lock_named( +} + +impl WritableInner for core::cell::Cell { + type WriteGuard<'a> = CellWriteGuard<'a, T> + where + Self: 'a; + type MutBorrowError = Infallible; + + #[inline] + fn write( &self, - source: LockSource, - ) -> Result, crate::error::LockingError> { - match self.try_lock() { - Some(it) => Ok(it), - None => Err(LockingError::WouldBlock { source }), - } + ) -> Result, Infallible> { + Ok(CellWriteGuard { parent: self, value: self.get() }) } } +impl ReadableInner for core::cell::RefCell { + type ReadGuard<'a> = core::cell::Ref<'a, T> + where + Self: 'a; + type BorrowError = core::cell::BorrowError; -pub(crate) trait RwLockTypesafe { - fn read_named(&self, source: LockSource) -> Result, LockingError>; - fn try_read_named(&self, source: LockSource) -> Result, LockingError>; - fn write_named(&self, source: LockSource) -> Result, LockingError>; - fn try_write_named(&self, source: LockSource) -> Result, LockingError>; + #[inline] + fn read(&self) -> Result, Self::BorrowError> { + Ok(self.borrow()) + } } -#[cfg(not(feature = "no_std"))] -impl RwLockTypesafe for RwLock { - fn read_named(&self, source: LockSource) -> Result, LockingError> { - match self.read() { - Ok(guard) => Ok(guard), - Err(_) => Err(LockingError::Poisoned { source }), - } + +impl WritableInner for core::cell::RefCell { + type WriteGuard<'a> = core::cell::RefMut<'a, T> + where + Self: 'a; + type MutBorrowError = core::cell::BorrowMutError; + + #[inline] + fn write( + &self, + ) -> Result, Self::MutBorrowError> { + Ok(self.borrow_mut()) + } + #[inline] + fn try_write( + &self, + ) -> Result, Self::MutBorrowError> { + self.try_borrow_mut() + } +} +impl ReadableInner for UnsafeCell { + type ReadGuard<'a> = &'a T + where + Self: 'a; + type BorrowError = Infallible; + #[inline] + fn read(&self) -> Result, Self::BorrowError> { + unsafe { Ok(&*self.get()) } + } +} +impl WritableInner for UnsafeCell { + type WriteGuard<'a> = &'a mut T + where + Self: 'a; + type MutBorrowError = Infallible; + + #[inline] + fn write( + &self, + ) -> Result, Self::MutBorrowError> { + unsafe { Ok(&mut *self.get())} } +} - fn try_read_named(&self, source: LockSource) -> Result, LockingError> { - match self.try_read() { - Ok(guard) => Ok(guard), - Err(std::sync::TryLockError::WouldBlock) => Err(LockingError::WouldBlock { source }), - Err(std::sync::TryLockError::Poisoned(_)) => Err(LockingError::Poisoned { source }), - } +/// A wrapper to allow using owned values in [unsafe implementation](ImplUnsafe) +/// state and [cells](core::cell::Cell) via [`Deref`]. +#[derive(Debug)] +#[repr(transparent)] +pub struct Owned(pub(crate) T); +impl From for Owned { + fn from(value: T) -> Self { + Owned(value) } +} +impl core::ops::Deref for Owned { + type Target = T; - fn write_named(&self, source: LockSource) -> Result, LockingError> { - match self.write() { - Ok(guard) => Ok(guard), - Err(_) => Err(LockingError::Poisoned { source }), - } + fn deref(&self) -> &Self::Target { + &self.0 } +} +impl DerefMut for Owned { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Unifies construction and dereferencing of smart pointers. +/// +/// Provides bounds and requirements for reference-like types (and [`Owned`]) to +/// be passed through [`ReadableInner`] and [`WritableInner`]. +pub trait Reference: Deref { + /// Constructs a new reference from `value`. + fn new(value: T) -> Self; +} - fn try_write_named(&self, source: LockSource) -> Result, LockingError> { - match self.try_write() { - Ok(guard) => Ok(guard), - Err(std::sync::TryLockError::WouldBlock) => Err(LockingError::WouldBlock { source }), - Err(std::sync::TryLockError::Poisoned(_)) => Err(LockingError::Poisoned { source }), - } +impl Reference for Rc { + fn new(value: T) -> Self { + Rc::new(value) } } -#[cfg(feature = "no_std")] -impl RwLockTypesafe for RwLock { - fn read_named(&self, _source: LockSource) -> Result, LockingError> { - Ok(self.read()) +#[cfg(feature = "unsafe_impl")] +impl Reference for Owned { + fn new(value: T) -> Self { + Owned(value) } +} + +/// Provides a guarded access to [`Cell`](core::cell::Cell) value for use via +/// [`ReadableInner`] and [`WritableInner`]. +pub struct CellWriteGuard<'a, T: Copy + 'a> { + parent: &'a core::cell::Cell, + value: T, +} - fn try_read_named(&self, source: LockSource) -> Result, LockingError> { - match self.try_read() { - Some(guard) => Ok(guard), - None => Err(LockingError::WouldBlock { source }), - } +impl<'a, T: Copy + 'a> Drop for CellWriteGuard<'a, T> { + #[inline] + fn drop(&mut self) { + self.parent.set(self.value); } +} + +impl<'a, T: Copy + 'a> Deref for CellWriteGuard<'a, T> { + type Target = T; - fn write_named(&self, _source: LockSource) -> Result, LockingError> { - Ok(self.write()) + #[inline] + fn deref(&self) -> &Self::Target { + &self.value } +} - fn try_write_named(&self, source: LockSource) -> Result, LockingError> { - match self.try_write() { - Some(guard) => Ok(guard), - None => Err(LockingError::WouldBlock { source }), - } +impl<'a, T: Copy + 'a> DerefMut for CellWriteGuard<'a, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value } } -#[cfg(not(feature = "debug"))] -pub trait DebugReq {} -#[cfg(not(feature = "debug"))] -impl DebugReq for T {} +pub(crate) mod sealed { + /// A marker trait to seal implementation of crate traits. + pub trait Sealed {} + impl Sealed for T {} +} +pub(crate) use sealed::Sealed; -#[cfg(feature = "debug")] -pub trait DebugReq: core::fmt::Debug {} -#[cfg(feature = "debug")] -impl DebugReq for T {} +/// Implementation details shared between memory container and reference types. +pub trait ImplDetails: Sized { + /// A reference to internal state. + type StateRef: Reference; -/// Size requirements for types pointed to by references -#[cfg(feature = "ptr_metadata")] -pub trait RefSizeReq {} -#[cfg(feature = "ptr_metadata")] -impl RefSizeReq for T {} + /// A wrapper for [`MemoryBase`]. + type Base: WritableInner + From; -/// 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 {} + /// A wrapper for [`SegmentTracker`]. + type Tracker: WritableInner + From; -/// Type requirements for values that can be stored. -pub trait StoreRequirements: 'static {} -impl StoreRequirements for T {} + /// Reference type returned when data is pushed into this implementation. + type PushResult: ConstructReference; -#[cfg(feature = "ptr_metadata")] -pub use core::marker::Unsize; -#[cfg(feature = "ptr_metadata")] -pub use core::ptr::{DynMetadata, Pointee}; + /// Indicates whether this implementation is allowed to grow. + const GROW: bool = true; +} + +/// Default implementation that uses [`std::cell::RefCell`] for storage and +/// [`Rc`] for state references. +#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ImplDefault; +impl ImplDetails for ImplDefault { + type StateRef = Rc; + type Base = core::cell::RefCell; + type Tracker = core::cell::RefCell; + type PushResult = EntryRef; +} + +/// Implementation which provides direct (unsafe) access to stored entries. +/// +/// Uses [`Cell`](std::cell::Cell) for storage and the stored data is [`Owned`] +/// by the caller. +#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg(feature = "unsafe_impl")] +pub struct ImplUnsafe; +#[cfg(feature = "unsafe_impl")] +impl ImplDetails for ImplUnsafe { + type StateRef = Owned; + type Base = core::cell::Cell; + type Tracker = UnsafeCell; + type PushResult = *mut T; + + const GROW: bool = false; +} + +/// Represents contigous memory that uses smart references. +pub trait ImplReferencing: ImplDetails { + /// The type handling concurrent mutable access exclusion. + type BorrowLock: WritableInner; + + /// A shared reference to data. + type SharedRef: Reference + Clone; -/// Returns [`Pointee`] metadata for provided pair of struct `S` and some -/// unsized type (e.g. a trait) `T`. + /// Marks reference state as no longer being borrowed. + fn unborrow_ref(_state: &Self::SharedRef>) {} +} + +impl ImplReferencing for ImplDefault { + type BorrowLock = core::cell::Cell; + + type SharedRef = Rc; + + fn unborrow_ref(state: &Self::SharedRef>) { + let next = match state.borrow_kind.get() { + BorrowState::Read(count) => BorrowState::Read(count - 1), + BorrowState::Write => BorrowState::Read(0), + }; + state.borrow_kind.set(next) + } +} + +/// Returns [`Pointee`](core::ptr::Pointee) metadata for provided pair of struct +/// `S` and some unsized type (e.g. a trait) `T`. /// -/// This metadata is usually a pointer to vtable of `T` implementation for -/// `S`, but can be something else and the value is considered internal to -/// the compiler. +/// This metadata is usually a pointer to vtable of `T` implementation for `S`, +/// but can be something else and the value is considered internal to the +/// compiler. #[cfg(feature = "ptr_metadata")] -pub const fn static_metadata() -> ::Metadata +pub const fn static_metadata() -> ::Metadata where - S: Unsize, + S: core::marker::Unsize, { let (_, metadata) = (core::ptr::NonNull::::dangling().as_ptr() as *const T).to_raw_parts(); metadata @@ -191,9 +374,39 @@ where pub(crate) type DropFn = fn(*mut ()); pub(crate) const fn drop_fn() -> fn(*mut ()) { - if core::mem::needs_drop::() { - |ptr: *mut ()| unsafe { core::ptr::drop_in_place(ptr as *mut T) } - } else { - |_: *mut ()| {} + |ptr: *mut ()| unsafe { core::ptr::drop_in_place(ptr as *mut T) } +} + +pub(crate) const fn is_layout_valid(size: usize, align: usize) -> bool { + if !align.is_power_of_two() { + return false; + }; + + size <= isize::MAX as usize - (align - 1) +} + +/// Trait that unifies passing either a [`Layout`] directly or a `&T` where `T: +/// Sized` as an argument to a function which requires a type layout. +/// +/// This trait is sealed to prevent users from implementing it for arbitrary +/// types which could voilate its purpose. +pub trait HasLayout: Sealed { + /// Returns a layout of the reference or a copy of the layout. + fn as_layout(&self) -> Layout; +} + +/// Base implementation, allowing direct use of `Layout`. +impl HasLayout for Layout { + #[inline] + fn as_layout(&self) -> Layout { + *self + } +} + +/// Layout can be inferred from any reference to a [`Sized`] type. +impl HasLayout for &T { + #[inline] + fn as_layout(&self) -> Layout { + Layout::new::() } }