#write_volatile generates non-aligned store instructions (or does it?)

1 messages · Page 1 of 1 (latest)

subtle spade
#

I was writing some code for the GBA (Gameboy Advance) The tricky bit is that the memory bus for the VRAM is 16 bits, and if you don't align correctly your memory load/store, you will have bad behaviors (8 bit stores will "duplicate" the 8 bits to the other 8 bits in the 16 bit address).

One of the GBA "video modes" has the VRAM mapped to a large array of 8 bit values (tile indices). This means I have to somehow "pack" each 8 bit values into a single struct and load/store from memory using a "load/store 16 bits value" instruction (for store, it would be strh or str) (remember how the VRAM bus is 16 bits?)

The original code used a struct like that:

#[repr(C)]
#[repr(align(2))]
struct AffineTile {
    left: u8,
    right: u8,
}

fn set_tile(offset: usize, index: u8) {
    let address = VRAM + offset / 2;
    let mut previous_value = unsafe {
        ptr::read_volatile(address as *const AffineTile)
    };
    if offset % 2 == 0 {
        previous_value.left = index;
    } else {
        previous_value.right = index;
    }
    unsafe { ptr::write_volatile(address as *mut AffineTile, index) };
}

However, the result here was bogus. It looked like the generated instructions contained a strb the 8 bit store that resulted in duplicate tiles!

Indeed, when using cargo-show-asm, and afterward using rust -- --emit asm, I found out that the set_tile function contained a strb (and no strh!)

Is this intended behavior? I feel like this shouldn't happen, I'd expect write_volatile to specifically not break up stores.

#

To be precise, the rust doc (https://doc.rust-lang.org/stable/std/ptr/fn.write_volatile.html) do not warrant that assumption, and refers to the C standard "definition of volatile" for other questions linked here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

It doesn't define volatile, but how things interact with volatile, the closest to a "definition" is the end-note 134 on page 122. It still is ambiguous (as in: not specifically called out as "undefined behavior") what I should expect.

The solution I came around is to replace AffineTile with:

#[repr(transparent)]
struct AffineTile(u16);
impl AffineTile {
    fn set_left() {}
    fn set_right() {}
    fn new() -> Self {}
}

However, this is much less ergonomic.

#

I think this is called "tearing" and there is a lot of discussion in the linked issue around it. There seem to be both assumptions that write_volatile is tearing and not tearing in the linked issue

idle spruce
#

otherwise you couldn't, for example, do a write_volatile on a [u64; 64], no CPU can do that in one instruction

subtle spade
#

I guess so, but what makes u16 different from AfineTile(u8,u8) then?

idle spruce
#

🤷
i guess it doesn't want to merge the two fields