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 pub name:Option<String>,
29 pub path:Option<PathBuf>,
31 pub source_type:ModuleSourceType,
33 pub size:usize,
35 pub exported_functions:Vec<String>,
37 pub exported_memories:Vec<String>,
39 pub exported_tables:Vec<String>,
41 pub imports:Vec<ImportDeclaration>,
43 pub compiled_at:u64,
45 pub hash:Option<String>,
47}
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
51pub enum ModuleSourceType {
52 File,
54 Memory,
56 Url,
58 Generated,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ImportDeclaration {
65 pub module:String,
67 pub name:String,
69 pub kind:ImportKind,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub enum ImportKind {
76 Function,
78 Table,
80 Memory,
82 Global,
84 Tag,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ModuleLoadOptions {
91 pub lazy_compilation:bool,
93 pub enable_cache:bool,
95 pub cache_dir:Option<PathBuf>,
97 pub custom_linker:bool,
99 pub validate:bool,
101 pub optimized:bool,
103}
104
105impl Default for ModuleLoadOptions {
106 fn default() -> Self {
107 Self {
108 lazy_compilation:false,
109 enable_cache:true,
110 cache_dir:None,
111 custom_linker:false,
112 validate:true,
113 optimized:true,
114 }
115 }
116}
117
118pub struct WASMInstance {
120 pub instance:Instance,
122 pub store:Store<StoreLimits>,
124 pub id:String,
126 pub module:Arc<Module>,
128}
129
130pub struct ModuleLoaderImpl {
132 runtime:Arc<WASMRuntime>,
133 #[allow(dead_code)]
134 config:WASMConfig,
135 #[allow(dead_code)]
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 pub async fn load_from_file(&self, path:&Path) -> Result<WASMModule> {
153 dev_log!("wasm", "Loading WASM module from file: {:?}", path);
154
155 let wasm_bytes = fs::read(path).context(format!("Failed to read WASM file: {:?}", path))?;
156
157 self.load_from_memory(&wasm_bytes, ModuleSourceType::File)
158 .await
159 .map(|mut module| {
160 module.path = Some(path.to_path_buf());
161 module
162 })
163 }
164
165 pub async fn load_from_memory(&self, wasm_bytes:&[u8], source_type:ModuleSourceType) -> Result<WASMModule> {
167 dev_log!("wasm", "Loading WASM module from memory ({} bytes)", wasm_bytes.len());
168
169 if ModuleLoadOptions::default().validate {
171 if !self.runtime.validate_module(wasm_bytes)? {
172 return Err(anyhow::anyhow!("WASM module validation failed"));
173 }
174 }
175
176 let module = self.runtime.compile_module(wasm_bytes)?;
178
179 let module_info = self.extract_module_info(&module);
181
182 let wasm_module = WASMModule {
184 id:generate_module_id(&module_info.name),
185 name:module_info.name,
186 path:None,
187 source_type,
188 size:wasm_bytes.len(),
189 exported_functions:module_info.exports.functions,
190 exported_memories:module_info.exports.memories,
191 exported_tables:module_info.exports.tables,
192 imports:module_info.imports,
193 compiled_at:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(),
194 hash:self.compute_hash(wasm_bytes),
195 };
196
197 let mut loaded = self.loaded_modules.write().await;
199 loaded.push(wasm_module.clone());
200
201 dev_log!("wasm", "WASM module loaded successfully: {}", wasm_module.id);
202
203 Ok(wasm_module)
204 }
205
206 pub async fn load_from_url(&self, url:&str) -> Result<WASMModule> {
208 dev_log!("wasm", "Loading WASM module from URL: {}", url);
209
210 let response = reqwest::get(url)
212 .await
213 .context(format!("Failed to fetch WASM module from: {}", url))?;
214
215 if !response.status().is_success() {
216 return Err(anyhow::anyhow!("Failed to fetch WASM module: HTTP {}", response.status()));
217 }
218
219 let wasm_bytes = response.bytes().await?;
220
221 self.load_from_memory(&wasm_bytes, ModuleSourceType::Url).await
222 }
223
224 pub async fn instantiate(&self, module:&Module, mut store:Store<StoreLimits>) -> Result<WASMInstance> {
226 dev_log!("wasm", "Instantiating WASM module");
227
228 let linker = self.runtime.create_linker::<StoreLimits>(true)?;
230
231 let instance = linker
233 .instantiate(&mut store, module)
234 .map_err(|e| anyhow::anyhow!("Failed to instantiate WASM module: {}", e))?;
235
236 let instance_id = generate_instance_id();
237
238 dev_log!("wasm", "WASM module instantiated: {}", instance_id);
239
240 Ok(WASMInstance { instance, store, id:instance_id, module:Arc::new(module.clone()) })
241 }
242
243 pub async fn get_loaded_modules(&self) -> Vec<WASMModule> { self.loaded_modules.read().await.clone() }
245
246 pub async fn get_module_by_id(&self, id:&str) -> Option<WASMModule> {
248 let loaded = self.loaded_modules.read().await;
249 loaded.iter().find(|m| m.id == id).cloned()
250 }
251
252 pub async fn unload_module(&self, id:&str) -> Result<bool> {
254 let mut loaded = self.loaded_modules.write().await;
255 let pos = loaded.iter().position(|m| m.id == id);
256
257 if let Some(pos) = pos {
258 loaded.remove(pos);
259 dev_log!("wasm", "WASM module unloaded: {}", id);
260 Ok(true)
261 } else {
262 Ok(false)
263 }
264 }
265
266 fn extract_module_info(&self, module:&Module) -> ModuleInfo {
268 let mut exports = Exports { functions:Vec::new(), memories:Vec::new(), tables:Vec::new(), globals:Vec::new() };
269
270 let mut imports = Vec::new();
271
272 for export in module.exports() {
273 match export.ty() {
274 wasmtime::ExternType::Func(_) => exports.functions.push(export.name().to_string()),
275 wasmtime::ExternType::Memory(_) => exports.memories.push(export.name().to_string()),
276 wasmtime::ExternType::Table(_) => exports.tables.push(export.name().to_string()),
277 wasmtime::ExternType::Global(_) => exports.globals.push(export.name().to_string()),
278 _ => {},
279 }
280 }
281
282 for import in module.imports() {
283 let kind = match import.ty() {
284 wasmtime::ExternType::Func(_) => ImportKind::Function,
285 wasmtime::ExternType::Memory(_) => ImportKind::Memory,
286 wasmtime::ExternType::Table(_) => ImportKind::Table,
287 wasmtime::ExternType::Global(_) => ImportKind::Global,
288 _ => ImportKind::Tag,
289 };
290 imports.push(ImportDeclaration {
291 module:import.module().to_string(),
292 name:import.name().to_string(),
293 kind,
294 });
295 }
296
297 ModuleInfo {
298 name:None, exports,
300 imports,
301 }
302 }
303
304 fn compute_hash(&self, wasm_bytes:&[u8]) -> Option<String> {
306 use std::{
307 collections::hash_map::DefaultHasher,
308 hash::{Hash, Hasher},
309 };
310
311 let mut hasher = DefaultHasher::new();
312 wasm_bytes.hash(&mut hasher);
313 Some(format!("{:x}", hasher.finish()))
314 }
315}
316
317struct ModuleInfo {
320 name:Option<String>,
321 exports:Exports,
322 imports:Vec<ImportDeclaration>,
323}
324
325struct Exports {
326 functions:Vec<String>,
327 memories:Vec<String>,
328 tables:Vec<String>,
329 globals:Vec<String>,
330}
331
332fn generate_module_id(name:&Option<String>) -> String {
333 match name {
334 Some(n) => format!("module-{}", n.to_lowercase().replace(' ', "-")),
335 None => format!("module-{}", uuid::Uuid::new_v4()),
336 }
337}
338
339fn generate_instance_id() -> String { format!("instance-{}", uuid::Uuid::new_v4()) }
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[tokio::test]
346 async fn test_module_loader_creation() {
347 let runtime = Arc::new(WASMRuntime::new(WASMConfig::default()).await.unwrap());
348 let config = WASMConfig::default();
349 let loader = ModuleLoaderImpl::new(runtime, config);
350
351 assert_eq!(loader.get_loaded_modules().await.len(), 0);
353 }
354
355 #[test]
356 fn test_module_load_options_default() {
357 let options = ModuleLoadOptions::default();
358 assert_eq!(options.validate, true);
359 assert_eq!(options.enable_cache, true);
360 }
361
362 #[test]
363 fn test_generate_module_id() {
364 let id1 = generate_module_id(&Some("Test Module".to_string()));
365 let id2 = generate_module_id(&None);
366
367 assert!(id1.starts_with("module-"));
368 assert!(id2.starts_with("module-"));
369 assert_ne!(id1, id2);
370 }
371}
372
373