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