//! WASM Runtime Detection and Utilities //! //! Detects the WASM runtime environment (browser, Node.js, etc.) //! and provides utilities for performance measurement. use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; /// WASM runtime environment types #[wasm_bindgen] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum WasmRuntime { /// Running in a web browser Browser, /// Running in Node.js NodeJs, /// Running in a Web Worker WebWorker, /// Running in Deno Deno, /// Running in Wasmtime Wasmtime, /// Running in Wasmer Wasmer, /// Unknown runtime Unknown, } /// Information about the WASM runtime environment #[wasm_bindgen] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WasmRuntimeInfo { /// Detected runtime type runtime: WasmRuntime, /// Whether SIMD is available simd_available: bool, /// Whether threading is available threads_available: bool, /// Whether SharedArrayBuffer is available shared_memory: bool, /// Whether WebGPU is available webgpu_available: bool, /// Available memory in bytes (if detectable) available_memory: Option, /// User agent string (browser only) user_agent: Option, } #[wasm_bindgen] impl WasmRuntimeInfo { /// Detect the current runtime environment #[wasm_bindgen(constructor)] pub fn detect() -> Self { let (runtime, user_agent) = Self::detect_runtime(); let simd_available = Self::check_simd(); let threads_available = Self::check_threads(); let shared_memory = Self::check_shared_memory(); let webgpu_available = Self::check_webgpu(); let available_memory = Self::get_available_memory(); Self { runtime, simd_available, threads_available, shared_memory, webgpu_available, available_memory, user_agent, } } fn check_webgpu() -> bool { let global = js_sys::global(); js_sys::Reflect::get(&global, &"navigator".into()) .ok() .and_then(|nav| js_sys::Reflect::get(&nav, &"gpu".into()).ok()) .is_some_and(|gpu| !gpu.is_undefined()) } fn detect_runtime() -> (WasmRuntime, Option) { // Check for browser environment if let Ok(window) = js_sys::Reflect::get(&js_sys::global(), &"window".into()) { if !window.is_undefined() { let user_agent = web_sys::window().and_then(|w| w.navigator().user_agent().ok()); return (WasmRuntime::Browser, user_agent); } } // Check for Web Worker if let Ok(self_obj) = js_sys::Reflect::get(&js_sys::global(), &"WorkerGlobalScope".into()) { if !self_obj.is_undefined() { return (WasmRuntime::WebWorker, None); } } // Check for Node.js if let Ok(process) = js_sys::Reflect::get(&js_sys::global(), &"process".into()) { if !process.is_undefined() { if let Ok(versions) = js_sys::Reflect::get(&process, &"versions".into()) { if let Ok(node) = js_sys::Reflect::get(&versions, &"node".into()) { if !node.is_undefined() { return (WasmRuntime::NodeJs, None); } } } } } // Check for Deno if let Ok(deno) = js_sys::Reflect::get(&js_sys::global(), &"Deno".into()) { if !deno.is_undefined() { return (WasmRuntime::Deno, None); } } (WasmRuntime::Unknown, None) } fn check_simd() -> bool { // Check if WASM SIMD is supported #[cfg(feature = "simd")] { true } #[cfg(not(feature = "simd"))] { false } } fn check_threads() -> bool { // Check if SharedArrayBuffer is available (required for threads) Self::check_shared_memory() } fn check_shared_memory() -> bool { js_sys::Reflect::get(&js_sys::global(), &"SharedArrayBuffer".into()) .map(|v| !v.is_undefined()) .unwrap_or(false) } fn get_available_memory() -> Option { // Try to get memory info (Chrome only) if let Some(window) = web_sys::window() { if let Ok(performance) = js_sys::Reflect::get(&window, &"performance".into()) { if let Ok(memory) = js_sys::Reflect::get(&performance, &"memory".into()) { if let Ok(js_heap_limit) = js_sys::Reflect::get(&memory, &"jsHeapSizeLimit".into()) { if let Some(limit) = js_heap_limit.as_f64() { return Some(limit as usize); } } } } } None } /// Get the runtime type #[wasm_bindgen(getter)] pub fn runtime(&self) -> WasmRuntime { self.runtime } /// Check if SIMD is available #[wasm_bindgen(getter)] pub fn simd_available(&self) -> bool { self.simd_available } /// Check if threading is available #[wasm_bindgen(getter)] pub fn threads_available(&self) -> bool { self.threads_available } /// Check if shared memory is available #[wasm_bindgen(getter)] pub fn shared_memory(&self) -> bool { self.shared_memory } /// Check if WebGPU is available #[wasm_bindgen(getter)] pub fn webgpu_available(&self) -> bool { self.webgpu_available } /// Get available memory #[wasm_bindgen(getter)] pub fn available_memory(&self) -> Option { self.available_memory } /// Get user agent #[wasm_bindgen(getter)] pub fn user_agent(&self) -> Option { self.user_agent.clone() } /// Get high-resolution timestamp pub fn performance_now(&self) -> f64 { if let Some(window) = web_sys::window() { if let Some(performance) = window.performance() { return performance.now(); } } // Fallback for non-browser environments js_sys::Date::now() } /// Check if running in browser #[wasm_bindgen] pub fn is_browser(&self) -> bool { matches!(self.runtime, WasmRuntime::Browser) } /// Check if running in Node.js #[wasm_bindgen] pub fn is_node(&self) -> bool { matches!(self.runtime, WasmRuntime::NodeJs) } /// Check if running in a worker #[wasm_bindgen] pub fn is_worker(&self) -> bool { matches!(self.runtime, WasmRuntime::WebWorker) } /// Get runtime name as string #[wasm_bindgen] pub fn runtime_name(&self) -> String { match self.runtime { WasmRuntime::Browser => "browser".to_string(), WasmRuntime::NodeJs => "nodejs".to_string(), WasmRuntime::WebWorker => "webworker".to_string(), WasmRuntime::Deno => "deno".to_string(), WasmRuntime::Wasmtime => "wasmtime".to_string(), WasmRuntime::Wasmer => "wasmer".to_string(), WasmRuntime::Unknown => "unknown".to_string(), } } /// Get capabilities summary #[wasm_bindgen] pub fn capabilities_summary(&self) -> String { format!( "Runtime: {}, SIMD: {}, Threads: {}, SharedMem: {}, WebGPU: {}", self.runtime_name(), self.simd_available, self.threads_available, self.shared_memory, self.webgpu_available ) } /// Check if GPU acceleration is available #[wasm_bindgen] pub fn gpu_available(&self) -> bool { self.webgpu_available } /// Get recommended compute backend #[wasm_bindgen] pub fn recommended_backend(&self) -> String { if self.webgpu_available { "webgpu".to_string() } else { "cpu".to_string() } } } /// Console logging utilities for WASM #[wasm_bindgen] pub fn console_log(message: &str) { web_sys::console::log_1(&message.into()); } /// Console warning for WASM #[wasm_bindgen] pub fn console_warn(message: &str) { web_sys::console::warn_1(&message.into()); } /// Console error for WASM #[wasm_bindgen] pub fn console_error(message: &str) { web_sys::console::error_1(&message.into()); } #[cfg(test)] mod tests { use super::*; #[test] fn test_runtime_info() { // In test context, runtime will be Unknown let info = WasmRuntimeInfo { runtime: WasmRuntime::Unknown, simd_available: false, threads_available: false, shared_memory: false, webgpu_available: false, available_memory: None, user_agent: None, }; assert_eq!(info.runtime_name(), "unknown"); assert!(!info.is_browser()); assert!(!info.is_node()); assert!(!info.webgpu_available()); assert!(!info.gpu_available()); assert_eq!(info.recommended_backend(), "cpu"); } #[test] fn test_runtime_info_with_webgpu() { let info = WasmRuntimeInfo { runtime: WasmRuntime::Browser, simd_available: true, threads_available: true, shared_memory: true, webgpu_available: true, available_memory: Some(4 * 1024 * 1024 * 1024), user_agent: Some("Chrome/120".to_string()), }; assert!(info.is_browser()); assert!(info.webgpu_available()); assert!(info.gpu_available()); assert_eq!(info.recommended_backend(), "webgpu"); assert!(info.capabilities_summary().contains("WebGPU: true")); } }