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
24/// Returns the bounds for guarding a trapping f32-to-int conversion.
25///
26/// This function will return two floats, a lower bound and an upper bound,
27/// which can be used to test whether a WebAssembly f32-to-int conversion
28/// should trap. The float being converted must be greater than the lower bound
29/// and less than the upper bound for the conversion to proceed, otherwise a
30/// trap or infinity value should be generated.
31///
32/// The `signed` argument indicates whether a conversion to a signed integer is
33/// happening. If `false` a conversion to an unsigned integer is happening. The
34/// `out_bits` argument indicates how many bits are in the integer being
35/// converted to.
36pub const fn f32_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f32, f32) {
37    match (signed, out_bits) {
38        (true, 8) => (i8::min_value() as f32 - 1., i8::max_value() as f32 + 1.),
39        (true, 16) => (i16::min_value() as f32 - 1., i16::max_value() as f32 + 1.),
40        (true, 32) => (-2147483904.0, 2147483648.0),
41        (true, 64) => (-9223373136366403584.0, 9223372036854775808.0),
42        (false, 8) => (-1., u8::max_value() as f32 + 1.),
43        (false, 16) => (-1., u16::max_value() as f32 + 1.),
44        (false, 32) => (-1., 4294967296.0),
45        (false, 64) => (-1., 18446744073709551616.0),
46        _ => unreachable!(),
47    }
48}
49
50/// Same as [`f32_cvt_to_int_bounds`] but used for f64-to-int conversions.
51pub const fn f64_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f64, f64) {
52    match (signed, out_bits) {
53        (true, 8) => (i8::min_value() as f64 - 1., i8::max_value() as f64 + 1.),
54        (true, 16) => (i16::min_value() as f64 - 1., i16::max_value() as f64 + 1.),
55        (true, 32) => (-2147483649.0, 2147483648.0),
56        (true, 64) => (-9223372036854777856.0, 9223372036854775808.0),
57        (false, 8) => (-1., u8::max_value() as f64 + 1.),
58        (false, 16) => (-1., u16::max_value() as f64 + 1.),
59        (false, 32) => (-1., 4294967296.0),
60        (false, 64) => (-1., 18446744073709551616.0),
61        _ => unreachable!(),
62    }
63}
64
65pub trait WasmFloat {
66    fn wasm_trunc(self) -> Self;
67    fn wasm_copysign(self, sign: Self) -> Self;
68    fn wasm_floor(self) -> Self;
69    fn wasm_ceil(self) -> Self;
70    fn wasm_sqrt(self) -> Self;
71    fn wasm_abs(self) -> Self;
72    fn wasm_nearest(self) -> Self;
73    fn wasm_minimum(self, other: Self) -> Self;
74    fn wasm_maximum(self, other: Self) -> Self;
75    fn wasm_mul_add(self, b: Self, c: Self) -> Self;
76}
77
78impl WasmFloat for f32 {
79    #[inline]
80    fn wasm_trunc(self) -> f32 {
81        if self.is_nan() {
82            return f32::NAN;
83        }
84        #[cfg(feature = "std")]
85        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
86            return self.trunc();
87        }
88        libm::truncf(self)
89    }
90    #[inline]
91    fn wasm_copysign(self, sign: f32) -> f32 {
92        #[cfg(feature = "std")]
93        if true {
94            return self.copysign(sign);
95        }
96        libm::copysignf(self, sign)
97    }
98    #[inline]
99    fn wasm_floor(self) -> f32 {
100        if self.is_nan() {
101            return f32::NAN;
102        }
103        #[cfg(feature = "std")]
104        if !cfg!(target_arch = "riscv64") {
105            return self.floor();
106        }
107        libm::floorf(self)
108    }
109    #[inline]
110    fn wasm_ceil(self) -> f32 {
111        if self.is_nan() {
112            return f32::NAN;
113        }
114        #[cfg(feature = "std")]
115        if !cfg!(target_arch = "riscv64") {
116            return self.ceil();
117        }
118        libm::ceilf(self)
119    }
120    #[inline]
121    fn wasm_sqrt(self) -> f32 {
122        #[cfg(feature = "std")]
123        if true {
124            return self.sqrt();
125        }
126        libm::sqrtf(self)
127    }
128    #[inline]
129    fn wasm_abs(self) -> f32 {
130        #[cfg(feature = "std")]
131        if true {
132            return self.abs();
133        }
134        libm::fabsf(self)
135    }
136    #[inline]
137    fn wasm_nearest(self) -> f32 {
138        if self.is_nan() {
139            return f32::NAN;
140        }
141        #[cfg(feature = "std")]
142        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
143            return self.round_ties_even();
144        }
145        let round = libm::roundf(self);
146        if libm::fabsf(self - round) != 0.5 {
147            return round;
148        }
149        match round % 2.0 {
150            1.0 => libm::floorf(self),
151            -1.0 => libm::ceilf(self),
152            _ => round,
153        }
154    }
155    #[inline]
156    fn wasm_maximum(self, other: f32) -> f32 {
157        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
158        // stabilized
159        if self > other {
160            self
161        } else if other > self {
162            other
163        } else if self == other {
164            if self.is_sign_positive() && other.is_sign_negative() {
165                self
166            } else {
167                other
168            }
169        } else {
170            self + other
171        }
172    }
173    #[inline]
174    fn wasm_minimum(self, other: f32) -> f32 {
175        // FIXME: replace this with `self.minimum(other)` when
176        // rust-lang/rust#91079 is stabilized
177        if self < other {
178            self
179        } else if other < self {
180            other
181        } else if self == other {
182            if self.is_sign_negative() && other.is_sign_positive() {
183                self
184            } else {
185                other
186            }
187        } else {
188            self + other
189        }
190    }
191    #[inline]
192    fn wasm_mul_add(self, b: f32, c: f32) -> f32 {
193        // The MinGW implementation of `fma` differs from other platforms, so
194        // favor `libm` there instead.
195        #[cfg(feature = "std")]
196        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
197            return self.mul_add(b, c);
198        }
199        libm::fmaf(self, b, c)
200    }
201}
202
203impl WasmFloat for f64 {
204    #[inline]
205    fn wasm_trunc(self) -> f64 {
206        if self.is_nan() {
207            return f64::NAN;
208        }
209        #[cfg(feature = "std")]
210        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
211            return self.trunc();
212        }
213        libm::trunc(self)
214    }
215    #[inline]
216    fn wasm_copysign(self, sign: f64) -> f64 {
217        #[cfg(feature = "std")]
218        if true {
219            return self.copysign(sign);
220        }
221        libm::copysign(self, sign)
222    }
223    #[inline]
224    fn wasm_floor(self) -> f64 {
225        if self.is_nan() {
226            return f64::NAN;
227        }
228        #[cfg(feature = "std")]
229        if !cfg!(target_arch = "riscv64") {
230            return self.floor();
231        }
232        libm::floor(self)
233    }
234    #[inline]
235    fn wasm_ceil(self) -> f64 {
236        if self.is_nan() {
237            return f64::NAN;
238        }
239        #[cfg(feature = "std")]
240        if !cfg!(target_arch = "riscv64") {
241            return self.ceil();
242        }
243        libm::ceil(self)
244    }
245    #[inline]
246    fn wasm_sqrt(self) -> f64 {
247        #[cfg(feature = "std")]
248        if true {
249            return self.sqrt();
250        }
251        libm::sqrt(self)
252    }
253    #[inline]
254    fn wasm_abs(self) -> f64 {
255        #[cfg(feature = "std")]
256        if true {
257            return self.abs();
258        }
259        libm::fabs(self)
260    }
261    #[inline]
262    fn wasm_nearest(self) -> f64 {
263        if self.is_nan() {
264            return f64::NAN;
265        }
266        #[cfg(feature = "std")]
267        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
268            return self.round_ties_even();
269        }
270        let round = libm::round(self);
271        if libm::fabs(self - round) != 0.5 {
272            return round;
273        }
274        match round % 2.0 {
275            1.0 => libm::floor(self),
276            -1.0 => libm::ceil(self),
277            _ => round,
278        }
279    }
280    #[inline]
281    fn wasm_maximum(self, other: f64) -> f64 {
282        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
283        // stabilized
284        if self > other {
285            self
286        } else if other > self {
287            other
288        } else if self == other {
289            if self.is_sign_positive() && other.is_sign_negative() {
290                self
291            } else {
292                other
293            }
294        } else {
295            self + other
296        }
297    }
298    #[inline]
299    fn wasm_minimum(self, other: f64) -> f64 {
300        // FIXME: replace this with `self.minimum(other)` when
301        // rust-lang/rust#91079 is stabilized
302        if self < other {
303            self
304        } else if other < self {
305            other
306        } else if self == other {
307            if self.is_sign_negative() && other.is_sign_positive() {
308                self
309            } else {
310                other
311            }
312        } else {
313            self + other
314        }
315    }
316    #[inline]
317    fn wasm_mul_add(self, b: f64, c: f64) -> f64 {
318        // The MinGW implementation of `fma` differs from other platforms, so
319        // favor `libm` there instead.
320        #[cfg(feature = "std")]
321        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
322            return self.mul_add(b, c);
323        }
324        libm::fma(self, b, c)
325    }
326}