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 crate::dev_log;
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			dev_log!("wasm", "[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	pub async fn new(config:WASMConfig) -> Result<Self> {
116		dev_log!("wasm", "Creating WASM runtime with config: {:?}", config);
117
118		// Build the WASMtime engine
119		let engine_config = wasmtime::Config::new();
120		let engine_config = config.apply_to_engine_builder(engine_config)?;
121		let engine =
122			Engine::new(&engine_config).map_err(|e| anyhow::anyhow!("Failed to create WASMtime engine: {}", e))?;
123
124		// Initialize memory manager
125		let memory_limits = MemoryLimits {
126			max_memory_mb:config.memory_limit_mb,
127			// Set 75% of max for initial allocation
128			initial_memory_mb:(config.memory_limit_mb as f64 * 0.75) as u64,
129			max_table_size:1024,
130			// Set maximum of 100 instances
131			max_instances:100,
132			max_memories:10,
133			max_tables:10,
134		};
135		let memory_manager = Arc::new(RwLock::new(MemoryManagerImpl::new(memory_limits)));
136
137		dev_log!("wasm", "WASM runtime created successfully");
138
139		Ok(Self { engine, config, memory_manager, instances:Arc::new(RwLock::new(Vec::new())) })
140	}
141
142	/// Get a reference to the WASMtime engine
143	pub fn engine(&self) -> &Engine { &self.engine }
144
145	/// Get the runtime configuration
146	pub fn config(&self) -> &WASMConfig { &self.config }
147
148	/// Get the memory manager
149	pub fn memory_manager(&self) -> Arc<RwLock<MemoryManagerImpl>> { Arc::clone(&self.memory_manager) }
150
151	/// Create a new WASM store with limits
152	pub fn create_store(&self) -> Result<Store<StoreLimits>> {
153		let store_limits = StoreLimitsBuilder::new()
154	        .memory_size((self.config.memory_limit_mb * 1024 * 1024) as usize) // Convert MB to bytes
155	        .table_elements(1024)
156	        .instances(100)
157	        .memories(10)
158	        .tables(10)
159	        .build();
160
161		// Set fuel limit if enabled
162		let mut store = Store::new(&self.engine, store_limits);
163
164		if self.config.enable_fuel_metering {
165			// Set fuel based on execution time (rough approximation: 1 unit = 1000 ns)
166			let fuel = self.config.max_execution_time_ms * 1_000; // Convert ms to fuel
167			store
168				.set_fuel(fuel)
169				.map_err(|e| anyhow::anyhow!("Failed to set fuel limit: {}", e))?;
170		}
171
172		Ok(store)
173	}
174
175	/// Create a linker for the runtime
176	pub fn create_linker<T>(&self, async_support:bool) -> Result<Linker<T>>
177	where
178		T: Send, {
179		let mut linker = Linker::new(&self.engine);
180
181		// Configure WASI support if enabled using Wasmtime 20.0.2 API
182		if self.config.enable_wasi {
183			// In Wasmtime 20.0.2, WASI is configured via wasmtime_wasi crate
184			// The configuration involves:
185			// 1. Creating a WasiCtxBuilder with the desired configuration
186			// 2. Adding it to the linker using wasmtime_wasi::add_to_linker
187			//
188			// Note: Actual WASI implementation requires:
189			// - Runtime-dependent context (stdin, stdout, stderr, filesystem, etc.)
190			// - This is typically done per-store when creating WASM instances
191			//
192			// For now, we log that WASI is available and will be configured
193			// when actual WASM instances with WASI requirements are loaded
194			dev_log!("wasm", "[WASMRuntime] WASI support enabled, will be configured per-instance");
195		}
196
197		// Configure async support
198		if async_support {
199			linker.allow_shadowing(true);
200		}
201
202		Ok(linker)
203	}
204
205	/// Compile a WASM module from bytes
206	pub fn compile_module(&self, wasm_bytes:&[u8]) -> Result<Module> {
207		dev_log!("wasm", "Compiling WASM module ({} bytes)", wasm_bytes.len());
208
209		let module = Module::from_binary(&self.engine, wasm_bytes)
210			.map_err(|e| anyhow::anyhow!("Failed to compile WASM module: {}", e))?;
211
212		dev_log!("wasm", "WASM module compiled successfully");
213
214		Ok(module)
215	}
216
217	/// Validate a WASM module without compiling
218	pub fn validate_module(&self, wasm_bytes:&[u8]) -> Result<bool> {
219		dev_log!("wasm", "Validating WASM module ({} bytes)", wasm_bytes.len());
220
221		let result = Module::validate(&self.engine, wasm_bytes);
222
223		match result {
224			Ok(()) => {
225				dev_log!("wasm", "WASM module validation passed");
226				Ok(true)
227			},
228			Err(e) => {
229				dev_log!("wasm", "WASM module validation failed: {}", e);
230				Ok(false)
231			},
232		}
233	}
234
235	/// Register an instance
236	pub async fn register_instance(&self, instance_id:String) -> Result<()> {
237		let mut instances = self.instances.write().await;
238
239		// Check if we've exceeded the maximum number of instances
240		if instances.len() >= self.config.memory_limit_mb as usize * 100 {
241			return Err(anyhow::anyhow!("Maximum number of instances exceeded: {}", instances.len()));
242		}
243
244		instances.push(instance_id);
245		Ok(())
246	}
247
248	/// Unregister an instance
249	pub async fn unregister_instance(&self, instance_id:&str) -> Result<bool> {
250		let mut instances = self.instances.write().await;
251		let pos = instances.iter().position(|id| id == instance_id);
252
253		if let Some(pos) = pos {
254			instances.remove(pos);
255			Ok(true)
256		} else {
257			Ok(false)
258		}
259	}
260
261	/// Get the number of active instances
262	pub async fn instance_count(&self) -> usize { self.instances.read().await.len() }
263
264	/// Shutdown the runtime and cleanup resources
265	pub async fn shutdown(&self) -> Result<()> {
266		dev_log!("wasm", "Shutting down WASM runtime");
267
268		let instance_count = self.instance_count().await;
269		if instance_count > 0 {
270			dev_log!("wasm", "warn: shutting down with {} active instances", instance_count);
271		}
272
273		// Clear instances
274		self.instances.write().await.clear();
275
276		dev_log!("wasm", "WASM runtime shutdown complete");
277
278		Ok(())
279	}
280}
281
282#[cfg(test)]
283mod tests {
284	use super::*;
285
286	#[tokio::test]
287	async fn test_wasm_runtime_creation() {
288		let runtime = WASMRuntime::new(WASMConfig::default()).await;
289		assert!(runtime.is_ok());
290	}
291
292	#[tokio::test]
293	async fn test_wasm_config_default() {
294		let config = WASMConfig::default();
295		assert!(config.enable_wasi);
296		assert_eq!(config.memory_limit_mb, 512);
297	}
298
299	#[tokio::test]
300	async fn test_create_store() {
301		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
302		let store = runtime.create_store();
303		assert!(store.is_ok());
304	}
305
306	#[tokio::test]
307	async fn test_instance_registration() {
308		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
309
310		runtime.register_instance("test-instance".to_string()).await.unwrap();
311		assert_eq!(runtime.instance_count().await, 1);
312
313		runtime.unregister_instance("test-instance").await.unwrap();
314		assert_eq!(runtime.instance_count().await, 0);
315	}
316
317	#[tokio::test]
318	async fn test_validate_module() {
319		let runtime = WASMRuntime::new(WASMConfig::default()).await.unwrap();
320
321		// Simple WASM module (empty)
322		let empty_wasm = vec![
323			0x00, 0x61, 0x73, 0x6D, // Magic number
324			0x01, 0x00, 0x00, 0x00, // Version 1
325		];
326
327		// This will fail validation because it's incomplete, but tests the method
328		let result = runtime.validate_module(&empty_wasm);
329		// We don't assert on the result since it depends on WASMtime
330		// implementation
331	}
332}
333
334impl std::fmt::Debug for WASMRuntime {
335	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "WASMRuntime") }
336}