Skip to content

Commit a887786

Browse files
nbdd0121ojeda
authored andcommitted
rust: ptr: add panicking index projection variant
There have been a few cases where the programmer knows that the indices are in bounds but the compiler cannot deduce that. This is also compiler-version-dependent, so using build indexing here can be problematic. On the other hand, it is also not ideal to use the fallible variant, as it adds an error handling path that is never hit. Add a new panicking index projection for this scenario. Like all panicking operations, this should be used carefully only in cases where the user knows the index is going to be in bounds, and panicking would indicate something is catastrophically wrong. To signify this, require users to explicitly denote the type of index being used. The existing two types of index projections also gain the keyworded version, which will be the recommended way going forward. The keyworded syntax also paves the way of perhaps adding more flavors in the future, e.g. `unsafe` index projection. However, unless the code is extremely performance sensitive and bounds checking cannot be tolerated, the panicking variant is safer and should be preferred, so it will be left to the future when demand arises. Signed-off-by: Gary Guo <gary@garyguo.net> Reviewed-by: Alexandre Courbot <acourbot@nvidia.com> Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Acked-by: Danilo Krummrich <dakr@kernel.org> Link: https://patch.msgid.link/20260602-projection-syntax-rework-v2-3-6989470f5440@garyguo.net [ Fixed broken intra-doc link. Added a few extra intra-doc links. Reworded some docs. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
1 parent e42cfd4 commit a887786

2 files changed

Lines changed: 85 additions & 16 deletions

File tree

rust/kernel/dma.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,9 @@ macro_rules! dma_write {
12071207
(@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => {
12081208
$crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*])
12091209
};
1210+
(@parse [$dma:expr] [$($proj:tt)*] [[$flavor:ident: $index:expr] $($rest:tt)*]) => {
1211+
$crate::dma_write!(@parse [$dma] [$($proj)* [$flavor: $index]] [$($rest)*])
1212+
};
12101213
(@parse [$dma:expr] [$($proj:tt)*] [[$index:expr]? $($rest:tt)*]) => {
12111214
$crate::dma_write!(@parse [$dma] [$($proj)* [$index]?] [$($rest)*])
12121215
};

rust/kernel/ptr/projection.rs

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ impl From<OutOfBound> for Error {
2626
///
2727
/// # Safety
2828
///
29-
/// For given input pointer `slice` and return value `output`, the implementation of `build_index`
30-
/// and `get` (if [`Some`] is returned) must ensure that:
29+
/// For a given input pointer `slice` and return value `output`, the implementation of `index`,
30+
/// `build_index` and `get` (if [`Some`] is returned) must ensure that:
3131
/// - `output` has the same provenance as `slice`;
3232
/// - `output.byte_offset_from(slice)` is between 0 to
3333
/// `KnownSize::size(slice) - KnownSize::size(output)`.
3434
///
35-
/// This means that if the input pointer is valid, then pointer returned by `get` or `build_index`
36-
/// is also valid.
35+
/// This means that if the input pointer is valid, then the pointer returned by `get`, `index`
36+
/// or `build_index` is also valid.
3737
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be used to index `{T}`")]
3838
#[doc(hidden)]
3939
pub unsafe trait ProjectIndex<T: ?Sized>: Sized {
@@ -42,6 +42,9 @@ pub unsafe trait ProjectIndex<T: ?Sized>: Sized {
4242
/// Returns an index-projected pointer, if in bounds.
4343
fn get(self, slice: *mut T) -> Option<*mut Self::Output>;
4444

45+
/// Returns an index-projected pointer; panic if out of bounds.
46+
fn index(self, slice: *mut T) -> *mut Self::Output;
47+
4548
/// Returns an index-projected pointer; fail the build if it cannot be proved to be in bounds.
4649
#[inline(always)]
4750
fn build_index(self, slice: *mut T) -> *mut Self::Output {
@@ -66,6 +69,11 @@ where
6669
<I as ProjectIndex<[T]>>::get(self, slice)
6770
}
6871

72+
#[inline(always)]
73+
fn index(self, slice: *mut [T; N]) -> *mut Self::Output {
74+
<I as ProjectIndex<[T]>>::index(self, slice)
75+
}
76+
6977
#[inline(always)]
7078
fn build_index(self, slice: *mut [T; N]) -> *mut Self::Output {
7179
<I as ProjectIndex<[T]>>::build_index(self, slice)
@@ -85,6 +93,16 @@ unsafe impl<T> ProjectIndex<[T]> for usize {
8593
Some(slice.cast::<T>().wrapping_add(self))
8694
}
8795
}
96+
97+
#[inline(always)]
98+
fn index(self, slice: *mut [T]) -> *mut T {
99+
// Leverage Rust built-in operators for bounds checking.
100+
// SAFETY: All non-null and aligned pointers are valid for ZST read.
101+
let zst_slice =
102+
unsafe { core::slice::from_raw_parts::<()>(core::ptr::dangling(), slice.len()) };
103+
let () = zst_slice[self];
104+
slice.cast::<T>().wrapping_add(self)
105+
}
88106
}
89107

90108
// SAFETY: `get`-returned pointer has the same provenance as `slice` and the offset is checked to
@@ -103,6 +121,18 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::Range<usize> {
103121
new_len,
104122
))
105123
}
124+
125+
#[inline(always)]
126+
fn index(self, slice: *mut [T]) -> *mut [T] {
127+
// Leverage Rust built-in operators for bounds checking.
128+
// SAFETY: All non-null and aligned pointers are valid for ZST read.
129+
let zst_slice =
130+
unsafe { core::slice::from_raw_parts::<()>(core::ptr::dangling(), slice.len()) };
131+
_ = zst_slice[self.clone()];
132+
133+
// SAFETY: Bounds checked.
134+
unsafe { self.get(slice).unwrap_unchecked() }
135+
}
106136
}
107137

