Skip to main content

Grove/WASM/
ModuleLoader.rs

1//! WASM Module Loader
2//!
3//! Handles loading, compiling, and instantiating WebAssembly modules.
4//! Provides utilities for working with WASM modules from various sources.
5
6use std::{
7	fs,
8	path::{Path, PathBuf},
9	sync::Arc,
10};
11
12use anyhow::{Context, Result};
13use serde::{Deserialize, Serialize};
14use tokio::sync::RwLock;
15use wasmtime::{Instance, Linker, Module, Store, StoreLimits};
16
17use crate::{
18	WASM::Runtime::{WASMConfig, WASMRuntime},
19	dev_log,
20};
21
22/// WASM module wrapper with metadata
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WASMModule {
25	/// Unique module identifier
26	pub id:String,
27	/// Module name (if available from name section)
28	pub name:Option<String>,
29	/// Path to the module file (if loaded from disk)
30	pub path:Option<PathBuf>,
31	/// Module source type
32	pub source_type:ModuleSourceType,
33	/// Module size in bytes
34	pub size:usize,
35	/// Exported functions
36	pub exported_functions:Vec<String>,
37	/// Exported memories
38	pub exported_memories:Vec<String>,
39	/// Exported tables
40	pub exported_tables:Vec<String>,
41	/// Import declarations
42	pub imports:Vec<ImportDeclaration>,
43	/// Compilation timestamp
44	pub compiled_at:u64,
45	/// Module hash (for caching)
46	pub hash:Option<String>,
47}
48
49/// Source type of a WASM module
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
51pub enum ModuleSourceType {
52	/// Module loaded from a file
53	File,
54	/// Module loaded from in-memory bytes
55	Memory,
56	/// Module loaded from a network URL
57	Url,
58	/// Module generated dynamically
59	Generated,
60}
61
62/// Import declaration for a WASM module
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ImportDeclaration {
65	/// Module name being imported from
66	pub module:String,
67	/// Name of the imported item
68	pub name:String,
69	/// Kind of import
70	pub kind:ImportKind,
71}
72
73/// Kind of import
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub enum ImportKind {
76	/// Function import
77	Function,
78	/// Table import
79	Table,
80	/// Memory import
81	Memory,
82	/// Global import
83	Global,
84	/// Tag import
85	Tag,
86}
87
88/// Module loading options
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ModuleLoadOptions {
91	/// Enable lazy compilation
92	pub lazy_compilation:bool,
93	/// Enable module caching
94	pub enable_cache:bool,
95	/// Cache directory path
96	pub cache_dir:Option<PathBuf>,
97	/// Custom linker configuration
98	pub custom_linker:bool,
99	/// Validate module before loading
100	pub validate:bool,
101	/// Optimized compilation
102	pub optimized:bool,
103}
104
105impl Default for ModuleLoadOptions {
106	fn default() -> Self {
107		Self {
108			lazy_compilation:false,
109			enable_cache:true,
110			cache_dir:None,
111			custom_linker:false,
112			validate:true,
113			optimized:true,
114		}
115	}
116}
117
118/// Module instance with store
119pub struct WASMInstance {
120	/// The WASM instance
121	pub instance:Instance,
122	/// The associated store
123	pub store:Store<StoreLimits>,
124	/// Instance ID
125	pub id:String,
126	/// Module reference
127	pub module:Arc<Module>,
128}
129
130/// WASM Module Loader
131pub struct ModuleLoaderImpl {
132	runtime:Arc<WASMRuntime>,
133	#[allow(dead_code)]
134	config:WASMConfig,
135	#[allow(dead_code)]
136	linkers:Arc<RwLock<Vec<Linker<()>>>>,
137	loaded_modules:Arc<RwLock<Vec<WASMModule>>>,
138}
139
140impl ModuleLoaderImpl {
141	/// Create a new module loader
142	pub fn new(runtime:Arc<WASMRuntime>, config:WASMConfig) -> Self {
143		Self {
144			runtime,
145			config,
146			linkers:Arc::new(RwLock::new(Vec::new())),
147			loaded_modules:Arc::new(RwLock::new(Vec::new())),
148		}
149	}
150
151	/// Load a WASM module from a file
152	pub async fn load_from_file(&self, path:&Path) -> Result<WASMModule> {
153		dev_log!("wasm", "Loading WASM module from file: {:?}", path);
154
155		let wasm_bytes = fs::read(path).context(format!("Failed to read WASM file: {:?}", path))?;
156
157		self.load_from_memory(&wasm_bytes, ModuleSourceType::File)
158			.await
159			.map(|mut module| {
160				module.path = Some(path.to_path_buf());
161				module
162			})
163	}
164
165	/// Load a WASM module from memory
166	pub async fn load_from_memory(&self, wasm_bytes:&[u8], source_type:ModuleSourceType) -> Result<WASMModule> {
167		dev_log!("wasm", "Loading WASM module from memory ({} bytes)", wasm_bytes.len());
168
169		// Validate if option is set
170		if ModuleLoadOptions::default().validate {
171			if !self.runtime.validate_module(wasm_bytes)? {
172				return Err(anyhow::anyhow!("WASM module validation failed"));
173			}
174		}
175
176		// Compile the module
177		let module = self.runtime.compile_module(wasm_bytes)?;
178
179		// Extract module information
180		let module_info = self.extract_module_info(&module);
181
182		// Create module wrapper
183		let wasm_module = WASMModule {
184			id:generate_module_id(&module_info.name),
185			name:module_info.name,
186			path:None,
187			source_type,
188			size:wasm_bytes.len(),
189			exported_functions:module_info.exports.functions,
190			exported_memories:module_info.exports.memories,
191			exported_tables:module_info.exports.tables,
192			imports:module_info.imports,
193			compiled_at:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(),
194			hash:self.compute_hash(wasm_bytes),
195		};
196
197		// Store the module
198		let mut loaded = self.loaded_modules.write().await;
199		loaded.push(wasm_module.clone());
200
201		dev_log!("wasm", "WASM module loaded successfully: {}", wasm_module.id);
202
203		Ok(wasm_module)
204	}
205
206	/// Load a WASM module from a URL
207	pub async fn load_from_url(&self, url:&str) -> Result<WASMModule> {
208		dev_log!("wasm", "Loading WASM module from URL: {}", url);
209
210		// Fetch the module
211		let response = reqwest::get(url)
212			.await
213			.context(format!("Failed to fetch WASM module from: {}", url))?;
214
215		if !response.status().is_success() {
216			return Err(anyhow::anyhow!("Failed to fetch WASM module: HTTP {}", response.status()));
217		}
218
219		let wasm_bytes = response.bytes().await?;
220
221		self.load_from_memory(&wasm_bytes, ModuleSourceType::Url).await
222	}
223
224	/// Instantiate a loaded module
225	pub async fn instantiate(&self, module:&Module, mut store:Store<StoreLimits>) -> Result<WASMInstance> {
226		dev_log!("wasm", "Instantiating WASM module");
227
228		// Create linker with StoreLimits type
229		let linker = self.runtime.create_linker::<StoreLimits>(true)?;
230
231		// Instantiate
232		let instance = linker
233			.instantiate(&mut store, module)
234			.map_err(|e| anyhow::anyhow!("Failed to instantiate WASM module: {}", e))?;
235
236		let instance_id = generate_instance_id();
237
238		dev_log!("wasm", "WASM module instantiated: {}", instance_id);
239
240		Ok(WASMInstance { instance, store, id:instance_id, module:Arc::new(module.clone()) })
241	}
242
243	/// Get all loaded modules
244	pub async fn get_loaded_modules(&self) -> Vec<WASMModule> { self.loaded_modules.read().await.clone() }
245
246	/// Get a loaded module by ID
247	pub async fn get_module_by_id(&self, id:&str) -> Option<WASMModule> {
248		let loaded = self.loaded_modules.read().await;
249		loaded.iter().find(|m| m.id == id).cloned()
250	}
251
252	/// Unload a module
253	pub async fn unload_module(&self, id:&str) -> Result<bool> {
254		let mut loaded = self.loaded_modules.write().await;
255		let pos = loaded.iter().position(|m| m.id == id);
256
257		if let Some(pos) = pos {
258			loaded.remove(pos);
259			dev_log!("wasm", "WASM module unloaded: {}", id);
260			Ok(true)
261		} else {
262			Ok(false)
263		}
264	}
265
266	/// Extract module information from a compiled module
267	fn extract_module_info(&self, module:&Module) -> ModuleInfo {
268		let mut exports = Exports { functions:Vec::new(), memories:Vec::new(), tables:Vec::new(), globals:Vec::new() };
269
270		let mut imports = Vec::new();
271
272		for export in module.exports() {
273			match export.ty() {
274				wasmtime::ExternType::Func(_) => exports.functions.push(export.name().to_string()),
275				wasmtime::ExternType::Memory(_) => exports.memories.push(export.name().to_string()),
276				wasmtime::ExternType::Table(_) => exports.tables.push(export.name().to_string()),
277				wasmtime::ExternType::Global(_) => exports.globals.push(export.name().to_string()),
278				_ => {},
279			}
280		}
281
282		for import in module.imports() {
283			let kind = match import.ty() {
284				wasmtime::ExternType::Func(_) => ImportKind::Function,
285				wasmtime::ExternType::Memory(_) => ImportKind::Memory,
286				wasmtime::ExternType::Table(_) => ImportKind::Table,
287				wasmtime::ExternType::Global(_) => ImportKind::Global,
288				_ => ImportKind::Tag,
289			};
290			imports.push(ImportDeclaration {
291				module:import.module().to_string(),
292				name:import.name().to_string(),
293				kind,
294			});
295		}
296
297		ModuleInfo {
298			name:None, // Would need to parse name section
299			exports,
300			imports,
301		}
302	}
303
304	/// Compute a hash of the WASM bytes for caching
305	fn compute_hash(&self, wasm_bytes:&[u8]) -> Option<String> {
306		use std::{
307			collections::hash_map::DefaultHasher,
308			hash::{Hash, Hasher},
309		};
310
311		let mut hasher = DefaultHasher::new();
312		wasm_bytes.hash(&mut hasher);
313		Some(format!("{:x}", hasher.finish()))
314	}
315}
316
317// Helper structures and functions
318
319struct ModuleInfo {
320	name:Option<String>,
321	exports:Exports,
322	imports:Vec<ImportDeclaration>,
323}
324
325struct Exports {
326	functions:Vec<String>,
327	memories:Vec<String>,
328	tables:Vec<String>,
329	globals:Vec<String>,
330}
331
332fn generate_module_id(name:&Option<String>) -> String {
333	match name {
334		Some(n) => format!("module-{}", n.to_lowercase().replace(' ', "-")),
335		None => format!("module-{}", uuid::Uuid::new_v4()),
336	}
337}
338
339fn generate_instance_id() -> String { format!("instance-{}", uuid::Uuid::new_v4()) }
340
341#[cfg(test)]
342mod tests {
343	use super::*;
344
345	#[tokio::test]
346	async fn test_module_loader_creation() {
347		let runtime = Arc::new(WASMRuntime::new(WASMConfig::default()).await.unwrap());
348		let config = WASMConfig::default();
349		let loader = ModuleLoaderImpl::new(runtime, config);
350
351		// Just test creation
352		assert_eq!(loader.get_loaded_modules().await.len(), 0);
353	}
354
355	#[test]
356	fn test_module_load_options_default() {
357		let options = ModuleLoadOptions::default();
358		assert_eq!(options.validate, true);
359		assert_eq!(options.enable_cache, true);
360	}
361
362	#[test]
363	fn test_generate_module_id() {
364		let id1 = generate_module_id(&Some("Test Module".to_string()));
365		let id2 = generate_module_id(&None);
366
367		assert!(id1.starts_with("module-"));
368		assert!(id2.starts_with("module-"));
369		assert_ne!(id1, id2);
370	}
371}
372
373// Add uuid dependency to Cargo.toml if needed
374// uuid = { version = "1.6", features = ["v4"] }