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