Re: C aggregate passing (Rust kernel policy)

From: Ventura Jack
Date: Mon Feb 24 2025 - 12:09:26 EST


On Mon, Feb 24, 2025 at 5:47 AM Benno Lossin <benno.lossin@xxxxxxxxx> wrote:
>
> On 24.02.25 13:21, Ventura Jack wrote:
> >
> > From what I can see in the documentation, `&UnsafeCell<T>` also does not
> > behave like `T*` in C. In C, especially if "strict aliasing" is turned
> > off in the
> > compiler, `T*` does not have aliasing requirements. You can have multiple
> > C `T*` pointers pointing to the same object, and mutate the same object.
>
> This is true for `&UnsafeCell<T>`. You can have multiple of those and
> mutate the same value via only shared references. Note that
> `UnsafeCell<T>` is `!Sync`, so it cannot be shared across threads, so
> all of those shared references have to be on the same thread. (there is
> the `SyncUnsafeCell<T>` type that is `Sync`, so it does allow for
> across-thread mutations, but that is much more of a footgun, since you
> still have to synchronize the writes/reads)
>
> > The documentation for `UnsafeCell` conversely spends a lot of space
> > discussing invariants and aliasing requirements.
>
> Yes, since normally in Rust, you can either have exactly one mutable
> reference, or several shared references (which cannot be used to mutate
> a value). `UnsafeCell<T>` is essentially a low-level primitive that can
> only be used with `unsafe` to build for example a mutex.
>
> > I do not understand why you claim:
> >
> > "`&UnsafeCell<T>` behaves like `T*` in C,"
> >
> > That statement is false as far as I can figure out, though I have taken it
> > out of context here.
>
> Not sure how you arrived at that conclusion, the following code is legal
> and sound Rust:
>
> let val = UnsafeCell::new(42);
> let x = &val;
> let y = &val;
> unsafe {
> *x.get() = 0;
> *y.get() = 42;
> *x.get() = 24;
> }
>
> You can't do this with `&mut i32`.

I think I see what you mean. The specific Rust "const reference"
`&UnsafeCell<T>` sort of behaves like C `T*`. But you have to get a
Rust "mutable raw pointer" `*mut T` when working with it using
`UnsafeCell::get()`. And you have to be careful with lifetimes if you
do any casts or share it or certain other things. And to dereference a
Rust "mutable raw pointer", you must use unsafe Rust. And you have to
understand aliasing.

One example I tested against MIRI:

use std::cell::UnsafeCell;

fn main() {

let val: UnsafeCell<i32> = UnsafeCell::new(42);
let x: & UnsafeCell<i32> = &val;
let y: & UnsafeCell<i32> = &val;

unsafe {

// UB.
//let pz: & i32 = & *val.get();

// UB.
//let pz: &mut i32 = &mut *val.get();

// Okay.
//let pz: *const i32 = &raw const *val.get();

// Okay.
let pz: *mut i32 = &raw mut *val.get();

let px: *mut i32 = x.get();
let py: *mut i32 = y.get();

*px = 0;
*py += 42;
*px += 24;

println!("x, y, z: {}, {}, {}", *px, *py, *pz);
}
}

It makes sense that the Rust "raw pointers" `*const i32` and `*mut
i32` are fine here, and that taking Rust "references" `& i32` and
`&mut i32` causes UB, since Rust "references" have aliasing rules that
must be followed.

Best, VJ.