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