Modular_bitfields and alignment

⚓ Rust    📅 2025-12-06    👤 surdeus    👁️ 1      

surdeus

Hi,

I've coded something and asked chatgpt that told me it's wrong to do the following.
In the modular_bitfield crate, the underlying storage is [u8;N], with N the size of the struct.

use modular_bitfield::prelude::*;

        #[bitfield]
        #[derive(Debug, Default)]

        struct MyFrame {
                length:     B24,
                typed:      B8,
                unused:     B8,
                reserved:   B1,
                identifier: B31,
                reserved2:  B1,
                other:      B31
        }

        #[test]

        fn t0() {

                let mut large_vec = vec![0_u8; 1024];

                let my_struct = MyFrame::new()
                        .with_length(65879)
                        .with_typed(45)
                        .with_identifier(589764)
                        .with_other(6);

                let bytes = my_struct.into_bytes();

                let dbg = &large_vec[5..5 + bytes.len()];

                assert_eq!(dbg.len(), bytes.len());

                large_vec[5..5 + bytes.len()].copy_from_slice(&bytes);

                large_vec[51..51 + bytes.len()].copy_from_slice(&bytes);


                let s0 = unsafe {

                        let ptr = large_vec[5..].as_ptr() as *const MyFrame;

                        ptr.as_ref_unchecked()
                };


                assert_eq!(s0.length() as u32, 65879);

                assert_eq!(s0.typed() as u32, 45);

                assert_eq!(s0.identifier() as u32, 589764);

                assert_eq!(s0.other() as u32, 6);


                let s0 = unsafe {

                        let ptr = large_vec[51..].as_ptr() as *const MyFrame;

                        ptr.as_ref_unchecked()
                };

                assert_eq!(s0.length() as u32, 65879);

                assert_eq!(s0.typed() as u32, 45);

                assert_eq!(s0.identifier() as u32, 589764);

                assert_eq!(s0.other() as u32, 6);
        }

This is the struct definition expanded macro:

#[derive(Default)]

struct MyFrame {
        bytes: [::core::primitive::u8;
                (((<B24 as ::modular_bitfield::Specifier>::BITS
                        + <B8 as ::modular_bitfield::Specifier>::BITS
                        + <B8 as ::modular_bitfield::Specifier>::BITS
                        + <B1 as ::modular_bitfield::Specifier>::BITS
                        + <B31 as ::modular_bitfield::Specifier>::BITS
                        + <B1 as ::modular_bitfield::Specifier>::BITS
                        + <B31 as ::modular_bitfield::Specifier>::BITS
                        - 1)
                        / 8)
                        + 1)
                        * 8
                        / 8]
}

const _: () = {

        impl ::modular_bitfield::private::checks::CheckTotalSizeMultipleOf8 for MyFrame {
                type Size = ::modular_bitfield::private::checks::TotalSize<
                        ::modular_bitfield::private::checks::BitCount<
                                {

                                        (<B24 as ::modular_bitfield::Specifier>::BITS
                                                + <B8 as ::modular_bitfield::Specifier>::BITS
                                                + <B8 as ::modular_bitfield::Specifier>::BITS
                                                + <B1 as ::modular_bitfield::Specifier>::BITS
                                                + <B31 as ::modular_bitfield::Specifier>::BITS
                                                + <B1 as ::modular_bitfield::Specifier>::BITS
                                                + <B31 as ::modular_bitfield::Specifier>::BITS)
                                                % 8
                                }
                        >
                >;
        }
};

The code is to be run on x86_64 arches only.

So far, the results are consistent and the test completes without issue. Chat tells me that it's very dangerous and INCORRECT and that I should use the from_bytes method, which incurs a copy. But that fact that the underlying storage is a [u8;N] tells me that the crate handles unaligned reads perfectly.

So I went ahead and found that all the paths are selected at compile time and that there is no risk.

pub fn read_specifier<T>(bytes: &[u8], offset: usize) -> <T as Specifier>::Bytes
where
    T: Specifier,
    PushBuffer<T::Bytes>: Default + PushBits,
{
    let end = offset + <T as Specifier>::BITS;
    let ls_byte = offset / 8; // compile-time
    let ms_byte = (end - 1) / 8; // compile-time

    // Truncation is always valid due to mod 8 value range
    #[allow(clippy::cast_possible_truncation)]
    let lsb_offset = (offset % 8) as u32; // compile-time
    #[allow(clippy::cast_possible_truncation)]
    let msb_offset = (end % 8) as u32; // compile-time
    let msb_offset = if msb_offset == 0 { 8 } else { msb_offset };

    let mut buffer = push_buffer::<T>();

    if lsb_offset == 0 && msb_offset == 8 {
        // Edge-case for whole bytes manipulation.
        for byte in bytes[ls_byte..=ms_byte].iter().rev() {
            buffer.push_bits(8, *byte);
        }
    } else {
        if ls_byte != ms_byte {
            // Most-significant byte
            buffer.push_bits(msb_offset, bytes[ms_byte]);
        }
        if ms_byte - ls_byte >= 2 {
            // Middle bytes
            for byte in bytes[(ls_byte + 1)..ms_byte].iter().rev() {
                buffer.push_bits(8, *byte);
            }
        }
        if ls_byte == ms_byte {
            buffer.push_bits(
                u32::try_from(<T as Specifier>::BITS).unwrap(),
                bytes[ls_byte] >> lsb_offset,
            );
        } else {
            buffer.push_bits(8 - lsb_offset, bytes[ls_byte] >> lsb_offset);
        }
    }
    buffer.into_bytes()
}

Look, this code is to be used in a very performance sensitive context, we are deep into unsafe territory here. Is my understanding ok? I think Chat is overly conservative here.

1 post - 1 participant

Read full topic

🏷️ Rust_feed