Rust Alignment on Different Platforms

(If this question is better suited to the internals forum, I would be happy to move it. It seems like it's on the fence as I write it.)

For both embedded development and testing reasons, I would like to implement a Global Allocator for a no_std program with its own backing store.

When I looked at Rust allocator crates, thinking that someone surely has done this already, all of them simply rely on libc's malloc, and have no way to specify a different way to get allocation backing. They are focused on the algorithm for the allocations: zone-based, record-based, metadata, fragmentation, etc.

This old thread helped make clear what my real problem was: I didn't need an allocator, but my own version of malloc and free written in Rust to replace the libc's.

As an example, if I were on an embedded device, I would want every heap allocation to go into a static 16k array called the_heap, which would be created via a linker symbol. Such a no_std platform has no libc involved.

Given that Rust's allocators are alignment aware, and the platforms I plan to use this on vary between 32 and 64-bit, there is one important thing mentioned in the POSIX documentation for malloc that I am trying to work out:

The pointer returned if the allocation succeeds shall be suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object in the space allocated (until the space is explicitly freed or reallocated).

This brings up several questions:

  • How would I know what that is for a given platform? (I assume this is not easy.)
  • Could I just pick a number -- say, 16 bytes -- and always be correct at the expense of wasted space?
  • Are there cases where Rust communicates over-specified alignments (i.e. user-specified as higher than the required alignment of the base type) in strange ways, like calloc N slots instead of malloc 4 * n bytes?

Or, has someone already solved my problem some other clever way?

Hi,

I’ve implemented a bare metal allocator for my Raspberry Pi projects.
Check out: ruspiro-allocator

As you might see in the implementation of the allocator trait the required alignment for the memory allocation is given in the Layout parameter. Whether Rust already does some clever calculation on this alignment I’ve no clue. As I’m targeting only one platform at the moment I don’t know if my own allocator would need to add some clever magic to it...

BR

If you are trying to figure out what's the guaranteed minimal alignment of a valid return value of malloc(), then that's alignof(max_align_t).

Not all of them use malloc. Specifically, I've written static-alloc for this purpose. If a bump allocator which can not deallocate is enough for your purpose then it could fit perfectly.¹ You might even get around having to use a linker script to create the_heap and define it in Rust. Usage would look like this:

/// The heap is a 16kB array, aligned to 64 bytes.
#[repr(align(64))]
struct TheHeap([u8; 1 << 16]);

#[global_allocator]
static THE_HEAP: static_alloc::Slab<TheHeap> = static_alloc::Slab::uninit();

fn main() {
      let v = vec![0xdeadbeef_u32; 128];
      println!("{:x?}", v);
}

¹ The intention is to have a global heap that you can partition during bootup into multiple smaller byte regions for each thread or task. Then each task can perform any allocation/deallocation strategy it desires within that region, potentially supporting deallocation. That makes the implementation significantly easier and avoids the overhead of storing a separate structure keeping track of allocations which might waste a few precious bytes.

3 Likes

Thanks, @HeroicKatora, I missed that crate! It's exactly what I'm looking for!

Thanks for the tip, @H2CO3, I'll remember that. I suspect I'll need it again eventually.

Thanks for the offer, @2ndTaleStudio. If I had responded to you first, I would say that it was interesting, but has one disadvantage. I was hoping to test heap behavior on x86_64 with the allocator, in addition to writing bare metal stuff. Since your library has some assembly in it, I imagine I would need to shim some things out.

Hey @jhwgh1968 fair point. I guess the aarch32/64 assembly is still there for some historical reasons. It reminds me to see if I can get rid of it :wink: