From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 5C35A1FF0AB for ; Wed, 09 Sep 2026 18:10:02 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id C841D2156A; Wed, 09 Sep 2026 18:10:01 +0200 (CEST) From: "Max R. Carrara" To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v3 1/9] proxmox-alloc: introduce proxmox-alloc with `LayoutAwareBox` type Date: Wed, 9 Sep 2026 17:40:15 +0200 Message-ID: <20260909154027.595374-2-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260909154027.595374-1-m.carrara@proxmox.com> References: <20260909154027.595374-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1788968421472 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.562 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_SHORT 0.001 Use of a URL Shortener for very short URL RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: m.carrara@proxmox.com X-Mailman-Rule-Hits: max-size X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; news-moderation; no-subject; digests; suspicious-header Message-ID-Hash: F3OAHCUHLWB2GQKAZ5XIVOWH3DLJGS2I X-Message-ID-Hash: F3OAHCUHLWB2GQKAZ5XIVOWH3DLJGS2I X-Mailman-Approved-At: Wed, 09 Sep 2026 18:09:47 +0200 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Introduce a new crate called `proxmox-alloc`, an allocation and collections library similar to `core::alloc`. Add `LayoutAwareBox`, a type which tracks the `Layout` its memory was allocated with. Rust requires that the alignment [align] an allocation uses matches that its corresponding deallocation uses. It is considered undefined behavior if the alignments do not match [ub]. Additionally, Rust does not (and cannot) track whether a given type's memory was allocated with a custom layout. Instead, it will always assume that the alignment of a given value is that of its type. As a more concrete example, if you have a page-size-aligned buffer stored as `Box<[u8]>` and drop it, Rust will basically use `align_of::()` on deallocation under the hood, which is `1` instead of PAGESIZE, resulting in undefined behavior. `LayoutAwareBox` solves this issue by tracking which layout was used during T's allocation, passing it to `dealloc()` in its `drop` handler. This solves the issue of mismatching alignments on deallocation. Additionally, `LayoutAwareBox` provides several convenience methods for creating heap-allocated values and slices with a particular alignment. It also implements many of the standard library's traits wherever applicable. In particular, `LayoutAwareBox` implements almost all traits of `Box`, except: - unstable traits or trait impls that require unstable features (as of writing, Rust 1.94.1) - traits that involve strings or string-like types, such as `From for LayoutAwareBox`, `Clone for LayoutAwareBox`, `From for LayoutAwareBox`, and so on - traits that require non-trivial memory manipulation, such as `Clone for LayoutAwareBox where T: ?Sized` --> cloning the memory inside a `LayoutAwareBox` itself is trivial, but if `T: ?Sized`, it cannot be `Clone`. Note that `Box` does not provide such an impl either. If we want to clone DSTs in the future, we will most likely need some kind of custom trait that we can rely on. - fn-traits, such as `Fn`, `AsyncFnMut`, etc. - `AsFd`, `AsHandle`, `Future`, and `Unpin` In other words, `LayoutAwareBox` will work fine for regular types, DSTs and slices. Other trait impls can always be added later once we actually need them. Finally, this commit also adds ample documentation and tests for `LayoutAwareBox` in order to make it easier to use for developers who are not as familiar with lower-level Rust. [align]: https://en.wikipedia.org/wiki/Data_structure_alignment [dst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html Signed-off-by: Max R. Carrara --- Cargo.toml | 1 + proxmox-alloc/Cargo.toml | 20 + proxmox-alloc/debian/changelog | 5 + proxmox-alloc/debian/control | 30 + proxmox-alloc/debian/copyright | 18 + proxmox-alloc/debian/debcargo.toml | 7 + proxmox-alloc/src/aware_boxed.rs | 2548 ++++++++++++++++++++++++++++ proxmox-alloc/src/lib.rs | 21 + 8 files changed, 2650 insertions(+) create mode 100644 proxmox-alloc/Cargo.toml create mode 100644 proxmox-alloc/debian/changelog create mode 100644 proxmox-alloc/debian/control create mode 100644 proxmox-alloc/debian/copyright create mode 100644 proxmox-alloc/debian/debcargo.toml create mode 100644 proxmox-alloc/src/aware_boxed.rs create mode 100644 proxmox-alloc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 16e91c94..c00515e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "proxmox-access-control", "proxmox-acme", "proxmox-acme-api", + "proxmox-alloc", "proxmox-api-macro", "proxmox-apt", "proxmox-apt-api-types", diff --git a/proxmox-alloc/Cargo.toml b/proxmox-alloc/Cargo.toml new file mode 100644 index 00000000..4eeeb787 --- /dev/null +++ b/proxmox-alloc/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "proxmox-alloc" +version = "0.1.0" +description = "Proxmox allocation and collections library" + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +exclude.workspace = true + +[dependencies] + +[dev-dependencies] +libc.workspace = true + +[features] +default = [] diff --git a/proxmox-alloc/debian/changelog b/proxmox-alloc/debian/changelog new file mode 100644 index 00000000..23c7d28a --- /dev/null +++ b/proxmox-alloc/debian/changelog @@ -0,0 +1,5 @@ +rust-proxmox-alloc (0.1.0-1) trixie; urgency=medium + + * Initial packaging. + + -- Proxmox Support Team Wed, 09 Sep 2026 16:00:50 +0200 diff --git a/proxmox-alloc/debian/control b/proxmox-alloc/debian/control new file mode 100644 index 00000000..5adef4e2 --- /dev/null +++ b/proxmox-alloc/debian/control @@ -0,0 +1,30 @@ +Source: rust-proxmox-alloc +Section: rust +Priority: optional +Build-Depends: debhelper-compat (= 13), + dh-sequence-cargo +Build-Depends-Arch: cargo:native , + rustc:native (>= 1.85) , + libstd-rust-dev +Maintainer: Proxmox Support Team +Standards-Version: 4.7.2 +Vcs-Git: git://git.proxmox.com/git/proxmox.git +Vcs-Browser: https://git.proxmox.com/?p=proxmox.git +Homepage: https://proxmox.com +X-Cargo-Crate: proxmox-alloc + +Package: librust-proxmox-alloc-dev +Architecture: any +Multi-Arch: same +Depends: + ${misc:Depends} +Provides: + librust-proxmox-alloc+default-dev (= ${binary:Version}), + librust-proxmox-alloc-0-dev (= ${binary:Version}), + librust-proxmox-alloc-0+default-dev (= ${binary:Version}), + librust-proxmox-alloc-0.1-dev (= ${binary:Version}), + librust-proxmox-alloc-0.1+default-dev (= ${binary:Version}), + librust-proxmox-alloc-0.1.0-dev (= ${binary:Version}), + librust-proxmox-alloc-0.1.0+default-dev (= ${binary:Version}) +Description: Proxmox allocation and collections library - Rust source code + Source code for Debianized Rust crate "proxmox-alloc" diff --git a/proxmox-alloc/debian/copyright b/proxmox-alloc/debian/copyright new file mode 100644 index 00000000..77952eba --- /dev/null +++ b/proxmox-alloc/debian/copyright @@ -0,0 +1,18 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: + * +Copyright: 2019 - 2026 Proxmox Server Solutions GmbH +License: AGPL-3.0-or-later + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) any + later version. + . + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + details. + . + You should have received a copy of the GNU Affero General Public License along + with this program. If not, see . diff --git a/proxmox-alloc/debian/debcargo.toml b/proxmox-alloc/debian/debcargo.toml new file mode 100644 index 00000000..b7864cdb --- /dev/null +++ b/proxmox-alloc/debian/debcargo.toml @@ -0,0 +1,7 @@ +overlay = "." +crate_src_path = ".." +maintainer = "Proxmox Support Team " + +[source] +vcs_git = "git://git.proxmox.com/git/proxmox.git" +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" diff --git a/proxmox-alloc/src/aware_boxed.rs b/proxmox-alloc/src/aware_boxed.rs new file mode 100644 index 00000000..81e4b37a --- /dev/null +++ b/proxmox-alloc/src/aware_boxed.rs @@ -0,0 +1,2548 @@ +//! The `LayoutAwareBox` type for more involved heap allocations. +//! +//! `LayoutAwareBox` is a pointer to a type that uniquely owns a heap +//! allocation of type `T`, similar to the standard library's [`Box`], while +//! also keeping track of the [`Layout`] that was used for the allocation. +//! +//! The main problem that [`LayoutAwareBox`] addresses it that the Rust compiler +//! does not (and cannot!) track the alignment that was used for any given +//! (heap) allocation of a type `T`. In cases where the alignment matters, the +//! compiler will default to use the *type's alignment* instead. +//! +//! The only instance where this currently matters is when a given `T` is being +//! deallocated. The Rust language requires that the memory layout (and thus +//! alignment) of an allocation is the same for its corresponding deallocation. +//! **It is considered [undefined behavior] if this is not fulfilled.** +//! +//! To illustrate the above, consider this example: +//! +//! ```no_run +//! // For illustration purposes only; do *not* do this in your code. +//! let buf_len = 1024; +//! let align = 128; +//! +//! let layout = std::alloc::Layout::from_size_align(buf_len, align).unwrap(); +//! +//! let thin_ptr: *mut u8 = unsafe { std::alloc::alloc_zeroed(layout) }; +//! +//! let fat_ptr: *mut [u8] = std::ptr::slice_from_raw_parts_mut(thin_ptr, buf_len); +//! assert!(!fat_ptr.is_null()); +//! +//! let boxed: Box<[u8]> = unsafe { Box::from_raw(fat_ptr) }; +//! +//! drop(boxed); +//! ``` +//! +//! Here we allocate a simple buffer containing 1024 bytes, with an alignment of +//! 128 bytes. [`alloc_zeroed`] returns a plain `*mut u8`, which we then convert +//! to a a slice (or "fat pointer") that is aware of its own size. Finally, we +//! pass this new pointer to [`Box::from_raw`] and create a new `Box<[u8]>` +//! which now owns our heap-allocated buffer with our fancy alignment. The +//! [`Box`] will make sure that our buffer is deallocated correctly once +//! dropped. We make this explicit here by using [`drop`] directly. +//! +//! While our logic might seem sound here, this will actually be reported as +//! [undefined behavior] by [Miri]: +//! +//! ```text +//! error: Undefined Behavior: incorrect layout on deallocation: +//! alloc42389 has size 1024 and alignment 128, but gave size 1024 and alignment 1! +//! ``` +//! +//! Therefore, it is necessary to keep ahold of the [`Layout`] that was used +//! for such kinds of allocations, which [`LayoutAwareBox`] will do for you. +//! +//! # Examples +//! +//! Move a value from the stack on the heap, using a specific alignment for the +//! allocation: +//! +//! ``` +//! use proxmox_alloc::LayoutAwareBox; +//! +//! struct SomeData { +//! first: u32, +//! second: u32, +//! } +//! +//! let value = SomeData { first: 10, second: 20 }; +//! +//! let aware_box: LayoutAwareBox = LayoutAwareBox::new(value, 64) +//! .expect("infallible"); +//! ``` +//! +//! Create a page-size-aligned, zero-initialized buffer using +//! [`LayoutAwareBox::slice_fill`]: +//! +//! ``` +//! use libc; +//! +//! use proxmox_alloc::LayoutAwareBox; +//! +//! let buf_len = 2usize.pow(14); +//! +//! let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! assert!(page_size > 0, "failed to query PAGESIZE"); +//! +//! let align = page_size as usize; +//! +//! let pagesize_buf = LayoutAwareBox::<[u8]>::slice_fill(buf_len, align, 0).unwrap(); +//! +//! assert_eq!(pagesize_buf.len(), buf_len); +//! ``` +//! +//! The same can also be done by using [`LayoutAwareBox::slice_fill_with`] and +//! passing [`Default::default`]: +//! +//! ``` +//! # use libc; +//! # use proxmox_alloc::LayoutAwareBox; +//! # let buf_len = 2usize.pow(14); +//! # let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! # assert!(page_size > 0, "failed to query PAGESIZE"); +//! # let align = page_size as usize; +//! let pagesize_buf = +//! LayoutAwareBox::<[u8]>::slice_fill_with(buf_len, align, Default::default).unwrap(); +//! ``` +//! +//! Note that if you want to retrieve the layout that was used for the +//! allocation, you should use [`LayoutAwareBox::layout`]: +//! +//! ``` +//! # use libc; +//! # use proxmox_alloc::LayoutAwareBox; +//! # let buf_len = 2usize.pow(14); +//! # let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! # assert!(page_size > 0, "failed to query PAGESIZE"); +//! let align = page_size as usize; +//! let pagesize_buf = LayoutAwareBox::<[u8]>::slice_fill(buf_len, align, 0).unwrap(); +//! +//! // The alignment of the inner T will be align_of::() in all cases, since Rust +//! // does not and cannot track alignment: +//! assert_eq!(align_of_val(&*pagesize_buf), align_of::()); +//! +//! // Instead, get it from the layout directly: +//! let layout = LayoutAwareBox::layout(&pagesize_buf); +//! assert_eq!(layout.align(), align); +//! ``` +//! +//! Sometimes you might need to create a zero-sized buffer. This can easily be +//! done using [`LayoutAwareBox::slice_empty`], which avoids allocations +//! altogether: +//! +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let empty_buf = LayoutAwareBox::<[u8]>::slice_empty(); +//! ``` +//! +//! For convenience, you can also convert from a couple existing owned types: +//! +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let vec: Vec = vec![0, 1, 2, 3, 4]; +//! let aware_boxed = LayoutAwareBox::<[usize]>::from(vec); +//! +//! assert_eq!(&*aware_boxed, &[0, 1, 2, 3, 4]); +//! ``` +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let boxed: Box<[usize]> = Box::new([42, 67]); +//! let aware_boxed = LayoutAwareBox::<[usize]>::from(boxed); +//! +//! assert_eq!(&*aware_boxed, &[42, 67]); +//! ``` +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let array: [i32; 4] = [0; 4]; +//! let aware_boxed = LayoutAwareBox::<[i32]>::from(array); +//! +//! assert_eq!(&*aware_boxed, &[0, 0, 0, 0]); +//! ``` +//! +//! See the [trait implementations](LayoutAwareBox#trait-implementations) for +//! more. +//! +//! For using `LayoutAwareBox` for custom dynamically sized types, see +//! [below](#custom-dynamically-sized-types). +//! +//! # Custom Dynamically Sized Types +//! +//! ***Hint:** If you already know your way around [DSTs][DST] and want to skip +//! the explanation, see [here](#full-examples) for complete examples.* +//! +//! While [`LayoutAwareBox::slice_fill`] and other methods work great in cases +//! where all you need is a buffer allocated with a custom alignment, you +//! sometimes might have data that comes with a structured part, often called +//! the *header*, and a dynamically sized *payload*. This is relatively common +//! in the world of networking and file formats. +//! +//! [`LayoutAwareBox`] cannot help you with the allocation and initialization of +//! such data, but it can however help you ensure that you do not cause any +//! [undefined behavior] during deallcations. +//! +//! In general, you would structure such data as follows: +//! +//! ``` +//! #[repr(C, packed)] +//! struct PacketHeader { +//! src: [u8; 4], +//! dst: [u8; 4], +//! } +//! +//! #[repr(C, packed)] +//! struct Packet { +//! header: PacketHeader, +//! payload: [u8], +//! } +//! ``` +//! +//! In this example here, the `payload` is our dynamically sized field. We +//! cannot have more than one dynamically sized field anywhere in our struct's +//! definition, and it must always be at the very end of our struct. The +//! presence of such a field makes `Packet` a [dynamically sized type +//! (DST)][DST]. +//! +//! We also chose [`#[repr(C, packed)]`][alt-reprs] as our struct's +//! representation to avoid any field reordering by the Rust compiler, and also +//! to maximize FFI compatibility. +//! +//! Note that while it is possible to omit the `PacketHeader` struct entirely +//! and instead place `src` and `dst` in `Packet` directly, having a separate +//! struct to model the header is useful for size calculations later on. +//! +//! If we then want to allocate a `Packet`, we have to do that by hand. +//! +//! First, we need a suitable [`Layout`]. For the purposes of this example, we +//! will choose an arbitrary size and alignment: +//! +//! ``` +//! # #[repr(C, packed)] +//! # struct PacketHeader { +//! # src: [u8; 4], +//! # dst: [u8; 4], +//! # } +//! # +//! # #[repr(C, packed)] +//! # struct Packet { +//! # header: PacketHeader, +//! # payload: [u8], +//! # } +//! # +//! use std::alloc::Layout; +//! +//! let alloc_size = 1024; +//! let align = 128; +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! ``` +//! +//! Next, we need to allocate the memory for our `Packet` using our layout, +//! calling [`handle_alloc_error`](std::alloc::handle_alloc_error) if our +//! allocation failed: +//! +//! ``` +//! # #[repr(C, packed)] +//! # struct PacketHeader { +//! # src: [u8; 4], +//! # dst: [u8; 4], +//! # } +//! # +//! # #[repr(C, packed)] +//! # struct Packet { +//! # header: PacketHeader, +//! # payload: [u8], +//! # } +//! # +//! # use std::alloc::Layout; +//! # +//! # let alloc_size = 1024; +//! # let align = 128; +//! # +//! # let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! use std::alloc::alloc_zeroed; +//! use std::alloc::handle_alloc_error; +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! # unsafe { std::alloc::dealloc(thin_ptr, layout) }; +//! ``` +//! +//! `thin_ptr` here is now a `*mut u8`, a raw pointer that points to some bytes. +//! We cannot simply cast this to a `*mut Packet` however. Rust actually +//! differentiates between two types of pointers: +//! +//! 1. "Thin" pointers that point to types whose size is known at compile time, +//! meaning they implement the [`Sized`] trait. Thin pointers only consist of +//! an address. +//! 2. "Fat" pointers that point to types whose size is *not* known at compile +//! time, or in other words, [dynamically sized types][DST]. Fat pointers +//! consist of an address and a length, and are the reason why Rust knows how +//! long a string slice `&str` is, for example. +//! +//! In fact, the compiler will complain if we try to cast our `thin_ptr`: +//! +//! ```compile_fail +//! # #[repr(C, packed)] +//! # struct PacketHeader { +//! # src: [u8; 4], +//! # dst: [u8; 4], +//! # } +//! # +//! # #[repr(C, packed)] +//! # struct Packet { +//! # header: PacketHeader, +//! # payload: [u8], +//! # } +//! # +//! # use std::alloc::Layout; +//! # +//! # let alloc_size = 1024; +//! # let align = 128; +//! # +//! # let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! # use std::alloc::alloc_zeroed; +//! # use std::alloc::handle_alloc_error; +//! # +//! # // SAFETY: layout's size is not 0. +//! # let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! let fat_ptr = thin_ptr as *mut Packet; +//! ``` +//! +//! Instead, we must first convert `thin_ptr` to a fat pointer by associating it +//! with a *length*, which we get by subtracting our header's size from the +//! number of bytes we allocated. This will make our `*mut u8` a `*mut [u8]`, +//! which we then can cast without the compiler complaining: +//! +//! ``` +//! # #[repr(C, packed)] +//! # struct PacketHeader { +//! # src: [u8; 4], +//! # dst: [u8; 4], +//! # } +//! # +//! # #[repr(C, packed)] +//! # struct Packet { +//! # header: PacketHeader, +//! # payload: [u8], +//! # } +//! # +//! # use std::alloc::Layout; +//! # +//! # let alloc_size = 1024; +//! # let align = 128; +//! # +//! # let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! # use std::alloc::alloc_zeroed; +//! # use std::alloc::handle_alloc_error; +//! # +//! # // SAFETY: layout's size is not 0. +//! # let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! use std::ptr::slice_from_raw_parts_mut; +//! +//! let payload_len = alloc_size - size_of::(); +//! +//! let fat_ptr = slice_from_raw_parts_mut(thin_ptr, payload_len) as *mut Packet; +//! # unsafe { std::alloc::dealloc(fat_ptr as *mut u8, layout) }; +//! ``` +//! +//! Finally, we can construct our `LayoutAwareBox` by using +//! [`LayoutAwareBox::from_raw_parts`]: +//! +//! ``` +//! # #[repr(C, packed)] +//! # struct PacketHeader { +//! # src: [u8; 4], +//! # dst: [u8; 4], +//! # } +//! # +//! # #[repr(C, packed)] +//! # struct Packet { +//! # header: PacketHeader, +//! # payload: [u8], +//! # } +//! # +//! # use std::alloc::Layout; +//! # +//! # let alloc_size = 1024; +//! # let align = 128; +//! # +//! # let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! # use std::alloc::alloc_zeroed; +//! # use std::alloc::handle_alloc_error; +//! # +//! # // SAFETY: layout's size is not 0. +//! # let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! # use std::ptr::slice_from_raw_parts_mut; +//! # +//! # let payload_len = alloc_size - size_of::(); +//! # +//! # let fat_ptr = slice_from_raw_parts_mut(thin_ptr, payload_len) as *mut Packet; +//! # use proxmox_alloc::LayoutAwareBox; +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and writes, and its pointee was +//! // zero-initialized. Its length is within the bounds of its allocation. +//! let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! For a condensed version of this example, as well as examples on how to deal +//! with payloads that do not consist of plain bytes, see the [full +//! examples](#full-examples) below. +//! +//! ## Full Examples +//! +//! A custom [dynamically sized type][DST] with a payload consisting of bytes: +//! +//! ``` +//! #[repr(C, packed)] +//! struct PacketHeader { +//! src: [u8; 4], +//! dst: [u8; 4], +//! } +//! +//! #[repr(C, packed)] +//! struct Packet { +//! header: PacketHeader, +//! payload: [u8], +//! } +//! +//! # use proxmox_alloc::LayoutAwareBox; +//! use std::alloc::Layout; +//! use std::alloc::alloc_zeroed; +//! use std::alloc::handle_alloc_error; +//! use std::ptr::slice_from_raw_parts_mut; +//! +//! let alloc_size = 1024; +//! let align = 128; +//! +//! let payload_len = alloc_size - size_of::(); +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = slice_from_raw_parts_mut(thin_ptr, payload_len) as *mut Packet; +//! +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and writes, +//! // and its pointee was zero-initialized. Its length is within the bounds +//! // of its allocation. +//! let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! For payloads that do not consist of plain bytes and instead store some oddly +//! sized type, you must either +//! +//! 1. shrink the allocation size to the resulting size of your [DST], or +//! 2. manually add padding to your DST's header until it fits your allocation +//! +//! **In either case, it is important that the size your resulting type is equal +//! to that of its allocation.** A mismatch between the data's size and the +//! allocation's size is considered [undefined behavior] and gets caught by +//! [Miri]. +//! +//! Shrinking the allocation size: +//! +//! ``` +//! #[repr(C, packed)] +//! struct PacketHeader { +//! src: [u8; 4], +//! dst: [u8; 4], +//! } +//! +//! #[repr(C, packed)] +//! struct Packet { +//! header: PacketHeader, +//! payload: [[u8; 7]], +//! } +//! +//! # use proxmox_alloc::LayoutAwareBox; +//! use std::alloc::Layout; +//! use std::alloc::alloc_zeroed; +//! use std::alloc::handle_alloc_error; +//! use std::ptr::slice_from_raw_parts_mut; +//! +//! let max_alloc_size = 1024; +//! let align = 128; +//! +//! let header_size = size_of::(); +//! let elem_size = size_of::<[u8; 7]>(); +//! +//! let payload_len = (max_alloc_size - header_size) / elem_size; +//! +//! // Allocated size *must* correspond to the actual size that Packet will occupy! +//! let alloc_size = payload_len * elem_size + header_size; +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = slice_from_raw_parts_mut(thin_ptr, payload_len) as *mut Packet; +//! +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and writes, and its pointee was +//! // zero-initialized. Its length is within the bounds of its allocation. +//! let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! Manually adding padding to the header, which you most likely will have to +//! calculate by hand: +//! +//! ``` +//! #[repr(C, packed)] +//! struct PacketHeader { +//! src: [u8; 4], +//! dst: [u8; 4], +//! _padding: [u8; 13], +//! } +//! +//! #[repr(C, packed)] +//! struct Packet { +//! header: PacketHeader, +//! payload: [[u8; 17]], +//! } +//! +//! # use proxmox_alloc::LayoutAwareBox; +//! use std::alloc::Layout; +//! use std::alloc::alloc_zeroed; +//! use std::alloc::handle_alloc_error; +//! use std::ptr::slice_from_raw_parts_mut; +//! +//! let max_alloc_size = 1024; +//! let align = 128; +//! +//! let header_size = size_of::(); +//! let elem_size = size_of::<[u8; 17]>(); +//! +//! let payload_len = (max_alloc_size - header_size) / elem_size; +//! +//! // Allocated size *must* correspond to the actual size that Packet will occupy! +//! let alloc_size = payload_len * elem_size + header_size; +//! assert_eq!(max_alloc_size, alloc_size); +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = slice_from_raw_parts_mut(thin_ptr, payload_len) as *mut Packet; +//! +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and writes, and its pointee was +//! // zero-initialized. Its length is within the bounds of its allocation. +//! let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! [`Layout`]: std::alloc::Layout +//! [`alloc_zeroed`]: std::alloc::alloc_zeroed +//! +//! [DST]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts +//! [Miri]: https://github.com/rust-lang/miri +//! [alignment]: https://en.wikipedia.org/wiki/Data_structure_alignment +//! [array]: std::array +//! [alt-reprs]: https://doc.rust-lang.org/nomicon/other-reprs.html +//! [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + +use std::alloc; +use std::iter; +use std::mem; +use std::mem::ManuallyDrop; +use std::ptr; + +/// The error type returned by certain methods of [`LayoutAwareBox`]. +#[derive(Debug)] +pub enum LayoutAwareBoxError { + /// Memory allocation failed, which is most likely caused by being out of memory. + OutOfMemory, + /// Alignment is zero or not a power of 2. + InvalidAlignment, + /// The requested amount of elements does not fit into a slice. This may + /// also happen if the total size of the slice would be larger than + /// [`isize::MAX`]. + TooManyElements, +} + +impl LayoutAwareBoxError { + #[inline] + const fn as_str(&self) -> &'static str { + use LayoutAwareBoxError::*; + + match self { + InvalidAlignment => "invalid alignment - must be a power of 2", + OutOfMemory => "memory allocation failed - out of memory?", + TooManyElements => "too many elements - requested allocation does not fit into slice", + } + } +} + +impl std::error::Error for LayoutAwareBoxError {} + +impl std::fmt::Display for LayoutAwareBoxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +/// A pointer to a type that uniquely owns a heap allocation of type `T`, while +/// also keeping track of the [`Layout`] that was used for the allocation. +/// +/// See the [module-level documentation][crate::aware_boxed] for more +/// information. +/// +/// [`Layout`]: std::alloc::Layout +pub struct LayoutAwareBox { + inner: ManuallyDrop>, + layout: alloc::Layout, +} + +impl Drop for LayoutAwareBox +where + T: ?Sized, +{ + fn drop(&mut self) { + let inner_ref: &T = &**self.inner; + if mem::size_of_val(inner_ref) == 0 { + // Since we have a zero-sized type, we can just manually call drop, + // which will call the drop handler of T (if any) and cause the + // deallocation to be a no-op. + + // SAFETY: `self` is being dropped, so `self.inner` is not used + // again afterwards. + unsafe { ManuallyDrop::drop(&mut self.inner) }; + return; + } + + // SAFETY: We are about to manually drop self.inner and do not use it + // anymore afterwards. + let inner = unsafe { ManuallyDrop::take(&mut self.inner) }; + let ptr = Box::into_raw(inner); + + // SAFETY: The value behind ptr is valid for reading & writing, properly + // aligned, and valid to drop; dealloc is called with the layout that + // has been used for allocation. + unsafe { + ptr::drop_in_place(ptr); + alloc::dealloc(ptr as *mut u8, self.layout); + } + } +} + +impl LayoutAwareBox +where + T: ?Sized, +{ + /// Consumes the passed [`LayoutAwareBox`], returning the underlying + /// pointer and [`Layout`] as a tuple. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed = LayoutAwareBox::<[usize]>::from([42, 67, 1337]); + /// + /// let (ptr, layout) = LayoutAwareBox::into_raw_parts(aware_boxed); + /// + /// // SAFETY: We just got this pointer and layout from into_raw_parts. + /// let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(ptr, layout) }; + /// ``` + /// + /// [`Layout`]: std::alloc::Layout + #[inline] + pub fn into_raw_parts(mut from: Self) -> (*mut T, alloc::Layout) { + let layout = from.layout; + + // SAFETY: We consume `from`, so `from.inner` is not used again afterwards + let boxed = unsafe { ManuallyDrop::take(&mut from.inner) }; + // Prevent LayoutAwareBox::drop() from being called + mem::forget(from); + + (Box::into_raw(boxed), layout) + } + + /// Create a new [`LayoutAwareBox`] from a pointer and a [`Layout`]. + /// + /// Note that if `T` is [zero-sized], the passed layout will be ignored + /// and a new one using [`Layout::for_value`] will be created instead. + /// + /// # Safety + /// + /// Improper use of this function can lead to [undefined behavior] and other + /// issues. + /// + /// In general, the same safety requirements as for [`Box::from_raw`] apply + /// for this function. + /// + /// **Additionally,** the caller must also guarantee that the passed pointer + /// points to a `T` which was allocated using the passed layout **and** has + /// the same size as its underlying allocation. + /// + /// The latter in particular is easy to miss: If you allocate `1024` bytes + /// for a struct that only takes up `1000` bytes for some reason, + /// deallocating this struct will *still* be considered undefined behavior + /// and will be spotted by [Miri]. + /// + /// See [Custom Dynamically Sized Types][custom] for a complete walkthrough + /// and examples. + /// + /// [`Layout::for_value`]: std::alloc::Layout::for_value + /// [`Layout`]: std::alloc::Layout + /// + /// [Miri]: https://github.com/rust-lang/miri + /// [custom]: crate::aware_boxed#custom-dynamically-sized-types + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts + #[inline] + pub unsafe fn from_raw_parts(ptr: *mut T, mut layout: alloc::Layout) -> Self { + // SAFETY: The caller guarantees that ptr is non-null, valid for reads, + // and properly aligned. + unsafe { + let pointee_size = mem::size_of_val(&mut *ptr); + if pointee_size == 0 { + // SAFETY: The caller guarantees that ptr is non-null, valid for + // reads, and properly aligned. + layout = alloc::Layout::for_value(&*ptr); + } + } + + // SAFETY: Caller guarantees that ptr is non-null, valid for reads and + // writes, properly aligned, and valid to drop. + let boxed = unsafe { Box::from_raw(ptr) }; + let inner = ManuallyDrop::new(boxed); + + Self { inner, layout } + } + + /// Returns a copy of the stored [`Layout`]. + /// + /// # Examples + /// + /// ``` + /// use std::alloc::Layout; + /// # use proxmox_alloc::LayoutAwareBox; + /// + /// let aware_boxed = LayoutAwareBox::<[usize]>::from([42, 67]); + /// let layout = LayoutAwareBox::layout(&aware_boxed); + /// + /// assert_eq!( + /// layout, + /// Layout::from_size_align(size_of::() * 2, align_of::()).unwrap() + /// ); + /// ``` + /// + /// [`Layout`]: std::alloc::Layout + #[inline] + pub fn layout(from: &Self) -> alloc::Layout { + from.layout + } +} + +impl LayoutAwareBox { + /// Moves `T` onto the heap and allocate its memory with the passed + /// minimum alignment. + /// + /// The alignment must always be a power of 2. This also includes `2^0`, + /// which is `1`. + /// + /// Should the passed alignment be smaller than that of `T`, the alignment + /// of `T` will be used directly instead. + /// + /// Note that if `T` is [zero-sized], the passed layout will be ignored + /// and a new one using [`Layout::for_value`] will be created instead, while + /// also not actually performing any allocation. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_box = LayoutAwareBox::new(67u8, 1).expect("infallible"); + /// ``` + /// + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts + #[inline] + pub fn new(value: T, mut min_align: usize) -> Result { + let size = size_of::(); + if size == 0 { + let layout = alloc::Layout::for_value(&value); + return Ok(Self { + inner: ManuallyDrop::new(Box::new(value)), + layout: layout, + }); + } + + if min_align < align_of::() { + min_align = align_of::(); + } + + let layout = alloc::Layout::from_size_align(size, min_align) + .map_err(|_| LayoutAwareBoxError::InvalidAlignment)?; + + // SAFETY: Requirements on layout are enforced by from_size_align. + let ptr = unsafe { alloc::alloc(layout) as *mut T }; + if ptr.is_null() { + return Err(LayoutAwareBoxError::OutOfMemory); + } + + // ptr points to uninitialized memory, and is currently not a valid + // instance of T. Therefore, we have to write to it without dropping the + // invalid pointee it currently stores. `value` must also not be + // dropped. + // + // SAFETY: ptr is non-null, valid for writes and properly aligned, + // guaranteed earlier through from_size_align. + unsafe { ptr.write(value) }; + + // SAFETY: ptr is non-null, properly sized and aligned, and doesn't + // alias with other pointers -- it is also consumed here, meaning it + // cannot be used elsewhere. + let new = unsafe { Self::from_raw_parts(ptr, layout) }; + + Ok(new) + } +} + +impl LayoutAwareBox<[T]> { + /// Convenience method to return a `LayoutAwareBox<[T]>` with no elements in + /// it. This implies that the stored `[T]` is [zero-sized] and has an + /// alignment equal to [`align_of::()`][align_of]. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed = LayoutAwareBox::<[usize]>::slice_empty(); + /// + /// assert_eq!(size_of_val(&*aware_boxed), 0); + /// assert_eq!(align_of_val(&*aware_boxed), align_of::()); + /// ``` + /// + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts + #[inline] + pub fn slice_empty() -> Self { + Self::from([]) + } + + #[inline] + fn zero_sized_slice(len: usize) -> Option { + if size_of::() == 0 { + let mut v = Vec::new(); + debug_assert_eq!(v.capacity(), usize::MAX); + + // SAFETY: Vec where T is zero-sized always results in a vector + // with a capacity of usize::MAX. Therefore, the resulting length + // can never be outside the reserved capacity here. + unsafe { v.set_len(len) }; + + let boxed = Box::from(v); + let layout = alloc::Layout::for_value(&*boxed); + + return Some(Self { + inner: ManuallyDrop::new(boxed), + layout: layout, + }); + } + + if len == 0 { + return Some(Self::slice_empty()); + } + + None + } + + #[inline] + fn slice_alloc( + len: usize, + mut min_align: usize, + ) -> Result<(ptr::NonNull, alloc::Layout), LayoutAwareBoxError> { + debug_assert_ne!(size_of::(), 0, "size_of::() is zero!"); + + let max_len = (isize::MAX as usize) / size_of::(); + if len > max_len { + return Err(LayoutAwareBoxError::TooManyElements); + } + + let alloc_size = size_of::().saturating_mul(len); + + if min_align < align_of::() { + min_align = align_of::(); + } + + if !min_align.is_power_of_two() { + return Err(LayoutAwareBoxError::InvalidAlignment); + } + + // Note that since we already checked whether alignment is a power of + // two, this can only fail if our allocation would be too large. + let layout = alloc::Layout::from_size_align(alloc_size, min_align) + .map_err(|_| LayoutAwareBoxError::TooManyElements)?; + + // SAFETY: Layout cannot have a size of 0 in this case. + let thin_ptr = unsafe { alloc::alloc(layout) as *mut T }; + let thin_ptr = ptr::NonNull::new(thin_ptr).ok_or(LayoutAwareBoxError::OutOfMemory)?; + + Ok((thin_ptr, layout)) + } + + /// Constructs a `LayoutAwareBox<[T]>` using the given length and alignment, + /// and initializes its elements with the provided value. + /// + /// This method otherwise has the same behavior as [`LayoutAwareBox::new`] + /// when it comes to alignment and zero-sized types. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed_buf = LayoutAwareBox::<[u8]>::slice_fill(1024, 128, 0) + /// .expect("infallible"); + /// + /// assert!(aware_boxed_buf.iter().all(|elem| *elem == 0)); + /// ``` + #[inline] + pub fn slice_fill(len: usize, min_align: usize, value: T) -> Result + where + T: Clone, + { + return Self::slice_fill_with(len, min_align, { + let mut fill_iter = iter::repeat_n(value, len); + // SAFETY: We always remain within the bounds of iteration. + // If the inner value.clone() panics, we unwind before + // unwrap_unchecked() is called. + move || unsafe { fill_iter.next().unwrap_unchecked() } + }); + } + + /// Constructs a `LayoutAwareBox<[T]>` using the given length and alignment, + /// and initializes its elements by calling the provided function. + /// + /// This method otherwise has the same behavior as [`LayoutAwareBox::new`] + /// when it comes to alignment and zero-sized types. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed_buf = LayoutAwareBox::<[u8]>::slice_fill_with(1024, 128, Default::default) + /// .expect("infallible"); + /// + /// assert!(aware_boxed_buf.iter().all(|elem| *elem == 0)); + /// ``` + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let mut counter: usize = 0; + /// + /// let init_func = || { + /// let current = counter; + /// counter += 1; + /// current + /// }; + /// + /// let aware_boxed = LayoutAwareBox::<[usize]>::slice_fill_with(1024, 128, init_func) + /// .expect("infallible"); + /// + /// assert_eq!(&aware_boxed[0..4], &[0, 1, 2, 3]); + /// assert_eq!(aware_boxed.len(), counter); + /// assert_eq!(aware_boxed[aware_boxed.len() - 1], counter - 1); + /// ``` + #[inline] + pub fn slice_fill_with( + len: usize, + min_align: usize, + mut func: F, + ) -> Result + where + F: FnMut() -> T, + { + if let Some(new) = Self::zero_sized_slice(len) { + return Ok(new); + } + + let (thin_ptr, layout) = Self::slice_alloc(len, min_align)?; + + let mut iter_ptr = thin_ptr; + + // SAFETY: + // * We checked that thin_ptr is not null in slice_alloc. + // * We set the length to 0, so the resulting thick_ptr points to an + // empty slice. + // * We handled zero-sized slices above. + let mut new = unsafe { + let thick_ptr = core::slice::from_raw_parts_mut(thin_ptr.as_ptr(), 0); + Self { + inner: ManuallyDrop::new(Box::from_raw(thick_ptr)), + layout: layout, + } + }; + + let mut local_len = 0; + + for _ in 0..len { + // SAFETY: thin_ptr is valid for writing and we never go out of + // bounds during iteration. + unsafe { + iter_ptr.write(func()); + iter_ptr = iter_ptr.add(1); + } + + // Increment length in every step in case func() panics. + local_len += 1; + + // Update the inner pointer with the incremented length so that + // existing values are successfully dropped if func() panics in the + // next iteration. + // + // SAFETY: + // * The same guarantees as for the initial construction apply. + // * local_len always stays within the bounds of the allocation. + // * We can safely assign to new.inner without dropping the existing + // value, since we use ManuallyDrop. + unsafe { + let thick_ptr = core::slice::from_raw_parts_mut(thin_ptr.as_ptr(), local_len); + new.inner = ManuallyDrop::new(Box::from_raw(thick_ptr)); + }; + } + + Ok(new) + } + + #[inline] + pub fn from_slice_align(slice: &[T], min_align: usize) -> Result + where + T: Clone, + { + return Self::slice_fill_with(slice.len(), min_align, { + let mut slice_iter = slice.iter(); + // SAFETY: We always remain within the bounds of iteration. + move || unsafe { slice_iter.next().unwrap_unchecked().clone() } + }); + } +} + +impl Default for LayoutAwareBox +where + T: Default, +{ + #[inline] + fn default() -> Self { + let inner: ManuallyDrop> = Default::default(); + let inner_ref: &T = &**inner; + let layout = alloc::Layout::for_value(inner_ref); + Self { inner, layout } + } +} + +impl Default for LayoutAwareBox<[T]> { + #[inline] + fn default() -> Self { + Self::slice_empty() + } +} + +impl Clone for LayoutAwareBox +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + if size_of::() == 0 { + return Self { + inner: self.inner.clone(), + layout: self.layout, + }; + } + + // SAFETY: Requirements on layout are guaranteed due to the fact that we + // are re-using an existing layout. We also checked whether T is a ZST + // above. + let ptr = unsafe { alloc::alloc(self.layout) as *mut T }; + + // We obviously cannot return a Result, so all we can do is call + // handle_alloc_error directly. + if ptr.is_null() { + alloc::handle_alloc_error(self.layout); + } + + // SAFETY: ptr is non-null, valid for writes and properly aligned, + // since we re-used an existing layout. + unsafe { + let inner_ref: &T = &**self.inner; + ptr.write(inner_ref.clone()); + Self::from_raw_parts(ptr, self.layout) + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + (**self).clone_from(&(**source)) + } +} + +impl Clone for LayoutAwareBox<[T]> +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + let res = Self::from_slice_align(self, self.layout.align()); + + if matches!(res, Err(LayoutAwareBoxError::OutOfMemory)) { + alloc::handle_alloc_error(self.layout); + } + + res.expect("clone failed") + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + *self = source.clone() + } +} + +impl std::ops::Deref for LayoutAwareBox +where + T: ?Sized, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for LayoutAwareBox +where + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl PartialEq for LayoutAwareBox +where + T: PartialEq + ?Sized, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&**self, &**other) + } + + #[inline] + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, other: &Self) -> bool { + PartialEq::ne(&**self, &**other) + } +} + +impl Eq for LayoutAwareBox where T: Eq + ?Sized {} + +impl PartialOrd for LayoutAwareBox +where + T: PartialOrd + ?Sized, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + PartialOrd::partial_cmp(&**self, &**other) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + PartialOrd::lt(&**self, &**other) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + PartialOrd::le(&**self, &**other) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + PartialOrd::ge(&**self, &**other) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + PartialOrd::gt(&**self, &**other) + } +} + +impl Ord for LayoutAwareBox +where + T: Ord + ?Sized, +{ + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + Ord::cmp(&**self, &**other) + } +} + +impl std::hash::Hash for LayoutAwareBox +where + T: std::hash::Hash + ?Sized, +{ + #[inline] + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&**self, state) + } +} + +impl std::hash::Hasher for LayoutAwareBox +where + T: std::hash::Hasher + ?Sized, +{ + #[inline] + fn finish(&self) -> u64 { + (**self).finish() + } + + #[inline] + fn write(&mut self, bytes: &[u8]) { + (**self).write(bytes) + } + + #[inline] + fn write_u8(&mut self, i: u8) { + (**self).write_u8(i) + } + + #[inline] + fn write_u16(&mut self, i: u16) { + (**self).write_u16(i) + } + + #[inline] + fn write_u32(&mut self, i: u32) { + (**self).write_u32(i) + } + + #[inline] + fn write_u64(&mut self, i: u64) { + (**self).write_u64(i) + } + + #[inline] + fn write_u128(&mut self, i: u128) { + (**self).write_u128(i) + } + + #[inline] + fn write_usize(&mut self, i: usize) { + (**self).write_usize(i) + } + + #[inline] + fn write_i8(&mut self, i: i8) { + (**self).write_i8(i) + } + + #[inline] + fn write_i16(&mut self, i: i16) { + (**self).write_i16(i) + } + + #[inline] + fn write_i32(&mut self, i: i32) { + (**self).write_i32(i) + } + + #[inline] + fn write_i64(&mut self, i: i64) { + (**self).write_i64(i) + } + + #[inline] + fn write_i128(&mut self, i: i128) { + (**self).write_i128(i) + } + + #[inline] + fn write_isize(&mut self, i: isize) { + (**self).write_isize(i) + } +} + +impl std::fmt::Display for LayoutAwareBox +where + T: std::fmt::Display + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&**self, f) + } +} + +impl std::fmt::Debug for LayoutAwareBox +where + T: std::fmt::Debug + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&**self, f) + } +} + +impl std::fmt::Pointer for LayoutAwareBox +where + T: std::fmt::Pointer + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Pointer::fmt(&**self, f) + } +} + +impl core::borrow::Borrow for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn borrow(&self) -> &T { + self + } +} + +impl core::borrow::BorrowMut for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn borrow_mut(&mut self) -> &mut T { + self + } +} + +impl core::convert::AsRef for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn as_ref(&self) -> &T { + self + } +} + +impl core::convert::AsMut for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl core::error::Error for LayoutAwareBox +where + E: core::error::Error, +{ + #[inline] + fn cause(&self) -> Option<&dyn std::error::Error> { + core::error::Error::source(&**self) + } + + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + core::error::Error::source(&**self) + } +} + +impl<'a, T> IntoIterator for &'a LayoutAwareBox<[T]> { + type Item = &'a T; + + type IntoIter = core::slice::Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl FromIterator for LayoutAwareBox<[I]> { + #[inline] + fn from_iter>(iter: T) -> Self { + let boxed = Box::from_iter(iter); + let layout = alloc::Layout::for_value(&*boxed); + Self { + inner: ManuallyDrop::new(boxed), + layout, + } + } +} + +impl Iterator for LayoutAwareBox +where + I: Iterator + ?Sized, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + (**self).next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (**self).size_hint() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + (**self).nth(n) + } +} + +impl DoubleEndedIterator for LayoutAwareBox +where + I: DoubleEndedIterator + ?Sized, +{ + #[inline] + fn next_back(&mut self) -> Option { + (**self).next_back() + } + + #[inline] + fn nth_back(&mut self, n: usize) -> Option { + (**self).nth_back(n) + } +} +impl ExactSizeIterator for LayoutAwareBox +where + I: ExactSizeIterator + ?Sized, +{ + #[inline] + fn len(&self) -> usize { + (**self).len() + } +} + +impl std::iter::FusedIterator for LayoutAwareBox where I: std::iter::FusedIterator + ?Sized {} + +impl std::io::Read for LayoutAwareBox +where + R: std::io::Read + ?Sized, +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + (**self).read(buf) + } + + #[inline] + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + (**self).read_vectored(bufs) + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { + (**self).read_to_end(buf) + } + + #[inline] + fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { + (**self).read_to_string(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + (**self).read_exact(buf) + } +} + +impl std::io::Write for LayoutAwareBox +where + W: std::io::Write + ?Sized, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> { + (**self).write_fmt(fmt) + } +} + +impl std::io::Seek for LayoutAwareBox +where + S: std::io::Seek + ?Sized, +{ + #[inline] + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + (**self).seek(pos) + } + + #[inline] + fn rewind(&mut self) -> std::io::Result<()> { + (**self).rewind() + } + + #[inline] + fn stream_position(&mut self) -> std::io::Result { + (**self).stream_position() + } + + #[inline] + fn seek_relative(&mut self, offset: i64) -> std::io::Result<()> { + (**self).seek_relative(offset) + } +} + +impl std::io::BufRead for LayoutAwareBox +where + B: std::io::BufRead + ?Sized, +{ + #[inline] + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } + + #[inline] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> std::io::Result { + (**self).read_until(byte, buf) + } + + #[inline] + fn skip_until(&mut self, byte: u8) -> std::io::Result { + (**self).skip_until(byte) + } + + #[inline] + fn read_line(&mut self, buf: &mut String) -> std::io::Result { + (**self).read_line(buf) + } +} + +// NOTE: The `From` conversion impls below are all "trivial" in the sense that +// each `Box` we store in `Self` can drop and deallocate its inner value just +// fine. This is because we do not use `T: ?Sized`, so therefore we may use +// `TypeSize::Known`. + +impl From for LayoutAwareBox { + #[inline] + fn from(value: T) -> Self { + let layout = alloc::Layout::for_value(&value); + Self { + inner: ManuallyDrop::new(Box::new(value)), + layout, + } + } +} + +impl From> for LayoutAwareBox { + #[inline] + fn from(value: Box) -> Self { + let layout = alloc::Layout::for_value(&*value); + Self { + inner: ManuallyDrop::new(value), + layout, + } + } +} + +impl From> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: Box<[T]>) -> Self { + let layout = alloc::Layout::for_value(&*value); + Self { + inner: ManuallyDrop::new(value), + layout, + } + } +} + +impl From> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: Vec) -> Self { + let boxed = Box::from(value); + let layout = alloc::Layout::for_value(&*boxed); + Self { + inner: ManuallyDrop::new(boxed), + layout, + } + } +} + +impl From<[T; N]> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: [T; N]) -> Self { + let layout = alloc::Layout::for_value(&value); + Self { + inner: ManuallyDrop::new(Box::from(value)), + layout, + } + } +} + +impl From<&[T]> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// This conversion allocates on the heap and performs a copy of `&[T]` and + /// its contents. + #[inline] + fn from(value: &[T]) -> Self { + let layout = alloc::Layout::for_value(value); + Self { + inner: ManuallyDrop::new(Box::from(value)), + layout, + } + } +} +impl From<&mut [T]> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// This conversion allocates on the heap and performs a copy of `&mut [T]` + /// and its contents. + #[inline] + fn from(value: &mut [T]) -> Self { + let layout = alloc::Layout::for_value(value); + Self { + inner: ManuallyDrop::new(Box::from(value)), + layout, + } + } +} + +impl From> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// When cow is the [`Cow::Borrowed`](std::borrow::Cow::Borrowed) variant, + /// this conversion allocates on the heap and copies the underlying slice. + /// Otherwise, it will try to reuse the owned [`Vec`]’s allocation. + #[inline] + fn from(value: std::borrow::Cow<'_, [T]>) -> Self { + let boxed = Box::from(value); + let layout = alloc::Layout::for_value(&*boxed); + Self { + inner: ManuallyDrop::new(boxed), + layout, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn zero_sized() { + let boxed_unit = LayoutAwareBox::new((), 128).expect("infallible"); + let inner_ref: &() = &*boxed_unit; + + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(boxed_unit); + + let empty_slice = LayoutAwareBox::<[u8]>::slice_empty(); + let inner_ref: &[u8] = &*empty_slice; + + assert_eq!(inner_ref.len(), 0, "len != 0"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(empty_slice); + + let empty_unit_slice = LayoutAwareBox::<[()]>::slice_empty(); + let inner_ref: &[()] = &*empty_unit_slice; + + assert_eq!(inner_ref.len(), 0, "len != 0"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(empty_unit_slice); + + let filled_unit_slice = LayoutAwareBox::<[()]>::slice_fill(13, 1, ()).expect("infallible"); + let inner_ref: &[()] = &*filled_unit_slice; + + assert_eq!(inner_ref.len(), 13, "len != 13"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + for element in &filled_unit_slice { + assert_eq!(*element, ()); + } + + assert_eq!(filled_unit_slice.into_iter().len(), 13); + + drop(filled_unit_slice); + } + + #[test] + fn zero_sized_layout() { + let aware_boxed = LayoutAwareBox::new((), 1).expect("infallible"); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::for_value(&()), + ); + } + + #[test] + fn from_boxed() { + let boxed: Box = Box::new(42); + let aware_boxed = LayoutAwareBox::from(boxed); + + assert_eq!( + aware_boxed, + LayoutAwareBox::new(42, align_of::()).expect("infallible"), + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(size_of::(), align_of::()).unwrap(), + "layout of value from Box should always have size = size_of::(), align = align_of::()" + ); + + drop(aware_boxed); + } + + #[test] + fn from_boxed_slice() { + let boxed: Box<[usize]> = Box::new([42, 67]); + let aware_boxed = LayoutAwareBox::<[usize]>::from(boxed); + + assert_eq!(aware_boxed, LayoutAwareBox::from(vec![42usize, 67])); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from Box should always have size = size_of::() * len, align = align_of::()" + ); + + drop(aware_boxed); + } + + #[test] + fn from_array() { + let array: [usize; 3] = [42, 67, 1337]; + let aware_boxed = LayoutAwareBox::<[usize]>::from(array); + + assert_eq!(aware_boxed, LayoutAwareBox::from(vec![42usize, 67, 1337])); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from [T; N] should always have size = size_of::() * N, align = align_of::()" + ); + + drop(aware_boxed); + } + + #[test] + fn from_vec() { + let vec: Vec = vec![0, 1, 2, 3, 4, 5]; + let aware_boxed = LayoutAwareBox::<[usize]>::from(vec); + + assert_eq!(aware_boxed.len(), 6); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from vector should have size = size_of::() * len, align = align_of::()" + ); + + drop(aware_boxed); + } + + #[test] + fn from_slice() { + let vec = (0..1024).collect::>(); + let aware_boxed = LayoutAwareBox::<[usize]>::from(vec.as_slice()); + + assert_eq!(aware_boxed.len(), vec.len()); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * vec.len(), + align_of::() + ) + .unwrap(), + "layout of slice should have size = size_of::() * len, align = align_of::()" + ); + + drop(vec); + drop(aware_boxed); + + let mut vec = (0..1024).collect::>(); + let aware_boxed = LayoutAwareBox::<[usize]>::from(vec.as_mut_slice()); + + assert_eq!(aware_boxed.len(), vec.len()); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * vec.len(), + align_of::() + ) + .unwrap(), + "layout of slice should have size = size_of::() * len, align = align_of::()" + ); + + drop(vec); + drop(aware_boxed); + } + + #[test] + fn from_cow() { + let owned_cow = std::borrow::Cow::from((0..128).collect::>()); + assert!(matches!(owned_cow, std::borrow::Cow::Owned(_))); + + let aware_boxed = LayoutAwareBox::<[usize]>::from(owned_cow); + let other = (0..128).collect::>(); + + assert_eq!(aware_boxed, other); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&other), + ); + + drop(aware_boxed); + drop(other); + + let vec = (0..128).collect::>(); + let borrowed_cow = std::borrow::Cow::from(vec.as_slice()); + assert!(matches!(borrowed_cow, std::borrow::Cow::Borrowed(_))); + + let aware_boxed = LayoutAwareBox::<[usize]>::from(borrowed_cow); + let other = (0..128).collect::>(); + + assert_eq!(aware_boxed, other); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&other), + ); + + drop(aware_boxed); + drop(other); + } + + #[test] + fn slice_fill_sized() { + let aware_boxed = + LayoutAwareBox::slice_fill(7, align_of::(), 8u32).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 7, "inner_ref.len() != 7"); + + assert_eq!( + align_of_val(inner_ref), + align_of::(), + concat!( + "for any [T] where T: Sized, align_of_val() is always align_of::(),", + " because the compiler does not track alignment" + ) + ); + + assert_eq!( + size_of_val(inner_ref), + size_of::() * 7, + "[T] where T: Sized with N elements should always have a size of size_of::() * N" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "[T] where T: Sized with N elements should always have a layout where size = size_of::() * N and the alignment that was specified" + ); + + assert_eq!(inner_ref, &[8u32; 7], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(7, align_of::(), 8u32).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(7, align_of::(), || 8u32).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_sized_empty() { + let aware_boxed = LayoutAwareBox::slice_fill(0, 1024, 1337usize).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 0, "inner_ref.len() != 0"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "[T] where T: Sized with 0 elements should always have a size of 0" + ); + + assert_eq!( + align_of_val(inner_ref), + align_of::(), + "[T] where T: Sized with 0 elements should always have an alignment of align_of::()" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::()).unwrap(), + "[T] where T: Sized with 0 elements should always have a layout where size = 0, align = align_of::()" + ); + + assert_eq!(inner_ref, &[], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_empty(), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(0, 1024, || 1337usize).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_unsized() { + let aware_boxed = LayoutAwareBox::slice_fill(13, 1024, ()).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 13, "inner_ref.len() != 13"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "nonempty slice of a zero-sized type should not have any size" + ); + + assert_eq!( + align_of_val(inner_ref), + 1, + "nonempty slice of a zero-sized type should always have an alignment of 1" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::<()>()).unwrap(), + "layout of nonempty [T] where T is zero-sized should always have size = 0 and align = 1" + ); + + assert_eq!(inner_ref, &[(); 13], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(13, 32, ()).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(13, 1024, || ()).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_unsized_empty() { + let aware_boxed = LayoutAwareBox::slice_fill(0, 1024, ()).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 0, "inner_ref.len() != 0"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "empty slice of a zero-sized type should not have any size" + ); + + assert_eq!( + align_of_val(inner_ref), + 1, + "empty slice of a zero-sized type should always have an alignment of 1" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::<()>()).unwrap(), + "layout of empty [T] where T is zero-sized should always have size = 0 and align = 1" + ); + + assert_eq!(inner_ref, &[(); 0], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(0, 32, ()).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(0, 1024, || ()).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn from_slice_align() { + // large min alignment + // --> stays as is + let min_align = 1024; + let array: [u64; 4] = [1, 2, 3, 4]; + let aware_boxed = LayoutAwareBox::from_slice_align(&array, min_align).expect("infallible"); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(size_of_val(&array), min_align) + .expect("infallible") + ); + + drop(aware_boxed); + + // small min alignment + // --> gets increased to align_of_val::() + let min_align = 1; + let array: [u64; 4] = [5, 6, 7, 8]; + let aware_boxed = LayoutAwareBox::from_slice_align(&array, min_align).expect("infallible"); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(size_of_val(&array), align_of_val(&array)) + .expect("infallible") + ); + + drop(aware_boxed); + } + + #[test] + fn clone_sized() { + let min_align = 1024; + + let aware_boxed = LayoutAwareBox::::new(42, min_align).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::::new(String::from("Ferris is cool!"), min_align) + .expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = + LayoutAwareBox::>::new((0..128).collect(), min_align).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::new( + Box::new(Box::new(Box::::from(String::from( + "indirection galore", + )))), + min_align, + ) + .expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = + LayoutAwareBox::new(std::sync::Arc::::from("arc"), min_align).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + } + + #[test] + fn clone_sized_with_side_effects() { + #[derive(Clone, Debug, Default)] + struct Tracker { + inner: std::sync::Arc<()>, + } + + impl Tracker { + fn count(&self) -> usize { + std::sync::Arc::strong_count(&self.inner) + } + } + + let tracker = Tracker::default(); + assert_eq!(tracker.count(), 1); + + let aware_boxed = LayoutAwareBox::new(tracker, 1).expect("infallible"); + assert_eq!(aware_boxed.count(), 1); + + let cloned_1 = aware_boxed.clone(); + assert_eq!(aware_boxed.count(), 2); + assert_eq!(cloned_1.count(), 2); + + let cloned_2 = aware_boxed.clone(); + assert_eq!(aware_boxed.count(), 3); + assert_eq!(cloned_2.count(), 3); + + let (ptr, layout) = LayoutAwareBox::into_raw_parts(cloned_2); + assert_eq!(aware_boxed.count(), 3); + + let cloned_2_restored = unsafe { LayoutAwareBox::from_raw_parts(ptr, layout) }; + assert_eq!(aware_boxed.count(), 3); + + drop(cloned_2_restored); + assert_eq!(aware_boxed.count(), 2); + } + + #[test] + fn clone_unsized() { + // Will get ignored! + let min_align = 1024; + let aware_boxed = LayoutAwareBox::new((), min_align).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_eq!( + &*aware_boxed as *const _, &*cloned as *const _, + "zero-sized types will always have the same pointer" + ); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::<[usize]>::slice_empty(); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_eq!( + &*aware_boxed as *const _, &*cloned as *const _, + "empty slices are zero-sized types and thus will always have the same pointer" + ); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::slice_fill(128, 1, ()).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_eq!( + &*aware_boxed as *const _, &*cloned as *const _, + "slices storing zero-sized types are considered zero-sized and thus will always have the same pointer" + ); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::<[()]>::slice_empty(); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_eq!( + &*aware_boxed as *const _, &*cloned as *const _, + "empty slices are zero-sized types and thus will always have the same pointer" + ); + + drop(aware_boxed); + drop(cloned); + } + + #[test] + fn clone_unsized_with_side_effects() { + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; + + static CLONE_COUNTER: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Default)] + struct Tracker; + + impl Clone for Tracker { + fn clone(&self) -> Self { + CLONE_COUNTER.fetch_add(1, Ordering::Relaxed); + Self {} + } + } + + assert_eq!(size_of::(), 0); + + let aware_boxed = LayoutAwareBox::::default(); + + assert_eq!(CLONE_COUNTER.load(Ordering::SeqCst), 0); + + let _ = aware_boxed.clone(); + assert_eq!(CLONE_COUNTER.load(Ordering::SeqCst), 1); + + let _ = aware_boxed.clone(); + assert_eq!(CLONE_COUNTER.load(Ordering::SeqCst), 2); + + let _ = aware_boxed.clone(); + assert_eq!(CLONE_COUNTER.load(Ordering::SeqCst), 3); + } + + #[test] + fn from_raw_parts_unsized_slice() { + let len = 11; + let size = size_of::() * len; + let align = align_of::(); + + let layout = std::alloc::Layout::from_size_align(size, align).expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, len); + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_box = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + + let inner_ref = &*aware_box; + + assert_eq!(inner_ref.len(), len, "inner_ref.len() != len"); + assert_eq!(size_of_val(inner_ref), len, "size_of_val(inner_ref) != len"); + } + + #[test] + fn from_raw_parts_unsized_type() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + } + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [u8], + } + + // -- construction + let alloc_size = 1024; + let header_size = size_of::(); + let data_len = alloc_size - header_size; + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_len ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } + + #[test] + fn from_raw_parts_unsized_type_weird_data() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + } + + #[repr(Rust)] + #[allow(unused)] + struct MyPayloadElement([u8; 7]); + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [MyPayloadElement], + } + + // -- construction + let max_alloc_size = 1024; + let header_size = size_of::(); + + let data_element_size = size_of::(); + let data_len = (max_alloc_size - header_size) / data_element_size; + + // Allocated size *must* correspond to actual size that MyDST will occupy + let alloc_size = data_len * data_element_size + header_size; + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_element_size * data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_element_size * data_len \ + ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } + + #[test] + fn from_raw_parts_unsized_type_weird_data_padded() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + _pad1: u8, + _pad2: u8, + _pad3: u8, + _pad4: u8, + } + + #[repr(Rust)] + #[allow(unused)] + struct MyPayloadElement([u8; 7]); + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [MyPayloadElement], + } + + // -- construction + let max_alloc_size = 1024; + let header_size = size_of::(); + + let data_element_size = size_of::(); + let data_len = (max_alloc_size - header_size) / data_element_size; + + // Allocated size *must* correspond to actual size that MyDST will occupy + let alloc_size = data_len * data_element_size + header_size; + + assert_eq!( + max_alloc_size, alloc_size, + "MyDST will not occupy all {max_alloc_size} allocated bytes after construction" + ); + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_element_size * data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_element_size * data_len \ + ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } +} diff --git a/proxmox-alloc/src/lib.rs b/proxmox-alloc/src/lib.rs new file mode 100644 index 00000000..7d3183d0 --- /dev/null +++ b/proxmox-alloc/src/lib.rs @@ -0,0 +1,21 @@ +//! # The Proxmox allocation and collections library +//! +//! This library provides smart pointers and collections for managing +//! heap-allocated values, similar to [`core::alloc`]. +//! +//! ## Layout-Aware Box +//! +//! The [`LayoutAwareBox`] is a smart pointer type that, unlike the regular +//! [`Box`], also tracks which [`Layout`] was used to allocate its contents. +//! +//! [`LayoutAwareBox`] can be used to conveniently allocate values on the heap +//! with a particular alignment and is compatible with many standard library +//! features. +//! +//! [`Layout`]: std::alloc::Layout +//! +//! [packed]: https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.align-packed + +pub mod aware_boxed; +pub use aware_boxed::LayoutAwareBox; +pub use aware_boxed::LayoutAwareBoxError; -- 2.47.3