Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/core/src/math.rs
3071 views
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.
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.
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 {
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
82
impl 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
207
impl 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
}
331
332