3

Recently, I wanted to allocate a vector of u64s aligned to a 128-byte boundaries so I could use AVX512F functions. My first pass of a function to copy any Vec<u64> to an aligned Vec<u64>:

fn copy_to_aligned(inp: Vec<u64>) -> Vec<u64> {
    // note: when the returned vec is freed, the alignment / layout information
    //       will be lost. The internal memory will be freed using the alignment
    //       of a u64.
    let aligned_layout = Layout::from_size_align(inp.len() * size_of::<u64>(), 128)
        .unwrap();
    let new_vec = unsafe {
        let new_vec_mem = alloc_zeroed(aligned_layout) as *mut u64;
        copy_nonoverlapping(inp.as_ptr(), new_vec_mem, inp.len());
        Vec::from_raw_parts(new_vec_mem, inp.len(), inp.len())
    };

    return new_vec;
}

I now realize that this code is wrong: while the new vector is created with aligned memory and the appropriate content, when the new vector is dropped, the memory will be de-allocated with the alignment of u64. The documentation for from_raw_parts mentions this pitfall as well.

1) Why does the alignment of an allocation matter when I am freeing that allocation? For example, in C, I can free any pointer by simply calling free -- the alignment of the allocation is irrelevant. What is special about Rust's allocator that it needs to know the alignment?

2) What is the correct way to allocate this aligned vector? Should I use a boxed slice? Write my own struct with a custom Drop implementation that gets the alignment right? Other sources suggest creating a struct and specifying the alignment, and then making a Vec of those structs (causing the de-allocation to use the right layout). But even with repr(C), this won't ensure that all of my u64s are nice and densely packed next to each other, will it? I imagine arbitrary padding could be added to the struct.

Ryan Marcus
  • 966
  • 8
  • 21

1 Answers1

3

I was able to figure this out with some help from Rust Discord users svr and seri.

Essentially, the allocator for the Rust API assumes that a Layout is provided for both allocation and deallocation. That means that the memory allocator gets to know the requested alignment of an allocation at deallocation time. You can find the API in the Rust book.

This API could be used to do something like keeping memory allocated to different alignments in different pools. As it turns out, the current default implementation doesn't take advantage of this information during deallocation, but this could change in the future.

Currently, the best (only?) way to allocate a vector of u64s aligned to an N-byte boundary is to create a struct with one alignment's worth of u64's as fields, and then use the align hint, as described here: How do I allocate a Vec<u8> that is aligned to the size of the cache line?

Ryan Marcus
  • 966
  • 8
  • 21