Skip to main content

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}