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

Reply via email to