use synth_synthesis::{ArmOp, MemAddr, Reg, VfpReg};
use synth_backend::ArmEncoder;
use synth_core::target::FPUPrecision;
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::Single))
}
fn arm_encoder() -> ArmEncoder {
ArmEncoder::new_arm32()
}
#[test]
fn test_vadd_f32_s0_s0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE300A00, "VADD.F32 S0, S0, S0");
}
#[test]
fn test_vadd_f32_s1_s2_s3_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S1,
sn: VfpReg::S2,
sm: VfpReg::S3,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE710A21, "VADD.F32 S1, S2, S3");
}
#[test]
fn test_vsub_f32_s0_s0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Sub {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE300A40, "VSUB.F32 S0, S0, S0");
}
#[test]
fn test_vmul_f32_s4_s5_s6_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Mul {
sd: VfpReg::S4,
sn: VfpReg::S5,
sm: VfpReg::S6,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE222A83, "VMUL.F32 S4, S5, S6");
}
#[test]
fn test_vdiv_f32_s0_s0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Div {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE800A00, "VDIV.F32 S0, S0, S0");
}
#[test]
fn test_vneg_f32_s0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Neg {
sd: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEEB10A40, "VNEG.F32 S0, S0");
}
#[test]
fn test_vabs_f32_s0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Abs {
sd: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEEB00AC0, "VABS.F32 S0, S0");
}
#[test]
fn test_vsqrt_f32_s2_s4_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Sqrt {
sd: VfpReg::S2,
sm: VfpReg::S4,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEEB11AC2, "VSQRT.F32 S2, S4");
}
#[test]
fn test_vldr_f32_s0_r11_offset0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Load {
sd: VfpReg::S0,
addr: MemAddr::imm(Reg::R11, 0),
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xED9B0A00, "VLDR S0, [R11, #0]");
}
#[test]
fn test_vldr_f32_s0_r0_offset16_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Load {
sd: VfpReg::S0,
addr: MemAddr::imm(Reg::R0, 16),
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xED900A04, "VLDR S0, [R0, #16]");
}
#[test]
fn test_vstr_f32_s0_r11_offset0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Store {
sd: VfpReg::S0,
addr: MemAddr::imm(Reg::R11, 0),
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xED8B0A00, "VSTR S0, [R11, #0]");
}
#[test]
fn test_vmov_s0_r0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32ReinterpretI32 {
sd: VfpReg::S0,
rm: Reg::R0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE000A10, "VMOV S0, R0");
}
#[test]
fn test_vmov_r0_s0_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::I32ReinterpretF32 {
rd: Reg::R0,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE100A10, "VMOV R0, S0");
}
#[test]
fn test_vmov_s1_r3_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32ReinterpretI32 {
sd: VfpReg::S1,
rm: Reg::R3,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
assert_eq!(word, 0xEE003A90, "VMOV S1, R3");
}
#[test]
fn test_f32_eq_comparison_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Eq {
rd: Reg::R0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
assert_eq!(
bytes.len(),
14,
"F32 comparison should be 14 bytes for low regs"
);
let vcmp = thumb_bytes_to_word(&bytes[0..4]);
assert_eq!(vcmp, 0xEEB40A40, "VCMP.F32 S0, S0");
let vmrs = thumb_bytes_to_word(&bytes[4..8]);
assert_eq!(vmrs, 0xEEF1FA10, "VMRS APSR_nzcv, FPSCR");
}
#[test]
fn test_f32_const_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Const {
sd: VfpReg::S0,
value: 1.0f32,
})
.unwrap();
assert_eq!(bytes.len(), 12, "F32Const should be 12 bytes");
let vmov = thumb_bytes_to_word(&bytes[8..12]);
assert_eq!(vmov, 0xEE00CA10, "VMOV S0, R12");
}
#[test]
fn test_f32_const_zero_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Const {
sd: VfpReg::S0,
value: 0.0f32,
})
.unwrap();
assert_eq!(bytes.len(), 12, "F32Const 0.0 should be 12 bytes");
}
#[test]
fn test_f32_convert_i32s_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32ConvertI32S {
sd: VfpReg::S0,
rm: Reg::R0,
})
.unwrap();
assert_eq!(bytes.len(), 8, "F32ConvertI32S should be 8 bytes");
}
#[test]
fn test_i32_trunc_f32s_thumb() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::I32TruncF32S {
rd: Reg::R0,
sm: VfpReg::S0,
})
.unwrap();
assert_eq!(bytes.len(), 8, "I32TruncF32S should be 8 bytes");
}
#[test]
fn test_vadd_f32_s0_s0_s0_arm32() {
let enc = arm_encoder();
let bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = arm_bytes_to_word(&bytes);
assert_eq!(word, 0xEE300A00, "ARM32 VADD.F32 S0, S0, S0");
}
#[test]
fn test_vneg_f32_arm32() {
let enc = arm_encoder();
let bytes = enc
.encode(&ArmOp::F32Neg {
sd: VfpReg::S0,
sm: VfpReg::S0,
})
.unwrap();
let word = arm_bytes_to_word(&bytes);
assert_eq!(word, 0xEEB10A40, "ARM32 VNEG.F32 S0, S0");
}
#[test]
fn test_vldr_f32_arm32() {
let enc = arm_encoder();
let bytes = enc
.encode(&ArmOp::F32Load {
sd: VfpReg::S0,
addr: MemAddr::imm(Reg::R0, 8),
})
.unwrap();
let word = arm_bytes_to_word(&bytes);
assert_eq!(word, 0xED900A02, "ARM32 VLDR S0, [R0, #8]");
}
#[test]
fn test_vmov_s0_r0_arm32() {
let enc = arm_encoder();
let bytes = enc
.encode(&ArmOp::F32ReinterpretI32 {
sd: VfpReg::S0,
rm: Reg::R0,
})
.unwrap();
let word = arm_bytes_to_word(&bytes);
assert_eq!(word, 0xEE000A10, "ARM32 VMOV S0, R0");
}
#[test]
fn test_f32_ceil_encodes_successfully() {
let enc = thumb_encoder();
let result = enc.encode(&ArmOp::F32Ceil {
sd: VfpReg::S0,
sm: VfpReg::S0,
});
assert!(result.is_ok(), "F32Ceil should encode successfully");
assert_eq!(result.unwrap().len(), 36);
}
#[test]
fn test_f32_min_encodes_successfully() {
let enc = thumb_encoder();
let result = enc.encode(&ArmOp::F32Min {
sd: VfpReg::S0,
sn: VfpReg::S0,
sm: VfpReg::S0,
});
assert!(result.is_ok(), "F32Min should encode successfully");
assert_eq!(result.unwrap().len(), 18);
}
#[test]
fn test_sreg_encoding_high_registers() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S14,
sn: VfpReg::S15,
sm: VfpReg::S0,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
let expected = 0xEE377A80;
assert_eq!(
word, expected,
"VADD.F32 S14, S15, S0: got {word:#010X}, expected {expected:#010X}"
);
}
#[test]
fn test_sreg_encoding_odd_registers() {
let enc = thumb_encoder();
let bytes = enc
.encode(&ArmOp::F32Add {
sd: VfpReg::S1,
sn: VfpReg::S3,
sm: VfpReg::S5,
})
.unwrap();
let word = thumb_bytes_to_word(&bytes);
let expected = 0xEE710AA2;
assert_eq!(
word, expected,
"VADD.F32 S1, S3, S5: got {word:#010X}, expected {expected:#010X}"
);
}