108138
// SAFETY: Safety requirement guaranteed by the forwarded impl.
@@ -113,6 +143,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeTo<usize> {
113143
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
114144
(0..self.end).get(slice)
115145
}
146+
147+
#[inline(always)]
148+
fn index(self, slice: *mut [T]) -> *mut [T] {
149+
(0..self.end).index(slice)
150+
}
116151
}
117152

118153
// SAFETY: Safety requirement guaranteed by the forwarded impl.
@@ -123,6 +158,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFrom<usize> {
123158
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
124159
(self.start..slice.len()).get(slice)
125160
}
161+
162+
#[inline(always)]
163+
fn index(self, slice: *mut [T]) -> *mut [T] {
164+
(self.start..slice.len()).index(slice)
165+
}
126166
}
127167

128168
// SAFETY: `get` returned the pointer as is, so it always has the same provenance and offset of 0.
@@ -133,6 +173,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFull {
133173
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
134174
Some(slice)
135175
}
176+
177+
#[inline(always)]
178+
fn index(self, slice: *mut [T]) -> *mut [T] {
179+
slice
180+
}
136181
}
137182

138183
/// A helper trait to perform field projection.
@@ -210,10 +255,13 @@ unsafe impl<T: Deref> ProjectField<true> for T {
210255
/// If a mutable pointer is needed, the macro input can be prefixed with the `mut` keyword, i.e.
211256
/// `kernel::ptr::project!(mut ptr, projection)`. By default, a const pointer is created.
212257
///
213-
/// `ptr::project!` macro can perform both fallible indexing and build-time checked indexing.
214-
/// `[index]` form performs build-time bounds checking; if compiler fails to prove `[index]` is in
215-
/// bounds, compilation will fail. `[index]?` can be used to perform runtime bounds checking;
216-
/// `OutOfBound` error is raised via `?` if the index is out of bounds.
258+
/// The `ptr::project!` macro can perform both fallible indexing and build-time checked indexing.
259+
/// The syntax is of the form `[<flavor>: index]` where `flavor` indicates the way of handling
260+
/// index out-of-bounds errors.
261+
/// - `try` will raise an [`OutOfBound`] error (which is convertible to [`ERANGE`]).
262+
/// - `build` will use the [`build_assert!`] mechanism to have the compiler validate the index is
263+
/// in bounds.
264+
/// - `panic` will cause a Rust [`panic!`] if the index goes out of bounds.
217265
///
218266
/// # Examples
219267
///
@@ -231,17 +279,21 @@ unsafe impl<T: Deref> ProjectField<true> for T {
231279
/// }
232280
/// ```
233281
///
234-
/// Index projections are performed with `[index]`:
282+
/// Index projections are performed with `[<flavor>: index]`, where `flavor` is `try`, `build` or
283+
/// `panic`:
235284
///
236285
/// ```
237286
/// fn proj(ptr: *const [u8; 32]) -> Result {
238-
/// let field_ptr: *const u8 = kernel::ptr::project!(ptr, [1]);
287+
/// let field_ptr: *const u8 = kernel::ptr::project!(ptr, [build: 1]);
239288
/// // The following invocation, if uncommented, would fail the build.
240289
/// //
241-
/// // kernel::ptr::project!(ptr, [128]);
290+
/// // kernel::ptr::project!(ptr, [build: 128]);
242291
///
243292
/// // This will raise an `OutOfBound` error (which is convertible to `ERANGE`).
244-
/// kernel::ptr::project!(ptr, [128]?);
293+
/// kernel::ptr::project!(ptr, [try: 128]);
294+
///
295+
/// // This will panic at runtime if executed.
296+
/// kernel::ptr::project!(ptr, [panic: 128]);
245297
/// Ok(())
246298
/// }
247299
/// ```
@@ -251,7 +303,7 @@ unsafe impl<T: Deref> ProjectField<true> for T {
251303
/// ```
252304
/// let ptr: *const [u8; 32] = core::ptr::dangling();
253305
/// let field_ptr: Result<*const u8> = (|| -> Result<_> {
254-
/// Ok(kernel::ptr::project!(ptr, [128]?))
306+
/// Ok(kernel::ptr::project!(ptr, [try: 128]))
255307
/// })();
256308
/// assert!(field_ptr.is_err());
257309
/// ```
@@ -260,7 +312,7 @@ unsafe impl<T: Deref> ProjectField<true> for T {
260312
///
261313
/// ```
262314
/// let ptr: *mut [(u8, u16); 32] = core::ptr::dangling_mut();
263-
/// let field_ptr: *mut u16 = kernel::ptr::project!(mut ptr, [1].1);
315+
/// let field_ptr: *mut u16 = kernel::ptr::project!(mut ptr, [build: 1].1);
264316
/// ```
265317
#[macro_export]
266318
macro_rules! project_pointer {
@@ -283,16 +335,30 @@ macro_rules! project_pointer {
283335
$crate::ptr::project!(@gen $ptr, $($rest)*)
284336
};
285337
// Fallible index projection.
286-
(@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
338+
(@gen $ptr:ident, [try: $index:expr] $($rest:tt)*) => {
287339
let $ptr = $crate::ptr::projection::ProjectIndex::get($index, $ptr)
288340
.ok_or($crate::ptr::projection::OutOfBound)?;
289341
$crate::ptr::project!(@gen $ptr, $($rest)*)
290342
};
343+
// Panicking index projection.
344+
(@gen $ptr:ident, [panic: $index:expr] $($rest:tt)*) => {
345+
let $ptr = $crate::ptr::projection::ProjectIndex::index($index, $ptr);
346+
$crate::ptr::project!(@gen $ptr, $($rest)*)
347+
};
291348
// Build-time checked index projection.
292-
(@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
349+
(@gen $ptr:ident, [build: $index:expr] $($rest:tt)*) => {
293350
let $ptr = $crate::ptr::projection::ProjectIndex::build_index($index, $ptr);
294351
$crate::ptr::project!(@gen $ptr, $($rest)*)
295352
};
353+
354+
// For compatibility
355+
(@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
356+
$crate::ptr::project!(@gen $ptr, [try: $index] $($rest)*)
357+
};
358+
(@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
359+
$crate::ptr::project!(@gen $ptr, [build: $index] $($rest)*)
360+
};
361+
296362
(mut $ptr:expr, $($proj:tt)*) => {{
297363
let ptr: *mut _ = $ptr;
298364
$crate::ptr::project!(@gen ptr, $($proj)*);

0 commit comments

Comments
 (0)