Skip to main content

wasmtime_internal_core/
math.rs

1//! A minimal module for implementing float-related operations for
2//! WebAssembly in terms of the native platform primitives.
3//!
4//! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime
5//! > project and is not intended for general use. APIs are not strictly
6//! > reviewed for safety and usage outside of Wasmtime may have bugs. If
7//! > you're interested in using this feel free to file an issue on the
8//! > Wasmtime repository to start a discussion about doing so, but otherwise
9//! > be aware that your usage of this crate is not supported.
10//!
11//! This crate is intended to assist with solving the portability issues such
12//! as:
13//!
14//! * Functions like `f32::trunc` are not available in `#![no_std]` targets.
15//! * The `f32::trunc` function is likely faster than the `libm` fallback.
16//! * Behavior of `f32::trunc` differs across platforms, for example it's
17//!   different on Windows and glibc on Linux. Additionally riscv64's
18//!   implementation of `libm` seems to have different NaN behavior than other
19//!   platforms.
20//! * Some wasm functions are in the Rust standard library, but not stable yet.
21//!
22//! There are a few locations throughout the codebase that these functions are
23//! needed so they're implemented only in a single location here rather than
24//! multiple.
25
26#![allow(missing_docs, reason = "self-describing methods")]
27
28/// Returns the bounds for guarding a trapping f32-to-int conversion.
29///
30/// This function will return two floats, a lower bound and an upper bound,
31/// which can be used to test whether a WebAssembly f32-to-int conversion
32/// should trap. The float being converted must be greater than the lower bound
33/// and less than the upper bound for the conversion to proceed, otherwise a
34/// trap or infinity value should be generated.
35///
36/// The `signed` argument indicates whether a conversion to a signed integer is
37/// happening. If `false` a conversion to an unsigned integer is happening. The
38/// `out_bits` argument indicates how many bits are in the integer being
39/// converted to.
40pub const fn f32_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f32, f32) {
41    match (signed, out_bits) {
42        (true, 8) => (i8::min_value() as f32 - 1., i8::max_value() as f32 + 1.),
43        (true, 16) => (i16::min_value() as f32 - 1., i16::max_value() as f32 + 1.),
44        (true, 32) => (-2147483904.0, 2147483648.0),
45        (true, 64) => (-9223373136366403584.0, 9223372036854775808.0),
46        (false, 8) => (-1., u8::max_value() as f32 + 1.),
47        (false, 16) => (-1., u16::max_value() as f32 + 1.),
48        (false, 32) => (-1., 4294967296.0),
49        (false, 64) => (-1., 18446744073709551616.0),
50        _ => unreachable!(),
51    }
52}
53
54/// Same as [`f32_cvt_to_int_bounds`] but used for f64-to-int conversions.
55pub const fn f64_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f64, f64) {
56    match (signed, out_bits) {
57        (true, 8) => (i8::min_value() as f64 - 1., i8::max_value() as f64 + 1.),
58        (true, 16) => (i16::min_value() as f64 - 1., i16::max_value() as f64 + 1.),
59        (true, 32) => (-2147483649.0, 2147483648.0),
60        (true, 64) => (-9223372036854777856.0, 9223372036854775808.0),
61        (false, 8) => (-1., u8::max_value() as f64 + 1.),
62        (false, 16) => (-1., u16::max_value() as f64 + 1.),
63        (false, 32) => (-1., 4294967296.0),
64        (false, 64) => (-1., 18446744073709551616.0),
65        _ => unreachable!(),
66    }
67}
68
69pub trait WasmFloat {
70    fn wasm_trunc(self) -> Self;
71    fn wasm_copysign(self, sign: Self) -> Self;
72    fn wasm_floor(self) -> Self;
73    fn wasm_ceil(self) -> Self;
74    fn wasm_sqrt(self) -> Self;
75    fn wasm_abs(self) -> Self;
76    fn wasm_nearest(self) -> Self;
77    fn wasm_minimum(self, other: Self) -> Self;
78    fn wasm_maximum(self, other: Self) -> Self;
79    fn wasm_mul_add(self, b: Self, c: Self) -> Self;
80}
81
82impl WasmFloat for f32 {
83    #[inline]
84    fn wasm_trunc(self) -> f32 {
85        if self.is_nan() {
86            return f32::NAN;
87        }
88        #[cfg(feature = "std")]
89        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
90            return self.trunc();
91        }
92        libm::truncf(self)
93    }
94    #[inline]
95    fn wasm_copysign(self, sign: f32) -> f32 {
96        #[cfg(feature = "std")]
97        if true {
98            return self.copysign(sign);
99        }
100        libm::copysignf(self, sign)
101    }
102    #[inline]
103    fn wasm_floor(self) -> f32 {
104        if self.is_nan() {
105            return f32::NAN;
106        }
107        #[cfg(feature = "std")]
108        if !cfg!(target_arch = "riscv64") {
109            return self.floor();
110        }
111        libm::floorf(self)
112    }
113    #[inline]
114    fn wasm_ceil(self) -> f32 {
115        if self.is_nan() {
116            return f32::NAN;
117        }
118        #[cfg(feature = "std")]
119        if !cfg!(target_arch = "riscv64") {
120            return self.ceil();
121        }
122        libm::ceilf(self)
123    }
124    #[inline]
125    fn wasm_sqrt(self) -> f32 {
126        #[cfg(feature = "std")]
127        if true {
128            return self.sqrt();
129        }
130        libm::sqrtf(self)
131    }
132    #[inline]
133    fn wasm_abs(self) -> f32 {
134        #[cfg(feature = "std")]
135        if true {
136            return self.abs();
137        }
138        libm::fabsf(self)
139    }
140    #[inline]
141    fn wasm_nearest(self) -> f32 {
142        if self.is_nan() {
143            return f32::NAN;
144        }
145        #[cfg(feature = "std")]
146        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
147            return self.round_ties_even();
148        }
149        let round = libm::roundf(self);
150        if libm::fabsf(self - round) != 0.5 {
151            return round;
152        }
153        match round % 2.0 {
154            1.0 => libm::floorf(self),
155            -1.0 => libm::ceilf(self),
156            _ => round,
157        }
158    }
159    #[inline]
160    fn wasm_maximum(self, other: f32) -> f32 {
161        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
162        // stabilized
163        if self > other {
164            self
165        } else if other > self {
166            other
167        } else if self == other {
168            if self.is_sign_positive() && other.is_sign_negative() {
169                self
170            } else {
171                other
172            }
173        } else {
174            self + other
175        }
176    }
177    #[inline]
178    fn wasm_minimum(self, other: f32) -> f32 {
179        // FIXME: replace this with `self.minimum(other)` when
180        // rust-lang/rust#91079 is stabilized
181        if self < other {
182            self
183        } else if other < self {
184            other
185        } else if self == other {
186            if self.is_sign_negative() && other.is_sign_positive() {
187                self
188            } else {
189                other
190            }
191        } else {
192            self + other
193        }
194    }
195    #[inline]
196    fn wasm_mul_add(self, b: f32, c: f32) -> f32 {
197        // The MinGW implementation of `fma` differs from other platforms, so
198        // favor `libm` there instead.
199        #[cfg(feature = "std")]
200        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
201            return self.mul_add(b, c);
202        }
203        libm::fmaf(self, b, c)
204    }
205}
206
207impl WasmFloat for f64 {
208    #[inline]
209    fn wasm_trunc(self) -> f64 {
210        if self.is_nan() {
211            return f64::NAN;
212        }
213        #[cfg(feature = "std")]
214        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
215            return self.trunc();
216        }
217        libm::trunc(self)
218    }
219    #[inline]
220    fn wasm_copysign(self, sign: f64) -> f64 {
221        #[cfg(feature = "std")]
222        if true {
223            return self.copysign(sign);
224        }
225        libm::copysign(self, sign)
226    }
227    #[inline]
228    fn wasm_floor(self) -> f64 {
229        if self.is_nan() {
230            return f64::NAN;
231        }
232        #[cfg(feature = "std")]
233        if !cfg!(target_arch = "riscv64") {
234            return self.floor();
235        }
236        libm::floor(self)
237    }
238    #[inline]
239    fn wasm_ceil(self) -> f64 {
240        if self.is_nan() {
241            return f64::NAN;
242        }
243        #[cfg(feature = "std")]
244        if !cfg!(target_arch = "riscv64") {
245            return self.ceil();
246        }
247        libm::ceil(self)
248    }
249    #[inline]
250    fn wasm_sqrt(self) -> f64 {
251        #[cfg(feature = "std")]
252        if true {
253            return self.sqrt();
254        }
255        libm::sqrt(self)
256    }
257    #[inline]
258    fn wasm_abs(self) -> f64 {
259        #[cfg(feature = "std")]
260        if true {
261            return self.abs();
262        }
263        libm::fabs(self)
264    }
265    #[inline]
266    fn wasm_nearest(self) -> f64 {
267        if self.is_nan() {
268            return f64::NAN;
269        }
270        #[cfg(feature = "std")]
271        if !cfg!(windows) && !cfg!(target_arch = "riscv64") {
272            return self.round_ties_even();
273        }
274        let round = libm::round(self);
275        if libm::fabs(self - round) != 0.5 {
276            return round;
277        }
278        match round % 2.0 {
279            1.0 => libm::floor(self),
280            -1.0 => libm::ceil(self),
281            _ => round,
282        }
283    }
284    #[inline]
285    fn wasm_maximum(self, other: f64) -> f64 {
286        // FIXME: replace this with `a.maximum(b)` when rust-lang/rust#91079 is
287        // stabilized
288        if self > other {
289            self
290        } else if other > self {
291            other
292        } else if self == other {
293            if self.is_sign_positive() && other.is_sign_negative() {
294                self
295            } else {
296                other
297            }
298        } else {
299            self + other
300        }
301    }
302    #[inline]
303    fn wasm_minimum(self, other: f64) -> f64 {
304        // FIXME: replace this with `self.minimum(other)` when
305        // rust-lang/rust#91079 is stabilized
306        if self < other {
307            self
308        } else if other < self {
309            other
310        } else if self == other {
311            if self.is_sign_negative() && other.is_sign_positive() {
312                self
313            } else {
314                other
315            }
316        } else {
317            self + other
318        }
319    }
320    #[inline]
321    fn wasm_mul_add(self, b: f64, c: f64) -> f64 {
322        // The MinGW implementation of `fma` differs from other platforms, so
323        // favor `libm` there instead.
324        #[cfg(feature = "std")]
325        if !(cfg!(windows) && cfg!(target_env = "gnu")) {
326            return self.mul_add(b, c);
327        }
328        libm::fma(self, b, c)
329    }
330}