use synth_backend::ArmEncoder;
use synth_core::target::FPUPrecision;
use synth_synthesis::{ArmOp, MemAddr, Reg, VfpReg};
fn thumb_bytes_to_word(bytes: &[u8]) -> u32 {
assert_eq!(bytes.len(), 4, "Expected 4-byte Thumb-2 VFP instruction");
let hw1 = u16::from_le_bytes([bytes[0], bytes[1]]) as u32;
let hw2 = u16::from_le_bytes([bytes[2], bytes[3]]) as u32;
(hw1 << 16) | hw2
}
fn arm_bytes_to_word(bytes: &[u8]) -> u32 {
assert_eq!(bytes.len(), 4, "Expected 4-byte ARM32 instruction");
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn thumb_encoder() -> ArmEncoder {
ArmEncoder::new_thumb2_with_fpu(Some(FPUPrecision::Double))
}
fn arm_encoder() -> ArmEncoder {
ArmEncoder::new_arm32()
}
#[test]
fn test_vadd_f64_d0_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Add {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE300B00,
"VADD.F64 D0, D0, D0"
);
}
#[test]
fn test_vadd_f64_d1_d2_d3_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Add {
dd: VfpReg::D1,
dn: VfpReg::D2,
dm: VfpReg::D3,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE321B03,
"VADD.F64 D1, D2, D3"
);
}
#[test]
fn test_vsub_f64_d0_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Sub {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE300B40,
"VSUB.F64 D0, D0, D0"
);
}
#[test]
fn test_vmul_f64_d4_d5_d6_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Mul {
dd: VfpReg::D4,
dn: VfpReg::D5,
dm: VfpReg::D6,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE254B06,
"VMUL.F64 D4, D5, D6"
);
}
#[test]
fn test_vdiv_f64_d0_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Div {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE800B00,
"VDIV.F64 D0, D0, D0"
);
}
#[test]
fn test_vdiv_f64_d15_d14_d13_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Div {
dd: VfpReg::D15,
dn: VfpReg::D14,
dm: VfpReg::D13,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEE8EFB0D,
"VDIV.F64 D15, D14, D13"
);
}
#[test]
fn test_vabs_f64_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Abs {
dd: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEEB00BC0, "VABS.F64 D0, D0");
}
#[test]
fn test_vneg_f64_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Neg {
dd: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEEB10B40, "VNEG.F64 D0, D0");
}
#[test]
fn test_vsqrt_f64_d0_d0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Sqrt {
dd: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEEB10BC0, "VSQRT.F64 D0, D0");
}
#[test]
fn test_vsqrt_f64_d3_d7_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Sqrt {
dd: VfpReg::D3,
dm: VfpReg::D7,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEEB13BC7, "VSQRT.F64 D3, D7");
}
#[test]
fn test_vabs_f64_d8_d9_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Abs {
dd: VfpReg::D8,
dm: VfpReg::D9,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEEB08BC9, "VABS.F64 D8, D9");
}
#[test]
fn test_vldr_f64_d0_r0_8_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Load {
dd: VfpReg::D0,
addr: MemAddr::imm(Reg::R0, 8),
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xED900B02,
"VLDR.64 D0, [R0, #8]"
);
}
#[test]
fn test_vstr_f64_d0_sp_0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Store {
dd: VfpReg::D0,
addr: MemAddr::imm(Reg::SP, 0),
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xED8D0B00,
"VSTR.64 D0, [SP, #0]"
);
}
#[test]
fn test_vldr_f64_d2_r1_16_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Load {
dd: VfpReg::D2,
addr: MemAddr::imm(Reg::R1, 16),
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xED912B04,
"VLDR.64 D2, [R1, #16]"
);
}
#[test]
fn test_f64_uses_cp11_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Add {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
let instr = thumb_bytes_to_word(&bytes);
assert_eq!(
(instr >> 8) & 0xF,
0xB,
"F64Add must use coprocessor 11 (0xB)"
);
let bytes = enc
.encode(&ArmOp::F64Sqrt {
dd: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
let instr = thumb_bytes_to_word(&bytes);
assert_eq!(
(instr >> 8) & 0xF,
0xB,
"F64Sqrt must use coprocessor 11 (0xB)"
);
let bytes = enc
.encode(&ArmOp::F64Load {
dd: VfpReg::D0,
addr: MemAddr::imm(Reg::R0, 0),
})
.unwrap();
let instr = thumb_bytes_to_word(&bytes);
assert_eq!(
(instr >> 8) & 0xF,
0xB,
"F64Load must use coprocessor 11 (0xB)"
);
}
#[test]
fn test_f64_uses_cp11_arm32() {
let enc = arm_encoder();
let bytes = enc
.encode(&ArmOp::F64Add {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
let instr = arm_bytes_to_word(&bytes);
assert_eq!(
(instr >> 8) & 0xF,
0xB,
"F64Add ARM32 must use coprocessor 11 (0xB)"
);
}
#[test]
fn test_f64_add_equals_f32_add_with_sz_bit() {
let enc = thumb_encoder();
let f32_bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let f64_bytes = enc
.encode(&ArmOp::F64Add {
dd: VfpReg::D0,
dn: VfpReg::D0,
dm: VfpReg::D0,
})
.unwrap();
let f32_word = thumb_bytes_to_word(&f32_bytes);
let f64_word = thumb_bytes_to_word(&f64_bytes);
assert_eq!(
f64_word ^ f32_word,
1 << 8,
"F32/F64 of the same op should differ only in the sz bit (bit 8)"
);
}
#[test]
fn test_vadd_f64_d1_d2_d3_arm32_matches_thumb() {
let thumb_bytes = thumb_encoder()
.encode(&ArmOp::F64Add {
dd: VfpReg::D1,
dn: VfpReg::D2,
dm: VfpReg::D3,
})
.unwrap();
let arm_bytes = arm_encoder()
.encode(&ArmOp::F64Add {
dd: VfpReg::D1,
dn: VfpReg::D2,
dm: VfpReg::D3,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&thumb_bytes),
arm_bytes_to_word(&arm_bytes)
);
}
#[test]
fn test_vmov_core_to_dreg_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64ReinterpretI64 {
dd: VfpReg::D0,
rmlo: Reg::R0,
rmhi: Reg::R1,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEC410B10, "VMOV D0, R0, R1");
}
#[test]
fn test_vmov_dreg_to_core_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::I64ReinterpretF64 {
rdlo: Reg::R0,
rdhi: Reg::R1,
dm: VfpReg::D0,
})
.unwrap();
assert_eq!(thumb_bytes_to_word(&bytes), 0xEC510B10, "VMOV R0, R1, D0");
}
#[test]
fn test_vcvt_f64_f32_d0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64PromoteF32 {
dd: VfpReg::D0,
sm: VfpReg::S0,
})
.unwrap();
assert_eq!(
thumb_bytes_to_word(&bytes),
0xEEB70AC0,
"VCVT.F64.F32 D0, S0"
);
}
#[test]
fn test_f64_arith_encodes_to_4_bytes_thumb() {
let enc = thumb_encoder();
for op in [
ArmOp::F64Add {
dd: VfpReg::D0,
dn: VfpReg::D1,
dm: VfpReg::D2,
},
ArmOp::F64Sub {
dd: VfpReg::D3,
dn: VfpReg::D4,
dm: VfpReg::D5,
},
ArmOp::F64Mul {
dd: VfpReg::D6,
dn: VfpReg::D7,
dm: VfpReg::D8,
},
ArmOp::F64Div {
dd: VfpReg::D9,
dn: VfpReg::D10,
dm: VfpReg::D11,
},
ArmOp::F64Abs {
dd: VfpReg::D12,
dm: VfpReg::D13,
},
ArmOp::F64Neg {
dd: VfpReg::D14,
dm: VfpReg::D15,
},
ArmOp::F64Sqrt {
dd: VfpReg::D0,
dm: VfpReg::D1,
},
] {
let bytes = enc.encode(&op).expect("encoding should succeed");
assert_eq!(bytes.len(), 4, "{op:?} should encode to 4 bytes");
let word = thumb_bytes_to_word(&bytes);
assert_eq!(
(word >> 8) & 0xF,
0xB,
"{op:?} must use cp11 (got cp{:X})",
(word >> 8) & 0xF
);
}
}
#[test]
fn test_f64_ldst_encodes_to_4_bytes_thumb() {
let enc = thumb_encoder();
for op in [
ArmOp::F64Load {
dd: VfpReg::D0,
addr: MemAddr::imm(Reg::R0, 0),
},
ArmOp::F64Load {
dd: VfpReg::D15,
addr: MemAddr::imm(Reg::R11, 32),
},
ArmOp::F64Store {
dd: VfpReg::D0,
addr: MemAddr::imm(Reg::SP, 0),
},
ArmOp::F64Store {
dd: VfpReg::D7,
addr: MemAddr::imm(Reg::R4, 64),
},
] {
let bytes = enc.encode(&op).expect("encoding should succeed");
assert_eq!(bytes.len(), 4, "{op:?} should encode to 4 bytes");
let word = thumb_bytes_to_word(&bytes);
assert_eq!((word >> 8) & 0xF, 0xB, "{op:?} must use cp11");
}
}
#[test]
fn test_f64_const_encodes_to_20_bytes_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F64Const {
dd: VfpReg::D0,
value: 1.5,
})
.unwrap();
assert_eq!(bytes.len(), 20, "F64Const should encode to 20 bytes");
}