Re: C aggregate passing (Rust kernel policy)

Benno Lossin
2025-02-24T12:47:47Z

On 24.02.25 13:21, Ventura Jack wrote:
> On Mon, Feb 24, 2025 at 3:31 AM Benno Lossin <benno.lossin@proton.me> wrote:
>>
>> On 24.02.25 10:57, Ventura Jack wrote:
>>>
>>> In regards to `UnsafeCell`, I believe that you are correct in regards
>>> to mutability. However, if I understand you correctly, and if I
>>> am not mistaken, I believe that you are wrong about `UnsafeCell`
>>> making it possible to opt-out of the aliasing rules. And thus that
>>> `UnsafeCell` does not behave like `T*` in C.
>>
>> `UnsafeCell<T>` does not behave like `T*` in C, because it isn't a
>> pointer. Like Gary said, `&UnsafeCell<T>` behaves like `T*` in C, while
>> `&mut UnsafeCell<T>` does not. That is what you quote from the docs
>> below. (Those ampersands mark references in Rust, pointers that have
>> additional guarantees [1])
>
> 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`.

> Is the argument in regards to mutability? But `T*` in C
> allows mutability. If you looked at C++ instead of C, maybe a `const`
> pointer would be closer in semantics and behavior.
>
>> below. (Those ampersands mark references in Rust, pointers that have
>> additional guarantees [1])
>>
>> [omitted]
>>
>> [1]: https://doc.rust-lang.org/std/primitive.reference.html
>
> There is also https://doc.rust-lang.org/reference/types/pointer.html .

Yes that is the description of all primitive pointer types. Both
references and raw pointers.

> But, references must follow certain aliasing rules, and in unsafe Rust,
> it is the programmer that has the burden of upholding those aliasing rules,
> right?

Indeed.

>> For disabling the uniqueness guarantee for `&mut`, we use an official
>> "hack" that the Rust language developers are working on replacing with
>> a better mechanism (this was also mentioned by Gary above).
>
> Are you referring to `Opaque`?

I am referring to the hack used by `Opaque`, it is `!Unpin` which
results in `&mut Opaque<T>` not having the `noalias` attribute.

>>> Documentation and related links for `Opaque`:
>>> https://rust.docs.kernel.org/kernel/types/struct.Opaque.html
>>> https://rust.docs.kernel.org/src/kernel/types.rs.html#307-310
>>> https://github.com/Rust-for-Linux/pinned-init
>>>
>>> It uses `UnsafeCell`, Rust "pinning", and the Rust for Linux library
>>> "pinned-init".
>>
>> pinned-init is not specific to `Opaque` and not really relevant with
>> respect to discussing aliasing guarantees.
>
> Is `Opaque` really able to avoid aliasing requirements for users,
> without internally using "pinned-init"/derivative or the pinning
> feature used in its implementation?

Yes, you can write `Opaque<T>` without using pinned-init. The hack
described above uses `PhantomPinned` to make `Opaque<T>: !Unpin`.

>>> "pinned-init" uses a number of experimental, unstable and nightly
>>> features of Rust.
>>
>> This is wrong. It uses no unstable features when you look at the version
>> in-tree (at `rust/kernel/init.rs`). The user-space version uses a single
>> unstable feature: `allocator_api` for accessing the `AllocError` type
>> from the standard library. You can disable the `alloc` feature and use
>> it on a stable compiler as written in the readme.
>
> Interesting, I did not realize that the Rust for Linux project uses
> a fork or derivative of "pinned-init" in-tree, not "pinned-init" itself.

Yes, that is something that I am working on at the moment.

> What I can read in the README.md:
> https://github.com/Rust-for-Linux/pinned-init/tree/main
>
> "Nightly Needed for alloc feature
>
> This library requires the allocator_api unstable feature
> when the alloc feature is enabled and thus this feature
> can only be used with a nightly compiler. When enabling
> the alloc feature, the user will be required to activate
> allocator_api as well.
>
> The feature is enabled by default, thus by default
> pinned-init will require a nightly compiler. However, using
> the crate on stable compilers is possible by disabling alloc.
> In practice this will require the std feature, because stable
> compilers have neither Box nor Arc in no-std mode."
>
> Rust in Linux uses no_std, right? So Rust in Linux would not be
> able to use the original "pinned_init" library as it currently is without
> using currently nightly/unstable features, until the relevant feature(s)
> is stabilized.

Yes, Rust for Linux uses `#![no_std]` (and also has its own alloc), so
it an use the stable version of pinned-init. However, there are several
differences between the current in-tree version and the user-space
version. I am working on some patches that fix that.

>>> Working with the library implementation requires having a good
>>> understanding of unsafe Rust and many advanced features of Rust.
>>
>> pinned-init was explicitly designed such that you *don't* have to write
>> unsafe code for initializing structures that require pinning from the
>> get-go (such as the kernel's mutex).
>
> Sorry, I sought to convey that I was referring to the internal library
> implementation, not the usage of the library.

Ah I see.

> For the library implementation, do you agree that a good
> understanding of unsafe Rust and many advanced features
> are required to work with the library implementation? Such as
> pinning?

Yes I agree.

---
Cheers,
Benno

>>> `Opaque` looks interesting. Do you know if it will become a more
>>> widely used abstraction outside the Linux kernel?
>>
>> Only in projects that do FFI with C/C++ (or other such languages).
>> Outside of that the `Opaque` type is rather useless, since it disables
>> normal guarantees and makes working with the inner type annoying.
>
> Interesting.
>
> Best, VJ.