Search code examples
rustmemory-alignment

testing for bytemuck casting alignment issues


I have the following struct with conversion logic:

enum TagType {
  DOUBLE,
  BYTE,
}

impl TagType {
  /// byte-size actually
  fn size(&self) -> usize {
    match self {
      TagType::DOUBLE => 8,
      TagType::BYTE => 1,
    }
  }
}
/// Entry with buffered data.
///
/// Should not be used for tags where the data fits in the offset field
/// byte-order of the data should be native-endian in this buffer
#[derive(Debug, PartialEq, Clone)]
pub struct BufferedEntry {
    pub tag_type: TagType,
    pub count: u64,
    pub data: Vec<u8>,
}

impl<'a> TryFrom<&'a BufferedEntry> for &'a [f64] {
    type Error = ();

    fn try_from(val: &'a BufferedEntry) -> Result<Self, Self::Error> {
        if val.data.len() != val.tag_type.size() * usize::try_from(val.count)? {
            return Err(());
        }
        match val.tag_type {
            TagType::DOUBLE => Ok(bytemuck::cast_slice(&val.data()[..])),
            _ => Err(()),
        }
    }
}

Now, this will panic if the input vec is not properly aligned to an 8-byte boundary, which I cannot do. Or well, rkyv has AlignedVec which should be exactly what I need. The question, however, is: How can I test for this? e.g. how could I write a test that allocates a non-64-aligned vector?


Solution

  • As far as I know, you can't force the Allocator to reduce alignment. Normal Allocator's typically have a minimum alignment. Instead, with nightly Rust, you can use your own dubious Allocator:

    #![feature(allocator_api)]
    #![feature(ptr_metadata)]
    
    use std::alloc::{AllocError, Allocator, Layout, System};
    use std::ptr::NonNull;
    
    fn main() {
        let unaligned = Vec::<u8, _>::with_capacity_in(1, MinimizeAlignment);
    
        // Ok.
        assert!(unaligned.as_ptr().addr() % 2 != 0);
    
        // Panic.
        bytemuck::cast_slice::<u8, f64>(&unaligned);
    }
    
    /// Allocations are aligned based on `Layout` (in
    /// that accessing them is perfectly sound), but
    /// not over-aligned unlike typical allocators
    /// which align to several words regardless of
    /// `Layout`.
    struct MinimizeAlignment;
    
    unsafe impl Allocator for MinimizeAlignment {
        fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
            let new =
                Layout::from_size_align(layout.size() + layout.align(), layout.align() * 2).unwrap();
            let slice = System.allocate(new)?;
            let (ptr, meta) = slice.to_raw_parts();
            let slice = unsafe { NonNull::from_raw_parts(ptr.byte_add(layout.align()), meta - layout.align()) };
            Ok(slice)
        }
    
        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
            let new =
                Layout::from_size_align(layout.size() + layout.align(), layout.align() * 2).unwrap();
            System.deallocate(unsafe { ptr.sub(layout.align()) }, new)
        }
    }