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}
100
101impl CompileConfig {
102 pub fn effective_safety_bounds(&self) -> SafetyBounds {
106 match (self.safety_bounds, self.bounds_check) {
107 (SafetyBounds::None, true) => SafetyBounds::Software,
108 (s, _) => s,
109 }
110 }
111}
112
113impl Default for CompileConfig {
114 fn default() -> Self {
115 Self {
116 opt_level: 2,
117 target: TargetSpec::cortex_m4(),
118 bounds_check: false,
119 safety_bounds: SafetyBounds::None,
120 hardware: String::new(),
121 no_optimize: false,
122 loom_compat: false,
123 num_imports: 0,
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct CodeRelocation {
135 pub offset: u32,
137 pub symbol: String,
139}
140
141#[derive(Debug, Clone)]
143pub struct CompiledFunction {
144 pub name: String,
146 pub code: Vec<u8>,
148 pub wasm_ops: Vec<WasmOp>,
150 pub relocations: Vec<CodeRelocation>,
152}
153
154#[derive(Debug)]
156pub struct CompilationResult {
157 pub functions: Vec<CompiledFunction>,
159 pub elf: Option<Vec<u8>>,
161 pub backend_name: String,
163}
164
165#[derive(Debug, Clone)]
167pub struct BackendCapabilities {
168 pub produces_elf: bool,
170 pub supports_rule_verification: bool,
172 pub supports_binary_verification: bool,
174 pub is_external: bool,
176}
177
178pub trait Backend: Send + Sync {
180 fn name(&self) -> &str;
182
183 fn capabilities(&self) -> BackendCapabilities;
185
186 fn supported_targets(&self) -> Vec<TargetSpec>;
188
189 fn compile_module(
191 &self,
192 module: &DecodedModule,
193 config: &CompileConfig,
194 ) -> std::result::Result<CompilationResult, BackendError>;
195
196 fn compile_function(
198 &self,
199 name: &str,
200 ops: &[WasmOp],
201 config: &CompileConfig,
202 ) -> std::result::Result<CompiledFunction, BackendError>;
203
204 fn is_available(&self) -> bool;
206}
207
208pub struct BackendRegistry {
210 backends: HashMap<String, Box<dyn Backend>>,
211}
212
213impl BackendRegistry {
214 pub fn new() -> Self {
215 Self {
216 backends: HashMap::new(),
217 }
218 }
219
220 pub fn register(&mut self, backend: Box<dyn Backend>) {
222 let name = backend.name().to_string();
223 self.backends.insert(name, backend);
224 }
225
226 pub fn get(&self, name: &str) -> Option<&dyn Backend> {
228 self.backends.get(name).map(|b| b.as_ref())
229 }
230
231 pub fn list(&self) -> Vec<&dyn Backend> {
233 self.backends.values().map(|b| b.as_ref()).collect()
234 }
235
236 pub fn available(&self) -> Vec<&dyn Backend> {
238 self.backends
239 .values()
240 .filter(|b| b.is_available())
241 .map(|b| b.as_ref())
242 .collect()
243 }
244}
245
246impl Default for BackendRegistry {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_registry_empty() {
258 let reg = BackendRegistry::new();
259 assert!(reg.list().is_empty());
260 assert!(reg.available().is_empty());
261 assert!(reg.get("arm").is_none());
262 }
263
264 #[test]
265 fn test_compile_config_default() {
266 let config = CompileConfig::default();
267 assert_eq!(config.opt_level, 2);
268 assert!(!config.bounds_check);
269 assert_eq!(config.safety_bounds, SafetyBounds::None);
270 assert!(!config.no_optimize);
271 }
272
273 #[test]
274 fn safety_bounds_parse_round_trip() {
275 for s in ["none", "mpu", "software", "mask"] {
276 let sb = SafetyBounds::parse(s).unwrap();
277 assert_eq!(sb.as_str(), s);
278 }
279 assert_eq!(SafetyBounds::parse("pmp").unwrap(), SafetyBounds::Mpu);
280 assert_eq!(SafetyBounds::parse("soft").unwrap(), SafetyBounds::Software);
281 assert!(SafetyBounds::parse("nonsense").is_err());
282 }
283
284 #[test]
285 fn effective_safety_bounds_legacy_promotes_to_software() {
286 let cfg = CompileConfig {
287 bounds_check: true,
288 ..Default::default()
289 };
290 assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Software);
291 }
292
293 #[test]
294 fn effective_safety_bounds_new_field_wins() {
295 let cfg = CompileConfig {
296 bounds_check: true,
297 safety_bounds: SafetyBounds::Mpu,
298 ..Default::default()
299 };
300 assert_eq!(cfg.effective_safety_bounds(), SafetyBounds::Mpu);
301 }
302}