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 wasmtime::{Instance, Linker, Module, Store, StoreLimits};
16
17use crate::{
18 WASM::Runtime::{WASMConfig, WASMRuntime},
19 dev_log,
20};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WASMModule {
25 pub id:String,
27
28 pub name:Option<String>,
30
31 pub path:Option<PathBuf>,
33
34 pub source_type:ModuleSourceType,
36
37 pub size:usize,
39
40 pub exported_functions:Vec<String>,
42
43 pub exported_memories:Vec<String>,
45
46 pub exported_tables:Vec<String>,
48
49 pub imports:Vec<ImportDeclaration>,
51
52 pub compiled_at:u64,
54
55 pub hash:Option<String>,
57}
58
59#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
61pub enum ModuleSourceType {
62 File,
64
65 Memory,
67
68 Url,
70
71 Generated,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ImportDeclaration {
78 pub module:String,
80
81 pub name:String,
83
84 pub kind:ImportKind,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
90pub enum ImportKind {
91 Function,
93
94 Table,
96
97 Memory,
99
100 Global,
102
103 Tag,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ModuleLoadOptions {
110 pub lazy_compilation:bool,
112
113 pub enable_cache:bool,
115
116 pub cache_dir:Option<PathBuf>,
118
119 pub custom_linker:bool,
121
122 pub validate:bool,
124
125 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
147pub struct WASMInstance {
149 pub instance:Instance,
151
152 pub store:Store<StoreLimits>,
154
155 pub id:String,
157
158 pub module:Arc<Module>,
160}
161
162pub 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 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 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 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 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 let module = self.runtime.compile_module(wasm_bytes)?;
216
217 let module_info = self.extract_module_info(&module);
219
220 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 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 pub async fn load_from_url(&self, url:&str) -> Result<WASMModule> {
257 dev_log!("wasm", "Loading WASM module from URL: {}", url);
258
259 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 pub async fn instantiate(&self, module:&Module, mut store:Store<StoreLimits>) -> Result<WASMInstance> {
275 dev_log!("wasm", "Instantiating WASM module");
276
277 let linker = self.runtime.create_linker::<StoreLimits>(true)?;
279
280 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 pub async fn get_loaded_modules(&self) -> Vec<WASMModule> { self.loaded_modules.read().await.clone() }
294
295 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 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 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, exports,
362
363 imports,
364 }
365 }
366
367 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
382struct 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 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