github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/moremath/moremath.go (about) 1 package moremath 2 3 import ( 4 "math" 5 ) 6 7 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/values.html#floating-point 8 const ( 9 // F32CanonicalNaNBits is the 32-bit float where payload's MSB equals 1 and others are all zero. 10 F32CanonicalNaNBits = uint32(0x7fc0_0000) 11 // F32CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F32CanonicalNaNBitsMask == F32CanonicalNaNBits" 12 F32CanonicalNaNBitsMask = uint32(0x7fff_ffff) 13 // F64CanonicalNaNBits is the 64-bit float where payload's MSB equals 1 and others are all zero. 14 F64CanonicalNaNBits = uint64(0x7ff8_0000_0000_0000) 15 // F64CanonicalNaNBitsMask can be used to judge the value `v` is canonical nan as "v&F64CanonicalNaNBitsMask == F64CanonicalNaNBits" 16 F64CanonicalNaNBitsMask = uint64(0x7fff_ffff_ffff_ffff) 17 // F32ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 32-bit arithmetic NaN values 18 F32ArithmeticNaNPayloadMSB = uint32(0x0040_0000) 19 // F32ExponentMask is used to extract the exponent of 32-bit floating point. 20 F32ExponentMask = uint32(0x7f80_0000) 21 // F32ArithmeticNaNBits is an example 32-bit arithmetic NaN. 22 F32ArithmeticNaNBits = F32CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN. 23 // F64ArithmeticNaNPayloadMSB is used to extract the most significant bit of payload of 64-bit arithmetic NaN values 24 F64ArithmeticNaNPayloadMSB = uint64(0x0008_0000_0000_0000) 25 // F64ExponentMask is used to extract the exponent of 64-bit floating point. 26 F64ExponentMask = uint64(0x7ff0_0000_0000_0000) 27 // F64ArithmeticNaNBits is an example 64-bit arithmetic NaN. 28 F64ArithmeticNaNBits = F64CanonicalNaNBits | 0b1 // Set first bit to make this different from the canonical NaN. 29 ) 30 31 // WasmCompatMin64 is the Wasm spec compatible variant of math.Min for 64-bit floating points. 32 func WasmCompatMin64(x, y float64) float64 { 33 switch { 34 case math.IsNaN(x) || math.IsNaN(y): 35 return returnF64NaNBinOp(x, y) 36 case math.IsInf(x, -1) || math.IsInf(y, -1): 37 return math.Inf(-1) 38 case x == 0 && x == y: 39 if math.Signbit(x) { 40 return x 41 } 42 return y 43 } 44 if x < y { 45 return x 46 } 47 return y 48 } 49 50 // WasmCompatMin32 is the Wasm spec compatible variant of math.Min for 32-bit floating points. 51 func WasmCompatMin32(x, y float32) float32 { 52 x64, y64 := float64(x), float64(y) 53 switch { 54 case math.IsNaN(x64) || math.IsNaN(y64): 55 return returnF32NaNBinOp(x, y) 56 case math.IsInf(x64, -1) || math.IsInf(y64, -1): 57 return float32(math.Inf(-1)) 58 case x == 0 && x == y: 59 if math.Signbit(x64) { 60 return x 61 } 62 return y 63 } 64 if x < y { 65 return x 66 } 67 return y 68 } 69 70 // WasmCompatMax64 is the Wasm spec compatible variant of math.Max for 64-bit floating points. 71 func WasmCompatMax64(x, y float64) float64 { 72 switch { 73 case math.IsNaN(x) || math.IsNaN(y): 74 return returnF64NaNBinOp(x, y) 75 case math.IsInf(x, 1) || math.IsInf(y, 1): 76 return math.Inf(1) 77 case x == 0 && x == y: 78 if math.Signbit(x) { 79 return y 80 } 81 return x 82 } 83 if x > y { 84 return x 85 } 86 return y 87 } 88 89 // WasmCompatMax32 is the Wasm spec compatible variant of math.Max for 32-bit floating points. 90 func WasmCompatMax32(x, y float32) float32 { 91 x64, y64 := float64(x), float64(y) 92 switch { 93 case math.IsNaN(x64) || math.IsNaN(y64): 94 return returnF32NaNBinOp(x, y) 95 case math.IsInf(x64, 1) || math.IsInf(y64, 1): 96 return float32(math.Inf(1)) 97 case x == 0 && x == y: 98 if math.Signbit(x64) { 99 return y 100 } 101 return x 102 } 103 if x > y { 104 return x 105 } 106 return y 107 } 108 109 // WasmCompatNearestF32 is the Wasm spec compatible variant of math.Round, used for Nearest instruction. 110 // For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic. 111 // 112 // e.g. math.Round(-4.5) results in -5 while this results in -4. 113 // 114 // See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic. 115 func WasmCompatNearestF32(f float32) float32 { 116 var res float32 117 // TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm 118 if f != 0 { 119 ceil := float32(math.Ceil(float64(f))) 120 floor := float32(math.Floor(float64(f))) 121 distToCeil := math.Abs(float64(f - ceil)) 122 distToFloor := math.Abs(float64(f - floor)) 123 h := ceil / 2.0 124 if distToCeil < distToFloor { 125 res = ceil 126 } else if distToCeil == distToFloor && float32(math.Floor(float64(h))) == h { 127 res = ceil 128 } else { 129 res = floor 130 } 131 } else { 132 res = f 133 } 134 return returnF32UniOp(f, res) 135 } 136 137 // WasmCompatNearestF64 is the Wasm spec compatible variant of math.Round, used for Nearest instruction. 138 // For example, this converts 1.9 to 2.0, and this has the semantics of LLVM's rint intrinsic. 139 // 140 // e.g. math.Round(-4.5) results in -5 while this results in -4. 141 // 142 // See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic. 143 func WasmCompatNearestF64(f float64) float64 { 144 // TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm 145 var res float64 146 if f != 0 { 147 ceil := math.Ceil(f) 148 floor := math.Floor(f) 149 distToCeil := math.Abs(f - ceil) 150 distToFloor := math.Abs(f - floor) 151 h := ceil / 2.0 152 if distToCeil < distToFloor { 153 res = ceil 154 } else if distToCeil == distToFloor && math.Floor(h) == h { 155 res = ceil 156 } else { 157 res = floor 158 } 159 } else { 160 res = f 161 } 162 return returnF64UniOp(f, res) 163 } 164 165 // WasmCompatCeilF32 is the same as math.Ceil on 32-bit except that 166 // the returned NaN value follows the Wasm specification on NaN 167 // propagation. 168 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 169 func WasmCompatCeilF32(f float32) float32 { 170 return returnF32UniOp(f, float32(math.Ceil(float64(f)))) 171 } 172 173 // WasmCompatCeilF64 is the same as math.Ceil on 64-bit except that 174 // the returned NaN value follows the Wasm specification on NaN 175 // propagation. 176 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 177 func WasmCompatCeilF64(f float64) float64 { 178 return returnF64UniOp(f, math.Ceil(f)) 179 } 180 181 // WasmCompatFloorF32 is the same as math.Floor on 32-bit except that 182 // the returned NaN value follows the Wasm specification on NaN 183 // propagation. 184 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 185 func WasmCompatFloorF32(f float32) float32 { 186 return returnF32UniOp(f, float32(math.Floor(float64(f)))) 187 } 188 189 // WasmCompatFloorF64 is the same as math.Floor on 64-bit except that 190 // the returned NaN value follows the Wasm specification on NaN 191 // propagation. 192 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 193 func WasmCompatFloorF64(f float64) float64 { 194 return returnF64UniOp(f, math.Floor(f)) 195 } 196 197 // WasmCompatTruncF32 is the same as math.Trunc on 32-bit except that 198 // the returned NaN value follows the Wasm specification on NaN 199 // propagation. 200 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 201 func WasmCompatTruncF32(f float32) float32 { 202 return returnF32UniOp(f, float32(math.Trunc(float64(f)))) 203 } 204 205 // WasmCompatTruncF64 is the same as math.Trunc on 64-bit except that 206 // the returned NaN value follows the Wasm specification on NaN 207 // propagation. 208 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 209 func WasmCompatTruncF64(f float64) float64 { 210 return returnF64UniOp(f, math.Trunc(f)) 211 } 212 213 func f32IsNaN(v float32) bool { 214 return v != v // this is how NaN is defined. 215 } 216 217 func f64IsNaN(v float64) bool { 218 return v != v // this is how NaN is defined. 219 } 220 221 // returnF32UniOp returns the result of 32-bit unary operation. This accepts `original` which is the operand, 222 // and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows 223 // the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling. 224 func returnF32UniOp(original, result float32) float32 { 225 // Following the same logic as in the reference interpreter: 226 // https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122 227 if !f32IsNaN(result) { 228 return result 229 } 230 if !f32IsNaN(original) { 231 return math.Float32frombits(F32CanonicalNaNBits) 232 } 233 return math.Float32frombits(math.Float32bits(original) | F32CanonicalNaNBits) 234 } 235 236 // returnF32UniOp returns the result of 64-bit unary operation. This accepts `original` which is the operand, 237 // and `result` which is its result. This returns the `result` as-is if the result is not NaN. Otherwise, this follows 238 // the same logic as in the reference interpreter as well as the amd64 and arm64 floating point handling. 239 func returnF64UniOp(original, result float64) float64 { 240 // Following the same logic as in the reference interpreter (== amd64 and arm64's behavior): 241 // https://github.com/WebAssembly/spec/blob/d48af683f5e6d00c13f775ab07d29a15daf92203/interpreter/exec/fxx.ml#L115-L122 242 if !f64IsNaN(result) { 243 return result 244 } 245 if !f64IsNaN(original) { 246 return math.Float64frombits(F64CanonicalNaNBits) 247 } 248 return math.Float64frombits(math.Float64bits(original) | F64CanonicalNaNBits) 249 } 250 251 // returnF64NaNBinOp returns a NaN for 64-bit binary operations. `x` and `y` are original floats 252 // and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation 253 // procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 254 func returnF64NaNBinOp(x, y float64) float64 { 255 if f64IsNaN(x) { 256 return math.Float64frombits(math.Float64bits(x) | F64CanonicalNaNBits) 257 } else { 258 return math.Float64frombits(math.Float64bits(y) | F64CanonicalNaNBits) 259 } 260 } 261 262 // returnF64NaNBinOp returns a NaN for 32-bit binary operations. `x` and `y` are original floats 263 // and at least one of them is NaN. The returned NaN is guaranteed to comply with the NaN propagation 264 // procedure: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/numerics.html#nan-propagation 265 func returnF32NaNBinOp(x, y float32) float32 { 266 if f32IsNaN(x) { 267 return math.Float32frombits(math.Float32bits(x) | F32CanonicalNaNBits) 268 } else { 269 return math.Float32frombits(math.Float32bits(y) | F32CanonicalNaNBits) 270 } 271 }