Skip to main content

wasmedge_sys/
compiler.rs

1//! Defines WasmEdge ahead-of-time compiler.
2
3use crate::{ffi, utils, utils::check, Config, WasmEdgeResult};
4use std::path::Path;
5use wasmedge_types::error::WasmEdgeError;
6
7/// Defines WasmEdge ahead-of-time(AOT) compiler and the relevant APIs.
8#[derive(Debug)]
9pub struct Compiler {
10    pub(crate) inner: InnerCompiler,
11}
12impl Drop for Compiler {
13    fn drop(&mut self) {
14        if !self.inner.0.is_null() {
15            unsafe { ffi::WasmEdge_CompilerDelete(self.inner.0) }
16        }
17    }
18}
19impl Compiler {
20    /// Creates a new AOT [compiler](crate::Compiler).
21    ///
22    /// # Error
23    ///
24    /// If fail to create a AOT [compiler](crate::Compiler), then an error is returned.
25    pub fn create(config: Option<&Config>) -> WasmEdgeResult<Self> {
26        let ctx = match config {
27            Some(config) => unsafe { ffi::WasmEdge_CompilerCreate(config.inner.0) },
28            None => unsafe { ffi::WasmEdge_CompilerCreate(std::ptr::null_mut()) },
29        };
30
31        match ctx.is_null() {
32            true => Err(Box::new(WasmEdgeError::CompilerCreate)),
33            false => Ok(Self {
34                inner: InnerCompiler(ctx),
35            }),
36        }
37    }
38
39    /// Compiles the input WASM from the given file path for the AOT mode and stores the result to the output file path.
40    ///
41    /// # Arguments
42    ///
43    /// * `wasm_file` - The input wasm file, of which the file extension should be one of `wasm` or`wat`.
44    ///
45    /// * `aot_file` - The generated AOT wasm file, of which the file extension should be `dylib` on macOS, `so` on Linux or `dll` on Windows.
46    ///
47    /// # Error
48    ///
49    /// If fail to compile, then an error is returned.
50    pub fn compile_from_file(
51        &self,
52        wasm_file: impl AsRef<Path>,
53        aot_file: impl AsRef<Path>,
54    ) -> WasmEdgeResult<()> {
55        match wasm_file.as_ref().extension() {
56            Some(extension) => match extension.to_str() {
57                Some("wasm") => self.compile_from_wasm_file(wasm_file, aot_file),
58                Some("wat") => {
59                    let bytes = wat::parse_file(wasm_file.as_ref())
60                        .map_err(|_| WasmEdgeError::Operation("Failed to parse wat file".into()))?;
61                    self.compile_from_bytes(bytes, aot_file)
62                }
63                _ => Err(Box::new(WasmEdgeError::Operation(
64                    "The wasm file's extension should be `wasm` or `wat`".into(),
65                ))),
66            },
67            None => Err(Box::new(WasmEdgeError::Operation(
68                "The wasm file's extension should be `wasm` or `wat`".into(),
69            ))),
70        }
71    }
72
73    fn compile_from_wasm_file(
74        &self,
75        wasm_file: impl AsRef<Path>,
76        aot_file: impl AsRef<Path>,
77    ) -> WasmEdgeResult<()> {
78        let in_path = utils::path_to_cstring(wasm_file.as_ref())?;
79        let out_path = utils::path_to_cstring(aot_file.as_ref())?;
80        unsafe {
81            check(ffi::WasmEdge_CompilerCompile(
82                self.inner.0,
83                in_path.as_ptr(),
84                out_path.as_ptr(),
85            ))
86        }
87    }
88
89    /// Compiles the input WASM from the given bytes for the AOT mode and stores the result to the output file path.
90    ///
91    /// # Argument
92    ///
93    /// * `wasm_bytes` - The in-memory WASM bytes.
94    ///
95    /// * `aot_file` - The generated AOT wasm file, of which the file extension should be `dylib` on macOS, `so` on Linux or `dll` on Windows.
96    ///
97    /// # Error
98    ///
99    /// If fail to compile, then an error is returned.
100    pub fn compile_from_bytes(
101        &self,
102        wasm_bytes: impl AsRef<[u8]>,
103        aot_file: impl AsRef<Path>,
104    ) -> WasmEdgeResult<()> {
105        let wasm_bytes = wasm_bytes.as_ref();
106        let out_path = utils::path_to_cstring(aot_file.as_ref())?;
107        unsafe {
108            check(ffi::WasmEdge_CompilerCompileFromBuffer(
109                self.inner.0,
110                wasm_bytes.as_ptr(),
111                wasm_bytes.len() as u64,
112                out_path.as_ptr(),
113            ))
114        }
115    }
116
117    /// Provides a raw pointer to the inner Compiler context.
118    #[cfg(feature = "ffi")]
119    #[cfg_attr(docsrs, doc(cfg(feature = "ffi")))]
120    pub fn as_ptr(&self) -> *const ffi::WasmEdge_CompilerContext {
121        self.inner.0 as *const _
122    }
123}
124
125#[derive(Debug)]
126pub(crate) struct InnerCompiler(pub(crate) *mut ffi::WasmEdge_CompilerContext);
127unsafe impl Send for InnerCompiler {}
128unsafe impl Sync for InnerCompiler {}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::{
134        AsInstance, CallingFrame, Compiler, Config, Executor, FuncType, Function, ImportModule,
135        Loader, Store, Validator, WasmValue,
136    };
137    use std::{
138        io::Read,
139        sync::{Arc, Mutex},
140        thread,
141    };
142    use wasmedge_types::{
143        error::{CoreError, CoreLoadError},
144        wat2wasm, CompilerOptimizationLevel, CompilerOutputFormat, RunMode,
145    };
146
147    #[test]
148    #[allow(clippy::assertions_on_result_states)]
149    fn test_compiler() {
150        {
151            let result = Config::create();
152            assert!(result.is_ok());
153            let config = result.unwrap();
154
155            // create a AOT Compiler without configuration
156            let result = Compiler::create(None);
157            assert!(result.is_ok());
158
159            // create a AOT Compiler with a given configuration
160            let result = Compiler::create(Some(&config));
161            assert!(result.is_ok());
162            let compiler = result.unwrap();
163
164            // compile a file for universal WASM output format
165            let in_path = std::env::current_dir()
166                .unwrap()
167                .ancestors()
168                .nth(2)
169                .unwrap()
170                .join("examples/wasmedge-sys/data/fibonacci.wat");
171            #[cfg(target_os = "linux")]
172            let out_path = std::path::PathBuf::from("test_aot.so");
173            #[cfg(target_os = "macos")]
174            let out_path = std::path::PathBuf::from("test_aot.dylib");
175            #[cfg(target_os = "windows")]
176            let out_path = std::path::PathBuf::from("test_aot.dll");
177            assert!(!out_path.exists());
178            let result = compiler.compile_from_file(in_path, &out_path);
179            assert!(result.is_ok());
180            assert!(out_path.exists());
181            assert!(std::fs::remove_file(out_path).is_ok());
182
183            // compile a virtual file
184            let result = compiler.compile_from_file("not_exist.wasm", "not_exist_ast.wasm");
185            assert!(result.is_err());
186            assert_eq!(
187                result.unwrap_err(),
188                Box::new(WasmEdgeError::Core(CoreError::Load(
189                    CoreLoadError::IllegalPath
190                )))
191            );
192
193            // the text format is not accepted
194            let out_path = std::path::PathBuf::from("test_aot_malformed.wasm");
195            let result = compiler.compile_from_bytes(b"(module)", &out_path);
196            assert!(result.is_err());
197            assert_eq!(
198                result.unwrap_err(),
199                Box::new(WasmEdgeError::Core(CoreError::Load(
200                    CoreLoadError::MalformedMagic
201                )))
202            );
203            assert!(!out_path.exists());
204
205            // empty is not accepted
206            let result = compiler.compile_from_bytes([], &out_path);
207            assert!(result.is_err());
208            assert_eq!(
209                result.unwrap_err(),
210                Box::new(WasmEdgeError::Core(CoreError::Load(
211                    CoreLoadError::UnexpectedEnd
212                )))
213            );
214            assert!(!out_path.exists());
215        }
216
217        {
218            let result = Config::create();
219            assert!(result.is_ok());
220            let mut config = result.unwrap();
221            // compile file for shared library output format
222            config.set_aot_compiler_output_format(CompilerOutputFormat::Native);
223
224            let result = Compiler::create(Some(&config));
225            assert!(result.is_ok());
226            let compiler = result.unwrap();
227            let in_path = std::env::current_dir()
228                .unwrap()
229                .ancestors()
230                .nth(2)
231                .unwrap()
232                .join("examples/wasmedge-sys/data/fibonacci.wat");
233            #[cfg(target_os = "linux")]
234            let out_path = std::path::PathBuf::from("test_aot_from_file.so");
235            #[cfg(target_os = "macos")]
236            let out_path = std::path::PathBuf::from("test_aot_from_file.dylib");
237            #[cfg(target_os = "windows")]
238            let out_path = std::path::PathBuf::from("test_aot_from_file.dll");
239            assert!(!out_path.exists());
240            let result = compiler.compile_from_file(in_path, &out_path);
241            assert!(result.is_ok());
242            assert!(out_path.exists());
243
244            // read buffer
245            let result = std::fs::File::open(&out_path);
246            assert!(result.is_ok());
247            let mut f = result.unwrap();
248            let mut buffer = [0u8; 4];
249            let result = f.read(&mut buffer);
250            assert!(result.is_ok());
251            let wasm_magic: [u8; 4] = [0x00, 0x61, 0x73, 0x6D];
252            assert_ne!(buffer, wasm_magic);
253
254            // cleanup
255            assert!(std::fs::remove_file(out_path).is_ok());
256        }
257
258        {
259            let result = wat2wasm(
260                br#"(module
261                    (export "fib" (func $fib))
262                    (func $fib (param $n i32) (result i32)
263                     (if
264                      (i32.lt_s
265                       (local.get $n)
266                       (i32.const 2)
267                      )
268                      (then
269                        (return (i32.const 1))
270                      )
271                     )
272                     (return
273                      (i32.add
274                       (call $fib
275                        (i32.sub
276                         (local.get $n)
277                         (i32.const 2)
278                        )
279                       )
280                       (call $fib
281                        (i32.sub
282                         (local.get $n)
283                         (i32.const 1)
284                        )
285                       )
286                      )
287                     )
288                    )
289                   )
290              "#,
291            );
292            assert!(result.is_ok());
293            let wasm_bytes = result.unwrap();
294
295            let result = Config::create();
296            assert!(result.is_ok());
297            let mut config = result.unwrap();
298            config.set_aot_optimization_level(CompilerOptimizationLevel::O0);
299            config.set_aot_compiler_output_format(CompilerOutputFormat::Native);
300
301            let result = Compiler::create(Some(&config));
302            assert!(result.is_ok());
303            let compiler = result.unwrap();
304            #[cfg(target_os = "linux")]
305            let out_path = std::path::PathBuf::from("test_aot_from_bytes.so");
306            #[cfg(target_os = "macos")]
307            let out_path = std::path::PathBuf::from("test_aot_from_bytes.dylib");
308            #[cfg(target_os = "windows")]
309            let out_path = std::path::PathBuf::from("test_aot_from_bytes.dll");
310            assert!(!out_path.exists());
311            let result = compiler.compile_from_bytes(wasm_bytes, &out_path);
312            assert!(result.is_ok());
313            assert!(out_path.exists());
314
315            // cleanup
316            assert!(std::fs::remove_file(out_path).is_ok());
317        }
318    }
319
320    #[test]
321    #[ignore]
322    #[allow(clippy::assertions_on_result_states)]
323    fn test_compiler_send() {
324        let result = Config::create();
325        assert!(result.is_ok());
326        let config = result.unwrap();
327
328        // create a AOT Compiler without configuration
329        let result = Compiler::create(None);
330        assert!(result.is_ok());
331
332        // create a AOT Compiler with a given configuration
333        let result = Compiler::create(Some(&config));
334        assert!(result.is_ok());
335        let compiler = result.unwrap();
336
337        let handle = thread::spawn(move || {
338            // compile a file for universal WASM output format
339            let in_path = std::env::current_dir()
340                .unwrap()
341                .join("examples/data/fibonacci.wat");
342            #[cfg(target_os = "linux")]
343            let out_path = std::path::PathBuf::from("test_aot_fib_send.so");
344            #[cfg(target_os = "macos")]
345            let out_path = std::path::PathBuf::from("test_aot_fib_send.dylib");
346            #[cfg(target_os = "windows")]
347            let out_path = std::path::PathBuf::from("test_aot_fib_send.dll");
348            assert!(!out_path.exists());
349            let result = compiler.compile_from_file(in_path, &out_path);
350            assert!(result.is_ok());
351            assert!(out_path.exists());
352            assert!(std::fs::remove_file(out_path).is_ok());
353        });
354
355        handle.join().unwrap();
356    }
357
358    #[test]
359    #[ignore]
360    #[allow(clippy::assertions_on_result_states)]
361    fn test_compiler_sync() {
362        let result = Config::create();
363        assert!(result.is_ok());
364        let config = result.unwrap();
365
366        // create a AOT Compiler without configuration
367        let result = Compiler::create(None);
368        assert!(result.is_ok());
369
370        // create a AOT Compiler with a given configuration
371        let result = Compiler::create(Some(&config));
372        assert!(result.is_ok());
373        let compiler = Arc::new(Mutex::new(result.unwrap()));
374
375        let compiler_cloned = Arc::clone(&compiler);
376        let handle = thread::spawn(move || {
377            let result = compiler_cloned.lock();
378            assert!(result.is_ok());
379            let compiler = result.unwrap();
380
381            // compile a file for universal WASM output format
382            let in_path = std::env::current_dir()
383                .unwrap()
384                .join("examples/data/fibonacci.wat");
385            let out_path = std::path::PathBuf::from("fibonacci_sync_thread_aot.wasm");
386            assert!(!out_path.exists());
387            let result = compiler.compile_from_file(in_path, &out_path);
388            assert!(result.is_ok());
389            assert!(out_path.exists());
390            assert!(std::fs::remove_file(out_path).is_ok());
391        });
392
393        {
394            let result = compiler.lock();
395            assert!(result.is_ok());
396            let compiler_main = result.unwrap();
397            // compile a file for universal WASM output format
398            let in_path = std::env::current_dir()
399                .unwrap()
400                .join("examples/data/fibonacci.wat");
401            #[cfg(target_os = "linux")]
402            let out_path = std::path::PathBuf::from("test_aot_fib_sync.so");
403            #[cfg(target_os = "macos")]
404            let out_path = std::path::PathBuf::from("test_aot_fib_sync.dylib");
405            #[cfg(target_os = "windows")]
406            let out_path = std::path::PathBuf::from("test_aot_fib_sync.dll");
407            assert!(!out_path.exists());
408            let result = compiler_main.compile_from_file(in_path, &out_path);
409            assert!(result.is_ok());
410            assert!(out_path.exists());
411            assert!(std::fs::remove_file(out_path).is_ok());
412        }
413
414        handle.join().unwrap();
415    }
416
417    #[test]
418    fn test_aot() -> Result<(), Box<dyn std::error::Error>> {
419        // create a Config context
420        let mut config = Config::create()?;
421        // enable options
422        config.tail_call(true);
423        config.annotations(true);
424        config.memory64(true);
425        config.threads(true);
426        config.exception_handling(true);
427        config.function_references(true);
428        config.set_aot_optimization_level(CompilerOptimizationLevel::O0);
429        config.set_aot_compiler_output_format(CompilerOutputFormat::Native);
430        config.interruptible(true);
431        // run the compiled module in AOT mode (since WasmEdge 0.17.0, the
432        // default mode is interpreter, which ignores the AOT-compiled code)
433        config.set_run_mode(RunMode::Aot);
434
435        // create an executor
436        let mut executor = Executor::create(Some(&config), None)?;
437
438        // create a store
439        let mut store = Store::create()?;
440
441        let import = create_spec_test_module();
442        executor.register_import_module(&mut store, &import)?;
443
444        // set the AOT compiler options
445        let result = Compiler::create(Some(&config));
446        assert!(result.is_ok());
447        let compiler = result.unwrap();
448
449        // compile a file for universal WASM output format
450        let in_path = std::env::current_dir()
451            .unwrap()
452            .ancestors()
453            .nth(2)
454            .unwrap()
455            .join("examples/wasmedge-sys/data/fibonacci.wat");
456        #[cfg(target_os = "macos")]
457        let out_path = std::path::PathBuf::from("fibonacci_aot.dylib");
458        #[cfg(target_os = "linux")]
459        let out_path = std::path::PathBuf::from("fibonacci_aot.so");
460        #[cfg(target_os = "windows")]
461        let out_path = std::path::PathBuf::from("fibonacci_aot.dll");
462        assert!(!out_path.exists());
463        let result = compiler.compile_from_file(in_path, &out_path);
464        assert!(result.is_ok());
465        assert!(out_path.exists());
466
467        {
468            // register the wasm module as named module
469            let extern_module = Loader::create(Some(&config))?.from_file(&out_path)?;
470            Validator::create(Some(&config))?.validate(&extern_module)?;
471            let mut extern_instance =
472                executor.register_named_module(&mut store, &extern_module, "extern")?;
473
474            let mut fib = extern_instance.get_func_mut("fib")?;
475            let returns = executor.call_func(&mut fib, [WasmValue::from_i32(5)])?;
476            assert_eq!(returns[0].to_i32(), 8);
477        }
478
479        {
480            // register the wasm module as active module
481            let active_module = Loader::create(Some(&config))?.from_file(&out_path)?;
482            Validator::create(Some(&config))?.validate(&active_module)?;
483            let mut active_instance =
484                executor.register_active_module(&mut store, &active_module)?;
485
486            let mut fib = active_instance.get_func_mut("fib")?;
487            let returns = executor.call_func(&mut fib, [WasmValue::from_i32(5)])?;
488            assert_eq!(returns[0].to_i32(), 8);
489        }
490
491        // remove the wasm file by the compiler
492        assert!(std::fs::remove_file(&out_path).is_ok());
493
494        Ok(())
495    }
496
497    #[cfg(feature = "aot")]
498    fn create_spec_test_module() -> ImportModule<()> {
499        // create an ImportObj module
500        let result = ImportModule::create("spectest", Box::new(()));
501        assert!(result.is_ok());
502        let mut import = result.unwrap();
503
504        // create a host function
505        let func_ty = FuncType::new(vec![], vec![]);
506        let result = unsafe {
507            Function::create_sync_func(&func_ty, spec_test_print, import.get_host_data_mut(), 0)
508        };
509        assert!(result.is_ok());
510        let host_func = result.unwrap();
511        // add host function "print"
512        import.add_func("print", host_func);
513        import
514    }
515
516    #[cfg(feature = "aot")]
517    fn spec_test_print(
518        _data: &mut (),
519        _inst: &mut crate::Instance,
520        _frame: &mut CallingFrame,
521        _inputs: Vec<WasmValue>,
522    ) -> Result<Vec<WasmValue>, CoreError> {
523        Ok(vec![])
524    }
525}