1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct WASMModule {
27 pub id:String,
29 pub name:Option<String>,
31 pub path:Option<PathBuf>,
33 pub source_type:ModuleSourceType,
35 pub size:usize,
37 pub exported_functions:Vec<String>,
39 pub exported_memories:Vec<String>,
41 pub exported_tables:Vec<String>,
43 pub imports:Vec<ImportDeclaration>,
45 pub compiled_at:u64,
47 pub hash:Option<String>,
49}
50
51#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
53pub enum ModuleSourceType {
54File,
56Memory,
58Url,
60Generated,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ImportDeclaration {
67 pub module:String,
69 pub name:String,
71 pub kind:ImportKind,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77pub enum ImportKind {
78Function,
80Table,
82Memory,
84Global,
86Tag,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ModuleLoadOptions {
93 pub lazy_compilation:bool,
95 pub enable_cache:bool,
97 pub cache_dir:Option<PathBuf>,
99 pub custom_linker:bool,
101 pub validate:bool,
103 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
120pub struct WASMInstance {
122 pub instance:Instance,
124 pub store:Store<StoreLimits>,
126 pub id:String,
128 pub module:Arc<Module>,
130}
131
132pub 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 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 #[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 #[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 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 let module = self.runtime.compile_module(wasm_bytes)?;
180
181 let module_info = self.extract_module_info(&module);
183
184 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 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 #[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 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 #[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 let linker = self.runtime.create_linker::<StoreLimits>(true)?;
234
235 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 pub async fn get_loaded_modules(&self) -> Vec<WASMModule> { self.loaded_modules.read().await.clone() }
249
250 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 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 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, exports,
304 imports,
305 }
306 }
307
308 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
321struct 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 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