Vec<MaybeUninit<u8>> aligned to 128-bit boundaries?

Is it possible to have a Vec<MaybeUninit> but require that the 0-th element is aligned to a 128-bit boundary ?

this should work

#[repr(C, align(128))]
struct MyU8(u8);

let vec: Vec<MaybeUninit<MyU8>> = Vec::new();
1 Like

@Mokuz That aligns it to 128-byte boundaries, not 128-bit boundaries.

I'd like the 0-th element to be on a 128-bit boundary, and the rest to be contiguous in memory. Does above create a 127-byte gap between adjacent elements ?

Note also that this will align all MyU8's to the specified boundary, not just the first one. For example, std::mem::size_of::<[MyU8; 2]>() == 256.

2 Likes

What do you need it for? You say a vector, so do you need resizing?

1 Like

Stack for a Lisp interpreter, where types are {i, u, f} x {32, 64}, [f32; 4] (XMM registers), user defined structs (C style).

One way to handle this would be to use Vec<MaybeUninit<u128>>, and then handle the multi-packed items yourself.

2 Likes

Yeah, I'm starting to think this is the simplest solution.

Another option is to go for something like this:

use std::alloc::Layout;
use std::mem::MaybeUninit;
use std::ops::{Deref, DerefMut};

const ALIGN: usize = 128 / 8;

pub struct AlignedBytes {
    alloc: *mut u8,
    len: usize,
}

impl AlignedBytes {
    pub fn new(len: usize) -> Self {
        assert!(len > 0);
        
        let layout = Layout::from_size_align(len, ALIGN).unwrap();
        let alloc = unsafe { std::alloc::alloc(layout) };

        if alloc.is_null() {
            panic!("Alloc failed.");
        }
        
        Self {
            alloc,
            len,
        }
    }
    
    pub fn resize(&mut self, len: usize) {
        assert!(len > 0);
        
        let old_layout = Layout::from_size_align(self.len, ALIGN).unwrap();
        let new_alloc = unsafe { std::alloc::realloc(self.alloc, old_layout, len) };
        
        if new_alloc.is_null() {
            panic!("Resize failed.");
        } else {
            self.alloc = new_alloc;
            self.len = len;
        }
    }
}

impl Drop for AlignedBytes {
    fn drop(&mut self) {
        let layout = Layout::from_size_align(self.len, ALIGN).unwrap();
        unsafe {
            std::alloc::dealloc(self.alloc, layout);
        }
    }
}

impl Deref for AlignedBytes {
    type Target = [MaybeUninit<u8>];
    
    fn deref(&self) -> &Self::Target {
        unsafe {
            std::slice::from_raw_parts(self.alloc.cast(), self.len)
        }
    }
}

impl DerefMut for AlignedBytes {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe {
            std::slice::from_raw_parts_mut(self.alloc.cast(), self.len)
        }
    }
}
5 Likes

You could also do the memory management yourself with something like this:

#[repr(C, align(4096))]  // Most memory systems use 4k pages
struct StackPage {
    data: [MaybeUninit<u8>; 3968], // 4k-128
    prev: Option<Box<StackPage>>,
    len: usize
}

Is it possible to get the address of a reference?

If so, using my_vec[16-addr%16..] should work, if you don't really need the Vec aligned.

Also, it looks like there are crates for aligned allocators...

Be aware that u128 is usually aligned like u64.

2 Likes

I understand that this is more a LLVM issue than a Rust issue. What I don't understand: is this fixed now?

Are you using nightly?

If so, why not use a custom allocator?

#![feature(allocator_api)]
use std::alloc::{AllocError, Allocator, Layout, System};
use std::mem::MaybeUninit;
use std::ptr::NonNull;

struct AlignedSystemAlloc<const N: usize> {}

unsafe impl<const N: usize> Allocator for AlignedSystemAlloc<N> {
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        System.allocate(layout.align_to(N).unwrap_or(layout))
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        System.deallocate(ptr, layout)
    }
}

fn main() {
    let vec = Vec::<MaybeUninit<u8>, _>::with_capacity_in(10, AlignedSystemAlloc::<128> {});
    assert_eq!(vec.as_ptr() as usize % 128, 0)
}

Playground link

1 Like

I'm pretty sure this is unsound because the layouts won't match.

In allocate you align the input layout but in deallocate you don't

this should fix it

unsafe impl<const N: usize> Allocator for AlignedSystemAlloc<N> {
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        System.allocate(layout.align_to(N).map_err(|_| AllocError)?) // here '1
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        System.deallocate(
            ptr,
            // NOTE: it's ub to deallocate a pointer with wrong layout
            layout.align_to(N).unwrap_or_else(|_| unsafe {
                // SAFETY: if `align_to(N)` passed once at '1 it should pass again
                core::hint::unreachable_unchecked()
            }),
        )
    }
}

Miri didn't complain about your code but it doesn't complain about mine either so I hope I'm right.

You are right of course, thanks!

Though it should work with layout.align_to(N).unwrap_or(layout) in both methods as well as it uses the same layout for alloc and dealloc.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.