wasmtime_math/
lib.rs

1//! A minimal helper crate for implementing float-related operations for
2//! WebAssembly in terms of the native platform primitives.
3//!
4//! This crate is intended to assist with solving the portability issues such
5//! as:
6//!
7//! * Functions like `f32::trunc` are not available in `#![no_std]` targets.
8//! * The `f32::trunc` function is likely faster than the `libm` fallback.
9//! * Behavior of `f32::trunc` differs across platforms, for example it's
10//!   different on Windows and glibc on Linux. Additionally riscv64's
11//!   implementation of `libm` seems to have different NaN behavior than other
12//!   platforms.
13//! * Some wasm functions are in the Rust standard library, but not stable yet.
14//!
15//! There are a few locations throughout the codebase that these functions are
16//! needed so they're implemented only in a single location here rather than
17//! multiple.
18
19#![no_std]
20
21#[cfg(feature = "std")]
22extern crate std;
23
24pub trait WasmFloat {
25    fn wasm_trunc(self) -> Self;
26    fn wasm_copysign(self, sign: Self) -> Self;
27    fn wasm_floor(self) -> Self;
28    fn wasm_ceil(self) -> Self;
29    fn wasm_sqrt(self) -> Self;
30    fn wasm_abs(self) -> Self;
31    fn wasm_nearest(self) -> Self;
32    fn wasm_minimum(self, other: Self) -> Self;
33    fn wasm_maximum(self, other: Self) -> Self;
34    fn wasm_mul_add(self, b: Self, c: Self) -> Self;
35}
36
37impl WasmFloat for f32 {
38    #[inline]
39    fn wasm_trunc(self) -> f32 {
40        #[cfg(feature = "std")]
41        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
42            return self.trunc();
43        }
44        if self.is_nan() {
45            return f32::NAN;
46        }
47        libm::truncf(self)
48    }
49    #[inline]
50    fn wasm_copysign(self, sign: f32) -> f32 {
51        #[cfg(feature = "std")]
52        if true {
53            return self.copysign(sign);
54        }
55        libm::copysignf(self, sign)
56    }
57    #[inline]
58    fn wasm_floor(self) -> f32 {
59        #[cfg(feature = "std")]
60        if !cfg!(target_arch = "riscv64") {
61            return self.floor();
62        }
63        if self.is_nan() {
64            return f32::NAN;
65        }
66        libm::floorf(self)
67    }
68    #[inline]
69    fn wasm_ceil(self) -> f32 {
70        #[cfg(feature = "std")]
71        if !cfg!(target_arch = "riscv64") {
72            return self.ceil();
73        }
74        if self.is_nan() {
75            return f32::NAN;
76        }
77        libm::ceilf(self)
78    }
79    #[inline]
80    fn wasm_sqrt(self) -> f32 {
81        #[cfg(feature = "std")]
82        if true {
83            return self.sqrt();
84        }
85        libm::sqrtf(self)
86    }
87    #[inline]
88    fn wasm_abs(self) -> f32 {
89        #[cfg(feature = "std")]
90        if true {
91            return self.abs();
92        }
93        libm::fabsf(self)
94    }
95    #[inline]
96    fn wasm_nearest(self) -> f32 {
97        #[cfg(feature = "std")]
98        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
99            return self.round_ties_even();
100        }
101        if self.is_nan() {
102            return f32::NAN;
103        }
104        let round = libm::roundf(self);
105        if libm::fabsf(self - round) != 0.5 {
106            return round;
107        }
108        match round % 2.0 {
109            1.0 => libm::floorf(self),
110            -1.0 => libm::ceilf(self),
111            _ => round,
112        }
113    }
114    #[inline]
115    fn wasm_maximum(self, other: f32) -> f32 {
116        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
117        // stabilized
118        if self > other {
119            self
120        } else if other > self {
121            other
122        } else if self == other {
123            if self.is_sign_positive() && other.is_sign_negative() {
124                self
125            } else {
126                other
127            }
128        } else {
129            self + other
130        }
131    }
132    #[inline]
133    fn wasm_minimum(self, other: f32) -> f32 {
134        // FIXME: replace this with `self.minimum(other)` when
135        // rust-lang/rust#91079 is stabilized
136        if self < other {
137            self
138        } else if other < self {
139            other
140        } else if self == other {
141            if self.is_sign_negative() && other.is_sign_positive() {
142                self
143            } else {
144                other
145            }
146        } else {
147            self + other
148        }
149    }
150    #[inline]
151    fn wasm_mul_add(self, b: f32, c: f32) -> f32 {
152        // The MinGW implementation of `fma` differs from other platforms, so
153        // favor `libm` there instead.
154        #[cfg(feature = "std")]
155        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
156            return self.mul_add(b, c);
157        }
158        libm::fmaf(self, b, c)
159    }
160}
161
162impl WasmFloat for f64 {
163    #[inline]
164    fn wasm_trunc(self) -> f64 {
165        #[cfg(feature = "std")]
166        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
167            return self.trunc();
168        }
169        if self.is_nan() {
170            return f64::NAN;
171        }
172        libm::trunc(self)
173    }
174    #[inline]
175    fn wasm_copysign(self, sign: f64) -> f64 {
176        #[cfg(feature = "std")]
177        if true {
178            return self.copysign(sign);
179        }
180        libm::copysign(self, sign)
181    }
182    #[inline]
183    fn wasm_floor(self) -> f64 {
184        #[cfg(feature = "std")]
185        if !cfg!(target_arch = "riscv64") {
186            return self.floor();
187        }
188        if self.is_nan() {
189            return f64::NAN;
190        }
191        libm::floor(self)
192    }
193    #[inline]
194    fn wasm_ceil(self) -> f64 {
195        #[cfg(feature = "std")]
196        if !cfg!(target_arch = "riscv64") {
197            return self.ceil();
198        }
199        if self.is_nan() {
200            return f64::NAN;
201        }
202        libm::ceil(self)
203    }
204    #[inline]
205    fn wasm_sqrt(self) -> f64 {
206        #[cfg(feature = "std")]
207        if true {
208            return self.sqrt();
209        }
210        libm::sqrt(self)
211    }
212    #[inline]
213    fn wasm_abs(self) -> f64 {
214        #[cfg(feature = "std")]
215        if true {
216            return self.abs();
217        }
218        libm::fabs(self)
219    }
220    #[inline]
221    fn wasm_nearest(self) -> f64 {
222        #[cfg(feature = "std")]
223        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
224            return self.round_ties_even();
225        }
226        if self.is_nan() {
227            return f64::NAN;
228        }
229        let round = libm::round(self);
230        if libm::fabs(self - round) != 0.5 {
231            return round;
232        }
233        match round % 2.0 {
234            1.0 => libm::floor(self),
235            -1.0 => libm::ceil(self),
236            _ => round,
237        }
238    }
239    #[inline]
240    fn wasm_maximum(self, other: f64) -> f64 {
241        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
242        // stabilized
243        if self > other {
244            self
245        } else if other > self {
246            other
247        } else if self == other {
248            if self.is_sign_positive() && other.is_sign_negative() {
249                self
250            } else {
251                other
252            }
253        } else {
254            self + other
255        }
256    }
257    #[inline]
258    fn wasm_minimum(self, other: f64) -> f64 {
259        // FIXME: replace this with `self.minimum(other)` when
260        // rust-lang/rust#91079 is stabilized
261        if self < other {
262            self
263        } else if other < self {
264            other
265        } else if self == other {
266            if self.is_sign_negative() && other.is_sign_positive() {
267                self
268            } else {
269                other
270            }
271        } else {
272            self + other
273        }
274    }
275    #[inline]
276    fn wasm_mul_add(self, b: f64, c: f64) -> f64 {
277        // The MinGW implementation of `fma` differs from other platforms, so
278        // favor `libm` there instead.
279        #[cfg(feature = "std")]
280        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
281            return self.mul_add(b, c);
282        }
283        libm::fma(self, b, c)
284    }
285}