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::{Context, Result};
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11use tracing::{debug, info, instrument, warn};
12use wasmtime::{Engine, Instance, 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 = Engine::new(&engine_config).context("Failed to create WASMtime engine")?;
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		info!("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 mut 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.set_fuel(fuel).context("Failed to set fuel limit")?;
168		}
169
170		Ok(store)
171	}
172
173	/// Create a linker for the runtime
174	pub fn create_linker<T>(&self, async_support:bool) -> Result<Linker<T>>
175	where
176		T: Send, {
177		let mut linker = Linker::new(&self.engine);
178
179		// Configure WASI support if enabled using Wasmtime 20.0.2 API
180		if self.config.enable_wasi {
181			// In Wasmtime 20.0.2, WASI is configured via wasmtime_wasi crate
182			// The configuration involves:
183			// 1. Creating a WasiCtxBuilder with the desired configuration
184			// 2. Adding it to the linker using wasmtime_wasi::add_to_linker
185			//
186			// Note: Actual WASI implementation requires:
187			// - Runtime-dependent context (stdin, stdout, stderr, filesystem, etc.)
188			// - This is typically done per-store when creating WASM instances
189			//
190			// For now, we log that WASI is available and will be configured
191			// when actual WASM instances with WASI requirements are loaded
192			debug!("[WASMRuntime] WASI support enabled, will be configured per-instance");
193		}
194
195		// Configure async support
196		if async_support {
197			linker.allow_shadowing(true);
198		}
199
200		Ok(linker)
201	}
202
203	/// Compile a WASM module from bytes
204	#[instrument(skip(self, wasm_bytes))]
205	pub fn compile_module(&self, wasm_bytes:&[u8]) -> Result<Module> {
206		debug!("Compiling WASM module ({} bytes)", wasm_bytes.len());
207
208		let module = Module::from_binary(&self.engine, wasm_bytes).context("Failed to compile WASM module")?;
209
210		debug!("WASM module compiled successfully");
211
212		Ok(module)
213	}
214
215	/// Validate a WASM module without compiling
216	#[instrument(skip(self, wasm_bytes))]
217	pub fn validate_module(&self, wasm_bytes:&[u8]) -> Result<bool> {
218		debug!("Validating WASM module ({} bytes)", wasm_bytes.len());
219
220		let result = Module::validate(&self.engine, wasm_bytes);
221
222		match result {
223			Ok(()) => {
224				debug!("WASM module validation passed");
225				Ok(true)
226			},
227			Err(e) => {
228				debug!("WASM module validation failed: {}", e);
229				Ok(false)
230			},
231		}
232	}
233
234	/// Register an instance
235	pub async fn register_instance(&self, instance_id:String) -> Result<()> {
236		let mut instances = self.instances.write().await;
237
238		// Check if we've exceeded the maximum number of instances
239		if instances.len() >= self.config.memory_limit_mb as usize * 100 {
240			return Err(anyhow::anyhow!("Maximum number of instances exceeded: {}", instances.len()));
241		}
242
243		instances.push(instance_id);
244		Ok(())
245	}
246
247	/// Unregister an instance
248	pub async fn unregister_instance(&self, instance_id:&str) -> Result<bool> {
249		let mut instances = self.instances.write().await;
250		let pos = instances.iter().position(|id| id == instance_id);
251
252		if let Some(pos) = pos {
253			instances.remove(pos);
254			Ok(true)
255		} else {
256			Ok(false)
257		}
258	}
259
260	/// Get the number of active instances
261	pub async fn instance_count(&self) -> usize { self.instances.read().await.len() }
262
263	/// Shutdown the runtime and cleanup resources
264	#[instrument(skip(self))]
265	pub async fn shutdown(&self) -> Result<()> {
266		info!("Shutting down WASM runtime");
267
268		let instance_count = self.instance_count().await;
269		if instance_count > 0 {
270			warn!("Shutting down with {} active instances", instance_count);
271		}
272
273		// Clear instances
274		self.instances.write().await.clear();
275
276		info!("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 {
336		write!(f, "WASMRuntime")
337	}
338}