Re: [PATCH v2 02/16] rust: io: add `IoRepr` trait

"Gary Guo" <[email protected]>
Newsgroups dev.linux.lists.nova-gpu,dev.linux.lists.driver-core,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.kernel.vger.rust-for-linux
Message-ID <[email protected]>
On Mon Aug 10, 2026 at 10:30 AM BST, Alexandre Courbot wrote:
> On Thu Aug 6, 2026 at 1:35 AM JST, Gary Guo wrote:
>> +/// Trait indicating the underlying primitive types to be used for I/O operations.
>> +///
>> +/// Implementing trait allows arbitrary types to be used for I/O operations, not just raw
>> +/// primitives.
>> +///
>> +/// The layout of the type and the underlying primitive must match; this is enforced via const
>> +/// assertions when I/O methods are used, as the type system cannot represent this.
>> +/// [`IoRepr::from_repr`] and [`IoRepr::into_expr`] can be overridden for conversions, however it
>> +/// should be noted that they are only invoked on value read/write operations and are not invoked
>> +/// on byte operations such as [`Io::copy_read`].
>> +///
>> +/// # Examples
>> +///
>> +/// ```
>> +/// # use kernel::io::*;
>> +/// #[repr(transparent)]
>> +/// #[derive(FromBytes, IntoBytes)]
>> +/// pub struct MyNewType(u32);
>> +///
>> +/// impl IoRepr for MyNewType {
>> +///     type Repr = u32;
>> +/// }
>> +///
>> +/// #[repr(C)]
>> +/// pub struct MyStruct {
>> +///     raw: u32,
>> +///     new_type: MyNewType,
>> +/// }
>> +///
>> +/// # fn test(mmio: Mmio<'_, MyStruct>) {
>> +/// // let mmio: Mmio<'_, MyStruct>;
>> +/// let val: u32 = io_read!(mmio, .raw); // Raw primitive read
>> +/// io_write!(mmio, .raw, val);          // Raw primitve write
>> +/// let val: MyNewType = io_read!(mmio, .new_type); // Read via `IoRepr`.
>> +/// io_write!(mmio, .new_type, val);                // Write via `IoRepr`.
>> +/// # }
>> +/// ```
>> +pub trait IoRepr: FromBytes + IntoBytes + Sized {
>> +    /// The backing I/O capable type.
>> +    type Repr: FromBytes + IntoBytes;
>> +
>> +    /// Convert from [`IoRepr::Repr`] to `Self`.
>> +    #[inline(always)]
>> +    fn from_repr(repr: Self::Repr) -> Self {
>> +        transmute_neo(repr)
>> +    }
>> +
>> +    /// Convert from `Self` to [`IoRepr::Repr`].
>> +    #[inline(always)]
>> +    fn into_repr(this: Self) -> Self::Repr {
>> +        transmute_neo(this)
>> +    }
>> +}
>> +
>> +macro_rules! impl_io_repr {
>> +    ($($ty:ty => $backing:ty,)*) => {
>> +        $(impl IoRepr for $ty {
>> +            type Repr = $backing;
>> +        })*
>> +    };
>> +}
>> +
>> +impl_io_repr! {
>> +    u8 => u8,
>> +    u16 => u16,
>> +    u32 => u32,
>> +    u64 => u64,
>> +    i8 => u8,
>> +    i16 => u16,
>> +    i32 => u32,
>> +    i64 => u64,
>> +}
>
> ... and `IoRepr` and its implementations for primitive types should also
> be part of `transmute` IMHO (after being renamed to e.g. `Repr`), for
> there is nothing I/O exclusive to it. It just indicates that one type
> can be represented by another, a property that is again useful outside
> of I/O.
>
> That way `bitfield` gets a dependency on `transmute` rather than `io`,
> which doesn't break layering.
>
> In order to avoid `Repr::Repr` we can also rename the associated type to
> `Raw` and update the method names accordingly to `from_raw`/`into_raw` -
> which would have allowed us to remove the `bitfield` methods of the same
> name if they weren't needed in const context! But at least it makes
> things align nicely.

So I am working on this design for `kernel::mem`:

    pub const unsafe fn transmute_neo_unchecked<Src, Dst>(val: Src) -> Dst { ... }
    pub const fn transmute_neo<Src: IntoBytes, Dst: FromBytes>(val: Src) -> Dst { ... }

    // Round-trip transmutable.
    pub unsafe trait AsRepr: Sized {
        /// Primitive representation of this type.
        type Repr;

        unsafe fn from_repr_unchecked(repr: Self::Repr) -> Self { ... }
        fn into_repr(this: Self) -> Self::Repr { ... }

        // Name from conceptually having this (not actually added)
        // fn as_repr(this: &Self) -> &Self::Repr { ... }
    }

    // Bi-directional transmutable.
    pub unsafe trait AsReprMut: AsRepr {
        fn from_repr(repr: Self::Repr) -> Self { ... }

        // Name from conceptually having this (not actually added)
        // fn as_repr_mut(this: &mut Self) -> &mut Self::Repr { ... }
    }

This design works well with both `Atomic` and `Io`: atomic can use
`T: AsRepr<Repr: AtomicImpl>` while I/O can use
`T: AsReprMut<Repr>, IO: IoCapable<<T as AsReprMut>::Repr>`.

One thing that I ran into is with signed/unsignedness of repr. Say for example
you have

    #[repr(i8)]
    enum Foo {
        ...
    }

Then it'd be more natural to have

    type Repr = i8;

Similarly if user specified u8, then we would naturally want to put u8 there.

However, to avoid duplicating signed/unsignedness code, `Atomic` and `Io` would
need to pick a preferred signedness. So far, `Atomic` uses signed integers,
while `Io` uses unsigned integers.

Would it make sense to canonicalize everything to unsigned integers (even if
users explicitly specify signed integer) for this trait, and convert `Atomic` to
use unsigned types as impl (we can always cast sign back in the impl before
calling C)?

Best,
Gary
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.