1use crate::target::TargetSpec;
7use crate::wasm_decoder::DecodedModule;
8use crate::wasm_op::WasmOp;
9use std::collections::HashMap;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum BackendError {
15 #[error("compilation failed: {0}")]
16 CompilationFailed(String),
17
18 #[error("backend not available: {0}")]
19 NotAvailable(String),
20
21 #[error("unsupported configuration: {0}")]
22 UnsupportedConfig(String),
23
24 #[error("external tool error: {0}")]
25 ExternalToolError(String),
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum SafetyBounds {
38 #[default]
40 None,
41 Mpu,
43 Software,
45 Mask,
47}
48
49impl SafetyBounds {
50 pub fn parse(s: &str) -> std::result::Result<Self, String> {
52 match s {
53 "none" => Ok(SafetyBounds::None),
54 "mpu" | "pmp" => Ok(SafetyBounds::Mpu),
55 "software" | "soft" => Ok(SafetyBounds::Software),
56 "mask" | "masking" => Ok(SafetyBounds::Mask),
57 other => Err(format!(
58 "unknown --safety-bounds value '{}'; expected one of: none, mpu, software, mask",
59 other
60 )),
61 }
62 }
63
64 pub fn as_str(self) -> &'static str {
66 match self {
67 SafetyBounds::None => "none",
68 SafetyBounds::Mpu => "mpu",
69 SafetyBounds::Software => "software",
70 SafetyBounds::Mask => "mask",
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct CompileConfig {
78 pub opt_level: u8,
80 pub target: TargetSpec,
82 pub bounds_check: bool,
87 pub safety_bounds: SafetyBounds,
91 pub hardware: String,
93 pub no_optimize: bool,
95 pub loom_compat: bool,
97 pub num_imports: u32,
99 pub func_arg_counts: Vec<u32>,
104 pub type_arg_counts: Vec<u32>,
107}
108
109impl CompileConfig {
110 pub fn effective_safety_bounds(&self) -> SafetyBounds {
114 match (self.safety_bounds, self.bounds_check) {
115 (SafetyBounds::None, true) => SafetyBounds::Software,
116 (s, _) => s,
117 }
118 }
119}
120
121impl Default for CompileConfig {
122 fn default() -> Self {
123 Self {
124 opt_level: 2,
125 target: TargetSpec::cortex_m4(),
126 bounds_check: false,
127 safety_bounds: SafetyBounds::None,
128 hardware: String::new(),
129 no_optimize: false,
130 loom_compat: false,
131 num_imports: 0,
132 func_arg_counts: Vec::new(),
133 type_arg_counts: Vec::new(),
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct CodeRelocation {
145 pub offset: u32,
147 pub symbol: String,
149}
150
151#[derive(Debug, Clone)]
153pub struct CompiledFunction {
154 pub name: String,
156 pub code: Vec<u8>,
158 pub wasm_ops: Vec<WasmOp>,
160 pub relocations: Vec<CodeRelocation>,
162}
163
164#[derive(Debug)]
166pub struct CompilationResult {
167 pub functions: Vec<CompiledFunction>,
169 pub elf: Option<Vec<u8>>,
171 pub backend_name: String,
173}
174
175#[derive(Debug, Clone)]
177pub struct BackendCapabilities {
178 pub produces_elf: bool,
180 pub supports_rule_verification: bool,
182 pub supports_binary_verification: bool,
184 pub is_external: bool,
186}
187
188pub trait Backend: Send + Sync {
190 fn name(&self) -> &str;
192
193 fn capabilities(&self) -> BackendCapabilities;
195
196 fn supported_targets(&self) -> Vec<TargetSpec>;
198
199 fn compile_module(
201 &self,
202 module: &DecodedModule,
203 config: &CompileConfig,
204 ) -> std::result::Result<CompilationResult, BackendError>;
205
206 fn compile_function(
208 &self,
209 name: &str,
210 ops: &[WasmOp],
211 config: &CompileConfig,
212 ) -> std::result::Result<CompiledFunction, BackendError>;
213
214 fn is_available(&self) -> bool;
216}
217
218pub struct BackendRegistry {
220 backends: HashMap<String, Box<dyn Backend>>,
221}
222
223impl BackendRegistry {
224 pub fn new() -> Self {
225 Self {
226 backends: HashMap::new(),
227 }
228 }
229
230 pub fn register(&mut self, backend: Box<dyn Backend>) {
232 let name = backend.name().to_string();
233 self.backends.insert(name, backend);
234 }
235
236 pub fn get(&self, name: &str) -> Option<&dyn Backend> {
238 self.backends.get(name).map(|b| b.as_ref())
239 }
240
241 pub fn list(&self) -> Vec<&dyn Backend> {
243 self.backends.values().map(|b| b.as_ref()).collect()
244 }
245
246 pub fn available(&self) -> Vec<&dyn Backend> {
248 self.backends
249 .values()
250 .filter(|b| b.is_available())
251 .map(|b| b.as_ref())
252 .collect()
253 }
254}
255
256impl Default for BackendRegistry {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_registry_empty() {
268 let reg = BackendRegistry::new();
269 assert!(reg.list().is_empty());
270 assert!(reg.available().is_empty());
271 assert!(reg.get("arm").is_none());
272 }
273
274 #[test]
275 fn test_compile_config_default() {
276 let config = CompileConfig::default();
277 assert_eq!(config.opt_level, 2);
278 assert!(!config.bounds_check);
279 assert_eq!(config.safety_bounds, SafetyBounds::None);
280 assert!(!config.no_optimize);
281 }
282
283 #[test]
284 fn safety_bounds_parse_round_trip() {
285 for s in ["none", "mpu", "software", "mask"] {
286 let sb = SafetyBounds::parse(s).unwrap();
287 assert_eq!(sb.as_str(), s);
288 }
289 assert_eq!(SafetyBounds::parse("pmp").unwrap(), SafetyBounds::Mpu);
290 assert_eq!(SafetyBounds::parse("soft").unwrap(), SafetyBounds::Software);
291 assert!(SafetyBounds::parse("nonsense").is_err());
292 }
293
294 #[test]
295 fn effective_safety_bounds_legacy_promotes_to_software() {
296 let cfg = CompileConfig {
297 bounds_check: true,
298 ..Default::default()
299 };
300 assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Software);
301 }
302
303 #[test]
304 fn effective_safety_bounds_new_field_wins() {
305 let cfg = CompileConfig {
306 bounds_check: true,
307 safety_bounds: SafetyBounds::Mpu,
308 ..Default::default()
309 };
310 assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Mpu);
311 }
312}