moremath.go

  1package moremath
  2
  3import (
  4	"math"
  5)
  6
  7// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/values.html#floating-point
  8const (
  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.
 32func 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.
 51func 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.
 71func 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.
 90func 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.
115func 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.
143func 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
169func 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
177func 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
185func 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
193func 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
201func 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
209func WasmCompatTruncF64(f float64) float64 {
210	return returnF64UniOp(f, math.Trunc(f))
211}
212
213func f32IsNaN(v float32) bool {
214	return v != v // this is how NaN is defined.
215}
216
217func 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.
224func 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.
239func 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
254func 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
265func 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}