Skip to main content

Grove/WASM/
Runtime.rs

1//! WASM Runtime Module
2//!
3//! Provides WASMtime engine and store management for executing WebAssembly
4//! modules. This module handles the core WASM runtime infrastructure.
5
6use std::sync::Arc;
7
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11use wasmtime::{Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, WasmBacktraceDetails};
12
13use crate::{
14	WASM::{
15		DEFAULT_MAX_EXECUTION_TIME_MS,
16		DEFAULT_MEMORY_LIMIT_MB,
17		MemoryManager::{MemoryLimits, MemoryManagerImpl},
18	},
19	dev_log,
20};
21
22/// Configuration for the WASM runtime
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WASMConfig {
25	/// Memory limit in MB for WASM modules
26	pub memory_limit_mb:u64,
27
28	/// Maximum execution time in milliseconds
29	pub max_execution_time_ms:u64,
30
31	/// Enable WASI (WebAssembly System Interface)
32	pub enable_wasi:bool,
33
34	/// Enable debugging support
35	pub enable_debug:bool,
36
37	/// Allow WASM modules to spawn threads
38	pub allow_threads:bool,
39
40	/// Allow WASM modules to access host memory
41	pub allow_host_memory:bool,
42
43	/// Enable fuel metering for execution limits
44	pub enable_fuel_metering:bool,
45}
46
47impl Default for WASMConfig {
48	fn default() -> Self {
49		Self {
50			memory_limit_mb:DEFAULT_MEMORY_LIMIT_MB,
51
52			max_execution_time_ms:DEFAULT_MAX_EXECUTION_TIME_MS,
53
54			enable_wasi:true,
55
56			enable_debug:cfg!(debug_assertions),
57
58			allow_threads:false,
59
60			allow_host_memory:false,
61
62			enable_fuel_metering:true,
63		}
64	}
65}
66
67impl WASMConfig {
68	/// Create a new WASM configuration with custom settings
69	pub fn new(memory_limit_mb:u64, max_execution_time_ms:u64, enable_wasi:bool) -> Self {
70		Self { memory_limit_mb, max_execution_time_ms, enable_wasi, ..Default::default() }
71	}
72
73	/// Apply this configuration to a WASMtime engine builder
74	fn apply_to_engine_builder(&self, mut builder:wasmtime::Config) -> Result<wasmtime::Config> {
75		// Enable WASM
76		builder.wasm_component_model(false);
77
78		// WASI support is configured later through the linker
79		// In Wasmtime 20.0.2, WASI is enabled via wasmtime_wasi crate integration
80		// The actual WASI preview1 and preview2 support is added at runtime
81		// when the linker is configured with WASI modules
82		if self.enable_wasi {
83			// WASI preview1 support is now handled through wasmtime_wasi::add_to_linker
84			// which will be called in create_linker()
85			dev_log!("wasm", "[WASMRuntime] WASI support enabled, will be configured in linker");
86		}
87
88		// Enable fuel metering for execution limits
89		if self.enable_fuel_metering {
90			builder.consume_fuel(true);
91		}
92
93		// Enable multi-memory if needed
94		builder.wasm_multi_memory(false);
95
96		// Enable multi-threading if allowed
97		builder.wasm_threads(self.allow_threads);
98
99		// Enable reference types
100		builder.wasm_reference_types(true);
101
102		// Enable SIMD if available
103		builder.wasm_simd(true);
104
105		// Enable bulk memory operations
106		builder.wasm_bulk_memory(true);
107
108		// Enable debugging in debug builds
109		if self.enable_debug {
110			builder.debug_info(true);
111
112			builder.wasm_backtrace_details(WasmBacktraceDetails::Enable);
113		}
114
115		Ok(builder)
116	}
117}
118
119/// WASM Runtime - manages WASMtime engine and stores
120#[derive(Clone)]
121pub struct WASMRuntime {
122	engine:Engine,
123
124	config:WASMConfig,
125
126	memory_manager:Arc<RwLock<MemoryManagerImpl>>,
127
128	instances:Arc<RwLock<Vec<String>>>,
129}
130
131impl WASMRuntime {
132	/// Create a new WASM runtime with the given configuration
133	pub async fn new(config:WASMConfig) -> Result<Self> {
134		dev_log!("wasm", "Creating WASM runtime with config: {:?}", config);
135
136		// Build the WASMtime engine
137		let engine_config = wasmtime::Config::new();
138
139		let engine_config = config.apply_to_engine_builder(engine_config)?;
140
141		let engine =
142			Engine::new(&engine_config).map_err(|e| anyhow::anyhow!("Failed to create WASMtime engine: {}", e))?;
143
144		// Initialize memory manager
145		let memory_limits = MemoryLimits {
146			max_memory_mb:config.memory_limit_mb,
147
148			// Set 75% of max for initial allocation
149			initial_memory_mb:(config.memory_limit_mb as f64 * 0.75) as u64,
150
151			max_table_size:1024,
152
153			// Set maximum of 100 instances
154			max_instances:100,
155
156			max_memories:10,
157
158			max_tables:10,
159		};
160
161		let memory_manager = Arc::new(RwLock::new(MemoryManagerImpl::new(memory_limits)));
162
163		dev_log!("wasm", "WASM runtime created successfully");
164
165		Ok(Self { engine, config, memory_manager, instances:Arc::new(RwLock::new(Vec::new())) })
166	}
167
168	/// Get a reference to the WASMtime engine
169	pub fn engine(&self) -> &Engine { &self.engine }
170
171	/// Get the runtime configuration
172	pub fn config(&self) -> &WASMConfig { &self.config }
173
174	/// Get the memory manager
175	pub fn memory_manager(&self) -> Arc<RwLock<MemoryManagerImpl>> { Arc::clone(&self.memory_manager) }
176
177	/// Create a new WASM store with limits
178	pub fn create_store(&self) -> Result<Store<StoreLimits>> {
179		let store_limits = StoreLimitsBuilder::new()
180	        .memory_size((self.config.memory_limit_mb * 1024 * 1024) as usize) // Convert MB to bytes
181	        .table_elements(1024)
182	        .instances(100)
183	        .memories(10)
184	        .tables(10)
185	        .build();
186
187		// Set fuel limit if enabled
188		let mut store = Store::new(&self.engine, store_limits);
189
190		if self.config.enable_fuel_metering {
191			// Set fuel based on execution time (rough approximation: 1 unit = 1000 ns)
192			let fuel = self.config.max_execution_time_ms * 1_000; // Convert ms to fuel
193			store
194				.set_fuel(fuel)
195				.map_err(|e| anyhow::anyhow!("Failed to set fuel limit: {}", e))?;
196		}
197
198		Ok(store)
199	}
200
201	/// Create a linker for the runtime
202	pub fn create_linker<T>(&self, async_support:bool) -> Result<Linker<T>>
203	where
204		T: Send, {
205		let mut linker = Linker::new(&self.engine);
206
207		// Configure WASI support if enabled using Wasmtime 20.0.2 API
208		if self.config.enable_wasi {
209			// In Wasmtime 20.0.2, WASI is configured via wasmtime_wasi crate
210			// The configuration involves:
211			// 1. Creating a WasiCtxBuilder with the desired configuration
212			// 2. Adding it to the linker using wasmtime_wasi::add_to_linker
213			//
214			// Note: Actual WASI implementation requires:
215			// - Runtime-dependent context (stdin, stdout, stderr, filesystem, etc.)
216			// - This is typically done per-store when creating WASM instances
217			//
218			// For now, we log that WASI is available and will be configured
219			// when actual WASM instances with WASI requirements are loaded
220			dev_log!("wasm", "[WASMRuntime] WASI support enabled, will be configured per-instance");
221		}
222
223		// Configure async support
224		if async_support {
225			linker.allow_shadowing(true);
226		}
227
228		Ok(linker)
229	}
230
231	/// Compile a WASM module from bytes
232	pub fn compile_module(&self, wasm_bytes:&[u8]) -> Result<Module> {
233		dev_log!("wasm", "Compiling WASM module ({} bytes)", wasm_bytes.len());
234
235		let module = Module::from_binary(&self.engine, wasm_bytes)
236			.map_err(|e| anyhow::anyhow!("Failed to compile WASM module: {}", e))?;
237
238		dev_log!("wasm", "WASM module compiled successfully");
239
240		Ok(module)
241	}
242
243	/// Validate a WASM module without compiling
244	pub fn validate_module(&self, wasm_bytes:&[u8]) -> Result<bool> {
245		dev_log!("wasm", "Validating WASM module ({} bytes)", wasm_bytes.len());
246
247		let result = Module::validate(&self.engine, wasm_bytes);
248
249		match result {
250			Ok(()) => {
251				dev_log!("wasm", "WASM module validation passed");
252
253				Ok(true)
254			},
255
256			Err(e) => {
257				dev_log!("wasm", "WASM module validation failed: {}", e);
258
259				Ok(false)
260			},
261		}
262	}
263
264	/// Register an instance
265	pub async fn register_instance(&self, instance_id:String) -> Result<()> {
266		let mut instances = self.instances.write().await;
267
268		// Check if we've exceeded the maximum number of instances
269		if instances.len() >= self.config.memory_limit_mb as usize * 100 {
270			return Err(anyhow::anyhow!("Maximum number of instances exceeded: {}", instances.len()));
271		}
272
273		instances.push(instance_id);
274
275		Ok(())
276	}
277
278	/// Unregister an instance
279	pub async fn unregister_instance(&self, instance_id:&str) -> Result<bool> {
280		let mut instances = self.instances.write().await;
281
282		let pos = instances.iter().position(|id| id == instance_id);
283
284		if let Some(pos) = pos {
285			instances.remove(pos);
286
287			Ok(true)
288		} else {
289			Ok(false)
290		}
291	}
292
293	/// Get the number of active instances
294	pub async fn instance_count(&self) -> usize { self.instances.read().await.len() }
295
296	/// Shutdown the runtime and cleanup resources
297	pub async fn shutdown(&self) -> Result<()> {
298		dev_log!("wasm", "Shutting down WASM runtime");
299
300		let instance_count = self.instance_count().await;
301
302		if instance_count > 0 {
303			dev_log!("wasm", "warn: shutting down with {} active instances", instance_count);
304		}
305
306		// Clear instances
307		self.instances.write().await.clear();
308
309		dev_log!("wasm", "WASM runtime shutdown complete");
310
311		Ok(())
312	}
313}
314
315#[cfg(test)]
316mod tests {
317
318	use super::*;
319
320	#[tokio::test]
321	async fn test_wasm_runtime_creation() {
322		let runtime = WASMRuntime::new(WASMConfig::default()).await;
323
324		assert!(runtime.is_ok());
325	}
326
327	#[tokio::test]
328	async fn test_wasm_config_default() {
329		let config = WASMConfig::default();
330
331		assert!(config.enable_wasi);
332
333		assert_eq!(config.memory_limit_mb, 512);
334	}
335
336	#[tokio::test]
337	async fn test_create_store() {
338		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
339
340		let store = runtime.create_store();
341
342		assert!(store.is_ok());
343	}
344
345	#[tokio::test]
346	async fn test_instance_registration() {
347		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
348
349		runtime.register_instance("test-instance".to_string()).await.unwrap();
350
351		assert_eq!(runtime.instance_count().await, 1);
352
353		runtime.unregister_instance("test-instance").await.unwrap();
354
355		assert_eq!(runtime.instance_count().await, 0);
356	}
357
358	#[tokio::test]
359	async fn test_validate_module() {
360		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
361
362		// Simple WASM module (empty)
363		let empty_wasm = vec![
364			0x00, 0x61, 0x73, 0x6D, // Magic number
365			0x01, 0x00, 0x00, 0x00, // Version 1
366		];
367
368		// This will fail validation because it's incomplete, but tests the method
369		let result = runtime.validate_module(&empty_wasm);
370
371		// We don't assert on the result since it depends on WASMtime
372		// implementation
373	}
374}
375
376impl std::fmt::Debug for WASMRuntime {
377	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "WASMRuntime") }
378}