Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion crates/synth-backend/src/arm_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)"
);
}
}
4 changes: 4 additions & 0 deletions crates/synth-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
144 changes: 144 additions & 0 deletions crates/synth-core/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,53 @@ pub struct TargetSpec {
pub fpu: Option<FPUPrecision>,
}

// ============================================================================
// 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 {
Expand Down Expand Up @@ -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");
}
}
Loading
Loading