From 97e581827b74ac2218872e3f802ff54cd41f2f0f Mon Sep 17 00:00:00 2001 From: Andy Wong Date: Sun, 10 Mar 2024 23:02:03 -0700 Subject: [PATCH] add rust wasm vec exports --- .cargo/config.toml | 2 + Cargo.toml | 5 +- README.md | 2 +- package-lock.json | 17 +++- package.json | 3 + src/float.rs | 8 +- src/lib.rs | 6 ++ src/matrix.rs | 4 +- src/matrix_special.rs | 58 ++++++++++++++ src/wasm/bindings.rs | 0 src/wasm/float.rs | 56 ------------- src/wasm/jsmath.rs | 66 ++++++++++++++-- src/wasm/mod.rs | 6 +- src/wasm/ptr.rs | 21 +++++ src/wasm/vec.rs | 178 ++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 356 insertions(+), 76 deletions(-) create mode 100644 .cargo/config.toml delete mode 100644 src/wasm/bindings.rs delete mode 100644 src/wasm/float.rs create mode 100644 src/wasm/ptr.rs create mode 100644 src/wasm/vec.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b1d39e9 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = ["-C", "link-args=-z stack-size=65536"] diff --git a/Cargo.toml b/Cargo.toml index ad74bc6..dd8b09f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,13 +31,14 @@ jsmath = [] [dependencies] num = { version = "0.4", default-features = false } +paste = { version = "1.0" } serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } [dev-dependencies] serde_json = "1.0" -[target.'cfg(target_family = "wasm")'.lib] -crate-type = ["cdylib"] +[lib] +crate-type = ["cdylib", "rlib"] [profile.release] lto = true diff --git a/README.md b/README.md index e085264..751d8a6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Features: - `libm` - enables trigonometry related functions in `no_std` environment using `libm`. - `jsmath` - enables trigonometry related functions in `no_std` WebAssembly environment using JS Math binding. - `serde` - enables `serde` serialize/deserialize implementations -- `wasm` - (WIP) enables WebAssembly export bindings +- `wasm` - (WIP) produces standalone WebAssembly component --- diff --git a/package-lock.json b/package-lock.json index df885fd..5f31e67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "assemblyscript": "^0.27", "babel-jest": "^29.5", "babel-plugin-add-import-extension": "^1.6", + "binaryen": "^116.0", "copyfiles": "^2.4", "cross-env": "^7.0", "eslint": "^8.50", @@ -3847,6 +3848,16 @@ "url": "https://opencollective.com/assemblyscript" } }, + "node_modules/assemblyscript/node_modules/binaryen": { + "version": "116.0.0-nightly.20240114", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", + "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", + "dev": true, + "bin": { + "wasm-opt": "bin/wasm-opt", + "wasm2js": "bin/wasm2js" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -4105,9 +4116,9 @@ } }, "node_modules/binaryen": { - "version": "116.0.0-nightly.20240114", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", - "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", + "version": "116.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz", + "integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==", "dev": true, "bin": { "wasm-opt": "bin/wasm-opt", diff --git a/package.json b/package.json index f1802f9..52f410d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "scripts": { "prepublishOnly": "npm run build && npm test && npm run docs", "clean": "rimraf coverage/ docs/ dist/ target/ **/__tests__/**/*.spec.map **/__tests__/**/*.spec.wat", + "build:rust": "cargo build --release --target wasm32-unknown-unknown --features jsmath,wasm --no-default-features", + "postbuild:rust": "wasm-opt -Oz -o dist/munum.wasm target/wasm32-unknown-unknown/release/munum.wasm", "prebuild": "npm run lint", "build": "npm run tsc", "lint": "eslint assembly --ext .ts,.tsx", @@ -69,6 +71,7 @@ "assemblyscript": "^0.27", "babel-jest": "^29.5", "babel-plugin-add-import-extension": "^1.6", + "binaryen": "^116.0", "copyfiles": "^2.4", "cross-env": "^7.0", "eslint": "^8.50", diff --git a/src/float.rs b/src/float.rs index 91bcb17..97b9a18 100644 --- a/src/float.rs +++ b/src/float.rs @@ -1,14 +1,14 @@ //! Float type helpers. use crate::{Matrix, Quaternion}; -use num::traits::{float::FloatCore, NumAssign, NumCast}; +use num::{traits::{float::FloatCore, NumAssign, NumCast}, Zero}; /// Standard tolerance epsilon pub const EPSILON: f32 = 128. * f32::EPSILON; /// Returns a standard tolerance epsilon -pub fn epsilon() -> T { - NumCast::from(EPSILON).expect("incompatible type") +pub fn epsilon() -> T { + NumCast::from(EPSILON).unwrap_or(T::zero()) } /// Trait for float operations @@ -29,7 +29,7 @@ pub trait FloatOps: Copy { fn tan(self) -> Self; } -#[cfg(any(feature = "std", feature = "libm"))] +#[cfg(all(any(feature = "std", feature = "libm"), not(feature = "jsmath")))] impl FloatOps for T { #[inline] fn acos(self) -> T { diff --git a/src/lib.rs b/src/lib.rs index 1ef2630..1d80748 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,3 +25,9 @@ pub use quat::{quat, Quaternion}; #[cfg(target_arch = "wasm32")] mod wasm; + +#[cfg(all(target_arch = "wasm32", feature = "wasm", not(test)))] +#[panic_handler] +fn panic(_panic: &core::panic::PanicInfo<'_>) -> ! { + core::arch::wasm32::unreachable() +} diff --git a/src/matrix.rs b/src/matrix.rs index 9dc1dbc..c556f66 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -136,7 +136,7 @@ impl Index<(usize, usize)> /// Indexing into the `Matrix` by (row, column). #[inline] fn index(&self, (row, col): (usize, usize)) -> &Self::Output { - &self.0[col][row] + &self.0[col % C][row % R] } } @@ -146,7 +146,7 @@ impl IndexMut<(usize, usize /// Mutably indexing into the `Matrix` by (row, column). #[inline] fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output { - &mut self.0[col][row] + &mut self.0[col % C][row % R] } } diff --git a/src/matrix_special.rs b/src/matrix_special.rs index bd36492..65f9c99 100644 --- a/src/matrix_special.rs +++ b/src/matrix_special.rs @@ -131,6 +131,64 @@ impl From> for [T; R] { } } +impl From> for Vec3 { + /// Augments a `Vec2` into a `Vec3` + /// The resulting `Vec3` contains the given `Vec2` with z = 1. + /// + /// # Examples + /// ``` + /// # use munum::{Vec2, Vec3}; + /// let v = Vec3::from(Vec2::::from_slice(&[2, 3])); + /// assert_eq!(*v.as_ref(), [2, 3, 1]); + /// ``` + fn from(v: Vec2) -> Self { + Vec3::new([[v[0], v[1], T::one()]]) + } +} + +impl From> for Vec2 { + /// Creates a `Vec2` from the (x, y) of a `Vec3` + /// + /// # Examples + /// ``` + /// # use munum::{Vec2, Vec3}; + /// let v = Vec2::from(Vec3::::from_slice(&[2, 3, 4])); + /// assert_eq!(*v.as_ref(), [2, 3]); + /// ``` + fn from(v: Vec3) -> Self { + Vec2::new([[v[0], v[1]]]) + } +} + +impl From> for Vec4 { + /// Augments a `Vec3` into a `Vec4` + /// The resulting `Vec4` contains the given `Vec3` with w = 1. + /// + /// # Examples + /// ``` + /// # use munum::{Vec3, Vec4}; + /// let v = Vec4::from(Vec3::::from_slice(&[2, 3, 4])); + /// assert_eq!(*v.as_ref(), [2, 3, 4, 1]); + /// ``` + fn from(v: Vec3) -> Self { + Vec4::new([[v[0], v[1], v[2], T::one()]]) + } +} + +impl From> for Vec3 { + /// Creates a `Vec3` from the (x, y, z) of a `Vec4` + /// + /// # Examples + /// ``` + /// # use munum::{Vec3, Vec4}; + /// let v = Vec3::from(Vec4::::from_slice(&[2, 3, 4, 1])); + /// assert_eq!(*v.as_ref(), [2, 3, 4]); + /// ``` + fn from(v: Vec4) -> Self { + Vec3::new([[v[0], v[1], v[2]]]) + } +} + impl From> for Mat3 { /// Augments a `Mat2` into a `Mat3` /// The resulting `Mat3` contains the given `Mat2` on upper-left with the lower-right element = 1. diff --git a/src/wasm/bindings.rs b/src/wasm/bindings.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/wasm/float.rs b/src/wasm/float.rs deleted file mode 100644 index 8794075..0000000 --- a/src/wasm/float.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::jsmath; -use crate::FloatOps; - -impl FloatOps for f64 { - #[inline] - fn acos(self) -> f64 { - jsmath::acos(self) - } - - #[inline] - fn cos(self) -> f64 { - jsmath::cos(self) - } - - #[inline] - fn sin(self) -> f64 { - jsmath::sin(self) - } - - #[inline] - fn sqrt(self) -> f64 { - jsmath::sqrt(self) - } - - #[inline] - fn tan(self) -> f64 { - jsmath::tan(self) - } -} - -impl FloatOps for f32 { - #[inline] - fn acos(self) -> f32 { - jsmath::acos(self.into()) as f32 - } - - #[inline] - fn cos(self) -> f32 { - jsmath::cos(self.into()) as f32 - } - - #[inline] - fn sin(self) -> f32 { - jsmath::sin(self.into()) as f32 - } - - #[inline] - fn sqrt(self) -> f32 { - jsmath::sqrt(self.into()) as f32 - } - - #[inline] - fn tan(self) -> f32 { - jsmath::tan(self.into()) as f32 - } -} diff --git a/src/wasm/jsmath.rs b/src/wasm/jsmath.rs index d497d6c..bf806c9 100644 --- a/src/wasm/jsmath.rs +++ b/src/wasm/jsmath.rs @@ -1,8 +1,64 @@ +use crate::FloatOps; + #[link(wasm_import_module = "jsmath")] extern "C" { - fn acos(x: f64) -> f64; - fn cos(x: f64) -> f64; - fn sin(x: f64) -> f64; - fn sqrt(x: f64) -> f64; - fn tan(x: f64) -> f64; + pub fn acos(x: f64) -> f64; + pub fn cos(x: f64) -> f64; + pub fn sin(x: f64) -> f64; + pub fn sqrt(x: f64) -> f64; + pub fn tan(x: f64) -> f64; +} + +impl FloatOps for f64 { + #[inline] + fn acos(self) -> f64 { + unsafe { acos(self) } + } + + #[inline] + fn cos(self) -> f64 { + unsafe { cos(self) } + } + + #[inline] + fn sin(self) -> f64 { + unsafe { sin(self) } + } + + #[inline] + fn sqrt(self) -> f64 { + unsafe { sqrt(self) } + } + + #[inline] + fn tan(self) -> f64 { + unsafe { tan(self) } + } +} + +impl FloatOps for f32 { + #[inline] + fn acos(self) -> f32 { + unsafe { acos(self.into()) as f32 } + } + + #[inline] + fn cos(self) -> f32 { + unsafe { cos(self.into()) as f32 } + } + + #[inline] + fn sin(self) -> f32 { + unsafe { sin(self.into()) as f32 } + } + + #[inline] + fn sqrt(self) -> f32 { + unsafe { sqrt(self.into()) as f32 } + } + + #[inline] + fn tan(self) -> f32 { + unsafe { tan(self.into()) as f32 } + } } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index f9468e5..e2e30db 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -1,9 +1,9 @@ //! WebAssembly bindings. +mod ptr; + #[cfg(feature = "jsmath")] mod jsmath; -#[cfg(feature = "jsmath")] -mod float; #[cfg(feature = "wasm")] -mod bindings; +mod vec; diff --git a/src/wasm/ptr.rs b/src/wasm/ptr.rs new file mode 100644 index 0000000..ecb0404 --- /dev/null +++ b/src/wasm/ptr.rs @@ -0,0 +1,21 @@ +use crate::Matrix; +use num::traits::NumAssign; + +/// Trait for loading a value from a pointer. +pub trait Load { + /// Loads the value from the pointer. + fn load(&self) -> T; +} + +impl Load> + for *const Matrix +{ + #[inline] + fn load(&self) -> Matrix { + if let Some(&m) = unsafe { self.as_ref() } { + m + } else { + Matrix::::default() + } + } +} diff --git a/src/wasm/vec.rs b/src/wasm/vec.rs new file mode 100644 index 0000000..a4450ae --- /dev/null +++ b/src/wasm/vec.rs @@ -0,0 +1,178 @@ +use super::ptr::Load; +use crate::{Mat2, Mat3, Mat4, Vec2, Vec3, Vec4}; +use paste::paste; + +macro_rules! export_vec { + ($name:ident, $vec_type:ty, $mat_type:ty, $t:ty) => { + paste! { + #[export_name = concat!("munum:wasm/", stringify!($name), "#get")] + pub extern "C" fn [<$name _get>](ptr: *const $t) -> *const $t { + ptr + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#copy")] + pub extern "C" fn [<$name _copy>](dst: *mut $vec_type, src: *const $vec_type) -> *const $vec_type { + if let Some(o) = unsafe { dst.as_mut() } { + *o = src.load(); + } + dst + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#add")] + pub extern "C" fn [<$name _add>]( + out: *mut $vec_type, + a: *const $vec_type, + b: *const $vec_type, + ) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load() + b.load(); + } + out + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#sub")] + pub extern "C" fn [<$name _sub>]( + out: *mut $vec_type, + a: *const $vec_type, + b: *const $vec_type, + ) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load() - b.load(); + } + out + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#mul")] + pub extern "C" fn [<$name _mul>]( + out: *mut $vec_type, + m: *const $mat_type, + a: *const $vec_type, + ) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = m.load() * a.load(); + } + out + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#scale")] + pub extern "C" fn [<$name _scale>](out: *mut $vec_type, a: *const $vec_type, s: f64) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load() * s; + } + out + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#norm")] + pub extern "C" fn [<$name _norm>](out: *mut $vec_type, a: *const $vec_type) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load().normalized(); + } + out + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#dot")] + pub extern "C" fn [<$name _dot>](a: *const $vec_type, b: *const $vec_type) -> $t { + a.load().dot(b.load()) + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#sqr-len")] + pub extern "C" fn [<$name _sqr_len>](a: *const $vec_type) -> $t { + a.load().sqr_len() + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#len")] + pub extern "C" fn [<$name _len>](a: *const $vec_type) -> $t { + a.load().len() + } + + #[export_name = concat!("munum:wasm/", stringify!($name), "#lerp")] + pub extern "C" fn [<$name _lerp>]( + out: *mut $vec_type, + a: *const $vec_type, + b: *const $vec_type, + t: $t, + ) -> *const $vec_type { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load().lerp(b.load(), t); + } + out + } + } + } +} + +export_vec!(vec2, Vec2, Mat2, f64); +export_vec!(vec3, Vec3, Mat3, f64); +export_vec!(vec4, Vec4, Mat4, f64); + +#[export_name = "munum:wasm/vec2#set"] +pub extern "C" fn vec2_set(out: *mut Vec2, x: f64, y: f64) -> *const Vec2 { + if let Some(v) = unsafe { out.as_mut() } { + v[(0, 0)] = x; + v[(1, 0)] = y; + } + out +} + +#[export_name = "munum:wasm/vec3#set"] +pub extern "C" fn vec3_set(out: *mut Vec3, x: f64, y: f64, z: f64) -> *const Vec3 { + if let Some(v) = unsafe { out.as_mut() } { + v[(0, 0)] = x; + v[(1, 0)] = y; + v[(2, 0)] = z; + } + out +} + +#[export_name = "munum:wasm/vec4#set"] +pub extern "C" fn vec4_set( + out: *mut Vec4, + x: f64, + y: f64, + z: f64, + w: f64, +) -> *const Vec4 { + if let Some(v) = unsafe { out.as_mut() } { + v[(0, 0)] = x; + v[(1, 0)] = y; + v[(2, 0)] = z; + v[(3, 0)] = w; + } + out +} + +#[export_name = "munum:wasm/vec2#mul3"] +pub extern "C" fn vec2_mul3( + out: *mut Vec2, + m: *const Mat3, + a: *const Vec2, +) -> *const Vec2 { + if let Some(o) = unsafe { out.as_mut() } { + *o = (m.load() * Vec3::from(a.load())).into(); + } + out +} + +#[export_name = "munum:wasm/vec3#mul4"] +pub extern "C" fn vec3_mul4( + out: *mut Vec3, + m: *const Mat4, + a: *const Vec3, +) -> *const Vec3 { + if let Some(o) = unsafe { out.as_mut() } { + *o = (m.load() * Vec4::from(a.load())).into(); + } + out +} + +#[export_name = "munum:wasm/vec3#cross"] +pub extern "C" fn vec3_cross( + out: *mut Vec3, + a: *const Vec3, + b: *const Vec3, +) -> *const Vec3 { + if let Some(o) = unsafe { out.as_mut() } { + *o = a.load().cross(b.load()); + } + out +}