Spaces:
Running
Running
| /** | |
| * WebAssembly Module Loader for MorphGuard | |
| * Handles loading and initialization of WebAssembly modules for high-performance tasks | |
| */ | |
| class WasmLoader { | |
| constructor(options = {}) { | |
| this.config = Object.assign({ | |
| wasmPath: '/static/wasm/', | |
| simdSupported: null, | |
| threadingSupported: null, | |
| fallbackToJS: true | |
| }, options); | |
| // Track loaded modules | |
| this.modules = {}; | |
| // Check for WebAssembly support and features | |
| this.detectFeatures(); | |
| } | |
| /** | |
| * Detect WebAssembly features | |
| */ | |
| async detectFeatures() { | |
| // Check basic WebAssembly support | |
| if (typeof WebAssembly === 'undefined') { | |
| this.config.simdSupported = false; | |
| this.config.threadingSupported = false; | |
| console.warn('WebAssembly not supported in this browser. Using JavaScript fallbacks.'); | |
| return; | |
| } | |
| // Check for SIMD support | |
| if (this.config.simdSupported === null) { | |
| try { | |
| // Test for SIMD support | |
| const simdTest = await WebAssembly.instantiate( | |
| new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11]), | |
| {} | |
| ); | |
| this.config.simdSupported = !!simdTest.instance.exports; | |
| } catch (e) { | |
| this.config.simdSupported = false; | |
| } | |
| } | |
| // Check for threading support | |
| if (this.config.threadingSupported === null) { | |
| this.config.threadingSupported = typeof SharedArrayBuffer !== 'undefined' && | |
| typeof Atomics !== 'undefined'; | |
| } | |
| console.log(`WebAssembly features: SIMD: ${this.config.simdSupported}, Threading: ${this.config.threadingSupported}`); | |
| } | |
| /** | |
| * Load a WebAssembly module | |
| * @param {string} moduleName - Name of the module to load | |
| * @param {object} importObject - Import object for WebAssembly instantiation | |
| * @returns {Promise<object>} - The loaded WebAssembly module | |
| */ | |
| async loadModule(moduleName, importObject = {}) { | |
| // Check if already loaded | |
| if (this.modules[moduleName]) { | |
| return this.modules[moduleName]; | |
| } | |
| try { | |
| // Determine file name based on available features | |
| let fileName = moduleName; | |
| if (this.config.simdSupported && this.config.threadingSupported) { | |
| fileName += '_simd_threaded.wasm'; | |
| } else if (this.config.simdSupported) { | |
| fileName += '_simd.wasm'; | |
| } else { | |
| fileName += '.wasm'; | |
| } | |
| // Load the WebAssembly module | |
| const response = await fetch(`${this.config.wasmPath}${fileName}`); | |
| if (!response.ok) { | |
| throw new Error(`Failed to load WebAssembly module: ${response.statusText}`); | |
| } | |
| const wasmBinary = await response.arrayBuffer(); | |
| // Set up default import object with memory and console logging | |
| const defaultImports = { | |
| env: { | |
| memory: new WebAssembly.Memory({ | |
| initial: 256, | |
| maximum: 512, | |
| shared: this.config.threadingSupported | |
| }), | |
| abort: (msg, file, line, column) => { | |
| console.error(`Abort called from ${file}:${line}:${column}: ${msg}`); | |
| }, | |
| log: (ptr, len) => { | |
| // In a real implementation, this would extract string from memory | |
| console.log('WASM log message'); | |
| } | |
| }, | |
| wasi_snapshot_preview1: { | |
| proc_exit: () => {}, | |
| fd_close: () => {}, | |
| fd_write: () => {}, | |
| fd_seek: () => {}, | |
| fd_read: () => {} | |
| } | |
| }; | |
| // Merge with provided import object | |
| const mergedImports = this.mergeImportObjects(defaultImports, importObject); | |
| // Instantiate the WebAssembly module | |
| const result = await WebAssembly.instantiate(wasmBinary, mergedImports); | |
| // Extract exports and create wrapper | |
| const instance = result.instance; | |
| const exports = instance.exports; | |
| // Create module wrapper with exports and memory helpers | |
| const moduleWrapper = { | |
| instance, | |
| exports, | |
| memory: instance.exports.memory || mergedImports.env.memory, | |
| getMemoryBuffer: () => new Uint8Array( | |
| (instance.exports.memory || mergedImports.env.memory).buffer | |
| ), | |
| allocateMemory: (size) => { | |
| if (typeof exports.malloc !== 'function') { | |
| throw new Error('Memory allocation function not available'); | |
| } | |
| return exports.malloc(size); | |
| }, | |
| freeMemory: (ptr) => { | |
| if (typeof exports.free !== 'function') { | |
| throw new Error('Memory deallocation function not available'); | |
| } | |
| exports.free(ptr); | |
| } | |
| }; | |
| // Cache the module | |
| this.modules[moduleName] = moduleWrapper; | |
| return moduleWrapper; | |
| } catch (error) { | |
| console.error(`Error loading WebAssembly module '${moduleName}':`, error); | |
| if (this.config.fallbackToJS) { | |
| console.warn(`Falling back to JavaScript implementation for '${moduleName}'`); | |
| return this.loadJSFallback(moduleName); | |
| } | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Load a JavaScript fallback implementation | |
| * @param {string} moduleName - Name of the module | |
| * @returns {Promise<object>} - JavaScript fallback module | |
| */ | |
| async loadJSFallback(moduleName) { | |
| try { | |
| // Check if fallback script exists | |
| const scriptPath = `${this.config.wasmPath}fallbacks/${moduleName}.js`; | |
| // Create and append script element | |
| return new Promise((resolve, reject) => { | |
| const script = document.createElement('script'); | |
| script.src = scriptPath; | |
| script.onload = () => { | |
| // Access global object created by script | |
| const fallbackModule = window[`${moduleName}Fallback`]; | |
| if (!fallbackModule) { | |
| reject(new Error(`JavaScript fallback module '${moduleName}' not found`)); | |
| return; | |
| } | |
| // Cache the fallback module | |
| this.modules[moduleName] = { | |
| isJSFallback: true, | |
| ...fallbackModule | |
| }; | |
| resolve(this.modules[moduleName]); | |
| }; | |
| script.onerror = () => { | |
| reject(new Error(`Failed to load JavaScript fallback for '${moduleName}'`)); | |
| }; | |
| document.head.appendChild(script); | |
| }); | |
| } catch (error) { | |
| console.error(`Error loading JavaScript fallback for '${moduleName}':`, error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Merge two import objects | |
| * @param {object} obj1 - First import object | |
| * @param {object} obj2 - Second import object | |
| * @returns {object} - Merged import object | |
| */ | |
| mergeImportObjects(obj1, obj2) { | |
| const result = { ...obj1 }; | |
| for (const key in obj2) { | |
| if (obj2.hasOwnProperty(key)) { | |
| if (result[key] && typeof result[key] === 'object' && typeof obj2[key] === 'object') { | |
| result[key] = { ...result[key], ...obj2[key] }; | |
| } else { | |
| result[key] = obj2[key]; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Check if a WebAssembly module is available | |
| * @param {string} moduleName - Name of the module | |
| * @returns {boolean} - True if module is loaded | |
| */ | |
| isModuleLoaded(moduleName) { | |
| return !!this.modules[moduleName]; | |
| } | |
| /** | |
| * Get a loaded WebAssembly module | |
| * @param {string} moduleName - Name of the module | |
| * @returns {object|null} - The loaded module or null if not loaded | |
| */ | |
| getModule(moduleName) { | |
| return this.modules[moduleName] || null; | |
| } | |
| /** | |
| * Copy data to WebAssembly memory | |
| * @param {object} module - WebAssembly module object | |
| * @param {ArrayBuffer|TypedArray} data - Data to copy | |
| * @returns {number} - Pointer to the allocated memory | |
| */ | |
| copyToWasmMemory(module, data) { | |
| if (!module || module.isJSFallback) { | |
| throw new Error('Cannot copy to memory of JavaScript fallback module'); | |
| } | |
| // Allocate memory | |
| const size = data.byteLength || data.length; | |
| const ptr = module.allocateMemory(size); | |
| if (!ptr) { | |
| throw new Error('Memory allocation failed'); | |
| } | |
| // Get memory buffer | |
| const memory = module.getMemoryBuffer(); | |
| // Copy data | |
| if (data instanceof ArrayBuffer) { | |
| memory.set(new Uint8Array(data), ptr); | |
| } else if (ArrayBuffer.isView(data)) { | |
| memory.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), ptr); | |
| } else { | |
| throw new Error('Unsupported data type for copying to WebAssembly memory'); | |
| } | |
| return ptr; | |
| } | |
| /** | |
| * Copy data from WebAssembly memory | |
| * @param {object} module - WebAssembly module object | |
| * @param {number} ptr - Pointer to memory | |
| * @param {number} size - Size of data to copy | |
| * @returns {Uint8Array} - Copied data | |
| */ | |
| copyFromWasmMemory(module, ptr, size) { | |
| if (!module || module.isJSFallback) { | |
| throw new Error('Cannot copy from memory of JavaScript fallback module'); | |
| } | |
| // Get memory buffer | |
| const memory = module.getMemoryBuffer(); | |
| // Copy data | |
| return memory.slice(ptr, ptr + size); | |
| } | |
| /** | |
| * Convert string to WebAssembly memory | |
| * @param {object} module - WebAssembly module object | |
| * @param {string} str - String to convert | |
| * @returns {object} - Pointer and size of string in memory | |
| */ | |
| stringToWasmMemory(module, str) { | |
| const encoder = new TextEncoder(); | |
| const encoded = encoder.encode(str); | |
| const ptr = this.copyToWasmMemory(module, encoded); | |
| return { | |
| ptr, | |
| size: encoded.length | |
| }; | |
| } | |
| /** | |
| * Convert WebAssembly memory to string | |
| * @param {object} module - WebAssembly module object | |
| * @param {number} ptr - Pointer to memory | |
| * @param {number} size - Size of string data | |
| * @returns {string} - Decoded string | |
| */ | |
| wasmMemoryToString(module, ptr, size) { | |
| const memory = this.copyFromWasmMemory(module, ptr, size); | |
| const decoder = new TextDecoder(); | |
| return decoder.decode(memory); | |
| } | |
| } | |
| // Export for use in main script | |
| window.WasmLoader = WasmLoader; |