Skip to main content

synth_backend/
w2c2_wrapper.rs

1//! w2c2 WebAssembly-to-C Transpiler Wrapper
2//!
3//! Provides Rust interface to the w2c2 transpiler
4
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use synth_core::backend::{
8    Backend, BackendCapabilities, BackendError, CompilationResult, CompileConfig, CompiledFunction,
9};
10use synth_core::target::TargetSpec;
11use synth_core::wasm_decoder::DecodedModule;
12use synth_core::wasm_op::WasmOp;
13use synth_core::{Error, Result};
14
15/// w2c2 transpiler wrapper
16pub struct W2C2Transpiler {
17    /// Path to w2c2 executable
18    w2c2_path: PathBuf,
19}
20
21impl W2C2Transpiler {
22    /// Create a new w2c2 transpiler wrapper
23    ///
24    /// # Arguments
25    /// * `w2c2_path` - Path to the w2c2 executable
26    pub fn new<P: AsRef<Path>>(w2c2_path: P) -> Self {
27        Self {
28            w2c2_path: w2c2_path.as_ref().to_path_buf(),
29        }
30    }
31
32    /// Try to find w2c2 in the system PATH
33    pub fn from_path() -> Result<Self> {
34        // Try common locations
35        let paths = vec!["w2c2", "./w2c2", "../w2c2/build/w2c2"];
36
37        for path in paths {
38            if Path::new(path).exists() {
39                return Ok(Self::new(path));
40            }
41        }
42
43        Err(Error::Other(
44            "w2c2 not found in PATH. Please install w2c2 from https://github.com/turbolent/w2c2"
45                .to_string(),
46        ))
47    }
48
49    /// Transpile a WebAssembly module to C
50    ///
51    /// # Arguments
52    /// * `wasm_path` - Path to input .wasm file
53    /// * `output_path` - Path to output .c file (w2c2 will also create a .h file)
54    /// * `options` - Transpilation options
55    pub fn transpile<P: AsRef<Path>>(
56        &self,
57        wasm_path: P,
58        output_path: P,
59        options: &TranspileOptions,
60    ) -> Result<TranspileResult> {
61        let wasm_path = wasm_path.as_ref();
62        let output_path = output_path.as_ref();
63
64        // Verify input exists
65        if !wasm_path.exists() {
66            return Err(Error::Other(format!(
67                "Input WASM file not found: {}",
68                wasm_path.display()
69            )));
70        }
71
72        // Build w2c2 command
73        let mut cmd = Command::new(&self.w2c2_path);
74        cmd.arg(wasm_path);
75        cmd.arg(output_path);
76
77        // Add options
78        if let Some(funcs_per_file) = options.functions_per_file {
79            cmd.arg("-f");
80            cmd.arg(funcs_per_file.to_string());
81        }
82
83        if let Some(threads) = options.threads {
84            cmd.arg("-t");
85            cmd.arg(threads.to_string());
86        }
87
88        if options.debug {
89            cmd.arg("-d");
90        }
91
92        // Execute w2c2
93        let output = cmd.output().map_err(|e| {
94            Error::Other(format!(
95                "Failed to execute w2c2: {}. Make sure w2c2 is installed and accessible.",
96                e
97            ))
98        })?;
99
100        if !output.status.success() {
101            let stderr = String::from_utf8_lossy(&output.stderr);
102            return Err(Error::Other(format!(
103                "w2c2 transpilation failed: {}",
104                stderr
105            )));
106        }
107
108        // Determine output files
109        let c_file = output_path.to_path_buf();
110        let h_file = output_path.with_extension("h");
111
112        // Verify output files were created
113        if !c_file.exists() {
114            return Err(Error::Other(format!(
115                "Expected output file not created: {}",
116                c_file.display()
117            )));
118        }
119
120        Ok(TranspileResult {
121            c_file,
122            h_file: if h_file.exists() { Some(h_file) } else { None },
123            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
124        })
125    }
126}
127
128/// Options for WebAssembly-to-C transpilation
129#[derive(Debug, Clone)]
130pub struct TranspileOptions {
131    /// Number of functions per output file (for large modules)
132    pub functions_per_file: Option<usize>,
133
134    /// Number of worker threads for parallel compilation
135    pub threads: Option<usize>,
136
137    /// Include debug information
138    pub debug: bool,
139}
140
141impl Default for TranspileOptions {
142    fn default() -> Self {
143        Self {
144            functions_per_file: None,
145            threads: Some(1), // Single-threaded by default for determinism
146            debug: false,
147        }
148    }
149}
150
151/// Result of WebAssembly-to-C transpilation
152#[derive(Debug, Clone)]
153pub struct TranspileResult {
154    /// Path to generated .c file
155    pub c_file: PathBuf,
156
157    /// Path to generated .h file (if created)
158    pub h_file: Option<PathBuf>,
159
160    /// stdout from w2c2
161    pub stdout: String,
162}
163
164/// W2C2 backend — two-stage pipeline: w2c2 (WASM→C) + arm-none-eabi-gcc (C→ELF)
165///
166/// Verification tier: binary-level translation validation (ASIL B).
167pub struct W2C2Backend {
168    /// Path to w2c2 executable (None = search PATH)
169    w2c2_path: Option<String>,
170    /// Path to cross-compiler (None = search for arm-none-eabi-gcc)
171    gcc_path: Option<String>,
172}
173
174impl W2C2Backend {
175    pub fn new() -> Self {
176        Self {
177            w2c2_path: None,
178            gcc_path: None,
179        }
180    }
181
182    pub fn with_paths(w2c2: impl Into<String>, gcc: impl Into<String>) -> Self {
183        Self {
184            w2c2_path: Some(w2c2.into()),
185            gcc_path: Some(gcc.into()),
186        }
187    }
188
189    fn find_w2c2(&self) -> Option<String> {
190        if let Some(ref path) = self.w2c2_path
191            && Path::new(path).exists()
192        {
193            return Some(path.clone());
194        }
195        W2C2Transpiler::from_path()
196            .ok()
197            .map(|t| t.w2c2_path.to_string_lossy().to_string())
198    }
199
200    fn find_gcc(&self) -> Option<String> {
201        if let Some(ref path) = self.gcc_path
202            && Path::new(path).exists()
203        {
204            return Some(path.clone());
205        }
206        Command::new("which")
207            .arg("arm-none-eabi-gcc")
208            .output()
209            .ok()
210            .filter(|o| o.status.success())
211            .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
212    }
213}
214
215impl Default for W2C2Backend {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl Backend for W2C2Backend {
222    fn name(&self) -> &str {
223        "w2c2"
224    }
225
226    fn capabilities(&self) -> BackendCapabilities {
227        BackendCapabilities {
228            produces_elf: true,
229            supports_rule_verification: false,
230            supports_binary_verification: true,
231            is_external: true,
232        }
233    }
234
235    fn supported_targets(&self) -> Vec<TargetSpec> {
236        vec![TargetSpec::cortex_m4(), TargetSpec::cortex_m7()]
237    }
238
239    fn compile_module(
240        &self,
241        _module: &DecodedModule,
242        _config: &CompileConfig,
243    ) -> std::result::Result<CompilationResult, BackendError> {
244        let _w2c2 = self.find_w2c2().ok_or_else(|| {
245            BackendError::NotAvailable(
246                "w2c2 not found. Install from https://github.com/turbolent/w2c2".to_string(),
247            )
248        })?;
249        let _gcc = self.find_gcc().ok_or_else(|| {
250            BackendError::NotAvailable(
251                "arm-none-eabi-gcc not found. Install ARM GNU toolchain.".to_string(),
252            )
253        })?;
254
255        // TODO: Write WASM to temp file, w2c2 transpile to C, gcc compile to ELF
256        Err(BackendError::CompilationFailed(
257            "w2c2 module compilation not yet implemented".to_string(),
258        ))
259    }
260
261    fn compile_function(
262        &self,
263        _name: &str,
264        _ops: &[WasmOp],
265        _config: &CompileConfig,
266    ) -> std::result::Result<CompiledFunction, BackendError> {
267        Err(BackendError::UnsupportedConfig(
268            "w2c2 only supports whole-module compilation (use compile_module)".to_string(),
269        ))
270    }
271
272    fn is_available(&self) -> bool {
273        self.find_w2c2().is_some() && self.find_gcc().is_some()
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use std::fs;
281
282    #[test]
283    fn test_transpile_options_default() {
284        let options = TranspileOptions::default();
285        assert_eq!(options.functions_per_file, None);
286        assert_eq!(options.threads, Some(1));
287        assert!(!options.debug);
288    }
289
290    #[test]
291    #[ignore] // Requires w2c2 to be installed
292    fn test_transpile_simple_module() {
293        // Create a minimal WASM module for testing
294        let wasm_bytes = vec![
295            0x00, 0x61, 0x73, 0x6d, // Magic number
296            0x01, 0x00, 0x00, 0x00, // Version
297        ];
298
299        let temp_dir = std::env::temp_dir();
300        let wasm_path = temp_dir.join("test_module.wasm");
301        let output_path = temp_dir.join("test_module.c");
302
303        // Write test WASM file
304        fs::write(&wasm_path, wasm_bytes).unwrap();
305
306        // Try to find w2c2
307        if let Ok(transpiler) = W2C2Transpiler::from_path() {
308            let options = TranspileOptions::default();
309            let result = transpiler.transpile(&wasm_path, &output_path, &options);
310
311            match result {
312                Ok(res) => {
313                    assert!(res.c_file.exists());
314                    println!("Successfully transpiled to: {}", res.c_file.display());
315                }
316                Err(e) => {
317                    println!(
318                        "Transpilation error (expected if w2c2 not installed): {}",
319                        e
320                    );
321                }
322            }
323        } else {
324            println!("w2c2 not found in PATH - skipping transpilation test");
325        }
326
327        // Cleanup
328        let _ = fs::remove_file(&wasm_path);
329        let _ = fs::remove_file(&output_path);
330        let _ = fs::remove_file(output_path.with_extension("h"));
331    }
332
333    #[test]
334    fn test_w2c2_backend_properties() {
335        let backend = W2C2Backend::new();
336        assert_eq!(backend.name(), "w2c2");
337        assert!(backend.capabilities().produces_elf);
338        assert!(backend.capabilities().is_external);
339        assert!(!backend.capabilities().supports_rule_verification);
340    }
341
342    #[test]
343    fn test_w2c2_function_compilation_unsupported() {
344        let backend = W2C2Backend::new();
345        let config = CompileConfig::default();
346        let result = backend.compile_function("test", &[WasmOp::I32Add], &config);
347        assert!(result.is_err());
348    }
349}