use crate::target::TargetSpec;
use crate::wasm_decoder::DecodedModule;
use crate::wasm_op::WasmOp;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BackendError {
#[error("compilation failed: {0}")]
CompilationFailed(String),
#[error("backend not available: {0}")]
NotAvailable(String),
#[error("unsupported configuration: {0}")]
UnsupportedConfig(String),
#[error("external tool error: {0}")]
ExternalToolError(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SafetyBounds {
#[default]
None,
Mpu,
Software,
Mask,
}
impl SafetyBounds {
pub fn parse(s: &str) -> std::result::Result<Self, String> {
match s {
"none" => Ok(SafetyBounds::None),
"mpu" | "pmp" => Ok(SafetyBounds::Mpu),
"software" | "soft" => Ok(SafetyBounds::Software),
"mask" | "masking" => Ok(SafetyBounds::Mask),
other => Err(format!(
"unknown --safety-bounds value '{}'; expected one of: none, mpu, software, mask",
other
)),
}
}
pub fn as_str(self) -> &'static str {
match self {
SafetyBounds::None => "none",
SafetyBounds::Mpu => "mpu",
SafetyBounds::Software => "software",
SafetyBounds::Mask => "mask",
}
}
}
#[derive(Debug, Clone)]
pub struct CompileConfig {
pub opt_level: u8,
pub target: TargetSpec,
pub bounds_check: bool,
pub safety_bounds: SafetyBounds,
pub hardware: String,
pub no_optimize: bool,
pub loom_compat: bool,
pub num_imports: u32,
pub func_arg_counts: Vec<u32>,
pub type_arg_counts: Vec<u32>,
pub relocatable: bool,
}
impl CompileConfig {
pub fn effective_safety_bounds(&self) -> SafetyBounds {
match (self.safety_bounds, self.bounds_check) {
(SafetyBounds::None, true) => SafetyBounds::Software,
(s, _) => s,
}
}
}
impl Default for CompileConfig {
fn default() -> Self {
Self {
opt_level: 2,
target: TargetSpec::cortex_m4(),
bounds_check: false,
safety_bounds: SafetyBounds::None,
hardware: String::new(),
no_optimize: false,
loom_compat: false,
num_imports: 0,
func_arg_counts: Vec::new(),
type_arg_counts: Vec::new(),
relocatable: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CodeRelocation {
pub offset: u32,
pub symbol: String,
}
#[derive(Debug, Clone)]
pub struct CompiledFunction {
pub name: String,
pub code: Vec<u8>,
pub wasm_ops: Vec<WasmOp>,
pub relocations: Vec<CodeRelocation>,
}
#[derive(Debug)]
pub struct CompilationResult {
pub functions: Vec<CompiledFunction>,
pub elf: Option<Vec<u8>>,
pub backend_name: String,
}
#[derive(Debug, Clone)]
pub struct BackendCapabilities {
pub produces_elf: bool,
pub supports_rule_verification: bool,
pub supports_binary_verification: bool,
pub is_external: bool,
}
pub trait Backend: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> BackendCapabilities;
fn supported_targets(&self) -> Vec<TargetSpec>;
fn compile_module(
&self,
module: &DecodedModule,
config: &CompileConfig,
) -> std::result::Result<CompilationResult, BackendError>;
fn compile_function(
&self,
name: &str,
ops: &[WasmOp],
config: &CompileConfig,
) -> std::result::Result<CompiledFunction, BackendError>;
fn is_available(&self) -> bool;
}
pub struct BackendRegistry {
backends: HashMap<String, Box<dyn Backend>>,
}
impl BackendRegistry {
pub fn new() -> Self {
Self {
backends: HashMap::new(),
}
}
pub fn register(&mut self, backend: Box<dyn Backend>) {
let name = backend.name().to_string();
self.backends.insert(name, backend);
}
pub fn get(&self, name: &str) -> Option<&dyn Backend> {
self.backends.get(name).map(|b| b.as_ref())
}
pub fn list(&self) -> Vec<&dyn Backend> {
self.backends.values().map(|b| b.as_ref()).collect()
}
pub fn available(&self) -> Vec<&dyn Backend> {
self.backends
.values()
.filter(|b| b.is_available())
.map(|b| b.as_ref())
.collect()
}
}
impl Default for BackendRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_empty() {
let reg = BackendRegistry::new();
assert!(reg.list().is_empty());
assert!(reg.available().is_empty());
assert!(reg.get("arm").is_none());
}
#[test]
fn test_compile_config_default() {
let config = CompileConfig::default();
assert_eq!(config.opt_level, 2);
assert!(!config.bounds_check);
assert_eq!(config.safety_bounds, SafetyBounds::None);
assert!(!config.no_optimize);
}
#[test]
fn safety_bounds_parse_round_trip() {
for s in ["none", "mpu", "software", "mask"] {
let sb = SafetyBounds::parse(s).unwrap();
assert_eq!(sb.as_str(), s);
}
assert_eq!(SafetyBounds::parse("pmp").unwrap(), SafetyBounds::Mpu);
assert_eq!(SafetyBounds::parse("soft").unwrap(), SafetyBounds::Software);
assert!(SafetyBounds::parse("nonsense").is_err());
}
#[test]
fn effective_safety_bounds_legacy_promotes_to_software() {
let cfg = CompileConfig {
bounds_check: true,
..Default::default()
};
assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Software);
}
#[test]
fn effective_safety_bounds_new_field_wins() {
let cfg = CompileConfig {
bounds_check: true,
safety_bounds: SafetyBounds::Mpu,
..Default::default()
};
assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Mpu);
}
}