xref: /wasmtime-44.0.1/crates/core/src/math.rs (revision 9acefdfe)
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.
f32_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f32, f32)40 pub 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.
f64_cvt_to_int_bounds(signed: bool, out_bits: u32) -> (f64, f64)55 pub 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 
69 pub trait WasmFloat {
wasm_trunc(self) -> Self70     fn wasm_trunc(self) -> Self;
wasm_copysign(self, sign: Self) -> Self71     fn wasm_copysign(self, sign: Self) -> Self;
wasm_floor(self) -> Self72     fn wasm_floor(self) -> Self;
wasm_ceil(self) -> Self73     fn wasm_ceil(self) -> Self;
wasm_sqrt(self) -> Self74     fn wasm_sqrt(self) -> Self;
wasm_abs(self) -> Self75     fn wasm_abs(self) -> Self;
wasm_nearest(self) -> Self76     fn wasm_nearest(self) -> Self;
wasm_minimum(self, other: Self) -> Self77     fn wasm_minimum(self, other: Self) -> Self;
wasm_maximum(self, other: Self) -> Self78     fn wasm_maximum(self, other: Self) -> Self;
wasm_mul_add(self, b: Self, c: Self) -> Self79     fn wasm_mul_add(self, b: Self, c: Self) -> Self;
80 }
81 
82 impl WasmFloat for f32 {
83     #[inline]
wasm_trunc(self) -> f3284     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]
wasm_copysign(self, sign: f32) -> f3295     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]
wasm_floor(self) -> f32103     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]
wasm_ceil(self) -> f32114     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]
wasm_sqrt(self) -> f32125     fn wasm_sqrt(self) -> f32 {
126         #[cfg(feature = "std")]
127         if true {
128             return self.sqrt();
129         }
130         libm::sqrtf(self)
131     }
132     #[inline]
wasm_abs(self) -> f32133     fn wasm_abs(self) -> f32 {
134         #[cfg(feature = "std")]
135         if true {
136             return self.abs();
137         }
138         libm::fabsf(self)
139     }
140     #[inline]
wasm_nearest(self) -> f32141     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]
wasm_maximum(self, other: f32) -> f32160     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]
wasm_minimum(self, other: f32) -> f32178     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]
wasm_mul_add(self, b: f32, c: f32) -> f32196     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 
207 impl WasmFloat for f64 {
208     #[inline]
wasm_trunc(self) -> f64209     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]
wasm_copysign(self, sign: f64) -> f64220     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]
wasm_floor(self) -> f64228     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]
wasm_ceil(self) -> f64239     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]
wasm_sqrt(self) -> f64250     fn wasm_sqrt(self) -> f64 {
251         #[cfg(feature = "std")]
252         if true {
253             return self.sqrt();
254         }
255         libm::sqrt(self)
256     }
257     #[inline]
wasm_abs(self) -> f64258     fn wasm_abs(self) -> f64 {
259         #[cfg(feature = "std")]
260         if true {
261             return self.abs();
262         }
263         libm::fabs(self)
264     }
265     #[inline]
wasm_nearest(self) -> f64266     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]
wasm_maximum(self, other: f64) -> f64285     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]
wasm_minimum(self, other: f64) -> f64303     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]
wasm_mul_add(self, b: f64, c: f64) -> f64321     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 }
331