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