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