From b8e07a22d094426ba2b1a39a325c6f507e47ce02 Mon Sep 17 00:00:00 2001 From: CrazyboyQCD Date: Thu, 12 Jun 2025 22:37:17 +0200 Subject: [PATCH] Squashed commit of the following: commit 790f7a799fdb05714f19354165d13f4072fcb862 Author: Marijn Suijten Date: Thu Jun 12 22:13:17 2025 +0200 CI: Ensure MSRV of `std` and `no_std` are tested independently commit fdf945c4c04dbe407f75d36f0c9f7c0ad922b3bd Author: Marijn Suijten Date: Thu Jun 12 22:10:10 2025 +0200 Test `visualizer` feature with `std` requirement separately from `no_std` commit dab3bb6d4ee3ccfc45bb384e0c468e44399df2c6 Author: Marijn Suijten Date: Thu Jun 12 22:07:26 2025 +0200 README: Remove manual reflows commit 6fd4a3a49acacf3f0156af6eda49b499de9d4658 Author: Marijn Suijten Date: Thu Jun 12 21:43:44 2025 +0200 Revert "update `CI`" This reverts commit e896fab066015af4531e11814d06ae533b5a889c. commit c7e3c3cd68ddfa19b385aa89eb893ac3654e704e Merge: c3e2d51 0c5fc95 Author: Marijn Suijten Date: Thu Jun 12 21:42:56 2025 +0200 Merge remote-tracking branch 'origin/main' into no-std-support commit c3e2d51d34747364ec5f57ceeadd6a9c6806ff47 Author: Marijn Suijten Date: Thu Jun 12 21:28:57 2025 +0200 Revert `version = ""` addition where not needed commit 477b6c585ca80b72bf24040c89fd409bc9f3c358 Author: Marijn Suijten Date: Thu Jun 12 21:36:43 2025 +0200 Fix greedy match that would truncate a trailing `, default-features ... "` commit b37c69f30b93e5e6b1af4e111114f467861cf8ac Author: Marijn Suijten Date: Thu Jun 12 21:32:39 2025 +0200 release: Fix unescaped `{` (otherwise treated as repetition quantifier) commit f93b1da112ea2cc313efa2401b4857d365231c53 Author: CrazyboyQCD Date: Thu Apr 17 09:04:22 2025 +0800 update `README` commit b0b38a988c0f1cd7da65fed002a31680354424e3 Author: CrazyboyQCD Date: Thu Apr 17 08:49:37 2025 +0800 update `release.toml` commit d27a6271654d4641a4365d624e4982b343a03f69 Merge: d73de57 642dc91 Author: CrazyboyQCD <53971641+CrazyboyQCD@users.noreply.github.com> Date: Thu Apr 3 09:05:10 2025 +0800 Merge branch 'main' into no-std-support commit d73de578d55f57d32f0bedd6082c099179bbcc1c Author: CrazyboyQCD Date: Thu Apr 3 08:59:50 2025 +0800 fix `CI` commit c4da9ba8ba5d05ad5260338058d662a4fba3ca7b Author: CrazyboyQCD Date: Thu Apr 3 08:42:56 2025 +0800 add `no_std` content in `README` commit 8bdedba86d3f903158454afc6d2ac4f05075bdac Author: CrazyboyQCD Date: Wed Apr 2 17:46:51 2025 +0800 fix lint commit e896fab066015af4531e11814d06ae533b5a889c Author: CrazyboyQCD Date: Wed Apr 2 17:44:42 2025 +0800 update `CI` commit 0ab0c8892d2bc65e990a06b7a018f8b6403a40a6 Author: CrazyboyQCD Date: Wed Apr 2 17:40:17 2025 +0800 fix lint commit 10d0742c45c0fdfd2e85a547807ee204abde214b Author: CrazyboyQCD Date: Wed Apr 2 17:35:05 2025 +0800 update missed `std` usages commit 66dbf08256adc7dbd40f59b212472bfde412b005 Author: CrazyboyQCD Date: Wed Apr 2 17:21:23 2025 +0800 add todo related with storing of `format_args!` outside `format!` commit 3d962a4f1b5a7521bebb58e661322167bb40d29e Author: CrazyboyQCD Date: Wed Apr 2 09:06:09 2025 +0800 keep style of compile error the same commit 412359315340caf55a24628fa551103557013f56 Author: CrazyboyQCD Date: Wed Apr 2 09:03:37 2025 +0800 update missed `std` usages commit 541e814784c49fa6c429341d09d3cdd84a0b7e5e Author: Marijn Suijten Date: Tue Apr 1 12:54:04 2025 +0200 CI: Simplify and complete `matrix` setup commit 3e453edf7478fd39418f5298997cda17d74d6d03 Author: CrazyboyQCD Date: Tue Apr 1 17:59:54 2025 +0800 emit compile error when none of `std` and `hashbrown` is enabled commit 694b6ff54c5b6d8e09cd5c2558aa7821d80e6823 Author: CrazyboyQCD Date: Tue Apr 1 17:24:56 2025 +0800 update `CI` with `no_std` commit 742a2ce6e19a5caa49000ef9c75773189867c5c5 Author: CrazyboyQCD Date: Tue Apr 1 17:23:55 2025 +0800 document the `hashbrown` feature in `Cargo.toml` commit 39abe5916905bdf9606b0a8cda0a49503065e4cb Author: CrazyboyQCD Date: Thu Feb 27 09:41:57 2025 +0800 update `CI` for `std` environment commit 578ada0c886489c7053b43c1dd6c03b34bda7d83 Author: CrazyboyQCD Date: Thu Feb 27 09:37:05 2025 +0800 prefer using `hashbrown`'s collections when `std` and `hashbrown` are both enabled commit 457411a2b49b857bd57e9fc5fcd251c11905b97d Author: CrazyboyQCD Date: Thu Feb 27 09:30:58 2025 +0800 simplify memory leak logging commit 3e00364b8ab621ee796470cae9bb46b186371cca Author: CrazyboyQCD Date: Thu Feb 27 09:14:16 2025 +0800 remove empty line and reorder import commit d59fe2692e0f4f656347656470eb87a41a966582 Author: CrazyboyQCD Date: Thu Feb 27 09:09:24 2025 +0800 restore trailing comma commit f2a6bfb2935aee04e80cd737eeb0f5366963e2eb Author: CrazyboyQCD Date: Thu Feb 13 10:50:51 2025 +0800 make `std` feature conflict with `hashbrown` in compile error and gate `free_list_allocator` by feature commit 4c45a0196a978d4d109b519272cfae6f25f980cf Author: CrazyboyQCD Date: Thu Feb 13 10:27:19 2025 +0800 reorder mod in allocator module commit d1caf98f7185aa83b3449479851c899451ac3bf7 Author: CrazyboyQCD Date: Thu Feb 13 10:17:14 2025 +0800 format backtrace ahead by feature instead of duplicated log statements commit a4d3463a66cd93d642b10e5ab2c013951e864a02 Author: CrazyboyQCD Date: Thu Feb 13 09:54:11 2025 +0800 reorder imports and mods commit 51e8723dca8401f1e3dfd6eecdb377a05b705b6c Author: CrazyboyQCD Date: Thu Feb 13 09:46:52 2025 +0800 add non_exhaustive for `AllocatorDebugSettings` for backward compatible commit 3cee15fa3962acb573bf60919ef37c6fb4c54e2a Author: CrazyboyQCD Date: Thu Feb 13 09:43:13 2025 +0800 fix typos in deps commit 4d7b66805c4ab071303ca2e829701c425aa44a3c Author: CrazyboyQCD Date: Thu Feb 13 09:43:01 2025 +0800 revert formatting of `Cargo.toml` commit 48be63c8ca0a4feeb0c9885d3c604830a07e41cd Author: CrazyboyQCD Date: Thu Feb 13 09:35:20 2025 +0800 add clippy lints for `no_std` maintenance commit eb9e04c5473c5fc24bc0d8613fd02738dc63ad9f Author: CrazyboyQCD Date: Wed Feb 12 16:20:03 2025 +0800 replace `std::*` with `core::*` or `alloc::*` commit e3ca7fb0e33a7932b6bd1ad5e36174188fc4f4a3 Author: CrazyboyQCD Date: Wed Feb 12 16:19:21 2025 +0800 remove `std::backtrace::Backtrace` in `no_std` commit 8f853b06f4113e88bbe81d1c7e0c2d13f5e3c970 Author: CrazyboyQCD Date: Wed Feb 12 16:03:09 2025 +0800 add `no_std` feature compile error commit fbc57a39c870315bfe1fc0af2498fc38938294f2 Author: CrazyboyQCD Date: Wed Feb 12 16:02:29 2025 +0800 add `default-feature = false` to some deps, `std` feature and `hashbrown` --- .github/workflows/ci.yml | 61 ++++++++----- Cargo.toml | 12 ++- README.md | 30 ++++++- README.tpl | 29 ++++++- release.toml | 6 +- .../dedicated_block_allocator/mod.rs | 56 ++++++++---- src/allocator/free_list_allocator/mod.rs | 87 ++++++++++++------- src/allocator/mod.rs | 19 ++-- src/d3d12/mod.rs | 33 +++++-- src/lib.rs | 21 +++++ src/metal/mod.rs | 28 ++++-- src/result.rs | 4 +- src/visualizer/allocation_reports.rs | 4 +- src/vulkan/mod.rs | 35 +++++--- 14 files changed, 313 insertions(+), 112 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8435e45b..309e1a04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,44 +4,56 @@ name: CI jobs: check_msrv: - name: Check MSRV (1.71.0) + name: Check MSRV strategy: matrix: - include: + target: - os: ubuntu-latest - features: vulkan + backend: vulkan - os: windows-latest - features: vulkan,d3d12 + backend: vulkan,d3d12 - os: macos-latest - features: vulkan,metal - runs-on: ${{ matrix.os }} + backend: vulkan,metal + version: + - msrv: 1.71.0 + features: std + - msrv: 1.81.0 + features: hashbrown + runs-on: ${{ matrix.target.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - name: Generate lockfile with minimal dependency versions run: cargo +nightly generate-lockfile -Zminimal-versions - - uses: dtolnay/rust-toolchain@1.71.0 - # Note that examples are extempt from the MSRV check, so that they can use newer Rust features - - run: cargo check --workspace --features ${{ matrix.features }} --no-default-features + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.version.msrv }} + # Note that examples are exempt from the MSRV check, so that they can use newer Rust features + - run: cargo check --workspace --no-default-features --features ${{ matrix.target.backend }},${{ matrix.version.features }} test: name: Test Suite strategy: matrix: - include: + target: - os: ubuntu-latest - features: vulkan,visualizer + backend: vulkan - os: windows-latest - features: vulkan,visualizer,d3d12 + backend: vulkan,d3d12 - os: macos-latest - features: vulkan,visualizer,metal - runs-on: ${{ matrix.os }} + backend: vulkan,metal + features: + - hashbrown + - std + - hashbrown,std + - visualizer,std + runs-on: ${{ matrix.target.os }} steps: - uses: actions/checkout@v4 - name: Cargo test all targets - run: cargo test --workspace --all-targets --features ${{ matrix.features }} --no-default-features + run: cargo test --workspace --all-targets --no-default-features --features ${{ matrix.target.backend }},${{ matrix.features }} - name: Cargo test docs - run: cargo test --workspace --doc --features ${{ matrix.features }} --no-default-features + run: cargo test --workspace --doc --no-default-features --features ${{ matrix.target.backend }},${{ matrix.features }} fmt: name: Rustfmt @@ -55,18 +67,23 @@ jobs: name: Clippy strategy: matrix: - include: + target: - os: ubuntu-latest - features: vulkan,visualizer + backend: vulkan - os: windows-latest - features: vulkan,visualizer,d3d12 + backend: vulkan,d3d12 - os: macos-latest - features: vulkan,visualizer,metal - runs-on: ${{ matrix.os }} + backend: vulkan,metal + features: + - hashbrown + - std + - hashbrown,std + - visualizer,std + runs-on: ${{ matrix.target.os }} steps: - uses: actions/checkout@v4 - name: Cargo clippy - run: cargo clippy --workspace --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings + run: cargo clippy --workspace --all-targets --no-default-features --features ${{ matrix.target.backend }},${{ matrix.features }} -- -D warnings doc: name: Build documentation diff --git a/Cargo.toml b/Cargo.toml index 399cbcec..33ddad8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,15 +23,16 @@ include = [ all-features = true [dependencies] -log = "0.4" -thiserror = "1.0" -presser = { version = "0.3" } +log = { version = "0.4", default-features = false } +thiserror = { version = "2.0", default-features = false } +presser = { version = "0.3", default-features = false } # Only needed for Vulkan. Disable all default features as good practice, # such as the ability to link/load a Vulkan library. ash = { version = "0.38", optional = true, default-features = false, features = ["debug"] } # Only needed for visualizer. egui = { version = ">=0.24, <=0.27", optional = true, default-features = false } egui_extras = { version = ">=0.24, <=0.27", optional = true, default-features = false } +hashbrown = { version = "0.15.2", optional = true } [target.'cfg(target_vendor = "apple")'.dependencies] objc2 = { version = "0.6", default-features = false, optional = true } @@ -88,9 +89,12 @@ name = "metal-buffer" required-features = ["metal"] [features] +std = ["presser/std"] visualizer = ["dep:egui", "dep:egui_extras"] vulkan = ["dep:ash"] d3d12 = ["dep:windows"] metal = ["dep:objc2", "dep:objc2-metal", "dep:objc2-foundation"] +# Enables the FreeListAllocator when `std` is not enabled by using the `hashbrown` crate +hashbrown = ["dep:hashbrown"] -default = ["d3d12", "vulkan", "metal"] +default = ["std", "d3d12", "vulkan", "metal"] diff --git a/README.md b/README.md index b1dec61c..b2d4a7f1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ let mut allocator = Allocator::new(&AllocatorCreateDesc { ``` ## Simple Metal allocation example + ```rust use gpu_allocator::metal::*; use gpu_allocator::MemoryLocation; @@ -161,9 +162,36 @@ drop(resource); allocator.free(&allocation).unwrap(); ``` +## `no_std` support + +`no_std` support can be enabled by compiling with `--no-default-features` to disable `std` support and `--features hashbrown` for `Hash` collections that are only defined in `std` for internal usages in crate. For example: + +```toml +[dependencies] +gpu-allocator = { version = "0.27.0", default-features = false, features = ["hashbrown", "other features"] } +``` + +To support both `std` and `no_std` builds in your project, use the following in your `Cargo.toml`: + +```toml +[features] +default = ["std", "other features"] + +std = ["gpu-allocator/std"] +hashbrown = ["gpu-allocator/hashbrown"] +other_features = [] + +[dependencies] +gpu-allocator = { version = "0.27.0", default-features = false } +``` + ## Minimum Supported Rust Version -The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust 1.71. Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. +The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust **1.71**. + +The `no_std` support requires Rust **1.81** or higher because `no_std` support of dependency `thiserror` requires `core::error::Error` which is stabilized in **1.81**. + +Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. ## License diff --git a/README.tpl b/README.tpl index dd3fd225..f437b990 100644 --- a/README.tpl +++ b/README.tpl @@ -19,9 +19,36 @@ gpu-allocator = "0.27.0" {{readme}} +## `no_std` support + +`no_std` support can be enabled by compiling with `--no-default-features` to disable `std` support and `--features hashbrown` for `Hash` collections that are only defined in `std` for internal usages in crate. For example: + +```toml +[dependencies] +gpu-allocator = { version = "0.27.0", default-features = false, features = ["hashbrown", "other features"] } +``` + +To support both `std` and `no_std` builds in your project, use the following in your `Cargo.toml`: + +```toml +[features] +default = ["std", "other features"] + +std = ["gpu-allocator/std"] +hashbrown = ["gpu-allocator/hashbrown"] +other_features = [] + +[dependencies] +gpu-allocator = { version = "0.27.0", default-features = false } +``` + ## Minimum Supported Rust Version -The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust 1.71. Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. +The MSRV for this crate and the `vulkan`, `d3d12` and `metal` features is Rust **1.71**. + +The `no_std` support requires Rust **1.81** or higher because `no_std` support of dependency `thiserror` requires `core::error::Error` which is stabilized in **1.81**. + +Any other features such as the `visualizer` (with all the `egui` dependencies) may have a higher requirement and are not tested in our CI. ## License diff --git a/release.toml b/release.toml index 619bc93a..abb7ce6f 100644 --- a/release.toml +++ b/release.toml @@ -6,6 +6,8 @@ sign-tag = true publish = false pre-release-replacements = [ - { file = "README.md", search = "gpu-allocator = .*", replace = "{{crate_name}} = \"{{version}}\"" }, - { file = "README.tpl", search = "gpu-allocator = .*", replace = "{{crate_name}} = \"{{version}}\"" }, + { file = "README.md", search = "gpu-allocator = \".*\"", replace = "{{crate_name}} = \"{{version}}\"" }, + { file = "README.tpl", search = "gpu-allocator = \".*\"", replace = "{{crate_name}} = \"{{version}}\"" }, + { file = "README.md", search = "gpu-allocator = \\{ version = \".*?\"", replace = "{{crate_name}} = { version = \"{{version}}\"" }, + { file = "README.tpl", search = "gpu-allocator = \\{ version = \".*?\"", replace = "{{crate_name}} = { version = \"{{version}}\"" }, ] diff --git a/src/allocator/dedicated_block_allocator/mod.rs b/src/allocator/dedicated_block_allocator/mod.rs index 746b4dcd..02bb13df 100644 --- a/src/allocator/dedicated_block_allocator/mod.rs +++ b/src/allocator/dedicated_block_allocator/mod.rs @@ -1,12 +1,19 @@ #![deny(unsafe_code, clippy::unwrap_used)] +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; + +use log::{log, Level}; #[cfg(feature = "visualizer")] pub(crate) mod visualizer; -use std::{backtrace::Backtrace, sync::Arc}; - -use log::{log, Level}; - use super::{AllocationReport, AllocationType, SubAllocator, SubAllocatorBase}; use crate::{AllocationError, Result}; @@ -16,6 +23,7 @@ pub(crate) struct DedicatedBlockAllocator { allocated: u64, /// Only used if [`crate::AllocatorDebugSettings::store_stack_traces`] is [`true`] name: Option, + #[cfg(feature = "std")] backtrace: Arc, } @@ -25,6 +33,7 @@ impl DedicatedBlockAllocator { size, allocated: 0, name: None, + #[cfg(feature = "std")] backtrace: Arc::new(Backtrace::disabled()), } } @@ -39,8 +48,8 @@ impl SubAllocator for DedicatedBlockAllocator { _allocation_type: AllocationType, _granularity: u64, name: &str, - backtrace: Arc, - ) -> Result<(u64, std::num::NonZeroU64)> { + #[cfg(feature = "std")] backtrace: Arc, + ) -> Result<(u64, core::num::NonZeroU64)> { if self.allocated != 0 { return Err(AllocationError::OutOfMemory); } @@ -53,15 +62,18 @@ impl SubAllocator for DedicatedBlockAllocator { self.allocated = size; self.name = Some(name.to_string()); - self.backtrace = backtrace; + #[cfg(feature = "std")] + { + self.backtrace = backtrace; + } #[allow(clippy::unwrap_used)] - let dummy_id = std::num::NonZeroU64::new(1).unwrap(); + let dummy_id = core::num::NonZeroU64::new(1).unwrap(); Ok((0, dummy_id)) } - fn free(&mut self, chunk_id: Option) -> Result<()> { - if chunk_id != std::num::NonZeroU64::new(1) { + fn free(&mut self, chunk_id: Option) -> Result<()> { + if chunk_id != core::num::NonZeroU64::new(1) { Err(AllocationError::Internal("Chunk ID must be 1.".into())) } else { self.allocated = 0; @@ -71,10 +83,10 @@ impl SubAllocator for DedicatedBlockAllocator { fn rename_allocation( &mut self, - chunk_id: Option, + chunk_id: Option, name: &str, ) -> Result<()> { - if chunk_id != std::num::NonZeroU64::new(1) { + if chunk_id != core::num::NonZeroU64::new(1) { Err(AllocationError::Internal("Chunk ID must be 1.".into())) } else { self.name = Some(name.into()); @@ -90,6 +102,20 @@ impl SubAllocator for DedicatedBlockAllocator { ) { let empty = "".to_string(); let name = self.name.as_ref().unwrap_or(&empty); + let backtrace_info; + #[cfg(feature = "std")] + { + // TODO: Allocation could be avoided here if https://github.com/rust-lang/rust/pull/139135 is merged and stabilized. + backtrace_info = format!( + ", + backtrace: {}", + self.backtrace + ) + } + #[cfg(not(feature = "std"))] + { + backtrace_info = "" + } log!( log_level, @@ -98,16 +124,14 @@ impl SubAllocator for DedicatedBlockAllocator { memory block: {} dedicated allocation: {{ size: 0x{:x}, - name: {}, - backtrace: {} + name: {}{backtrace_info} }} }}"#, memory_type_index, memory_block_index, self.size, name, - self.backtrace - ) + ); } fn report_allocations(&self) -> Vec { diff --git a/src/allocator/free_list_allocator/mod.rs b/src/allocator/free_list_allocator/mod.rs index d7cde2e8..db969d68 100644 --- a/src/allocator/free_list_allocator/mod.rs +++ b/src/allocator/free_list_allocator/mod.rs @@ -1,16 +1,23 @@ #![deny(unsafe_code, clippy::unwrap_used)] - -#[cfg(feature = "visualizer")] -pub(crate) mod visualizer; - -use std::{ - backtrace::Backtrace, - collections::{HashMap, HashSet}, - sync::Arc, +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, }; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; +#[cfg(all(feature = "std", not(feature = "hashbrown")))] +use std::collections::{HashMap, HashSet}; +#[cfg(feature = "hashbrown")] +use hashbrown::{HashMap, HashSet}; use log::{log, Level}; +#[cfg(feature = "visualizer")] +pub(crate) mod visualizer; + use super::{AllocationReport, AllocationType, SubAllocator, SubAllocatorBase}; use crate::{AllocationError, Result}; @@ -26,15 +33,16 @@ fn align_up(val: u64, alignment: u64) -> u64 { #[derive(Debug)] pub(crate) struct MemoryChunk { - pub(crate) chunk_id: std::num::NonZeroU64, + pub(crate) chunk_id: core::num::NonZeroU64, pub(crate) size: u64, pub(crate) offset: u64, pub(crate) allocation_type: AllocationType, pub(crate) name: Option, /// Only used if [`crate::AllocatorDebugSettings::store_stack_traces`] is [`true`] + #[cfg(feature = "std")] pub(crate) backtrace: Arc, - next: Option, - prev: Option, + next: Option, + prev: Option, } #[derive(Debug)] @@ -42,8 +50,8 @@ pub(crate) struct FreeListAllocator { size: u64, allocated: u64, pub(crate) chunk_id_counter: u64, - pub(crate) chunks: HashMap, - free_chunks: HashSet, + pub(crate) chunks: HashMap, + free_chunks: HashSet, } /// Test if two suballocations will overlap the same page. @@ -68,7 +76,7 @@ fn has_granularity_conflict(type0: AllocationType, type1: AllocationType) -> boo impl FreeListAllocator { pub(crate) fn new(size: u64) -> Self { #[allow(clippy::unwrap_used)] - let initial_chunk_id = std::num::NonZeroU64::new(1).unwrap(); + let initial_chunk_id = core::num::NonZeroU64::new(1).unwrap(); let mut chunks = HashMap::default(); chunks.insert( @@ -79,6 +87,7 @@ impl FreeListAllocator { offset: 0, allocation_type: AllocationType::Free, name: None, + #[cfg(feature = "std")] backtrace: Arc::new(Backtrace::disabled()), prev: None, next: None, @@ -100,7 +109,7 @@ impl FreeListAllocator { } /// Generates a new unique chunk ID - fn get_new_chunk_id(&mut self) -> Result { + fn get_new_chunk_id(&mut self) -> Result { if self.chunk_id_counter == u64::MAX { // End of chunk id counter reached, no more allocations are possible. return Err(AllocationError::OutOfMemory); @@ -108,19 +117,19 @@ impl FreeListAllocator { let id = self.chunk_id_counter; self.chunk_id_counter += 1; - std::num::NonZeroU64::new(id).ok_or_else(|| { + core::num::NonZeroU64::new(id).ok_or_else(|| { AllocationError::Internal("New chunk id was 0, which is not allowed.".into()) }) } /// Finds the specified `chunk_id` in the list of free chunks and removes if from the list - fn remove_id_from_free_list(&mut self, chunk_id: std::num::NonZeroU64) { + fn remove_id_from_free_list(&mut self, chunk_id: core::num::NonZeroU64) { self.free_chunks.remove(&chunk_id); } /// Merges two adjacent chunks. Right chunk will be merged into the left chunk fn merge_free_chunks( &mut self, - chunk_left: std::num::NonZeroU64, - chunk_right: std::num::NonZeroU64, + chunk_left: core::num::NonZeroU64, + chunk_right: core::num::NonZeroU64, ) -> Result<()> { // Gather data from right chunk and remove it let (right_size, right_next) = { @@ -162,14 +171,14 @@ impl SubAllocator for FreeListAllocator { allocation_type: AllocationType, granularity: u64, name: &str, - backtrace: Arc, - ) -> Result<(u64, std::num::NonZeroU64)> { + #[cfg(feature = "std")] backtrace: Arc, + ) -> Result<(u64, core::num::NonZeroU64)> { let free_size = self.size - self.allocated; if size > free_size { return Err(AllocationError::OutOfMemory); } - let mut best_fit_id: Option = None; + let mut best_fit_id: Option = None; let mut best_offset = 0u64; let mut best_aligned_size = 0u64; let mut best_chunk_size = 0u64; @@ -249,6 +258,7 @@ impl SubAllocator for FreeListAllocator { offset: free_chunk.offset, allocation_type, name: Some(name.to_string()), + #[cfg(feature = "std")] backtrace, prev: free_chunk.prev, next: Some(first_fit_id), @@ -278,7 +288,10 @@ impl SubAllocator for FreeListAllocator { chunk.allocation_type = allocation_type; chunk.name = Some(name.to_string()); - chunk.backtrace = backtrace; + #[cfg(feature = "std")] + { + chunk.backtrace = backtrace; + } self.remove_id_from_free_list(first_fit_id); @@ -290,7 +303,7 @@ impl SubAllocator for FreeListAllocator { Ok((best_offset, chunk_id)) } - fn free(&mut self, chunk_id: Option) -> Result<()> { + fn free(&mut self, chunk_id: Option) -> Result<()> { let chunk_id = chunk_id .ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?; @@ -302,7 +315,10 @@ impl SubAllocator for FreeListAllocator { })?; chunk.allocation_type = AllocationType::Free; chunk.name = None; - chunk.backtrace = Arc::new(Backtrace::disabled()); + #[cfg(feature = "std")] + { + chunk.backtrace = Arc::new(Backtrace::disabled()); + } self.allocated -= chunk.size; @@ -327,7 +343,7 @@ impl SubAllocator for FreeListAllocator { fn rename_allocation( &mut self, - chunk_id: Option, + chunk_id: Option, name: &str, ) -> Result<()> { let chunk_id = chunk_id @@ -362,7 +378,20 @@ impl SubAllocator for FreeListAllocator { } let empty = "".to_string(); let name = chunk.name.as_ref().unwrap_or(&empty); - + let backtrace_info; + #[cfg(feature = "std")] + { + // TODO: Allocation could be avoided here if https://github.com/rust-lang/rust/pull/139135 is merged and stabilized. + backtrace_info = format!( + ", + backtrace: {}", + chunk.backtrace + ) + } + #[cfg(not(feature = "std"))] + { + backtrace_info = "" + } log!( log_level, r#"leak detected: {{ @@ -373,8 +402,7 @@ impl SubAllocator for FreeListAllocator { size: 0x{:x}, offset: 0x{:x}, allocation_type: {:?}, - name: {}, - backtrace: {} + name: {}{backtrace_info} }} }}"#, memory_type_index, @@ -384,7 +412,6 @@ impl SubAllocator for FreeListAllocator { chunk.offset, chunk.allocation_type, name, - chunk.backtrace ); } } diff --git a/src/allocator/mod.rs b/src/allocator/mod.rs index ca2ead4a..523ebcbd 100644 --- a/src/allocator/mod.rs +++ b/src/allocator/mod.rs @@ -1,4 +1,9 @@ -use std::{backtrace::Backtrace, fmt, ops::Range, sync::Arc}; +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{fmt, string::String, vec::Vec}; +use core::ops::Range; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; use log::*; @@ -79,7 +84,7 @@ impl fmt::Debug for AllocationReport { impl fmt::Debug for AllocatorReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut allocations = self.allocations.clone(); - allocations.sort_by_key(|alloc| std::cmp::Reverse(alloc.size)); + allocations.sort_by_key(|alloc| core::cmp::Reverse(alloc.size)); let max_num_allocations_to_print = f.precision().unwrap_or(usize::MAX); allocations.truncate(max_num_allocations_to_print); @@ -87,7 +92,7 @@ impl fmt::Debug for AllocatorReport { f.debug_struct("AllocatorReport") .field( "summary", - &std::format_args!( + &core::format_args!( "{} / {}", fmt_bytes(self.total_allocated_bytes), fmt_bytes(self.total_capacity_bytes) @@ -113,14 +118,14 @@ pub(crate) trait SubAllocator: SubAllocatorBase + fmt::Debug + Sync + Send { allocation_type: AllocationType, granularity: u64, name: &str, - backtrace: Arc, - ) -> Result<(u64, std::num::NonZeroU64)>; + #[cfg(feature = "std")] backtrace: Arc, + ) -> Result<(u64, core::num::NonZeroU64)>; - fn free(&mut self, chunk_id: Option) -> Result<()>; + fn free(&mut self, chunk_id: Option) -> Result<()>; fn rename_allocation( &mut self, - chunk_id: Option, + chunk_id: Option, name: &str, ) -> Result<()>; diff --git a/src/d3d12/mod.rs b/src/d3d12/mod.rs index 294e85e0..9cb9a0ff 100644 --- a/src/d3d12/mod.rs +++ b/src/d3d12/mod.rs @@ -1,10 +1,13 @@ -use std::{ - backtrace::Backtrace, +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{boxed::Box, string::String, vec::Vec}; +use core::{ fmt, // TODO: Remove when bumping MSRV to 1.80 mem::size_of_val, - sync::Arc, }; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; use log::{debug, warn, Level}; use windows::Win32::{ @@ -119,7 +122,7 @@ impl<'a> AllocationCreateDesc<'a> { // SAFETY: `device` is a valid device handle, and no arguments (like pointers) are passed // that could induce UB. let allocation_info = - unsafe { device.GetResourceAllocationInfo(0, std::slice::from_ref(desc)) }; + unsafe { device.GetResourceAllocationInfo(0, core::slice::from_ref(desc)) }; let resource_category: ResourceCategory = desc.into(); AllocationCreateDesc { @@ -144,7 +147,7 @@ pub enum ID3D12DeviceVersion { Device12(ID3D12Device12), } -impl std::ops::Deref for ID3D12DeviceVersion { +impl core::ops::Deref for ID3D12DeviceVersion { type Target = ID3D12Device; fn deref(&self) -> &Self::Target { @@ -209,7 +212,7 @@ pub struct CommittedAllocationStatistics { #[derive(Debug)] pub struct Allocation { - chunk_id: Option, + chunk_id: Option, offset: u64, size: u64, memory_block_index: usize, @@ -220,7 +223,7 @@ pub struct Allocation { } impl Allocation { - pub fn chunk_id(&self) -> Option { + pub fn chunk_id(&self) -> Option { self.chunk_id } @@ -328,7 +331,7 @@ impl MemoryType { &mut self, device: &ID3D12DeviceVersion, desc: &AllocationCreateDesc<'_>, - backtrace: Arc, + #[cfg(feature = "std")] backtrace: Arc, allocation_sizes: &AllocationSizes, ) -> Result { let allocation_type = AllocationType::Linear; @@ -371,6 +374,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace, )?; @@ -394,6 +398,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace.clone(), ); @@ -444,6 +449,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace, ); let (offset, chunk_id) = match allocation { @@ -615,6 +621,7 @@ impl Allocator { let size = desc.size; let alignment = desc.alignment; + #[cfg(feature = "std")] let backtrace = Arc::new(if self.debug_settings.store_stack_traces { Backtrace::force_capture() } else { @@ -626,6 +633,7 @@ impl Allocator { "Allocating `{}` of {} bytes with an alignment of {}.", &desc.name, size, alignment ); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Allocation stack trace: {backtrace}"); @@ -651,13 +659,20 @@ impl Allocator { }) .ok_or(AllocationError::NoCompatibleMemoryTypeFound)?; - memory_type.allocate(&self.device, desc, backtrace, &self.allocation_sizes) + memory_type.allocate( + &self.device, + desc, + #[cfg(feature = "std")] + backtrace, + &self.allocation_sizes, + ) } pub fn free(&mut self, allocation: Allocation) -> Result<()> { if self.debug_settings.log_frees { let name = allocation.name.as_deref().unwrap_or(""); debug!("Freeing `{name}`."); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Free stack trace: {backtrace}"); diff --git a/src/lib.rs b/src/lib.rs index 2b59347f..0906aabf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,6 +168,7 @@ //! ``` //! //! # Simple Metal allocation example +//! //! ```no_run //! # #[cfg(feature = "metal")] //! # fn main() { @@ -207,6 +208,21 @@ //! # fn main() {} //! ``` #![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)] +#![warn( + clippy::alloc_instead_of_core, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core +)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[macro_use] +extern crate alloc; + +#[cfg(all(not(feature = "std"), feature = "visualizer"))] +compile_error!("Cannot enable `visualizer` feature in `no_std` environment."); + +#[cfg(not(any(feature = "std", feature = "hashbrown")))] +compile_error!("Either `std` or `hashbrown` feature must be enabled"); mod result; pub use result::*; @@ -240,6 +256,7 @@ pub enum MemoryLocation { GpuToCpu, } +#[non_exhaustive] #[derive(Copy, Clone, Debug)] pub struct AllocatorDebugSettings { /// Logs out debugging information about the various heaps the current device has on startup @@ -249,12 +266,14 @@ pub struct AllocatorDebugSettings { /// Stores a copy of the full backtrace for every allocation made, this makes it easier to debug leaks /// or other memory allocations, but storing stack traces has a RAM overhead so should be disabled /// in shipping applications. + #[cfg(feature = "std")] pub store_stack_traces: bool, /// Log out every allocation as it's being made with log level Debug, rather spammy so off by default pub log_allocations: bool, /// Log out every free that is being called with log level Debug, rather spammy so off by default pub log_frees: bool, /// Log out stack traces when either `log_allocations` or `log_frees` is enabled. + #[cfg(feature = "std")] pub log_stack_traces: bool, } @@ -263,9 +282,11 @@ impl Default for AllocatorDebugSettings { Self { log_memory_information: false, log_leaks_on_shutdown: true, + #[cfg(feature = "std")] store_stack_traces: false, log_allocations: false, log_frees: false, + #[cfg(feature = "std")] log_stack_traces: false, } } diff --git a/src/metal/mod.rs b/src/metal/mod.rs index 45c120cb..13972204 100644 --- a/src/metal/mod.rs +++ b/src/metal/mod.rs @@ -1,4 +1,8 @@ -use std::{backtrace::Backtrace, sync::Arc}; +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{boxed::Box, string::ToString, vec::Vec}; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; use log::debug; use objc2::{rc::Retained, runtime::ProtocolObject}; @@ -34,7 +38,7 @@ fn memory_location_to_metal(location: MemoryLocation) -> MTLResourceOptions { #[derive(Debug)] pub struct Allocation { - chunk_id: Option, + chunk_id: Option, offset: u64, size: u64, memory_block_index: usize, @@ -158,8 +162,8 @@ pub struct Allocator { allocation_sizes: AllocationSizes, } -impl std::fmt::Debug for Allocator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for Allocator { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.generate_report().fmt(f) } } @@ -235,7 +239,7 @@ impl MemoryType { &mut self, device: &ProtocolObject, desc: &AllocationCreateDesc<'_>, - backtrace: Arc, + #[cfg(feature = "std")] backtrace: Arc, allocation_sizes: &AllocationSizes, ) -> Result { let allocation_type = AllocationType::Linear; @@ -282,6 +286,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace, )?; @@ -305,6 +310,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace.clone(), ); @@ -359,6 +365,7 @@ impl MemoryType { allocation_type, 1, desc.name, + #[cfg(feature = "std")] backtrace, ); let (offset, chunk_id) = match allocation { @@ -482,6 +489,7 @@ impl Allocator { let size = desc.size; let alignment = desc.alignment; + #[cfg(feature = "std")] let backtrace = Arc::new(if self.debug_settings.store_stack_traces { Backtrace::force_capture() } else { @@ -493,6 +501,7 @@ impl Allocator { "Allocating `{}` of {} bytes with an alignment of {}.", &desc.name, size, alignment ); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Allocation stack trace: {backtrace}"); @@ -514,13 +523,20 @@ impl Allocator { }) .ok_or(AllocationError::NoCompatibleMemoryTypeFound)?; - memory_type.allocate(&self.device, desc, backtrace, &self.allocation_sizes) + memory_type.allocate( + &self.device, + desc, + #[cfg(feature = "std")] + backtrace, + &self.allocation_sizes, + ) } pub fn free(&mut self, allocation: &Allocation) -> Result<()> { if self.debug_settings.log_frees { let name = allocation.name.as_deref().unwrap_or(""); debug!("Freeing `{name}`."); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Free stack trace: {backtrace}"); diff --git a/src/result.rs b/src/result.rs index 8ccb98a7..50bfff33 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,3 +1,5 @@ +use alloc::string::String; + use thiserror::Error; #[derive(Error, Debug)] @@ -22,4 +24,4 @@ pub enum AllocationError { CastableFormatsRequiresAtLeastDevice12, } -pub type Result = ::std::result::Result; +pub type Result = ::core::result::Result; diff --git a/src/visualizer/allocation_reports.rs b/src/visualizer/allocation_reports.rs index c95b51ae..fb03e38c 100644 --- a/src/visualizer/allocation_reports.rs +++ b/src/visualizer/allocation_reports.rs @@ -92,7 +92,7 @@ pub(crate) fn render_allocation_reports_ui( (AllocationReportVisualizeSorting::None, _) => {} (AllocationReportVisualizeSorting::Idx, true) => allocations.sort_by_key(|(idx, _)| *idx), (AllocationReportVisualizeSorting::Idx, false) => { - allocations.sort_by_key(|(idx, _)| std::cmp::Reverse(*idx)) + allocations.sort_by_key(|(idx, _)| core::cmp::Reverse(*idx)) } (AllocationReportVisualizeSorting::Name, true) => { allocations.sort_by(|(_, alloc1), (_, alloc2)| alloc1.name.cmp(&alloc2.name)) @@ -104,7 +104,7 @@ pub(crate) fn render_allocation_reports_ui( allocations.sort_by_key(|(_, alloc)| alloc.size) } (AllocationReportVisualizeSorting::Size, false) => { - allocations.sort_by_key(|(_, alloc)| std::cmp::Reverse(alloc.size)) + allocations.sort_by_key(|(_, alloc)| core::cmp::Reverse(alloc.size)) } } diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index d57a9152..59572d49 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,4 +1,9 @@ -use std::{backtrace::Backtrace, fmt, marker::PhantomData, sync::Arc}; +#[cfg(feature = "std")] +use alloc::sync::Arc; +use alloc::{borrow::ToOwned, boxed::Box, string::ToString, vec::Vec}; +use core::{fmt, marker::PhantomData}; +#[cfg(feature = "std")] +use std::backtrace::Backtrace; use ash::vk; use log::{debug, Level}; @@ -46,7 +51,7 @@ pub struct AllocationCreateDesc<'a> { /// mark the entire [`Allocation`] as such, instead relying on the compiler to /// auto-implement this or fail if fields are added that violate this constraint #[derive(Clone, Copy, Debug)] -pub(crate) struct SendSyncPtr(std::ptr::NonNull); +pub(crate) struct SendSyncPtr(core::ptr::NonNull); // Sending is fine because mapped_ptr does not change based on the thread we are in unsafe impl Send for SendSyncPtr {} // Sync is also okay because Sending &Allocation is safe: a mutable reference @@ -150,7 +155,7 @@ pub struct AllocatorCreateDesc { /// [\[1\]]: presser#motivation #[derive(Debug)] pub struct Allocation { - chunk_id: Option, + chunk_id: Option, offset: u64, size: u64, memory_block_index: usize, @@ -199,7 +204,7 @@ impl Allocation { }) } - pub fn chunk_id(&self) -> Option { + pub fn chunk_id(&self) -> Option { self.chunk_id } @@ -242,7 +247,7 @@ impl Allocation { /// Returns a valid mapped pointer if the memory is host visible, otherwise it will return None. /// The pointer already points to the exact memory region of the suballocation, so no offset needs to be applied. - pub fn mapped_ptr(&self) -> Option> { + pub fn mapped_ptr(&self) -> Option> { self.mapped_ptr.map(|SendSyncPtr(p)| p) } @@ -250,7 +255,7 @@ impl Allocation { /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. pub fn mapped_slice(&self) -> Option<&[u8]> { self.mapped_ptr().map(|ptr| unsafe { - std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize) + core::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize) }) } @@ -258,7 +263,7 @@ impl Allocation { /// The slice already references the exact memory region of the allocation, so no offset needs to be applied. pub fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> { self.mapped_ptr().map(|ptr| unsafe { - std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize) + core::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize) }) } @@ -406,7 +411,7 @@ impl MemoryBlock { AllocationError::FailedToMap(e.to_string()) }) .and_then(|p| { - std::ptr::NonNull::new(p).map(SendSyncPtr).ok_or_else(|| { + core::ptr::NonNull::new(p).map(SendSyncPtr).ok_or_else(|| { AllocationError::FailedToMap("Returned mapped pointer is null".to_owned()) }) }) @@ -458,7 +463,7 @@ impl MemoryType { device: &ash::Device, desc: &AllocationCreateDesc<'_>, granularity: u64, - backtrace: Arc, + #[cfg(feature = "std")] backtrace: Arc, allocation_sizes: &AllocationSizes, ) -> Result { let allocation_type = if desc.linear { @@ -520,6 +525,7 @@ impl MemoryType { allocation_type, granularity, desc.name, + #[cfg(feature = "std")] backtrace, )?; @@ -546,6 +552,7 @@ impl MemoryType { allocation_type, granularity, desc.name, + #[cfg(feature = "std")] backtrace.clone(), ); @@ -554,7 +561,7 @@ impl MemoryType { let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr { let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) }; - std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) + core::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) } else { None }; @@ -610,6 +617,7 @@ impl MemoryType { allocation_type, granularity, desc.name, + #[cfg(feature = "std")] backtrace, ); let (offset, chunk_id) = match allocation { @@ -627,7 +635,7 @@ impl MemoryType { let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr { let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) }; - std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) + core::ptr::NonNull::new(offset_ptr).map(SendSyncPtr) } else { None }; @@ -769,6 +777,7 @@ impl Allocator { let size = desc.requirements.size; let alignment = desc.requirements.alignment; + #[cfg(feature = "std")] let backtrace = Arc::new(if self.debug_settings.store_stack_traces { Backtrace::force_capture() } else { @@ -780,6 +789,7 @@ impl Allocator { "Allocating `{}` of {} bytes with an alignment of {}.", &desc.name, size, alignment ); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Allocation stack trace: {backtrace}"); @@ -834,6 +844,7 @@ impl Allocator { &self.device, desc, self.buffer_image_granularity, + #[cfg(feature = "std")] backtrace.clone(), &self.allocation_sizes, ) @@ -856,6 +867,7 @@ impl Allocator { &self.device, desc, self.buffer_image_granularity, + #[cfg(feature = "std")] backtrace, &self.allocation_sizes, ) @@ -871,6 +883,7 @@ impl Allocator { if self.debug_settings.log_frees { let name = allocation.name.as_deref().unwrap_or(""); debug!("Freeing `{name}`."); + #[cfg(feature = "std")] if self.debug_settings.log_stack_traces { let backtrace = Backtrace::force_capture(); debug!("Free stack trace: {backtrace}");