* [PATCH 1/4] rust: add projection infrastructure
[not found] <20260214053344.1994776-1-gary@garyguo.net>
@ 2026-02-14 5:33 ` Gary Guo
2026-02-14 9:53 ` Benno Lossin
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Gary Guo @ 2026-02-14 5:33 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Nathan Chancellor, Nicolas Schier
Cc: Alexandre Courbot, rust-for-linux, linux-kernel, linux-kbuild
Add a generic infrastructure for performing field and index projections on
raw pointers. This will form the basis of performing I/O projections.
Pointers manipulations are intentionally using the safe wrapping variants
instead of the unsafe variants, as the latter requires pointers to be
inside an allocation which is not necessarily true for I/O pointers.
This projection macro protects against rogue `Deref` implementation, which
can causes the projected pointer to be outside the bounds of starting
pointer. This is extremely unlikely and Rust has a lint to catch this, but
is unsoundness regardless. The protection works by inducing type inference
ambiguity when `Deref` is implemented.
The projection macro supports both fallible and infallible index
projections. These are described in detail inside the documentation.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/lib.rs | 5 +
rust/kernel/projection.rs | 269 ++++++++++++++++++++++++++++++++++++++
scripts/Makefile.build | 4 +-
3 files changed, 277 insertions(+), 1 deletion(-)
create mode 100644 rust/kernel/projection.rs
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 3da92f18f4ee..50866b481bdb 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -20,6 +20,7 @@
#![feature(generic_nonzero)]
#![feature(inline_const)]
#![feature(pointer_is_aligned)]
+#![feature(slice_ptr_len)]
//
// Stable since Rust 1.80.0.
#![feature(slice_flatten)]
@@ -37,6 +38,9 @@
#![feature(const_ptr_write)]
#![feature(const_refs_to_cell)]
//
+// Stable since Rust 1.84.0.
+#![feature(strict_provenance)]
+//
// Expected to become stable.
#![feature(arbitrary_self_types)]
//
@@ -130,6 +134,7 @@
pub mod prelude;
pub mod print;
pub mod processor;
+pub mod projection;
pub mod ptr;
#[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)]
pub mod pwm;
diff --git a/rust/kernel/projection.rs b/rust/kernel/projection.rs
new file mode 100644
index 000000000000..200d116c39e2
--- /dev/null
+++ b/rust/kernel/projection.rs
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Infrastructure for handling projections.
+
+use core::{
+ mem::MaybeUninit,
+ ops::Deref, //
+};
+
+use crate::{
+ build_error,
+ error::{
+ code::ERANGE,
+ Error, //
+ }, //
+};
+
+/// Error raised when a projection is attempted on array or slices out of bounds.
+pub struct OutOfBound;
+
+impl From<OutOfBound> for Error {
+ #[inline(always)]
+ fn from(_: OutOfBound) -> Self {
+ ERANGE
+ }
+}
+
+/// A helper trait to perform index projection.
+///
+/// This is similar to `core::slice::SliceIndex`, but operate on raw pointers safely and fallibly.
+///
+/// # Safety
+///
+/// `get` must return a pointer in bounds of the provided pointer.
+#[doc(hidden)]
+pub unsafe trait ProjectIndex<T: ?Sized>: Sized {
+ type Output: ?Sized;
+
+ /// Returns an index-projected pointer, if in bounds.
+ fn get(self, slice: *mut T) -> Option<*mut Self::Output>;
+
+ /// Returns an index-projected pointer; fail the build if it cannot be proved to be in bounds.
+ #[inline(always)]
+ fn index(self, slice: *mut T) -> *mut Self::Output {
+ Self::get(self, slice).unwrap_or_else(|| build_error!())
+ }
+}
+
+// Forward array impl to slice impl.
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T, I, const N: usize> ProjectIndex<[T; N]> for I
+where
+ I: ProjectIndex<[T]>,
+{
+ type Output = <I as ProjectIndex<[T]>>::Output;
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T; N]) -> Option<*mut Self::Output> {
+ <I as ProjectIndex<[T]>>::get(self, slice)
+ }
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T; N]) -> *mut Self::Output {
+ <I as ProjectIndex<[T]>>::index(self, slice)
+ }
+}
+
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T> ProjectIndex<[T]> for usize {
+ type Output = T;
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T]) -> Option<*mut T> {
+ if self > slice.len() {
+ None
+ } else {
+ Some(slice.cast::<T>().wrapping_add(self))
+ }
+ }
+}
+
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T> ProjectIndex<[T]> for core::ops::Range<usize> {
+ type Output = [T];
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
+ let new_len = self.end.checked_sub(self.start)?;
+ if self.end > slice.len() {
+ return None;
+ }
+ Some(core::ptr::slice_from_raw_parts_mut(
+ slice.cast::<T>().wrapping_add(self.start),
+ new_len,
+ ))
+ }
+}
+
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeTo<usize> {
+ type Output = [T];
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
+ (0..self.end).get(slice)
+ }
+}
+
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFrom<usize> {
+ type Output = [T];
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
+ (self.start..slice.len()).get(slice)
+ }
+}
+
+// SAFETY: `get` returned pointers are in bounds.
+unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFull {
+ type Output = [T];
+
+ #[inline(always)]
+ fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
+ Some(slice)
+ }
+}
+
+/// A helper trait to perform field projection.
+///
+/// This trait has a `DEREF` generic parameter so it can be implemented twice for types that
+/// implement `Deref`. This will cause an ambiguity error and thus block `Deref` types being used
+/// as base of projection, as they can inject unsoundness.
+///
+/// # Safety
+///
+/// `proj` should invoke `f` with valid allocation, as documentation described.
+#[doc(hidden)]
+pub unsafe trait ProjectField<const DEREF: bool> {
+ /// Project a pointer to a type to a pointer of a field.
+ ///
+ /// `f` is always invoked with valid allocation so it can safely obtain raw pointers to fields
+ /// using `&raw mut`.
+ ///
+ /// This is needed because `base` might not point to a valid allocation, while `&raw mut`
+ /// requires pointers to be in bounds of a valid allocation.
+ ///
+ /// # Safety
+ ///
+ /// `f` must returns a pointer in bounds of the provided pointer.
+ unsafe fn proj<F>(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F) -> *mut F;
+}
+
+// SAFETY: `proj` invokes `f` with valid allocation.
+unsafe impl<T> ProjectField<false> for T {
+ #[inline(always)]
+ unsafe fn proj<F>(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
+ // Create a valid allocation to start projection, as `base` is not necessarily so.
+ let mut place = MaybeUninit::uninit();
+ let place_base = place.as_mut_ptr();
+ let field = f(place_base);
+ // SAFETY: `field` is in bounds from `base` per safety requirement.
+ let offset = unsafe { field.byte_offset_from(place_base) };
+ base.wrapping_byte_offset(offset).cast()
+ }
+}
+
+// SAFETY: vacuously satisfied.
+unsafe impl<T: Deref> ProjectField<true> for T {
+ #[inline(always)]
+ unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
+ build_error!("this function is a guard against `Deref` impl and is never invoked");
+ }
+}
+
+/// Create a projection from a raw pointer.
+///
+/// Supported projections include field projections and index projections.
+/// It is not allowed to project into types that implement custom `Deref` or `Index`.
+///
+/// The macro has basic syntax of `kernel::project_pointer!(ptr, projection)`, where `ptr` is an
+/// expression that evaluates to a raw pointer which serves as the base of projection. `projection`
+/// can be a projection expression of form `.field` (normally identifer, or numeral in case of
+/// tuple structs) or of form `[index]`.
+///
+/// If mutable pointer is needed, the macro input can be prefixed with `mut` keyword, i.e.
+/// `kernel::project_pointer!(mut ptr, projection)`. By default, a const pointer is created.
+///
+/// `project_pointer!` macro can perform both fallible indexing and build-time checked indexing.
+/// `[index]` form performs build-time bounds checking; if compiler fails to prove `[index]` is in
+/// bounds, compilation will fail. `[index]?` can be used to perform runtime bounds checking;
+/// `OutOfBound` error is raised via `?` if the index is out of bounds.
+///
+/// # Examples
+///
+/// Field projections are performed with `.field_name`:
+/// ```
+/// struct MyStruct { field: u32, }
+/// let ptr: *const MyStruct = core::ptr::dangling();
+/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .field);
+///
+/// struct MyTupleStruct(u32, u32);
+/// let ptr: *const MyTupleStruct = core::ptr::dangling();
+/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .1);
+/// ```
+///
+/// Index projections are performed with `[index]`:
+/// ```
+/// let ptr: *const [u8; 32] = core::ptr::dangling();
+/// let field_ptr: *const u8 = kernel::project_pointer!(ptr, [1]);
+/// // This will fail the build.
+/// // kernel::project_pointer!(ptr, [128]);
+/// // This will raise an `OutOfBound` error (which is convertable to `ERANGE`).
+/// // kernel::project_pointer!(ptr, [128]?);
+/// ```
+///
+/// If you need to match on the error instead of propagate, put the invocation inside a closure:
+/// ```
+/// let ptr: *const [u8; 32] = core::ptr::dangling();
+/// let field_ptr: Result<*const u8> = (|| -> Result<_> {
+/// Ok(kernel::project_pointer!(ptr, [128]?))
+/// })();
+/// assert!(field_ptr.is_err());
+/// ```
+///
+/// For mutable pointers, put `mut` as the first token in macro invocation.
+/// ```
+/// let ptr: *mut [(u8, u16); 32] = core::ptr::dangling_mut();
+/// let field_ptr: *mut u16 = kernel::project_pointer!(mut ptr, [1].1);
+/// ```
+#[macro_export]
+macro_rules! project_pointer {
+ (@gen $ptr:ident, ) => {};
+ // Field projection. `$field` needs to be `tt` to support tuple index like `.0`.
+ (@gen $ptr:ident, .$field:tt $($rest:tt)*) => {
+ // SAFETY: the provided closure always return in bounds pointer.
+ let $ptr = unsafe {
+ $crate::projection::ProjectField::proj($ptr, #[inline(always)] |ptr| {
+ // SAFETY: `$field` is in bounds, and no implicit `Deref` is possible (if the
+ // type implements `Deref`, Rust cannot infer the generic parameter `DEREF`).
+ &raw mut (*ptr).$field
+ })
+ };
+ $crate::project_pointer!(@gen $ptr, $($rest)*)
+ };
+ // Fallible index projection.
+ (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
+ let $ptr = $crate::projection::ProjectIndex::get($index, $ptr)
+ .ok_or($crate::projection::OutOfBound)?;
+ $crate::project_pointer!(@gen $ptr, $($rest)*)
+ };
+ // Build-time checked index projection.
+ (@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
+ let $ptr = $crate::projection::ProjectIndex::index($index, $ptr);
+ $crate::project_pointer!(@gen $ptr, $($rest)*)
+ };
+ (mut $ptr:expr, $($proj:tt)*) => {{
+ let ptr = $ptr;
+ $crate::project_pointer!(@gen ptr, $($proj)*);
+ ptr
+ }};
+ ($ptr:expr, $($proj:tt)*) => {{
+ let ptr = $ptr.cast_mut();
+ // We currently always project using mutable pointer, as it is not decided whether `&raw
+ // const` allows the resulting pointer to be mutated (see documentation of `addr_of!`).
+ $crate::project_pointer!(@gen ptr, $($proj)*);
+ ptr.cast_const()
+ }};
+}
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 32e209bc7985..3652b85be545 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -310,16 +310,18 @@ $(obj)/%.lst: $(obj)/%.c FORCE
# The features in this list are the ones allowed for non-`rust/` code.
#
+# - Stable since Rust 1.79.0: `feature(slice_ptr_len)`.
# - Stable since Rust 1.81.0: `feature(lint_reasons)`.
# - Stable since Rust 1.82.0: `feature(asm_const)`,
# `feature(offset_of_nested)`, `feature(raw_ref_op)`.
+# - Stable since Rust 1.84.0: `feature(strict_provenance)`.
# - Stable since Rust 1.87.0: `feature(asm_goto)`.
# - Expected to become stable: `feature(arbitrary_self_types)`.
# - To be determined: `feature(used_with_arg)`.
#
# Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on
# the unstable features in use.
-rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg
+rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,slice_ptr_len,strict_provenance,used_with_arg
# `--out-dir` is required to avoid temporaries being created by `rustc` in the
# current working directory, which may be not accessible in the out-of-tree
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-14 5:33 ` [PATCH 1/4] rust: add projection infrastructure Gary Guo
@ 2026-02-14 9:53 ` Benno Lossin
2026-02-14 10:36 ` Gary Guo
2026-02-14 10:27 ` Danilo Krummrich
2026-02-22 0:57 ` Benno Lossin
2 siblings, 1 reply; 7+ messages in thread
From: Benno Lossin @ 2026-02-14 9:53 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier
Cc: Alexandre Courbot, rust-for-linux, linux-kernel, linux-kbuild
On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
> Add a generic infrastructure for performing field and index projections on
> raw pointers. This will form the basis of performing I/O projections.
>
> Pointers manipulations are intentionally using the safe wrapping variants
> instead of the unsafe variants, as the latter requires pointers to be
> inside an allocation which is not necessarily true for I/O pointers.
>
> This projection macro protects against rogue `Deref` implementation, which
> can causes the projected pointer to be outside the bounds of starting
> pointer. This is extremely unlikely and Rust has a lint to catch this, but
> is unsoundness regardless. The protection works by inducing type inference
> ambiguity when `Deref` is implemented.
>
> The projection macro supports both fallible and infallible index
> projections. These are described in detail inside the documentation.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Cool work!
I was wondering how you'd make this safe and general, but just having a
primitive pointer projection macro makes a lot of sense. We'll have lots
of projection macros that use this under the hood instead of a single
one. I like this as a stop-gap solution until we have projections in the
language.
I have a few comments, with those addressed:
Reviewed-by: Benno Lossin <lossin@kernel.org>
> ---
> rust/kernel/lib.rs | 5 +
> rust/kernel/projection.rs | 269 ++++++++++++++++++++++++++++++++++++++
> scripts/Makefile.build | 4 +-
> 3 files changed, 277 insertions(+), 1 deletion(-)
> create mode 100644 rust/kernel/projection.rs
> +// SAFETY: `proj` invokes `f` with valid allocation.
> +unsafe impl<T> ProjectField<false> for T {
> + #[inline(always)]
> + unsafe fn proj<F>(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
> + // Create a valid allocation to start projection, as `base` is not necessarily so.
> + let mut place = MaybeUninit::uninit();
> + let place_base = place.as_mut_ptr();
> + let field = f(place_base);
> + // SAFETY: `field` is in bounds from `base` per safety requirement.
> + let offset = unsafe { field.byte_offset_from(place_base) };
> + base.wrapping_byte_offset(offset).cast()
> + }
There are several limitations with this impl. I don't think we can do
anything about them, but it's probably good to list them somewhere:
1. We do not support projecting fields of unsized types, so `MyStruct<dyn Trait>`.
(note that slices are supported with `ProjectIndex`)
2. Since this creates a `MaybeUninit<T>` on the stack, only small `T`
are supported. I'm not sure how much of this will be optimized away,
but it might be the case that it is not. Projecting in the same
function call stack multiple times might result in overrunning the
stack pretty quickly.
3. The `wrapping_byte_offset` function generates potentially worse
codegen when `base` points into a real allocation.
> +}
> +
> +// SAFETY: vacuously satisfied.
> +unsafe impl<T: Deref> ProjectField<true> for T {
> + #[inline(always)]
> + unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
> + build_error!("this function is a guard against `Deref` impl and is never invoked");
> + }
> +}
> +
> +/// Create a projection from a raw pointer.
> +///
I'd add a paragraph that explains that the pointer does not need to be
valid in any way. It should also explain that the returned pointer is
only valid when the original pointer was valid.
> +/// Supported projections include field projections and index projections.
> +/// It is not allowed to project into types that implement custom `Deref` or `Index`.
> +///
> +/// The macro has basic syntax of `kernel::project_pointer!(ptr, projection)`, where `ptr` is an
> +/// expression that evaluates to a raw pointer which serves as the base of projection. `projection`
> +/// can be a projection expression of form `.field` (normally identifer, or numeral in case of
> +/// tuple structs) or of form `[index]`.
> +///
> +/// If mutable pointer is needed, the macro input can be prefixed with `mut` keyword, i.e.
> +/// `kernel::project_pointer!(mut ptr, projection)`. By default, a const pointer is created.
> +///
> +/// `project_pointer!` macro can perform both fallible indexing and build-time checked indexing.
> +/// `[index]` form performs build-time bounds checking; if compiler fails to prove `[index]` is in
> +/// bounds, compilation will fail. `[index]?` can be used to perform runtime bounds checking;
> +/// `OutOfBound` error is raised via `?` if the index is out of bounds.
> +///
> +/// # Examples
> +///
> +/// Field projections are performed with `.field_name`:
> +/// ```
> +/// struct MyStruct { field: u32, }
> +/// let ptr: *const MyStruct = core::ptr::dangling();
I would only include one example that uses `dangling` and for the rest
just define a function that projects a raw pointer.
> +/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .field);
> +///
> +/// struct MyTupleStruct(u32, u32);
> +/// let ptr: *const MyTupleStruct = core::ptr::dangling();
> +/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .1);
> +/// ```
> +///
> +/// Index projections are performed with `[index]`:
> +/// ```
> +/// let ptr: *const [u8; 32] = core::ptr::dangling();
> +/// let field_ptr: *const u8 = kernel::project_pointer!(ptr, [1]);
> +/// // This will fail the build.
> +/// // kernel::project_pointer!(ptr, [128]);
> +/// // This will raise an `OutOfBound` error (which is convertable to `ERANGE`).
> +/// // kernel::project_pointer!(ptr, [128]?);
> +/// ```
> +///
> +/// If you need to match on the error instead of propagate, put the invocation inside a closure:
> +/// ```
> +/// let ptr: *const [u8; 32] = core::ptr::dangling();
> +/// let field_ptr: Result<*const u8> = (|| -> Result<_> {
> +/// Ok(kernel::project_pointer!(ptr, [128]?))
> +/// })();
> +/// assert!(field_ptr.is_err());
> +/// ```
> +///
> +/// For mutable pointers, put `mut` as the first token in macro invocation.
> +/// ```
> +/// let ptr: *mut [(u8, u16); 32] = core::ptr::dangling_mut();
> +/// let field_ptr: *mut u16 = kernel::project_pointer!(mut ptr, [1].1);
> +/// ```
> +#[macro_export]
> +macro_rules! project_pointer {
> + (@gen $ptr:ident, ) => {};
> + // Field projection. `$field` needs to be `tt` to support tuple index like `.0`.
> + (@gen $ptr:ident, .$field:tt $($rest:tt)*) => {
> + // SAFETY: the provided closure always return in bounds pointer.
> + let $ptr = unsafe {
> + $crate::projection::ProjectField::proj($ptr, #[inline(always)] |ptr| {
> + // SAFETY: `$field` is in bounds, and no implicit `Deref` is possible (if the
> + // type implements `Deref`, Rust cannot infer the generic parameter `DEREF`).
> + &raw mut (*ptr).$field
> + })
> + };
> + $crate::project_pointer!(@gen $ptr, $($rest)*)
> + };
> + // Fallible index projection.
> + (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
> + let $ptr = $crate::projection::ProjectIndex::get($index, $ptr)
> + .ok_or($crate::projection::OutOfBound)?;
> + $crate::project_pointer!(@gen $ptr, $($rest)*)
> + };
> + // Build-time checked index projection.
> + (@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
> + let $ptr = $crate::projection::ProjectIndex::index($index, $ptr);
> + $crate::project_pointer!(@gen $ptr, $($rest)*)
> + };
> + (mut $ptr:expr, $($proj:tt)*) => {{
> + let ptr = $ptr;
I'd add a type ascription `let ptr: *mut _ = $ptr;`
> + $crate::project_pointer!(@gen ptr, $($proj)*);
> + ptr
> + }};
> + ($ptr:expr, $($proj:tt)*) => {{
> + let ptr = $ptr.cast_mut();
This allows `$ptr` to be a random type with a `cast_mut` function. How
about:
let ptr: *const _ = $ptr;
let ptr: *mut _ = ::core::ptr::cast_mut(ptr);
Cheers,
Benno
> + // We currently always project using mutable pointer, as it is not decided whether `&raw
> + // const` allows the resulting pointer to be mutated (see documentation of `addr_of!`).
> + $crate::project_pointer!(@gen ptr, $($proj)*);
> + ptr.cast_const()
> + }};
> +}
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-14 5:33 ` [PATCH 1/4] rust: add projection infrastructure Gary Guo
2026-02-14 9:53 ` Benno Lossin
@ 2026-02-14 10:27 ` Danilo Krummrich
2026-02-22 0:57 ` Benno Lossin
2 siblings, 0 replies; 7+ messages in thread
From: Danilo Krummrich @ 2026-02-14 10:27 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Nathan Chancellor,
Nicolas Schier, Alexandre Courbot, rust-for-linux, linux-kernel,
linux-kbuild
On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
> diff --git a/rust/kernel/projection.rs b/rust/kernel/projection.rs
> new file mode 100644
> index 000000000000..200d116c39e2
> --- /dev/null
> +++ b/rust/kernel/projection.rs
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Infrastructure for handling projections.
> +
> +use core::{
> + mem::MaybeUninit,
> + ops::Deref, //
> +};
> +
> +use crate::{
> + build_error,
> + error::{
> + code::ERANGE,
> + Error, //
NIT: Why not use prelude instead?
> + }, //
> +};
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-14 9:53 ` Benno Lossin
@ 2026-02-14 10:36 ` Gary Guo
2026-02-14 14:48 ` Benno Lossin
0 siblings, 1 reply; 7+ messages in thread
From: Gary Guo @ 2026-02-14 10:36 UTC (permalink / raw)
To: Benno Lossin
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Nathan Chancellor,
Nicolas Schier, Alexandre Courbot, rust-for-linux, linux-kernel,
linux-kbuild
On 2026-02-14 09:53, Benno Lossin wrote:
> On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
>> Add a generic infrastructure for performing field and index projections on
>> raw pointers. This will form the basis of performing I/O projections.
>>
>> Pointers manipulations are intentionally using the safe wrapping variants
>> instead of the unsafe variants, as the latter requires pointers to be
>> inside an allocation which is not necessarily true for I/O pointers.
>>
>> This projection macro protects against rogue `Deref` implementation, which
>> can causes the projected pointer to be outside the bounds of starting
>> pointer. This is extremely unlikely and Rust has a lint to catch this, but
>> is unsoundness regardless. The protection works by inducing type inference
>> ambiguity when `Deref` is implemented.
>>
>> The projection macro supports both fallible and infallible index
>> projections. These are described in detail inside the documentation.
>>
>> Signed-off-by: Gary Guo <gary@garyguo.net>
>
> Cool work!
>
> I was wondering how you'd make this safe and general, but just having a
> primitive pointer projection macro makes a lot of sense. We'll have lots
> of projection macros that use this under the hood instead of a single
> one. I like this as a stop-gap solution until we have projections in the
> language.
>
> I have a few comments, with those addressed:
>
> Reviewed-by: Benno Lossin <lossin@kernel.org>
>
>> ---
>> rust/kernel/lib.rs | 5 +
>> rust/kernel/projection.rs | 269 ++++++++++++++++++++++++++++++++++++++
>> scripts/Makefile.build | 4 +-
>> 3 files changed, 277 insertions(+), 1 deletion(-)
>> create mode 100644 rust/kernel/projection.rs
>
>> +// SAFETY: `proj` invokes `f` with valid allocation.
>> +unsafe impl<T> ProjectField<false> for T {
>> + #[inline(always)]
>> + unsafe fn proj<F>(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
>> + // Create a valid allocation to start projection, as `base` is not necessarily so.
>> + let mut place = MaybeUninit::uninit();
>> + let place_base = place.as_mut_ptr();
>> + let field = f(place_base);
>> + // SAFETY: `field` is in bounds from `base` per safety requirement.
>> + let offset = unsafe { field.byte_offset_from(place_base) };
>> + base.wrapping_byte_offset(offset).cast()
>> + }
>
> There are several limitations with this impl. I don't think we can do
> anything about them, but it's probably good to list them somewhere:
> 1. We do not support projecting fields of unsized types, so `MyStruct<dyn Trait>`.
> (note that slices are supported with `ProjectIndex`)
> 2. Since this creates a `MaybeUninit<T>` on the stack, only small `T`
> are supported. I'm not sure how much of this will be optimized away,
> but it might be the case that it is not. Projecting in the same
> function call stack multiple times might result in overrunning the
> stack pretty quickly.
I've verified codegen and haven't managed to get this to actually generate `T` on the stack.
LLVM always figures out that the offset is the only thing that matters and optimize away
everything. `memoffset` crate also creates a temporary `MaybeUninit`, and given that it was
very widely used before `offset_of!` is stable, I think we should be able to rely on this being
okay even for large types.
Note that I've taken care to mark everything `#[inline(always)]` when possible, even
closures passed to `proj`.
> 3. The `wrapping_byte_offset` function generates potentially worse
> codegen when `base` points into a real allocation.
I'm highly skeptical that we'll lose any optimization, but this is indeed
a possibility in theory.
>
>> +}
>> +
>> +// SAFETY: vacuously satisfied.
>> +unsafe impl<T: Deref> ProjectField<true> for T {
>> + #[inline(always)]
>> + unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
>> + build_error!("this function is a guard against `Deref` impl and is never invoked");
>> + }
>> +}
>> +
>> +/// Create a projection from a raw pointer.
>> +///
>
> I'd add a paragraph that explains that the pointer does not need to be
> valid in any way. It should also explain that the returned pointer is
> only valid when the original pointer was valid.
>
>> +/// Supported projections include field projections and index projections.
>> +/// It is not allowed to project into types that implement custom `Deref` or `Index`.
>> +///
>> +/// The macro has basic syntax of `kernel::project_pointer!(ptr, projection)`, where `ptr` is an
>> +/// expression that evaluates to a raw pointer which serves as the base of projection. `projection`
>> +/// can be a projection expression of form `.field` (normally identifer, or numeral in case of
>> +/// tuple structs) or of form `[index]`.
>> +///
>> +/// If mutable pointer is needed, the macro input can be prefixed with `mut` keyword, i.e.
>> +/// `kernel::project_pointer!(mut ptr, projection)`. By default, a const pointer is created.
>> +///
>> +/// `project_pointer!` macro can perform both fallible indexing and build-time checked indexing.
>> +/// `[index]` form performs build-time bounds checking; if compiler fails to prove `[index]` is in
>> +/// bounds, compilation will fail. `[index]?` can be used to perform runtime bounds checking;
>> +/// `OutOfBound` error is raised via `?` if the index is out of bounds.
>> +///
>> +/// # Examples
>> +///
>> +/// Field projections are performed with `.field_name`:
>> +/// ```
>> +/// struct MyStruct { field: u32, }
>> +/// let ptr: *const MyStruct = core::ptr::dangling();
>
> I would only include one example that uses `dangling` and for the rest
> just define a function that projects a raw pointer.
>
>> +/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .field);
>> +///
>> +/// struct MyTupleStruct(u32, u32);
>> +/// let ptr: *const MyTupleStruct = core::ptr::dangling();
>> +/// let field_ptr: *const u32 = kernel::project_pointer!(ptr, .1);
>> +/// ```
>> +///
>> +/// Index projections are performed with `[index]`:
>> +/// ```
>> +/// let ptr: *const [u8; 32] = core::ptr::dangling();
>> +/// let field_ptr: *const u8 = kernel::project_pointer!(ptr, [1]);
>> +/// // This will fail the build.
>> +/// // kernel::project_pointer!(ptr, [128]);
>> +/// // This will raise an `OutOfBound` error (which is convertable to `ERANGE`).
>> +/// // kernel::project_pointer!(ptr, [128]?);
>> +/// ```
>> +///
>> +/// If you need to match on the error instead of propagate, put the invocation inside a closure:
>> +/// ```
>> +/// let ptr: *const [u8; 32] = core::ptr::dangling();
>> +/// let field_ptr: Result<*const u8> = (|| -> Result<_> {
>> +/// Ok(kernel::project_pointer!(ptr, [128]?))
>> +/// })();
>> +/// assert!(field_ptr.is_err());
>> +/// ```
>> +///
>> +/// For mutable pointers, put `mut` as the first token in macro invocation.
>> +/// ```
>> +/// let ptr: *mut [(u8, u16); 32] = core::ptr::dangling_mut();
>> +/// let field_ptr: *mut u16 = kernel::project_pointer!(mut ptr, [1].1);
>> +/// ```
>> +#[macro_export]
>> +macro_rules! project_pointer {
>> + (@gen $ptr:ident, ) => {};
>> + // Field projection. `$field` needs to be `tt` to support tuple index like `.0`.
>> + (@gen $ptr:ident, .$field:tt $($rest:tt)*) => {
>> + // SAFETY: the provided closure always return in bounds pointer.
>> + let $ptr = unsafe {
>> + $crate::projection::ProjectField::proj($ptr, #[inline(always)] |ptr| {
>> + // SAFETY: `$field` is in bounds, and no implicit `Deref` is possible (if the
>> + // type implements `Deref`, Rust cannot infer the generic parameter `DEREF`).
>> + &raw mut (*ptr).$field
>> + })
>> + };
>> + $crate::project_pointer!(@gen $ptr, $($rest)*)
>> + };
>> + // Fallible index projection.
>> + (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
>> + let $ptr = $crate::projection::ProjectIndex::get($index, $ptr)
>> + .ok_or($crate::projection::OutOfBound)?;
>> + $crate::project_pointer!(@gen $ptr, $($rest)*)
>> + };
>> + // Build-time checked index projection.
>> + (@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
>> + let $ptr = $crate::projection::ProjectIndex::index($index, $ptr);
>> + $crate::project_pointer!(@gen $ptr, $($rest)*)
>> + };
>> + (mut $ptr:expr, $($proj:tt)*) => {{
>> + let ptr = $ptr;
>
> I'd add a type ascription `let ptr: *mut _ = $ptr;`
>
>> + $crate::project_pointer!(@gen ptr, $($proj)*);
>> + ptr
>> + }};
>> + ($ptr:expr, $($proj:tt)*) => {{
>> + let ptr = $ptr.cast_mut();
>
> This allows `$ptr` to be a random type with a `cast_mut` function. How
> about:
>
> let ptr: *const _ = $ptr;
> let ptr: *mut _ = ::core::ptr::cast_mut(ptr);
I think `<*const _>::cast_mut($ptr)` probably would also do.
Thanks a lot for the review.
Best,
Gary
>
> Cheers,
> Benno
>
>> + // We currently always project using mutable pointer, as it is not decided whether `&raw
>> + // const` allows the resulting pointer to be mutated (see documentation of `addr_of!`).
>> + $crate::project_pointer!(@gen ptr, $($proj)*);
>> + ptr.cast_const()
>> + }};
>> +}
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-14 10:36 ` Gary Guo
@ 2026-02-14 14:48 ` Benno Lossin
0 siblings, 0 replies; 7+ messages in thread
From: Benno Lossin @ 2026-02-14 14:48 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Nathan Chancellor,
Nicolas Schier, Alexandre Courbot, rust-for-linux, linux-kernel,
linux-kbuild
On Sat Feb 14, 2026 at 11:36 AM CET, Gary Guo wrote:
> On 2026-02-14 09:53, Benno Lossin wrote:
>> On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
>>> +// SAFETY: `proj` invokes `f` with valid allocation.
>>> +unsafe impl<T> ProjectField<false> for T {
>>> + #[inline(always)]
>>> + unsafe fn proj<F>(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
>>> + // Create a valid allocation to start projection, as `base` is not necessarily so.
>>> + let mut place = MaybeUninit::uninit();
>>> + let place_base = place.as_mut_ptr();
>>> + let field = f(place_base);
>>> + // SAFETY: `field` is in bounds from `base` per safety requirement.
>>> + let offset = unsafe { field.byte_offset_from(place_base) };
>>> + base.wrapping_byte_offset(offset).cast()
>>> + }
>>
>> There are several limitations with this impl. I don't think we can do
>> anything about them, but it's probably good to list them somewhere:
>> 1. We do not support projecting fields of unsized types, so `MyStruct<dyn Trait>`.
>> (note that slices are supported with `ProjectIndex`)
>> 2. Since this creates a `MaybeUninit<T>` on the stack, only small `T`
>> are supported. I'm not sure how much of this will be optimized away,
>> but it might be the case that it is not. Projecting in the same
>> function call stack multiple times might result in overrunning the
>> stack pretty quickly.
>
> I've verified codegen and haven't managed to get this to actually generate `T` on the stack.
> LLVM always figures out that the offset is the only thing that matters and optimize away
> everything. `memoffset` crate also creates a temporary `MaybeUninit`, and given that it was
> very widely used before `offset_of!` is stable, I think we should be able to rely on this being
> okay even for large types.
Oh that's neat.
> Note that I've taken care to mark everything `#[inline(always)]` when possible, even
> closures passed to `proj`.
Yeah I saw that.
People might still encounter this issue in some fringe situation. I'm
not too worried, since klint can warn about the stack frame being too
large.
Speaking of klint, could it be possible to have a
`#[klint::optimized_away]` attribute that we can put on the `let place`,
klint would then error (or warn) when it's not optimized away (the name
isn't great :)
>
>> 3. The `wrapping_byte_offset` function generates potentially worse
>> codegen when `base` points into a real allocation.
>
> I'm highly skeptical that we'll lose any optimization, but this is indeed
> a possibility in theory.
I remember some Rust codegen expert wanting to use `offset` instead of
`wrapping_offset` in the projection operator of `NonNull` and raw
pointers (the original RFC I think).
>>> + ($ptr:expr, $($proj:tt)*) => {{
>>> + let ptr = $ptr.cast_mut();
>>
>> This allows `$ptr` to be a random type with a `cast_mut` function. How
>> about:
>>
>> let ptr: *const _ = $ptr;
>> let ptr: *mut _ = ::core::ptr::cast_mut(ptr);
>
> I think `<*const _>::cast_mut($ptr)` probably would also do.
That also works.
Cheers,
Benno
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-14 5:33 ` [PATCH 1/4] rust: add projection infrastructure Gary Guo
2026-02-14 9:53 ` Benno Lossin
2026-02-14 10:27 ` Danilo Krummrich
@ 2026-02-22 0:57 ` Benno Lossin
2026-02-22 10:52 ` Gary Guo
2 siblings, 1 reply; 7+ messages in thread
From: Benno Lossin @ 2026-02-22 0:57 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier
Cc: Alexandre Courbot, rust-for-linux, linux-kernel, linux-kbuild
On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
> +#[macro_export]
> +macro_rules! project_pointer {
> + (@gen $ptr:ident, ) => {};
> + // Field projection. `$field` needs to be `tt` to support tuple index like `.0`.
> + (@gen $ptr:ident, .$field:tt $($rest:tt)*) => {
> + // SAFETY: the provided closure always return in bounds pointer.
> + let $ptr = unsafe {
> + $crate::projection::ProjectField::proj($ptr, #[inline(always)] |ptr| {
By the way, how does this avoid `#![feature(stmt_expr_attributes)]`?
Cheers,
Benno
> + // SAFETY: `$field` is in bounds, and no implicit `Deref` is possible (if the
> + // type implements `Deref`, Rust cannot infer the generic parameter `DEREF`).
> + &raw mut (*ptr).$field
> + })
> + };
> + $crate::project_pointer!(@gen $ptr, $($rest)*)
> + };
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/4] rust: add projection infrastructure
2026-02-22 0:57 ` Benno Lossin
@ 2026-02-22 10:52 ` Gary Guo
0 siblings, 0 replies; 7+ messages in thread
From: Gary Guo @ 2026-02-22 10:52 UTC (permalink / raw)
To: Benno Lossin
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Nathan Chancellor,
Nicolas Schier, Alexandre Courbot, rust-for-linux, linux-kernel,
linux-kbuild
On 2026-02-22 00:57, Benno Lossin wrote:
> On Sat Feb 14, 2026 at 6:33 AM CET, Gary Guo wrote:
>> +#[macro_export]
>> +macro_rules! project_pointer {
>> + (@gen $ptr:ident, ) => {};
>> + // Field projection. `$field` needs to be `tt` to support tuple
>> index like `.0`.
>> + (@gen $ptr:ident, .$field:tt $($rest:tt)*) => {
>> + // SAFETY: the provided closure always return in bounds
>> pointer.
>> + let $ptr = unsafe {
>> + $crate::projection::ProjectField::proj($ptr,
>> #[inline(always)] |ptr| {
>
> By the way, how does this avoid `#![feature(stmt_expr_attributes)]`?
I don't know how, but attributes on closures passed direclty to
functions has been stable
for basically ~forever. A quick check says that this is available since
Rust 1.11.
Best,
Gary
>
> Cheers,
> Benno
>
>> + // SAFETY: `$field` is in bounds, and no implicit
>> `Deref` is possible (if the
>> + // type implements `Deref`, Rust cannot infer the
>> generic parameter `DEREF`).
>> + &raw mut (*ptr).$field
>> + })
>> + };
>> + $crate::project_pointer!(@gen $ptr, $($rest)*)
>> + };
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-02-22 10:53 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260214053344.1994776-1-gary@garyguo.net>
2026-02-14 5:33 ` [PATCH 1/4] rust: add projection infrastructure Gary Guo
2026-02-14 9:53 ` Benno Lossin
2026-02-14 10:36 ` Gary Guo
2026-02-14 14:48 ` Benno Lossin
2026-02-14 10:27 ` Danilo Krummrich
2026-02-22 0:57 ` Benno Lossin
2026-02-22 10:52 ` Gary Guo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox