scirs2_wasm/lib.rs
1//! # SciRS2-WASM: WebAssembly Bindings for SciRS2
2//!
3//! High-performance scientific computing in the browser and Node.js.
4//!
5//! ## Features
6//!
7//! - **Pure Rust**: 100% safe Rust code compiled to WASM
8//! - **SIMD Support**: Optional WASM SIMD (wasm32-simd128) acceleration
9//! - **Async Operations**: Non-blocking computations with async/await
10//! - **TypeScript Support**: Full TypeScript type definitions
11//! - **Memory Efficient**: Optimized memory management for browser environments
12//! - **Zero-copy**: Direct array buffer access when possible
13//!
14//! ## Modules
15//!
16//! - `array`: N-dimensional array operations
17//! - `linalg`: Linear algebra (matrix operations, decompositions)
18//! - `stats`: Statistical functions (distributions, tests, descriptive stats)
19//! - `fft`: Fast Fourier Transform operations
20//! - `signal`: Signal processing (filtering, convolution, wavelets)
21//! - `integrate`: Numerical integration and ODE solvers
22//! - `interpolate`: Interpolation (linear, spline, Lagrange, PCHIP, Akima)
23//! - `optimize`: Optimization algorithms (minimize, curve fitting)
24//! - `random`: Random number generation and distributions
25//!
26//! ## Example Usage (JavaScript)
27//!
28//! ```javascript
29//! import * as scirs2 from 'scirs2-wasm';
30//!
31//! // Initialize the library
32//! await scirs2.default();
33//!
34//! // Create arrays
35//! const a = scirs2.array([1, 2, 3, 4]);
36//! const b = scirs2.array([5, 6, 7, 8]);
37//!
38//! // Perform operations
39//! const sum = scirs2.add(a, b);
40//! const dot = scirs2.dot(a, b);
41//!
42//! // Statistical operations
43//! const mean = scirs2.mean(a);
44//! const std = scirs2.std(a);
45//!
46//! // Linear algebra
47//! const matrix = scirs2.array2d([[1, 2], [3, 4]]);
48//! const inv = scirs2.inv(matrix);
49//! const det = scirs2.det(matrix);
50//! ```
51
52#![deny(unsafe_op_in_unsafe_fn)]
53#![warn(missing_docs)]
54
55use std::alloc::{alloc, dealloc, Layout};
56
57use wasm_bindgen::prelude::*;
58use web_sys::console;
59
60pub mod array;
61pub mod error;
62pub mod fft;
63pub mod integrate;
64pub mod interpolate;
65pub mod linalg;
66pub mod optimize;
67pub mod random;
68pub mod signal;
69pub mod stats;
70pub mod utils;
71
72// Async streaming via wasm-bindgen-futures
73pub mod async_streaming;
74// SharedArrayBuffer / Atomics JS bindings
75pub mod shared_memory;
76
77// Incremental PCA for WASM
78pub mod incremental_pca;
79// MFCC feature extraction for WASM
80pub mod mfcc;
81// Parallel WASM (SharedArrayBuffer)
82pub mod parallel;
83// Streaming FFT for WASM
84pub mod streaming_fft;
85// Wavelet transforms for WASM
86pub mod wavelets;
87// WebGPU compute shaders
88pub mod webgpu;
89
90/// Initialize the WASM module with panic hooks and logging
91#[wasm_bindgen(start)]
92pub fn init() {
93 // Set panic hook for better error messages in the console
94 #[cfg(feature = "console_error_panic_hook")]
95 console_error_panic_hook::set_once();
96
97 // Log initialization message
98 log("SciRS2-WASM initialized successfully");
99}
100
101/// Get the version of SciRS2-WASM
102#[wasm_bindgen]
103pub fn version() -> String {
104 env!("CARGO_PKG_VERSION").to_string()
105}
106
107/// Log a message to the browser console
108#[wasm_bindgen]
109pub fn log(message: &str) {
110 console::log_1(&JsValue::from_str(message));
111}
112
113/// Check if WASM SIMD is supported in the current environment
114#[wasm_bindgen]
115pub fn has_simd_support() -> bool {
116 #[cfg(target_feature = "simd128")]
117 {
118 true
119 }
120 #[cfg(not(target_feature = "simd128"))]
121 {
122 false
123 }
124}
125
126/// Get system capabilities and features available in this build
127#[wasm_bindgen]
128pub fn capabilities() -> JsValue {
129 let caps = serde_json::json!({
130 "version": env!("CARGO_PKG_VERSION"),
131 "simd": has_simd_support(),
132 "features": {
133 "array": true,
134 "linalg": cfg!(feature = "linalg"),
135 "stats": cfg!(feature = "stats"),
136 "fft": cfg!(feature = "fft"),
137 "signal": cfg!(feature = "signal"),
138 "integrate": cfg!(feature = "integrate"),
139 "optimize": cfg!(feature = "optimize"),
140 "interpolate": cfg!(feature = "interpolate"),
141 },
142 "target": {
143 "arch": std::env::consts::ARCH,
144 "os": "wasm32",
145 "family": std::env::consts::FAMILY,
146 }
147 });
148
149 serde_wasm_bindgen::to_value(&caps).unwrap_or(JsValue::NULL)
150}
151
152/// Performance timing utilities for benchmarking WASM operations
153#[wasm_bindgen]
154pub struct PerformanceTimer {
155 start: f64,
156 label: String,
157}
158
159#[wasm_bindgen]
160impl PerformanceTimer {
161 /// Create a new performance timer with a label
162 #[wasm_bindgen(constructor)]
163 pub fn new(label: String) -> Result<PerformanceTimer, JsValue> {
164 let start = web_sys::window()
165 .ok_or_else(|| JsValue::from_str("No window object available"))?
166 .performance()
167 .ok_or_else(|| JsValue::from_str("No performance object available"))?
168 .now();
169
170 Ok(PerformanceTimer { start, label })
171 }
172
173 /// Get elapsed time in milliseconds
174 pub fn elapsed(&self) -> Result<f64, JsValue> {
175 let now = web_sys::window()
176 .ok_or_else(|| JsValue::from_str("No window object available"))?
177 .performance()
178 .ok_or_else(|| JsValue::from_str("No performance object available"))?
179 .now();
180
181 Ok(now - self.start)
182 }
183
184 /// Log the elapsed time to console
185 pub fn log_elapsed(&self) -> Result<(), JsValue> {
186 let elapsed = self.elapsed()?;
187 let message = format!("{}: {:.3}ms", self.label, elapsed);
188 console::log_1(&JsValue::from_str(&message));
189 Ok(())
190 }
191}
192
193/// Memory usage information for the WASM module
194#[wasm_bindgen]
195pub fn memory_usage() -> JsValue {
196 let info = serde_json::json!({
197 "note": "WASM memory usage should be checked via JavaScript Memory API"
198 });
199
200 serde_wasm_bindgen::to_value(&info).unwrap_or(JsValue::NULL)
201}
202
203// ---------------------------------------------------------------------------
204// WASM linear-memory allocation helpers
205// ---------------------------------------------------------------------------
206
207/// Allocate `len` f64 values in WASM linear memory.
208///
209/// Returns a pointer (as `usize`) to the start of the allocated buffer.
210/// The caller is responsible for eventually freeing the buffer by passing
211/// the same pointer and length to [`free_f64_array`].
212///
213/// Returns `0` when `len` is zero; panics on allocation failure.
214#[wasm_bindgen]
215pub fn alloc_f64_array(len: usize) -> usize {
216 if len == 0 {
217 return 0;
218 }
219 let layout = Layout::array::<f64>(len).expect("alloc_f64_array: layout overflow");
220 // SAFETY: layout is non-zero (len > 0 checked above).
221 let ptr = unsafe { alloc(layout) };
222 assert!(!ptr.is_null(), "alloc_f64_array: allocation failed");
223 ptr as usize
224}
225
226/// Free a buffer previously allocated with [`alloc_f64_array`].
227///
228/// Both `ptr` and `len` must match exactly the values used when the buffer
229/// was originally allocated. Passing mismatched values is undefined
230/// behaviour. A `ptr` of `0` or a `len` of `0` is silently ignored.
231#[wasm_bindgen]
232pub fn free_f64_array(ptr: usize, len: usize) {
233 if ptr == 0 || len == 0 {
234 return;
235 }
236 let layout = Layout::array::<f64>(len).expect("free_f64_array: layout overflow");
237 // SAFETY: `ptr` was returned by `alloc_f64_array` with the same `len`,
238 // and WASM is single-threaded so no concurrent access is possible.
239 unsafe { dealloc(ptr as *mut u8, layout) };
240}
241
242/// Return a `Float64Array` view into WASM linear memory at `ptr` / `len`.
243///
244/// This exposes a zero-copy window from JavaScript into the WASM heap.
245/// The returned typed array is only valid while the underlying allocation
246/// remains live; callers must not use the view after calling
247/// [`free_f64_array`] for the same pointer.
248///
249/// Available on the `wasm32` target only.
250#[cfg(target_arch = "wasm32")]
251#[wasm_bindgen]
252pub fn view_f64_array(ptr: usize, len: usize) -> js_sys::Float64Array {
253 // SAFETY: `ptr` points to a live, f64-aligned allocation of at least
254 // `len * 8` bytes allocated by `alloc_f64_array`. WASM is
255 // single-threaded, so there are no concurrent mutations.
256 let slice = unsafe { std::slice::from_raw_parts(ptr as *const f64, len) };
257 // SAFETY: `slice` is valid for the duration of this call; the returned
258 // Float64Array must not outlive the underlying allocation.
259 unsafe { js_sys::Float64Array::view(slice) }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_version() {
268 let v = version();
269 assert!(!v.is_empty());
270 }
271
272 #[test]
273 fn test_has_simd_support() {
274 // Should not panic
275 let _simd = has_simd_support();
276 }
277
278 // -----------------------------------------------------------------------
279 // alloc / free round-trip tests (native; WASM targets use wasm-bindgen-test)
280 // -----------------------------------------------------------------------
281
282 /// Allocating zero elements must return the sentinel 0 without allocating.
283 #[test]
284 fn test_alloc_zero_returns_zero() {
285 let ptr = alloc_f64_array(0);
286 assert_eq!(ptr, 0);
287 // free_f64_array(0, 0) must be a no-op.
288 free_f64_array(0, 0);
289 }
290
291 /// Allocate, write, read back, and free a single f64.
292 #[test]
293 fn test_alloc_free_single_element() {
294 let len = 1_usize;
295 let ptr = alloc_f64_array(len);
296 assert_ne!(ptr, 0);
297
298 // SAFETY: ptr is a valid, non-null allocation of len * 8 bytes.
299 unsafe {
300 let p = ptr as *mut f64;
301 p.write(std::f64::consts::PI);
302 let read_back = p.read();
303 assert!(
304 (read_back - std::f64::consts::PI).abs() < f64::EPSILON,
305 "expected PI, got {read_back}"
306 );
307 }
308
309 free_f64_array(ptr, len);
310 }
311
312 /// Allocate a larger buffer, write a pattern, verify it, then free.
313 #[test]
314 fn test_alloc_free_round_trip() {
315 let len = 256_usize;
316 let ptr = alloc_f64_array(len);
317 assert_ne!(ptr, 0);
318
319 // SAFETY: ptr is a valid allocation of len * 8 bytes.
320 unsafe {
321 let slice = std::slice::from_raw_parts_mut(ptr as *mut f64, len);
322 for (i, elem) in slice.iter_mut().enumerate() {
323 *elem = i as f64 * 1.5;
324 }
325 for (i, &val) in slice.iter().enumerate() {
326 let expected = i as f64 * 1.5;
327 assert!(
328 (val - expected).abs() < f64::EPSILON,
329 "index {i}: expected {expected}, got {val}"
330 );
331 }
332 }
333
334 free_f64_array(ptr, len);
335 }
336
337 /// free_f64_array with ptr==0 or len==0 must be silent no-ops.
338 #[test]
339 fn test_free_noop_on_null_or_zero_len() {
340 free_f64_array(0, 128);
341 free_f64_array(0, 0);
342 // Allocate then use len==0 to demonstrate no-op free path.
343 let ptr = alloc_f64_array(4);
344 free_f64_array(ptr, 0); // no-op — we still need to free properly
345 free_f64_array(ptr, 4); // actual free
346 }
347}