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