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  }