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