diff --git a/crates/synth-backend/src/arm_backend.rs b/crates/synth-backend/src/arm_backend.rs index 5dad1e2..b5a2370 100644 --- a/crates/synth-backend/src/arm_backend.rs +++ b/crates/synth-backend/src/arm_backend.rs @@ -13,7 +13,7 @@ use synth_core::wasm_decoder::DecodedModule; use synth_core::wasm_op::WasmOp; use synth_synthesis::{ ArmInstruction, ArmOp, BoundsCheckConfig, InstructionSelector, OptimizationConfig, - OptimizerBridge, RuleDatabase, + OptimizerBridge, RuleDatabase, validate_instructions, }; /// ARM Cortex-M backend using Synth's custom compiler pipeline @@ -184,6 +184,12 @@ fn compile_wasm_to_arm( .collect() }; + // ISA feature gate: validate that all generated instructions are supported + // by the target. This catches FPU instructions on no-FPU targets, double-precision + // instructions on single-precision targets, etc. + validate_instructions(&arm_instrs, config.target.fpu, &config.target.triple) + .map_err(|e| format!("ISA validation failed: {}", e))?; + // Encode to binary — use Thumb-2 for Cortex-M targets let use_thumb2 = matches!(config.target.isa, IsaVariant::Thumb2 | IsaVariant::Thumb); @@ -301,4 +307,101 @@ mod tests { let func = backend.compile_function("add", &ops, &config).unwrap(); assert!(func.relocations.is_empty()); } + + // ======================================================================== + // ISA feature gate tests — ensure the compiler never emits unsupported + // instructions for a given target + // ======================================================================== + + #[test] + fn test_f32_rejected_on_cortex_m3_no_fpu() { + let backend = ArmBackend::new(); + let ops = vec![WasmOp::F32Const(1.0), WasmOp::F32Const(2.0), WasmOp::F32Add]; + let config = CompileConfig { + target: TargetSpec::cortex_m3(), + no_optimize: true, + ..CompileConfig::default() + }; + + let result = backend.compile_function("fadd", &ops, &config); + assert!( + result.is_err(), + "f32 operations should fail on Cortex-M3 (no FPU)" + ); + } + + #[test] + fn test_f32_accepted_on_cortex_m4f() { + let backend = ArmBackend::new(); + let ops = vec![WasmOp::F32Const(1.0), WasmOp::F32Const(2.0), WasmOp::F32Add]; + let config = CompileConfig { + target: TargetSpec::cortex_m4f(), + no_optimize: true, + ..CompileConfig::default() + }; + + let result = backend.compile_function("fadd", &ops, &config); + assert!( + result.is_ok(), + "f32 operations should succeed on Cortex-M4F, got: {:?}", + result.unwrap_err() + ); + } + + #[test] + fn test_i32_works_on_all_targets() { + let backend = ArmBackend::new(); + let ops = vec![WasmOp::LocalGet(0), WasmOp::LocalGet(1), WasmOp::I32Add]; + + // Cortex-M3 (no FPU) + let config_m3 = CompileConfig { + target: TargetSpec::cortex_m3(), + no_optimize: true, + ..CompileConfig::default() + }; + assert!( + backend.compile_function("add", &ops, &config_m3).is_ok(), + "i32 ops should work on Cortex-M3" + ); + + // Cortex-M4F (single FPU) + let config_m4f = CompileConfig { + target: TargetSpec::cortex_m4f(), + no_optimize: true, + ..CompileConfig::default() + }; + assert!( + backend.compile_function("add", &ops, &config_m4f).is_ok(), + "i32 ops should work on Cortex-M4F" + ); + + // Cortex-M7DP (double FPU) + let config_m7dp = CompileConfig { + target: TargetSpec::cortex_m7dp(), + no_optimize: true, + ..CompileConfig::default() + }; + assert!( + backend.compile_function("add", &ops, &config_m7dp).is_ok(), + "i32 ops should work on Cortex-M7DP" + ); + } + + #[test] + fn test_f32_rejected_on_cortex_m4_no_fpu() { + // Cortex-M4 (without F suffix) has no FPU + let backend = ArmBackend::new(); + let ops = vec![WasmOp::F32Const(1.5), WasmOp::F32Const(2.5), WasmOp::F32Mul]; + let config = CompileConfig { + target: TargetSpec::cortex_m4(), + no_optimize: true, + ..CompileConfig::default() + }; + + let result = backend.compile_function("fmul", &ops, &config); + assert!( + result.is_err(), + "f32 operations should fail on Cortex-M4 (no FPU)" + ); + } } diff --git a/crates/synth-core/src/error.rs b/crates/synth-core/src/error.rs index 3e5e281..17f2096 100644 --- a/crates/synth-core/src/error.rs +++ b/crates/synth-core/src/error.rs @@ -22,6 +22,10 @@ pub enum Error { #[error("Target not supported: {0}")] UnsupportedTarget(String), + /// Instruction not supported on target + #[error("Unsupported instruction for target: {0}")] + UnsupportedInstruction(String), + /// Memory layout error #[error("Memory layout error: {0}")] MemoryLayoutError(String), diff --git a/crates/synth-core/src/target.rs b/crates/synth-core/src/target.rs index 2111558..7806695 100644 --- a/crates/synth-core/src/target.rs +++ b/crates/synth-core/src/target.rs @@ -294,6 +294,53 @@ pub struct TargetSpec { pub fpu: Option, } +// ============================================================================ +// ISA feature gating — determines which instructions each variant supports +// ============================================================================ + +impl CortexMVariant { + /// Returns whether this variant supports DSP instructions (SSAT, USAT, SMLAL, etc.) + /// Only ARMv7E-M (Cortex-M4+) and above have DSP extensions. + pub fn has_dsp(&self) -> bool { + !matches!(self, Self::M3) + } + + /// Returns whether this variant has a single-precision FPU + pub fn has_single_precision_fpu(&self) -> bool { + matches!(self, Self::M4F | Self::M7 | Self::M7DP | Self::M55) + } + + /// Returns whether this variant has a double-precision FPU + pub fn has_double_precision_fpu(&self) -> bool { + matches!(self, Self::M7DP) + } + + /// Returns whether this variant supports TrustZone security extensions + pub fn has_trustzone(&self) -> bool { + matches!(self, Self::M33 | Self::M55) + } + + /// Returns whether this variant supports Helium MVE (M-Profile Vector Extension) + pub fn has_helium(&self) -> bool { + matches!(self, Self::M55) + } +} + +impl TargetSpec { + /// Returns whether this target has a single-precision FPU + pub fn has_single_precision_fpu(&self) -> bool { + matches!( + self.fpu, + Some(FPUPrecision::Single) | Some(FPUPrecision::Double) + ) + } + + /// Returns whether this target has a double-precision FPU + pub fn has_double_precision_fpu(&self) -> bool { + matches!(self.fpu, Some(FPUPrecision::Double)) + } +} + impl TargetSpec { /// Cortex-M3 (ARMv7-M, no FPU, 8 MPU regions) pub fn cortex_m3() -> Self { @@ -458,4 +505,101 @@ mod tests { assert!(spec.has_fpu()); assert_eq!(spec.fpu, Some(FPUPrecision::Single)); } + + // ======================================================================== + // ISA feature gating tests + // ======================================================================== + + #[test] + fn test_cortex_m3_isa_features() { + let m3 = CortexMVariant::M3; + assert!(!m3.has_dsp(), "M3 should not have DSP"); + assert!(!m3.has_single_precision_fpu(), "M3 should not have FPU"); + assert!( + !m3.has_double_precision_fpu(), + "M3 should not have double FPU" + ); + assert!(!m3.has_trustzone(), "M3 should not have TrustZone"); + assert!(!m3.has_helium(), "M3 should not have Helium"); + } + + #[test] + fn test_cortex_m4_isa_features() { + let m4 = CortexMVariant::M4; + assert!(m4.has_dsp(), "M4 should have DSP"); + assert!( + !m4.has_single_precision_fpu(), + "M4 (no F) should not have FPU" + ); + assert!( + !m4.has_double_precision_fpu(), + "M4 should not have double FPU" + ); + } + + #[test] + fn test_cortex_m4f_isa_features() { + let m4f = CortexMVariant::M4F; + assert!(m4f.has_dsp(), "M4F should have DSP"); + assert!( + m4f.has_single_precision_fpu(), + "M4F should have single-precision FPU" + ); + assert!( + !m4f.has_double_precision_fpu(), + "M4F should not have double FPU" + ); + } + + #[test] + fn test_cortex_m7dp_isa_features() { + let m7dp = CortexMVariant::M7DP; + assert!(m7dp.has_dsp(), "M7DP should have DSP"); + assert!( + m7dp.has_single_precision_fpu(), + "M7DP should have single-precision FPU" + ); + assert!( + m7dp.has_double_precision_fpu(), + "M7DP should have double-precision FPU" + ); + } + + #[test] + fn test_cortex_m33_isa_features() { + let m33 = CortexMVariant::M33; + assert!(m33.has_dsp(), "M33 should have DSP"); + assert!(m33.has_trustzone(), "M33 should have TrustZone"); + assert!(!m33.has_helium(), "M33 should not have Helium"); + } + + #[test] + fn test_cortex_m55_isa_features() { + let m55 = CortexMVariant::M55; + assert!(m55.has_dsp(), "M55 should have DSP"); + assert!( + m55.has_single_precision_fpu(), + "M55 should have single-precision FPU" + ); + assert!(m55.has_trustzone(), "M55 should have TrustZone"); + assert!(m55.has_helium(), "M55 should have Helium"); + } + + #[test] + fn test_target_spec_fpu_precision_queries() { + let m3 = TargetSpec::cortex_m3(); + assert!(!m3.has_single_precision_fpu(), "M3 spec has no FPU"); + assert!(!m3.has_double_precision_fpu(), "M3 spec has no double FPU"); + + let m4f = TargetSpec::cortex_m4f(); + assert!(m4f.has_single_precision_fpu(), "M4F spec has single FPU"); + assert!( + !m4f.has_double_precision_fpu(), + "M4F spec has no double FPU" + ); + + let m7dp = TargetSpec::cortex_m7dp(); + assert!(m7dp.has_single_precision_fpu(), "M7DP spec has single FPU"); + assert!(m7dp.has_double_precision_fpu(), "M7DP spec has double FPU"); + } } diff --git a/crates/synth-synthesis/src/instruction_selector.rs b/crates/synth-synthesis/src/instruction_selector.rs index 132a03c..2fd48cb 100644 --- a/crates/synth-synthesis/src/instruction_selector.rs +++ b/crates/synth-synthesis/src/instruction_selector.rs @@ -1461,6 +1461,46 @@ impl InstructionSelector { } } +/// Validate that all ARM instructions in the sequence are supported by the target. +/// +/// This is the ISA feature gate: it checks each generated ARM instruction against +/// the target's FPU capabilities and returns an error for the first unsupported +/// instruction encountered. This must be called AFTER instruction selection but +/// BEFORE encoding, to ensure the compiler never emits an instruction that the +/// target platform cannot execute. +pub fn validate_instructions( + instructions: &[ArmInstruction], + fpu: Option, + target_name: &str, +) -> Result<()> { + for instr in instructions { + // Check FPU requirement (single-precision or higher) + if instr.op.requires_fpu() && fpu.is_none() { + return Err(synth_core::Error::UnsupportedInstruction(format!( + "instruction {} requires FPU, but target {} has no FPU", + instr.op.instruction_name(), + target_name, + ))); + } + + // Check double-precision FPU requirement + if instr.op.requires_double_precision_fpu() && !matches!(fpu, Some(FPUPrecision::Double)) { + let reason = if fpu.is_some() { + "only has single-precision FPU" + } else { + "has no FPU" + }; + return Err(synth_core::Error::UnsupportedInstruction(format!( + "instruction {} requires double-precision FPU, but target {} {}", + instr.op.instruction_name(), + target_name, + reason, + ))); + } + } + Ok(()) +} + /// Statistics from instruction selection #[derive(Debug, Clone, Default)] pub struct SelectionStats { @@ -1743,6 +1783,131 @@ mod tests { } } + #[test] + fn test_validate_instructions_rejects_fpu_on_no_fpu_target() { + // Simulate FPU instructions being generated, then validate against a no-FPU target + let instrs = vec![ArmInstruction { + op: ArmOp::F32Add { + sd: VfpReg::S0, + sn: VfpReg::S1, + sm: VfpReg::S2, + }, + source_line: Some(0), + }]; + let result = super::validate_instructions(&instrs, None, "cortex-m3"); + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("requires FPU"), + "Error should mention FPU requirement, got: {err}" + ); + assert!( + err.contains("cortex-m3"), + "Error should mention target name, got: {err}" + ); + } + + #[test] + fn test_validate_instructions_allows_fpu_on_fpu_target() { + let instrs = vec![ArmInstruction { + op: ArmOp::F32Add { + sd: VfpReg::S0, + sn: VfpReg::S1, + sm: VfpReg::S2, + }, + source_line: Some(0), + }]; + let result = + super::validate_instructions(&instrs, Some(FPUPrecision::Single), "cortex-m4f"); + assert!(result.is_ok()); + } + + #[test] + fn test_validate_instructions_rejects_f64_on_single_precision() { + let instrs = vec![ArmInstruction { + op: ArmOp::F64Add { + dd: VfpReg::D0, + dn: VfpReg::D1, + dm: VfpReg::D2, + }, + source_line: Some(0), + }]; + let result = + super::validate_instructions(&instrs, Some(FPUPrecision::Single), "cortex-m4f"); + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("double-precision"), + "Error should mention double-precision, got: {err}" + ); + } + + #[test] + fn test_validate_instructions_allows_f64_on_double_precision() { + let instrs = vec![ArmInstruction { + op: ArmOp::F64Add { + dd: VfpReg::D0, + dn: VfpReg::D1, + dm: VfpReg::D2, + }, + source_line: Some(0), + }]; + let result = + super::validate_instructions(&instrs, Some(FPUPrecision::Double), "cortex-m7dp"); + assert!(result.is_ok()); + } + + #[test] + fn test_validate_instructions_allows_integer_ops_on_all_targets() { + let instrs = vec![ + ArmInstruction { + op: ArmOp::Add { + rd: Reg::R0, + rn: Reg::R1, + op2: Operand2::Reg(Reg::R2), + }, + source_line: Some(0), + }, + ArmInstruction { + op: ArmOp::Mul { + rd: Reg::R0, + rn: Reg::R1, + rm: Reg::R2, + }, + source_line: Some(1), + }, + ArmInstruction { + op: ArmOp::Sdiv { + rd: Reg::R0, + rn: Reg::R1, + rm: Reg::R2, + }, + source_line: Some(2), + }, + ArmInstruction { + op: ArmOp::Clz { + rd: Reg::R0, + rm: Reg::R1, + }, + source_line: Some(3), + }, + ]; + + // Should pass on M3 (no FPU) + let result = super::validate_instructions(&instrs, None, "cortex-m3"); + assert!(result.is_ok(), "Integer ops should pass on cortex-m3"); + + // Should pass on M4F (with FPU) + let result = + super::validate_instructions(&instrs, Some(FPUPrecision::Single), "cortex-m4f"); + assert!(result.is_ok(), "Integer ops should pass on cortex-m4f"); + + // Should pass on M7DP (with double FPU) + let result = + super::validate_instructions(&instrs, Some(FPUPrecision::Double), "cortex-m7dp"); + assert!(result.is_ok(), "Integer ops should pass on cortex-m7dp"); + } + #[test] fn test_bounds_check_masking() { // With BoundsCheckConfig::Masking, loads should generate AND + LDR diff --git a/crates/synth-synthesis/src/lib.rs b/crates/synth-synthesis/src/lib.rs index a78fd1f..f33eaf7 100644 --- a/crates/synth-synthesis/src/lib.rs +++ b/crates/synth-synthesis/src/lib.rs @@ -10,6 +10,7 @@ pub mod wasm_decoder; pub use instruction_selector::{ ArmInstruction, BoundsCheckConfig, InstructionSelector, RegisterState, SelectionStats, + validate_instructions, }; pub use optimizer_bridge::{OptimizationConfig, OptimizationStats, OptimizerBridge}; pub use pattern_matcher::{ diff --git a/crates/synth-synthesis/src/rules.rs b/crates/synth-synthesis/src/rules.rs index c877ce0..a869ccd 100644 --- a/crates/synth-synthesis/src/rules.rs +++ b/crates/synth-synthesis/src/rules.rs @@ -987,6 +987,201 @@ pub enum ArmOp { }, // VCVT.U32.F64 Sd, Dm + VMOV Rd, Sd } +impl ArmOp { + /// Returns `true` if this instruction requires a single-precision (or higher) FPU. + /// + /// All VFP instructions (VADD, VSUB, VLDR, VCVT, etc.) require FPU hardware. + /// Targets without an FPU (e.g., Cortex-M3, Cortex-M4 without F suffix) cannot + /// execute these instructions. + pub fn requires_fpu(&self) -> bool { + matches!( + self, + ArmOp::F32Add { .. } + | ArmOp::F32Sub { .. } + | ArmOp::F32Mul { .. } + | ArmOp::F32Div { .. } + | ArmOp::F32Abs { .. } + | ArmOp::F32Neg { .. } + | ArmOp::F32Sqrt { .. } + | ArmOp::F32Ceil { .. } + | ArmOp::F32Floor { .. } + | ArmOp::F32Trunc { .. } + | ArmOp::F32Nearest { .. } + | ArmOp::F32Min { .. } + | ArmOp::F32Max { .. } + | ArmOp::F32Copysign { .. } + | ArmOp::F32Eq { .. } + | ArmOp::F32Ne { .. } + | ArmOp::F32Lt { .. } + | ArmOp::F32Le { .. } + | ArmOp::F32Gt { .. } + | ArmOp::F32Ge { .. } + | ArmOp::F32Const { .. } + | ArmOp::F32Load { .. } + | ArmOp::F32Store { .. } + | ArmOp::F32ConvertI32S { .. } + | ArmOp::F32ConvertI32U { .. } + | ArmOp::F32ConvertI64S { .. } + | ArmOp::F32ConvertI64U { .. } + | ArmOp::F32ReinterpretI32 { .. } + | ArmOp::I32ReinterpretF32 { .. } + | ArmOp::I32TruncF32S { .. } + | ArmOp::I32TruncF32U { .. } + | ArmOp::F64Add { .. } + | ArmOp::F64Sub { .. } + | ArmOp::F64Mul { .. } + | ArmOp::F64Div { .. } + | ArmOp::F64Abs { .. } + | ArmOp::F64Neg { .. } + | ArmOp::F64Sqrt { .. } + | ArmOp::F64Ceil { .. } + | ArmOp::F64Floor { .. } + | ArmOp::F64Trunc { .. } + | ArmOp::F64Nearest { .. } + | ArmOp::F64Min { .. } + | ArmOp::F64Max { .. } + | ArmOp::F64Copysign { .. } + | ArmOp::F64Eq { .. } + | ArmOp::F64Ne { .. } + | ArmOp::F64Lt { .. } + | ArmOp::F64Le { .. } + | ArmOp::F64Gt { .. } + | ArmOp::F64Ge { .. } + | ArmOp::F64Const { .. } + | ArmOp::F64Load { .. } + | ArmOp::F64Store { .. } + | ArmOp::F64ConvertI32S { .. } + | ArmOp::F64ConvertI32U { .. } + | ArmOp::F64ConvertI64S { .. } + | ArmOp::F64ConvertI64U { .. } + | ArmOp::F64PromoteF32 { .. } + | ArmOp::F64ReinterpretI64 { .. } + | ArmOp::I64ReinterpretF64 { .. } + | ArmOp::I64TruncF64S { .. } + | ArmOp::I64TruncF64U { .. } + | ArmOp::I32TruncF64S { .. } + | ArmOp::I32TruncF64U { .. } + ) + } + + /// Returns `true` if this instruction requires a double-precision FPU. + /// + /// Only targets with `FPUPrecision::Double` (e.g., Cortex-M7DP) can execute + /// double-precision VFP instructions. Single-precision FPU targets (M4F, M7) + /// cannot execute these. + pub fn requires_double_precision_fpu(&self) -> bool { + matches!( + self, + ArmOp::F64Add { .. } + | ArmOp::F64Sub { .. } + | ArmOp::F64Mul { .. } + | ArmOp::F64Div { .. } + | ArmOp::F64Abs { .. } + | ArmOp::F64Neg { .. } + | ArmOp::F64Sqrt { .. } + | ArmOp::F64Ceil { .. } + | ArmOp::F64Floor { .. } + | ArmOp::F64Trunc { .. } + | ArmOp::F64Nearest { .. } + | ArmOp::F64Min { .. } + | ArmOp::F64Max { .. } + | ArmOp::F64Copysign { .. } + | ArmOp::F64Eq { .. } + | ArmOp::F64Ne { .. } + | ArmOp::F64Lt { .. } + | ArmOp::F64Le { .. } + | ArmOp::F64Gt { .. } + | ArmOp::F64Ge { .. } + | ArmOp::F64Const { .. } + | ArmOp::F64Load { .. } + | ArmOp::F64Store { .. } + | ArmOp::F64ConvertI32S { .. } + | ArmOp::F64ConvertI32U { .. } + | ArmOp::F64ConvertI64S { .. } + | ArmOp::F64ConvertI64U { .. } + | ArmOp::F64PromoteF32 { .. } + | ArmOp::F64ReinterpretI64 { .. } + | ArmOp::I64ReinterpretF64 { .. } + | ArmOp::I64TruncF64S { .. } + | ArmOp::I64TruncF64U { .. } + | ArmOp::I32TruncF64S { .. } + | ArmOp::I32TruncF64U { .. } + ) + } + + /// Returns a human-readable name for this instruction (for error messages). + pub fn instruction_name(&self) -> &'static str { + match self { + ArmOp::F32Add { .. } => "VADD.F32", + ArmOp::F32Sub { .. } => "VSUB.F32", + ArmOp::F32Mul { .. } => "VMUL.F32", + ArmOp::F32Div { .. } => "VDIV.F32", + ArmOp::F32Abs { .. } => "VABS.F32", + ArmOp::F32Neg { .. } => "VNEG.F32", + ArmOp::F32Sqrt { .. } => "VSQRT.F32", + ArmOp::F32Ceil { .. } => "VRINTP.F32", + ArmOp::F32Floor { .. } => "VRINTM.F32", + ArmOp::F32Trunc { .. } => "VRINTZ.F32", + ArmOp::F32Nearest { .. } => "VRINTN.F32", + ArmOp::F32Min { .. } => "VMIN.F32", + ArmOp::F32Max { .. } => "VMAX.F32", + ArmOp::F32Copysign { .. } => "F32Copysign", + ArmOp::F32Eq { .. } => "VCMP.F32 (EQ)", + ArmOp::F32Ne { .. } => "VCMP.F32 (NE)", + ArmOp::F32Lt { .. } => "VCMP.F32 (LT)", + ArmOp::F32Le { .. } => "VCMP.F32 (LE)", + ArmOp::F32Gt { .. } => "VCMP.F32 (GT)", + ArmOp::F32Ge { .. } => "VCMP.F32 (GE)", + ArmOp::F32Const { .. } => "VMOV.F32", + ArmOp::F32Load { .. } => "VLDR.32", + ArmOp::F32Store { .. } => "VSTR.32", + ArmOp::F32ConvertI32S { .. } => "VCVT.F32.S32", + ArmOp::F32ConvertI32U { .. } => "VCVT.F32.U32", + ArmOp::F32ConvertI64S { .. } => "VCVT.F32.S64", + ArmOp::F32ConvertI64U { .. } => "VCVT.F32.U64", + ArmOp::F32ReinterpretI32 { .. } => "VMOV (F32<-I32)", + ArmOp::I32ReinterpretF32 { .. } => "VMOV (I32<-F32)", + ArmOp::I32TruncF32S { .. } => "VCVT.S32.F32", + ArmOp::I32TruncF32U { .. } => "VCVT.U32.F32", + ArmOp::F64Add { .. } => "VADD.F64", + ArmOp::F64Sub { .. } => "VSUB.F64", + ArmOp::F64Mul { .. } => "VMUL.F64", + ArmOp::F64Div { .. } => "VDIV.F64", + ArmOp::F64Abs { .. } => "VABS.F64", + ArmOp::F64Neg { .. } => "VNEG.F64", + ArmOp::F64Sqrt { .. } => "VSQRT.F64", + ArmOp::F64Ceil { .. } => "VRINTP.F64", + ArmOp::F64Floor { .. } => "VRINTM.F64", + ArmOp::F64Trunc { .. } => "VRINTZ.F64", + ArmOp::F64Nearest { .. } => "VRINTN.F64", + ArmOp::F64Min { .. } => "VMIN.F64", + ArmOp::F64Max { .. } => "VMAX.F64", + ArmOp::F64Copysign { .. } => "F64Copysign", + ArmOp::F64Eq { .. } => "VCMP.F64 (EQ)", + ArmOp::F64Ne { .. } => "VCMP.F64 (NE)", + ArmOp::F64Lt { .. } => "VCMP.F64 (LT)", + ArmOp::F64Le { .. } => "VCMP.F64 (LE)", + ArmOp::F64Gt { .. } => "VCMP.F64 (GT)", + ArmOp::F64Ge { .. } => "VCMP.F64 (GE)", + ArmOp::F64Const { .. } => "VMOV.F64", + ArmOp::F64Load { .. } => "VLDR.64", + ArmOp::F64Store { .. } => "VSTR.64", + ArmOp::F64ConvertI32S { .. } => "VCVT.F64.S32", + ArmOp::F64ConvertI32U { .. } => "VCVT.F64.U32", + ArmOp::F64ConvertI64S { .. } => "VCVT.F64.S64", + ArmOp::F64ConvertI64U { .. } => "VCVT.F64.U64", + ArmOp::F64PromoteF32 { .. } => "VCVT.F64.F32", + ArmOp::F64ReinterpretI64 { .. } => "VMOV (F64<-I64)", + ArmOp::I64ReinterpretF64 { .. } => "VMOV (I64<-F64)", + ArmOp::I64TruncF64S { .. } => "VCVT.S64.F64", + ArmOp::I64TruncF64U { .. } => "VCVT.U64.F64", + ArmOp::I32TruncF64S { .. } => "VCVT.S32.F64", + ArmOp::I32TruncF64U { .. } => "VCVT.U32.F64", + _ => "ARM", + } + } +} + /// ARM condition codes (based on NZCV flags) #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Condition {