macro_rules! read_data {
($reg:literal) => { ... };
}Expand description
Read a GTE data register (MFC2). Expands to a single MFC2 instruction
with the register index baked into the .word encoding, followed
by a NOP that fills the one-cycle load-delay slot.
Why the NOP: MIPS R3000 MFC2/CFC2 commit their result to the
CPU GPR at the END of the NEXT instruction – not at the end of
MFC2 itself. Rust’s inline-asm output binding (out("$8") value)
makes the compiler insert a move-from-$8 right after the .word
block, and that move runs in the delay slot → sees the
pre-MFC2 value of $8, not the coprocessor data. The emulator
models this correctly (see cpu::committing_load), so without
the NOP every read_data! call silently returns stale data.
Encoded as two .words: the MFC2 then a 32-bit zero (the MIPS
canonical NOP = SLL $0, $0, 0).