Search code examples
rustarduinoavrmemory-alignment

When exactly is "error[E0793]: reference to packed field is unaligned" triggered?


I've been banging my head against the wall for a while on this one. The context is that I'm trying to build an embedded VM for 8-bit AVR processors, but the question is not specific to the AVR.

Here's a stripped down bit of my code:

#[repr(C, packed(1))]
#[derive(Debug)]
struct Chunk<T> {
    chunktype: u8,
    color: u8,
    size: u16,
    object: T
}

impl<T> Deref for Chunk<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.object
    }
}

fn deref16(s: &Chunk<i16>) -> &i16 {
    &(s.object)
}

fn deref8(s: &Chunk<i8>) -> &i8 {
    &(s.object)
}

playground link

I half understand that a reference pointing to an unaligned address could be bad. (but couldn't the compiler just generate code that works around this problem, at a performance penalty of course?)

What I don't understand is why I only get the error on the implementation of Deref and on deref16, but the compiler is happy with deref8??

On the AVR the behaviour is slightly different. It doesn't seem to be a problem for whatever size I put in a specialised function like defer16 above. Even something like fn deref(s: &Chunk<[u8; 5]>) -> &[u8; 5] { &(s.object) } is OK. But.. the generic implementation of Deref still gives the same error.

It's an 8 bit machine, so I was expecting that alignment wouldn't be a problem at all.

Is the compiler unhappy about the generic Deref because can't be sure what type T will be, and unfortunately isn't smart enough to realise on the AVR everything is properly aligned? AFAIK Rust doesn't allow you to pack integers < 8 bits like C does, right?

Also it confuses me why the deref8 version works on the playground, but deref16 doesn't.


Solution

  • It generates an error when a field might not be aligned to what it's type requires, since you specify packed(1) that means your struct doesn't have any alignment requirement, so it's fields can't be aligned more strictly than align(1).

    For u8 and [u8; N] that's trivially true.

    For i16 that depends on your target specification, the AVR chips (usually with a custom target specification) have a data-layout of something like

    e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8
    

    particularly the i16:8 means that 16 bit integers use the "abi" 8 (are aligned to 8-bits), converted to Rust terms that's an alignment of 1, which also is compatible with packed(1).

    On the generic version, there is no way to tell the alignment of the arbitrary type, so it can't be "known to be the same".

    Also

    It's an 8 bit machine, so I was expecting that alignment wouldn't be a problem at all.

    Is generally a fallacy, you're not programming for any specific machine, you're programming for the Rust Abstract Machine and on it references always have to be aligned to whatever the type specifies.

    See also: