From 7c16ac1913150bf6b25358ff8c28e38687d39e4d Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Wed, 14 Jan 2026 13:54:05 +0100 Subject: [PATCH 1/3] osi: export core & alloc for macros Export the core and alloc dependencies for macros to use. There is little harm in re-exporting those dependencies, given that we already depend on them, and on their stability guarantees. This kinda blocks `osi::core` and `osi::alloc` for future use, but that seems ok'ish, and would conflict with current use of `core` and `alloc` inside of `osi`, anyway. Signed-off-by: David Rheinsberg --- lib/osi/src/lib.rs | 8 +++++--- lib/osi/src/mem.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/osi/src/lib.rs b/lib/osi/src/lib.rs index a7c547c..6b77b8a 100644 --- a/lib/osi/src/lib.rs +++ b/lib/osi/src/lib.rs @@ -37,12 +37,14 @@ #![no_std] -extern crate alloc; -extern crate core; - #[cfg(any(test, feature = "std"))] extern crate std; +// Used by macros via `$crate::{alloc,core}::*`, explicitly part of the public +// API. Usually of little use to code outside of this crate, though. +pub extern crate alloc; +pub extern crate core; + pub mod align; pub mod args; pub mod brand; diff --git a/lib/osi/src/mem.rs b/lib/osi/src/mem.rs index 5d536c2..111de0c 100644 --- a/lib/osi/src/mem.rs +++ b/lib/osi/src/mem.rs @@ -10,11 +10,11 @@ use core::mem::transmute_copy; macro_rules! crate_mem_typed_offset_of { ($container:ty, $field:ident, $ty:ty $(,)?) => { const { - let v = ::core::mem::MaybeUninit::<$container>::uninit(); + let v = $crate::core::mem::MaybeUninit::<$container>::uninit(); let _: *const $ty = unsafe { &raw const ((*v.as_ptr()).$field) }; - ::core::mem::offset_of!($container, $field) + $crate::core::mem::offset_of!($container, $field) } } } From 1a54b3583737aa0bb497638735f7f9d8a31b2fc7 Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Wed, 14 Jan 2026 13:57:42 +0100 Subject: [PATCH 2/3] osi/mem: introduce {slice_,}as_uninit() Two new helpers to get `MaybeUninit` for any read-only type. This is really useful as source operand to `copy_from_slice()` and similar, if the target is a `MaybeUninit`. Signed-off-by: David Rheinsberg --- lib/osi/src/mem.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/osi/src/mem.rs b/lib/osi/src/mem.rs index 111de0c..b77024c 100644 --- a/lib/osi/src/mem.rs +++ b/lib/osi/src/mem.rs @@ -3,6 +3,7 @@ //! This module contains functions to help dealing with direct memory //! manipulation and inspection. +use core::mem::MaybeUninit as Uninit; use core::mem::transmute_copy; #[doc(hidden)] @@ -85,7 +86,7 @@ pub const unsafe fn transmute_copy_uninit(src: &Src) -> Dst { // // SAFETY: Delegated to the caller. unsafe { - let mut dst = core::mem::MaybeUninit::::uninit(); + let mut dst = Uninit::::uninit(); copy_unaligned( src as *const Src, dst.as_mut_ptr() as *mut Src, @@ -114,7 +115,7 @@ pub const unsafe fn transmute_copy_uninit(src: &Src) -> Dst { // This is the slow-path that manually iterates the individual bytes, but works // with any data-type, as long as the data-type stays valid with swapped bytes. const unsafe fn bswap_slow(v: &T) -> T { - let mut r = core::mem::MaybeUninit::::uninit(); + let mut r = Uninit::::uninit(); let src = v as *const T as *const u8; let dst = r.as_mut_ptr() as *mut u8; @@ -159,6 +160,26 @@ pub const unsafe fn bswap_copy(v: &T) -> T { } } +/// Alias a type as a [`MaybeUninit`](core::mem::MaybeUninit). +/// +/// Any type can be aliased as a possibly uninitialized type. This is usually +/// only necessary when providing initialized data to code that can handle +/// possibly uninitialized data. +pub const fn as_uninit<'a, T>(v: &'a T) -> &'a Uninit { + unsafe { + core::mem::transmute::<&T, &Uninit>(v) + } +} + +/// Alias a slice as a [`MaybeUninit`](core::mem::MaybeUninit). +/// +/// This works like [`as_uninit()`] but for slices of `T`. +pub const fn slice_as_uninit<'a, T>(v: &'a [T]) -> &'a [Uninit] { + unsafe { + core::mem::transmute::<&[T], &[Uninit]>(v) + } +} + /// Alias a type as a byte slice. /// /// This function allows accessing any type as a slice of bytes. This is safe @@ -270,6 +291,22 @@ mod test { } } + // Verify uninit aliasing + #[test] + fn uninit_alias() { + let v: u16 = 0xf0f0; + + assert_eq!(v, 0xf0f0); + assert_eq!(unsafe { *as_uninit(&v).as_ptr() }, 0xf0f0); + + let v: [u16; 2] = [0xf0, 0xf1]; + + assert_eq!(v[0], 0xf0); + assert_eq!(v[1], 0xf1); + assert_eq!(unsafe { *slice_as_uninit(&v)[0].as_ptr() }, 0xf0); + assert_eq!(unsafe { *slice_as_uninit(&v)[1].as_ptr() }, 0xf1); + } + // Verify byte aliasing #[test] fn byte_alias() { From c5b4ce5d388d51fb058b05fe387358a33ca12397 Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Wed, 14 Jan 2026 13:58:59 +0100 Subject: [PATCH 3/3] osi/mem: mark `as_bytes()` and friends as unsafe Accessing uninitialized data is never safe in Rust. The reason is that some hardware platforms have support for actual hardware markers of uninitialized bytes, and thus LLVM can never provide a way to read such data, even if the target platform would support it. There is an argument to be made for Rust to use something else to model padding bytes, but that is not our decision to make, so lets just not read padding bytes for now. Signed-off-by: David Rheinsberg --- lib/osi/src/mem.rs | 75 ++++++++++++++++++++--------------- lib/sys/src/ffi/linux/test.rs | 20 ++++++---- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/lib/osi/src/mem.rs b/lib/osi/src/mem.rs index b77024c..11e158c 100644 --- a/lib/osi/src/mem.rs +++ b/lib/osi/src/mem.rs @@ -182,14 +182,16 @@ pub const fn slice_as_uninit<'a, T>(v: &'a [T]) -> &'a [Uninit] { /// Alias a type as a byte slice. /// -/// This function allows accessing any type as a slice of bytes. This is safe -/// for all types. However, the content of padding bytes is neither well -/// defined nor stable. -pub const fn as_bytes<'a, T>(v: &'a T) -> &'a [u8] { - // SAFETY: We retain the allocation size of `T` and its lifetime. Hence, - // the transmute is safe for as long as `'a`. Since `v` is - // borrowed, we prevent mutable access for the entire lifetime of - // the returned value. +/// This function allows accessing any type as a slice of bytes. +/// +/// ## Safety +/// +/// The caller must guarantee that: +/// +/// - `T` has no padding bytes, and all bytes of `v` belong to the type `T. +/// This implies that all bytes in `v` are initialized. +pub const unsafe fn as_bytes<'a, T>(v: &'a T) -> &'a [u8] { + // SAFETY: Propagated to caller. unsafe { core::slice::from_raw_parts::<'a, u8>( v as *const _ as *const _, @@ -206,15 +208,15 @@ pub const fn as_bytes<'a, T>(v: &'a T) -> &'a [u8] { /// /// ## Safety /// -/// Like [`as_bytes()`], this can be safely called on any type. However, unlike -/// [`as_bytes()`], this function grants mutable access and thus any mutable -/// use of the returned reference must guarantee not to violate any invariants -/// of `T`. +/// The caller must guarantee that: +/// +/// - `T` has no padding bytes, and all bytes of `v` belong to the type `T. +/// This implies that all bytes in `v` are initialized. +/// - Any bit sequence in `v` produces a valid value of type `T`. This +/// guarantees that no use of the returned reference ever produces invalid +/// values. pub const unsafe fn as_bytes_mut<'a, T>(v: &'a mut T) -> &'a mut [u8] { - // SAFETY: We retain the allocation size of `T` and its lifetime. Hence, - // the transmute is safe for as long as `'a`. Since `v` is - // borrowed, we claim mutable access for the entire lifetime of - // the returned value. + // SAFETY: Propagated to caller. unsafe { core::slice::from_raw_parts_mut::<'a, u8>( v as *mut _ as *mut _, @@ -227,9 +229,18 @@ pub const unsafe fn as_bytes_mut<'a, T>(v: &'a mut T) -> &'a mut [u8] { /// /// Compare the backing memory of two values for equality. Return `true` if the /// memory compares equal, false if not. Note that all memory must compare -/// equal, including padding bytes (which have no guaranteed nor stable value). -pub fn eq(a: &A, b: &B) -> bool { - *as_bytes(a) == *as_bytes(b) +/// equal. +/// +/// ## Safety +/// +/// The caller must guarantee that: +/// +/// - `A` and `B` have no padding bytes, and all bytes of `a` and `b` belong to +/// their respective type. This implies that all bytes in `a` and `b` are +/// initialized. +pub unsafe fn eq(a: &A, b: &B) -> bool { + // SAFETY: Propagated to caller. + unsafe { *as_bytes(a) == *as_bytes(b) } } #[cfg(test)] @@ -312,18 +323,18 @@ mod test { fn byte_alias() { let mut v: u16 = 0xf0f0; - assert_eq!(as_bytes(&v)[0], 0xf0); - assert_eq!(as_bytes(&v)[1], 0xf0); - unsafe { + assert_eq!(as_bytes(&v)[0], 0xf0); + assert_eq!(as_bytes(&v)[1], 0xf0); + as_bytes_mut(&mut v)[0] = 0x0f; as_bytes_mut(&mut v)[1] = 0x0f; - } - assert_eq!(as_bytes(&v)[0], 0x0f); - assert_eq!(as_bytes(&v)[1], 0x0f); + assert_eq!(as_bytes(&v)[0], 0x0f); + assert_eq!(as_bytes(&v)[1], 0x0f); - assert_eq!(v, 0x0f0f); + assert_eq!(v, 0x0f0f); + } } // Verify byte-wise equality @@ -331,11 +342,13 @@ mod test { fn byte_eq() { let v: u16 = 0xf0f0; - assert!(eq(&v, &0xf0f0u16)); - assert!(eq(&v, &[0xf0u8, 0xf0u8])); + unsafe { + assert!(eq(&v, &0xf0f0u16)); + assert!(eq(&v, &[0xf0u8, 0xf0u8])); - assert!(!eq(&v, &0x00f0u16)); - assert!(!eq(&v, &0xf0f0u32)); - assert!(!eq(&v, &[0xf0u16, 0xf0u16])); + assert!(!eq(&v, &0x00f0u16)); + assert!(!eq(&v, &0xf0f0u32)); + assert!(!eq(&v, &[0xf0u16, 0xf0u16])); + } } } diff --git a/lib/sys/src/ffi/linux/test.rs b/lib/sys/src/ffi/linux/test.rs index a768653..4772450 100644 --- a/lib/sys/src/ffi/linux/test.rs +++ b/lib/sys/src/ffi/linux/test.rs @@ -12,15 +12,19 @@ use native as libc; // Compare two `const` definitions for equality. This will compare their type // layout and memory content for equality. -fn eq_def_const(a: &A, b: &B) -> bool { - core::mem::size_of::() == core::mem::size_of::() - && core::mem::align_of::() == core::mem::align_of::() - && osi::mem::eq(a, b) +unsafe fn eq_def_const(a: &A, b: &B) -> bool { + // SAFETY: Propagated to caller. + unsafe { + core::mem::size_of::() == core::mem::size_of::() + && core::mem::align_of::() == core::mem::align_of::() + && osi::mem::eq(a, b) + } } // A 3-way variant of `eq_def_const()`. -fn eq3_def_const(a: &A, b: &B, c: &C) -> bool { - eq_def_const(a, b) && eq_def_const(a, c) +unsafe fn eq3_def_const(a: &A, b: &B, c: &C) -> bool { + // SAFETY: Propagated to caller. + unsafe { eq_def_const(a, b) && eq_def_const(a, c) } } // Verify that all supported platforms are available, by simply checking that @@ -37,5 +41,7 @@ fn platform_availability() { // Compare target APIs with native and libc APIs, and verify they match. #[test] fn comparison() { - assert!(eq3_def_const(&target::errno::EPERM, &native::errno::EPERM, &libc::errno::EPERM)); + unsafe { + assert!(eq3_def_const(&target::errno::EPERM, &native::errno::EPERM, &libc::errno::EPERM)); + } }