Skip to main content

barbacane_wasm/
engine.rs

1//! WASM engine configuration and AOT compilation.
2//!
3//! This module provides the core wasmtime engine with settings optimized
4//! for the Barbacane plugin runtime.
5
6use wasmtime::{Config, Engine, Module, OptLevel};
7
8use crate::error::WasmError;
9use crate::limits::PluginLimits;
10
11/// A compiled WASM module ready for instantiation.
12#[derive(Clone)]
13pub struct CompiledModule {
14    module: Module,
15    /// The plugin name this module belongs to.
16    pub name: String,
17    /// The plugin version.
18    pub version: String,
19    /// Whether this plugin needs the request body in `on_request`.
20    pub body_access: bool,
21}
22
23impl CompiledModule {
24    /// Get a reference to the underlying wasmtime module.
25    pub fn module(&self) -> &Module {
26        &self.module
27    }
28}
29
30/// The WASM engine that compiles and manages plugin modules.
31pub struct WasmEngine {
32    engine: Engine,
33    limits: PluginLimits,
34}
35
36impl WasmEngine {
37    /// Create a new WASM engine with default configuration.
38    pub fn new() -> Result<Self, WasmError> {
39        Self::with_limits(PluginLimits::default())
40    }
41
42    /// Create a new WASM engine with custom resource limits.
43    pub fn with_limits(limits: PluginLimits) -> Result<Self, WasmError> {
44        let mut config = Config::new();
45
46        // Use Cranelift for AOT compilation with speed optimization
47        config.cranelift_opt_level(OptLevel::Speed);
48
49        // Enable fuel consumption for execution time limiting
50        config.consume_fuel(true);
51
52        // Configure memory settings
53        config.max_wasm_stack(limits.max_stack_bytes);
54
55        // Enable reference types (for modern WASM features)
56        config.wasm_reference_types(true);
57
58        // Enable bulk memory operations
59        config.wasm_bulk_memory(true);
60
61        // Enable multi-value returns
62        config.wasm_multi_value(true);
63
64        // Disable WASM features we don't need
65        config.wasm_threads(false);
66
67        let engine = Engine::new(&config).map_err(|e| WasmError::EngineCreation(e.to_string()))?;
68
69        Ok(Self { engine, limits })
70    }
71
72    /// Get a reference to the underlying wasmtime engine.
73    pub fn engine(&self) -> &Engine {
74        &self.engine
75    }
76
77    /// Get the configured resource limits.
78    pub fn limits(&self) -> &PluginLimits {
79        &self.limits
80    }
81
82    /// AOT-compile a WASM module from bytes.
83    ///
84    /// The compiled module can be instantiated multiple times efficiently.
85    pub fn compile(
86        &self,
87        wasm_bytes: &[u8],
88        name: String,
89        version: String,
90        body_access: bool,
91    ) -> Result<CompiledModule, WasmError> {
92        let module = Module::new(&self.engine, wasm_bytes)
93            .map_err(|e| WasmError::Compilation(e.to_string()))?;
94
95        Ok(CompiledModule {
96            module,
97            name,
98            version,
99            body_access,
100        })
101    }
102
103    /// Validate a WASM module without fully compiling it.
104    ///
105    /// This is faster than full compilation and useful for quick validation.
106    pub fn validate(&self, wasm_bytes: &[u8]) -> Result<(), WasmError> {
107        Module::validate(&self.engine, wasm_bytes)
108            .map_err(|e| WasmError::Compilation(e.to_string()))
109    }
110}
111
112impl Default for WasmEngine {
113    fn default() -> Self {
114        Self::new().expect("failed to create default WASM engine")
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    // Minimal valid WASM module (empty module)
123    const MINIMAL_WASM: &[u8] = &[
124        0x00, 0x61, 0x73, 0x6d, // magic number
125        0x01, 0x00, 0x00, 0x00, // version
126    ];
127
128    #[test]
129    fn create_engine() {
130        let engine = WasmEngine::new();
131        assert!(engine.is_ok());
132    }
133
134    #[test]
135    fn create_engine_with_limits() {
136        let limits = PluginLimits::default().with_memory(32 * 1024 * 1024);
137        let engine = WasmEngine::with_limits(limits);
138        assert!(engine.is_ok());
139    }
140
141    #[test]
142    fn validate_minimal_wasm() {
143        let engine = WasmEngine::new().unwrap();
144        assert!(engine.validate(MINIMAL_WASM).is_ok());
145    }
146
147    #[test]
148    fn validate_invalid_wasm() {
149        let engine = WasmEngine::new().unwrap();
150        let invalid = &[0x00, 0x00, 0x00, 0x00];
151        assert!(engine.validate(invalid).is_err());
152    }
153
154    #[test]
155    fn compile_minimal_wasm() {
156        let engine = WasmEngine::new().unwrap();
157        let result = engine.compile(MINIMAL_WASM, "test".into(), "1.0.0".into(), false);
158        assert!(result.is_ok());
159    }
160
161    #[test]
162    fn compiled_module_has_metadata() {
163        let engine = WasmEngine::new().unwrap();
164        let module = engine
165            .compile(MINIMAL_WASM, "my-plugin".into(), "2.1.0".into(), false)
166            .unwrap();
167        assert_eq!(module.name, "my-plugin");
168        assert_eq!(module.version, "2.1.0");
169    }
170}