fix(cpu): accept ARM multiplies whose destination equals Rm

MUL/MLA with Rd=Rm and long multiplies with RdHi or RdLo equal to Rm returned
UnsupportedInstruction and stopped the machine. The ARM7TDMI reads every
operand before writing the result, so these forms execute normally. R15
operands and RdHi=RdLo remain diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhfHfHEuzv2AUTd6erW1gU
This commit is contained in:
0xrsydn 2026-10-14 08:38:10 +07:00
commit b535c08313
3 changed files with 23 additions and 3 deletions

View file

@ -256,9 +256,10 @@ impl Cpu {
let rs = ((instruction >> 8) & 15) as usize;
let rm = (instruction & 15) as usize;
let accumulate = instruction & (1 << 21) != 0;
if [rd, rn, rs, rm].contains(&15) || rd == rm || (!accumulate && rn != 0) {
if [rd, rn, rs, rm].contains(&15) || (!accumulate && rn != 0) {
return Err(self.unsupported(instruction));
}
// Rd may equal Rm: every operand is read before the destination is written.
let mut result = self.registers[rm].wrapping_mul(self.registers[rs]);
if accumulate {
result = result.wrapping_add(self.registers[rn]);
@ -278,7 +279,7 @@ impl Cpu {
let lo = ((instruction >> 12) & 15) as usize;
let rs = ((instruction >> 8) & 15) as usize;
let rm = (instruction & 15) as usize;
if [hi, lo, rs, rm].contains(&15) || hi == lo || hi == rm || lo == rm {
if [hi, lo, rs, rm].contains(&15) || hi == lo {
return Err(self.unsupported(instruction));
}
let mut result = if instruction & (1 << 22) != 0 {

View file

@ -487,6 +487,23 @@ fn long_multiply_supports_signed_unsigned_and_accumulation() {
}
}
#[test]
fn multiplies_read_every_operand_before_writing_an_aliased_destination() {
for (instruction, expected) in [
(0xe000_0190, [u32::MAX - 1, 2, 9, 5]), // MUL r0,r0,r1
(0xe020_3190, [3, 2, 9, 5]), // MLA r0,r0,r1,r3
(0xe083_0190, [u32::MAX - 1, 2, 9, 1]), // UMULL r0,r3,r0,r1 (RdLo=Rm)
(0xe080_2190, [1, 2, u32::MAX - 1, 5]), // UMULL r2,r0,r0,r1 (RdHi=Rm)
(0xe0a3_0190, [u32::MAX - 2, 2, 9, 7]), // UMLAL r0,r3,r0,r1 (+5:0xffffffff)
] {
let mut memory = program(&[instruction]);
let mut cpu = Cpu::new(ROM_START);
cpu.registers[..4].copy_from_slice(&[u32::MAX, 2, 9, 5]);
cpu.step(&mut memory).unwrap();
assert_eq!(cpu.registers[..4], expected, "{instruction:08x}");
}
}
#[test]
fn long_multiply_zero_flag_uses_both_halves() {
let mut memory = program(&[0xe093_2190]);
@ -510,7 +527,6 @@ fn invalid_or_unimplemented_encodings_do_not_change_state_or_memory() {
0xe790_1012, // illegal register-specified memory shift
0xe790_100f, // memory offset register PC
0xe1c0_10d0, // ARMv5 doubleword load, not an ARM7 signed store
0xe000_0190, // MUL Rd=Rm
0xe082_2190, // UMULL RdHi=RdLo
0xe8f0_0001, // LDM user-bank transfer with writeback
0xe100_f090, // SWP with PC destination