1use async_trait::async_trait;
12use wasmtime::component::{Component, Linker};
13use wasmtime::{Config, Engine, Store, StoreLimitsBuilder};
14
15use torvyn_types::ComponentId;
16
17use crate::config::WasmtimeEngineConfig;
18use crate::error::EngineError;
19use crate::traits::WasmEngine;
20use crate::types::{
21 CompiledComponent, CompiledComponentInner, ComponentInstance, ComponentInstanceInner,
22 HostState, ImportBindings, ImportBindingsInner, WasmtimeInstanceState,
23};
24
25pub struct WasmtimeEngine {
41 engine: Engine,
43
44 config: WasmtimeEngineConfig,
46}
47
48impl WasmtimeEngine {
49 pub fn new(config: WasmtimeEngineConfig) -> Result<Self, EngineError> {
56 let problems = config.validate();
57 if !problems.is_empty() {
58 return Err(EngineError::Internal {
59 reason: format!("Invalid engine configuration: {}", problems.join("; ")),
60 });
61 }
62
63 let mut wasmtime_config = Config::new();
64
65 if config.fuel_enabled {
71 wasmtime_config.consume_fuel(true);
72 }
73
74 wasmtime_config.wasm_simd(config.simd_enabled);
76
77 wasmtime_config.wasm_multi_memory(config.multi_memory);
79
80 wasmtime_config.wasm_component_model(true);
82
83 wasmtime_config.max_wasm_stack(config.stack_size);
85
86 if let Some(threads) = config.compilation_threads {
88 wasmtime_config.parallel_compilation(threads > 1);
89 }
90
91 match config.strategy {
93 crate::config::CompilationStrategy::Cranelift => {
94 wasmtime_config.strategy(wasmtime::Strategy::Cranelift);
95 }
96 crate::config::CompilationStrategy::Winch => {
97 wasmtime_config.strategy(wasmtime::Strategy::Cranelift);
100 }
101 }
102
103 let engine = Engine::new(&wasmtime_config).map_err(|e| EngineError::Internal {
104 reason: format!("Failed to create Wasmtime engine: {e}"),
105 })?;
106
107 Ok(Self { engine, config })
108 }
109
110 #[inline]
115 pub fn inner(&self) -> &Engine {
116 &self.engine
117 }
118
119 #[inline]
121 pub fn config(&self) -> &WasmtimeEngineConfig {
122 &self.config
123 }
124
125 fn create_store(&self, component_id: ComponentId) -> Store<HostState> {
129 let limits = StoreLimitsBuilder::new()
130 .memory_size(self.config.max_memory_bytes)
131 .table_elements(self.config.max_table_elements as usize)
132 .instances(self.config.max_instances as usize)
133 .trap_on_grow_failure(true) .build();
135
136 let host_state = HostState {
137 component_id,
138 limits,
139 fuel_budget: self.config.default_fuel,
140 };
141
142 let mut store = Store::new(&self.engine, host_state);
143
144 store.limiter(|state| &mut state.limits);
146
147 if self.config.fuel_enabled {
149 store
150 .set_fuel(self.config.default_fuel)
151 .expect("fuel should be configurable when consume_fuel is enabled");
152
153 if self.config.fuel_yield_interval > 0 {
155 store
156 .fuel_async_yield_interval(Some(self.config.fuel_yield_interval))
157 .expect("fuel yield interval should be configurable");
158 }
159 }
160
161 store
162 }
163
164 #[allow(dead_code)]
169 pub(crate) fn create_linker(&self) -> Linker<HostState> {
170 Linker::new(&self.engine)
171 }
172
173 #[allow(dead_code)]
178 pub(crate) fn import_bindings_from_linker(linker: Linker<HostState>) -> ImportBindings {
179 ImportBindings {
180 inner: ImportBindingsInner::Wasmtime(linker),
181 }
182 }
183}
184
185#[async_trait]
186impl WasmEngine for WasmtimeEngine {
187 fn compile_component(&self, bytes: &[u8]) -> Result<CompiledComponent, EngineError> {
188 let component =
189 Component::new(&self.engine, bytes).map_err(|e| EngineError::CompilationFailed {
190 reason: e.to_string(),
191 source_hint: None,
192 })?;
193
194 Ok(CompiledComponent {
195 inner: CompiledComponentInner::Wasmtime(component),
196 })
197 }
198
199 fn serialize_component(&self, compiled: &CompiledComponent) -> Result<Vec<u8>, EngineError> {
200 match &compiled.inner {
201 CompiledComponentInner::Wasmtime(component) => {
202 component.serialize().map_err(|e| EngineError::Internal {
203 reason: format!("Serialization failed: {e}"),
204 })
205 }
206 _ => Err(EngineError::Internal {
207 reason: "Cannot serialize non-Wasmtime component".into(),
208 }),
209 }
210 }
211
212 unsafe fn deserialize_component(
213 &self,
214 bytes: &[u8],
215 ) -> Result<Option<CompiledComponent>, EngineError> {
216 match unsafe { Component::deserialize(&self.engine, bytes) } {
220 Ok(component) => Ok(Some(CompiledComponent {
221 inner: CompiledComponentInner::Wasmtime(component),
222 })),
223 Err(e) => {
224 let msg = e.to_string();
225 if msg.contains("incompatible") || msg.contains("version") {
226 Ok(None)
227 } else {
228 Err(EngineError::DeserializationFailed { reason: msg })
229 }
230 }
231 }
232 }
233
234 async fn instantiate(
235 &self,
236 compiled: &CompiledComponent,
237 imports: ImportBindings,
238 component_id: ComponentId,
239 ) -> Result<ComponentInstance, EngineError> {
240 let component = match &compiled.inner {
241 CompiledComponentInner::Wasmtime(c) => c,
242 _ => {
243 return Err(EngineError::Internal {
244 reason: "Cannot instantiate non-Wasmtime component".into(),
245 });
246 }
247 };
248
249 let linker = match imports.inner {
250 ImportBindingsInner::Wasmtime(l) => l,
251 _ => {
252 return Err(EngineError::Internal {
253 reason: "Cannot use non-Wasmtime import bindings".into(),
254 });
255 }
256 };
257
258 let mut store = self.create_store(component_id);
259
260 let instance = linker
262 .instantiate_async(&mut store, component)
263 .await
264 .map_err(|e| EngineError::InstantiationFailed {
265 component_id,
266 reason: e.to_string(),
267 })?;
268
269 let func_process = instance.get_func(&mut store, "process");
271 let func_pull = instance.get_func(&mut store, "pull");
272 let func_push = instance.get_func(&mut store, "push");
273 let func_init = instance.get_func(&mut store, "init");
274 let func_teardown = instance.get_func(&mut store, "teardown");
275
276 let has_processor = func_process.is_some();
277 let has_source = func_pull.is_some();
278 let has_sink = func_push.is_some();
279 let has_lifecycle = func_init.is_some();
280
281 let state = WasmtimeInstanceState {
282 store,
283 instance,
284 func_process,
285 func_pull,
286 func_push,
287 func_init,
288 func_teardown,
289 };
290
291 Ok(ComponentInstance {
292 component_id,
293 inner: ComponentInstanceInner::Wasmtime(state),
294 has_lifecycle,
295 has_processor,
296 has_source,
297 has_sink,
298 })
299 }
300
301 fn set_fuel(&self, instance: &mut ComponentInstance, fuel: u64) -> Result<(), EngineError> {
303 match &mut instance.inner {
304 ComponentInstanceInner::Wasmtime(state) => {
305 state
306 .store
307 .set_fuel(fuel)
308 .map_err(|e| EngineError::Internal {
309 reason: format!("Failed to set fuel: {e}"),
310 })
311 }
312 _ => Err(EngineError::Internal {
313 reason: "set_fuel called on non-Wasmtime instance".into(),
314 }),
315 }
316 }
317
318 fn fuel_remaining(&self, instance: &ComponentInstance) -> Option<u64> {
320 match &instance.inner {
321 ComponentInstanceInner::Wasmtime(state) => state.store.get_fuel().ok(),
322 _ => None,
323 }
324 }
325
326 fn memory_usage(&self, instance: &ComponentInstance) -> usize {
328 match &instance.inner {
329 ComponentInstanceInner::Wasmtime(_state) => {
330 0
335 }
336 _ => 0,
337 }
338 }
339}
340
341#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_wasmtime_engine_creation() {
351 let config = WasmtimeEngineConfig::default();
352 let engine = WasmtimeEngine::new(config);
353 assert!(engine.is_ok());
354 }
355
356 #[test]
357 fn test_wasmtime_engine_invalid_config() {
358 let config = WasmtimeEngineConfig {
359 fuel_enabled: true,
360 default_fuel: 0, ..WasmtimeEngineConfig::default()
362 };
363 let engine = WasmtimeEngine::new(config);
364 assert!(engine.is_err());
365 }
366
367 #[test]
368 fn test_compile_invalid_bytes() {
369 let config = WasmtimeEngineConfig::default();
370 let engine = WasmtimeEngine::new(config).unwrap();
371 let result = engine.compile_component(b"not a wasm component");
372 assert!(result.is_err());
373 match result.unwrap_err() {
374 EngineError::CompilationFailed { .. } => {}
375 other => panic!("expected CompilationFailed, got: {other}"),
376 }
377 }
378
379 #[test]
380 fn test_compile_empty_bytes() {
381 let config = WasmtimeEngineConfig::default();
382 let engine = WasmtimeEngine::new(config).unwrap();
383 let result = engine.compile_component(b"");
384 assert!(result.is_err());
385 }
386
387 #[test]
388 fn test_compile_minimal_component_from_wat() {
389 let config = WasmtimeEngineConfig::default();
390 let engine = WasmtimeEngine::new(config).unwrap();
391
392 let wat = "(component)";
395 let _result = engine.compile_component(wat.as_bytes());
398 let component = Component::new(engine.inner(), wat);
399 assert!(component.is_ok(), "engine should compile minimal WAT");
400 }
401
402 #[test]
403 fn test_engine_config_accessors() {
404 let config = WasmtimeEngineConfig::default();
405 let engine = WasmtimeEngine::new(config).unwrap();
406 assert!(engine.config().fuel_enabled);
407 }
408
409 #[test]
410 fn test_serialize_deserialize_roundtrip() {
411 let config = WasmtimeEngineConfig::default();
412 let engine = WasmtimeEngine::new(config).unwrap();
413
414 let component = Component::new(engine.inner(), "(component)").expect("compile WAT");
416 let compiled = CompiledComponent {
417 inner: CompiledComponentInner::Wasmtime(component),
418 };
419
420 let bytes = engine
422 .serialize_component(&compiled)
423 .expect("serialize should work");
424 assert!(!bytes.is_empty());
425
426 let deserialized =
429 unsafe { engine.deserialize_component(&bytes) }.expect("deserialize should work");
430 assert!(deserialized.is_some());
431 }
432
433 #[tokio::test]
434 async fn test_instantiate_minimal_component() {
435 let config = WasmtimeEngineConfig::default();
436 let engine = WasmtimeEngine::new(config).unwrap();
437
438 let component = Component::new(engine.inner(), "(component)").expect("compile WAT");
439 let compiled = CompiledComponent {
440 inner: CompiledComponentInner::Wasmtime(component),
441 };
442
443 let linker = engine.create_linker();
444 let imports = WasmtimeEngine::import_bindings_from_linker(linker);
445 let component_id = ComponentId::new(1);
446
447 let instance = engine.instantiate(&compiled, imports, component_id).await;
448 assert!(instance.is_ok());
449
450 let inst = instance.unwrap();
451 assert_eq!(inst.component_id(), component_id);
452 assert!(!inst.has_processor());
454 assert!(!inst.has_source());
455 assert!(!inst.has_sink());
456 assert!(!inst.has_lifecycle());
457 }
458
459 #[tokio::test]
460 async fn test_fuel_set_and_read() {
461 let config = WasmtimeEngineConfig::default();
462 let engine = WasmtimeEngine::new(config).unwrap();
463
464 let component = Component::new(engine.inner(), "(component)").expect("compile WAT");
465 let compiled = CompiledComponent {
466 inner: CompiledComponentInner::Wasmtime(component),
467 };
468
469 let linker = engine.create_linker();
470 let imports = WasmtimeEngine::import_bindings_from_linker(linker);
471 let mut instance = engine
472 .instantiate(&compiled, imports, ComponentId::new(1))
473 .await
474 .unwrap();
475
476 let remaining = engine.fuel_remaining(&instance);
478 assert!(remaining.is_some());
479
480 engine.set_fuel(&mut instance, 500).unwrap();
482 assert_eq!(engine.fuel_remaining(&instance), Some(500));
483 }
484
485 #[test]
486 fn test_memory_usage_returns_zero_for_now() {
487 let config = WasmtimeEngineConfig::default();
488 let engine = WasmtimeEngine::new(config).unwrap();
489
490 let _ = engine;
493 }
494}