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.