diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-07-13 12:36:23 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-07-13 12:36:23 +0900 |
| commit | 6a6d911dfd02c0db94e2593b3d1d4d9afff77204 (patch) | |
| tree | 9f33aadaf11659ca09bc1111dda8939a81fbd189 | |
| parent | fb3d9b5d9c5a21024091a19003c8c94d99690787 (diff) | |
| download | php-waddiwasi-6a6d911dfd02c0db94e2593b3d1d4d9afff77204.tar.gz php-waddiwasi-6a6d911dfd02c0db94e2593b3d1d4d9afff77204.tar.zst php-waddiwasi-6a6d911dfd02c0db94e2593b3d1d4d9afff77204.zip | |
refactor: move all numeirc operations from Runtime class to NumericOps
| -rw-r--r-- | src/WebAssembly/Execution/NumericOps.php | 1055 | ||||
| -rw-r--r-- | src/WebAssembly/Execution/Runtime.php | 1060 |
2 files changed, 1459 insertions, 656 deletions
diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php index 895b8c2..b5e8cb1 100644 --- a/src/WebAssembly/Execution/NumericOps.php +++ b/src/WebAssembly/Execution/NumericOps.php @@ -5,16 +5,32 @@ declare(strict_types=1); namespace Nsfisis\Waddiwasi\WebAssembly\Execution; use InvalidArgumentException; +use function abs; use function assert; use function bcadd; use function bccomp; use function bcmod; use function bcpow; use function bcsub; +use function bindec; +use function ceil; +use function decbin; use function fdiv; +use function floor; +use function intdiv; +use function is_infinite; +use function is_int; use function is_nan; +use function max; +use function min; use function pack; +use function round; +use function sqrt; +use function substr; use function unpack; +use const PHP_INT_MAX; +use const PHP_INT_MIN; +use const PHP_ROUND_HALF_EVEN; final readonly class NumericOps { @@ -22,6 +38,1045 @@ final readonly class NumericOps { } + public static function f32Abs(float $x): float + { + return abs($x); + } + + public static function f32Add(float $x, float $y): float + { + return self::truncateF64ToF32($x + $y); + } + + public static function f32Ceil(float $x): float + { + return ceil($x); + } + + public static function f32ConvertI32S(int $x): float + { + return self::truncateF64ToF32((float) $x); + } + + public static function f32ConvertI32U(int $x): float + { + $x = self::convertS32ToU32($x); + return self::truncateF64ToF32((float) $x); + } + + public static function f32ConvertI64S(int $x): float + { + return self::truncateF64ToF32((float) $x); + } + + public static function f32ConvertI64U(int $x): float + { + return self::truncateF64ToF32((float) $x); + } + + public static function f32CopySign(float $x, float $y): float + { + $xSign = self::getFloatSign($x); + $ySign = self::getFloatSign($y); + return $xSign === $ySign ? $x : -$x; + } + + public static function f32DemoteF64(float $x): float + { + return $x; + } + + public static function f32Div(float $x, float $y): float + { + return self::truncateF64ToF32(fdiv($x, $y)); + } + + public static function f32Eq(float $x, float $y): bool + { + return $x === $y; + } + + public static function f32Floor(float $x): float + { + return floor($x); + } + + public static function f32Ge(float $x, float $y): bool + { + return $x >= $y; + } + + public static function f32Gt(float $x, float $y): bool + { + return $x > $y; + } + + public static function f32Le(float $x, float $y): bool + { + return $x <= $y; + } + + public static function f32Lt(float $x, float $y): bool + { + return $x < $y; + } + + public static function f32Max(float $x, float $y): float + { + if (is_nan($x) || is_nan($y)) { + // PHP's standard max() handles NaNs in diffrent way than WebAssembly spec does. + return NAN; + } + return max($x, $y); + } + + public static function f32Min(float $x, float $y): float + { + if (is_nan($x) || is_nan($y)) { + // PHP's standard min() handles NaNs in diffrent way than WebAssembly spec does. + return NAN; + } + return min($x, $y); + } + + public static function f32Mul(float $x, float $y): float + { + return self::truncateF64ToF32($x * $y); + } + + public static function f32Ne(float $x, float $y): bool + { + return $x !== $y; + } + + public static function f32Nearest(float $x): float + { + return round($x, mode: PHP_ROUND_HALF_EVEN); + } + + public static function f32Neg(float $x): float + { + return -$x; + } + + public static function f32ReinterpretI32(int $x): float + { + return self::reinterpretI32AsF32($x); + } + + public static function f32ReinterpretI64(int $x): float + { + return self::reinterpretI64AsF32($x); + } + + public static function f32Sqrt(float $x): float + { + return self::truncateF64ToF32(sqrt($x)); + } + + public static function f32Sub(float $x, float $y): float + { + return self::truncateF64ToF32($x - $y); + } + + public static function f32Trunc(float $x): float + { + if ($x < 0) { + return self::truncateF64ToF32(ceil($x)); + } else { + return self::truncateF64ToF32(floor($x)); + } + } + + public static function f64Abs(float $x): float + { + return abs($x); + } + + public static function f64Add(float $x, float $y): float + { + return $x + $y; + } + + public static function f64Ceil(float $x): float + { + return ceil($x); + } + + public static function f64ConvertI32S(int $x): float + { + return (float) $x; + } + + public static function f64ConvertI32U(int $x): float + { + return (float) $x; + } + + public static function f64ConvertI64S(int $x): float + { + return (float) $x; + } + + public static function f64ConvertI64U(int $x): float + { + return (float) $x; + } + + public static function f64CopySign(float $x, float $y): float + { + $xSign = self::getFloatSign($x); + $ySign = self::getFloatSign($y); + return $xSign === $ySign ? $x : -$x; + } + + public static function f64Div(float $x, float $y): float + { + return fdiv($x, $y); + } + + public static function f64Eq(float $x, float $y): bool + { + return $x === $y; + } + + public static function f64Floor(float $x): float + { + return floor($x); + } + + public static function f64Ge(float $x, float $y): bool + { + return $x >= $y; + } + + public static function f64Gt(float $x, float $y): bool + { + return $x > $y; + } + + public static function f64Le(float $x, float $y): bool + { + return $x <= $y; + } + + public static function f64Lt(float $x, float $y): bool + { + return $x < $y; + } + + public static function f64Max(float $x, float $y): float + { + if (is_nan($x) || is_nan($y)) { + // PHP's standard max() handles NaNs in diffrent way than WebAssembly spec does. + return NAN; + } + return max($x, $y); + } + + public static function f64Min(float $x, float $y): float + { + if (is_nan($x) || is_nan($y)) { + // PHP's standard min() handles NaNs in diffrent way than WebAssembly spec does. + return NAN; + } + return min($x, $y); + } + + public static function f64Mul(float $x, float $y): float + { + return $x * $y; + } + + public static function f64Ne(float $x, float $y): bool + { + return $x !== $y; + } + + public static function f64Nearest(float $x): float + { + return round($x, mode: PHP_ROUND_HALF_EVEN); + } + + public static function f64Neg(float $x): float + { + return -$x; + } + + public static function f64PromoteF32(float $x): float + { + return $x; + } + + public static function f64ReinterpretI32(int $x): float + { + return self::reinterpretI32AsF64($x); + } + + public static function f64ReinterpretI64(int $x): float + { + return self::reinterpretI64AsF64($x); + } + + public static function f64Sqrt(float $x): float + { + return sqrt($x); + } + + public static function f64Sub(float $x, float $y): float + { + return $x - $y; + } + + public static function f64Trunc(float $x): float + { + if ($x < 0) { + return ceil($x); + } else { + return floor($x); + } + } + + public static function i32Add(int $x, int $y): int + { + return self::convertU32ToS32(($x + $y) & 0xFFFFFFFF); + } + + public static function i32And(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return self::convertU32ToS32(($x & $y) & 0xFFFFFFFF); + } + + public static function i32Clz(int $x): int + { + $x = self::convertS32ToU32($x); + $zeros = 0; + for ($i = 31; 0 <= $i; $i--) { + if (($x & (1 << $i)) === 0) { + $zeros++; + } else { + break; + } + } + return $zeros; + } + + public static function i32Ctz(int $x): int + { + $x = self::convertS32ToU32($x); + $zeros = 0; + for ($i = 0; $i < 32; $i++) { + if (($x & (1 << $i)) === 0) { + $zeros++; + } else { + break; + } + } + return $zeros; + } + + public static function i32DivS(int $x, int $y): int|TrapKind + { + if ($y === 0) { + return TrapKind::DivideByZero; + } + if ($x === -2147483648 && $y === -1) { + return TrapKind::IntegerOverflow; + } + return intdiv($x, $y); + } + + public static function i32DivU(int $x, int $y): int|TrapKind + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + if ($y === 0) { + return TrapKind::DivideByZero; + } + return self::convertU32ToS32(intdiv($x, $y)); + } + + public static function i32Eq(int $x, int $y): bool + { + return $x === $y; + } + + public static function i32Eqz(int $x): bool + { + return $x === 0; + } + + public static function i32Extend16S(int $x): int + { + $x = self::convertS32ToU32($x); + $result = unpack('s', pack('S', $x & 0xFFFF)); + assert($result !== false); + return $result[1]; + } + + public static function i32Extend8S(int $x): int + { + $x = self::convertS32ToU32($x); + $result = unpack('c', pack('C', $x & 0xFF)); + assert($result !== false); + return $result[1]; + } + + public static function i32GeS(int $x, int $y): bool + { + return $x >= $y; + } + + public static function i32GeU(int $x, int $y): bool + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return $x >= $y; + } + + public static function i32GtS(int $x, int $y): bool + { + return $x > $y; + } + + public static function i32GtU(int $x, int $y): bool + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return $x > $y; + } + + public static function i32LeS(int $x, int $y): bool + { + return $x <= $y; + } + + public static function i32LeU(int $x, int $y): bool + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return $x <= $y; + } + + public static function i32LtS(int $x, int $y): bool + { + return $x < $y; + } + + public static function i32LtU(int $x, int $y): bool + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return $x < $y; + } + + public static function i32Mul(int $x, int $y): int + { + return self::convertU32ToS32(($x * $y) & 0xFFFFFFFF); + } + + public static function i32Ne(int $x, int $y): bool + { + return $x !== $y; + } + + public static function i32Or(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return self::convertU32ToS32(($x | $y) & 0xFFFFFFFF); + } + + public static function i32Popcnt(int $x): int + { + $x = self::convertS32ToU32($x); + $ones = 0; + for ($i = 0; $i < 32; $i++) { + if (($x & (1 << $i)) !== 0) { + $ones++; + } + } + return $ones; + } + + public static function i32ReinterpretF32(float $x): int + { + return self::reinterpretF32AsI32($x); + } + + public static function i32ReinterpretF64(float $x): int + { + return self::reinterpretF64AsI32($x); + } + + public static function i32RemS(int $x, int $y): int|TrapKind + { + if ($y === 0) { + return TrapKind::DivideByZero; + } + return $x % $y; + } + + public static function i32RemU(int $x, int $y): int|TrapKind + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + if ($y === 0) { + return TrapKind::DivideByZero; + } + return self::convertU32ToS32($x % $y); + } + + public static function i32RotL(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + $k = $y % 32; + return self::convertU32ToS32((($x << $k) | ($x >> (32 - $k))) & 0xFFFFFFFF); + } + + public static function i32RotR(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + $k = $y % 32; + return self::convertU32ToS32((($x >> $k) | ($x << (32 - $k))) & 0xFFFFFFFF); + } + + public static function i32Shl(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $k = $y % 32; + return self::convertU32ToS32(($x << $k) & 0xFFFFFFFF); + } + + public static function i32ShrS(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + $k = $y % 32; + $signed = $x & 0x80000000; + if ($signed !== 0) { + $result = $x; + for ($i = 0; $i < $k; $i++) { + $result = ($result >> 1) | 0x80000000; + } + return self::convertU32ToS32($result); + } else { + return $x >> $k; + } + } + + public static function i32ShrU(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + $k = $y % 32; + return self::convertU32ToS32($x >> $k); + } + + public static function i32Sub(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + $yNeg = ((~$y & 0xFFFFFFFF) + 1) & 0xFFFFFFFF; + return self::convertU32ToS32(($x + $yNeg) & 0xFFFFFFFF); + } + + public static function i32TruncF32S(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -2147483649.0 || 2147483648.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i32TruncF32U(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -1.0 || 4294967296.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return self::convertU32ToS32((int) $x); + } + + public static function i32TruncF64S(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -2147483649.0 || 2147483648.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i32TruncF64U(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -1.0 || 4294967296.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return self::convertU32ToS32((int) $x); + } + + public static function i32TruncSatF32S(float $x): int + { + if ($x < -2147483648.0) { + return -2147483648; + } elseif (2147483647.0 < $x) { + return 2147483647; + } else { + return (int) $x; + } + } + + public static function i32TruncSatF32U(float $x): int + { + if ($x < 0.0) { + return 0; + } elseif (4294967295.0 < $x) { + return 4294967295; + } else { + return (int) $x; + } + } + + public static function i32TruncSatF64S(float $x): int + { + if ($x < -2147483648.0) { + return -2147483648; + } elseif (2147483647.0 < $x) { + return 2147483647; + } else { + return (int) $x; + } + } + + public static function i32TruncSatF64U(float $x): int + { + if ($x < 0.0) { + return 0; + } elseif (4294967295.0 < $x) { + return 4294967295; + } else { + return (int) $x; + } + } + + public static function i32WrapI64(int $x): int + { + return self::convertU32ToS32($x & 0xFFFFFFFF); + } + + public static function i32Xor(int $x, int $y): int + { + $y = self::convertS32ToU32($y); + $x = self::convertS32ToU32($x); + return self::convertU32ToS32(($x ^ $y) & 0xFFFFFFFF); + } + + public static function i64Add(int $x, int $y): int + { + return self::bigIntToPhpInt(bcadd((string)$x, (string)$y)); + } + + public static function i64And(int $x, int $y): int + { + return $x & $y; + } + + public static function i64Clz(int $x): int + { + $zeros = 0; + for ($i = 63; 0 <= $i; $i--) { + if ($i === 63) { + if ($x < 0) { + break; + } else { + $zeros++; + } + } else { + if (($x & (1 << $i)) === 0) { + $zeros++; + } else { + break; + } + } + } + return $zeros; + } + + public static function i64Ctz(int $x): int + { + $zeros = 0; + for ($i = 0; $i < 64; $i++) { + if ($i === 63) { + if (0 <= $x) { + $zeros++; + } + } else { + if (($x & (1 << $i)) === 0) { + $zeros++; + } else { + break; + } + } + } + return $zeros; + } + + public static function i64DivS(int $x, int $y): int|TrapKind + { + if ($y === 0) { + return TrapKind::DivideByZero; + } + if ($x === PHP_INT_MIN && $y === -1) { + return TrapKind::IntegerOverflow; + } + return intdiv($x, $y); + } + + public static function i64DivU(int $x, int $y): int|TrapKind + { + $y = self::convertS64ToBigUInt($y); + $x = self::convertS64ToBigUInt($x); + if ($y === '0') { + return TrapKind::DivideByZero; + } + return self::bigIntToPhpInt(bcdiv($x, $y, 0)); + } + + public static function i64Eq(int $x, int $y): bool + { + return $x === $y; + } + + public static function i64Eqz(int $x): bool + { + return $x === 0; + } + + public static function i64Extend16S(int $x): int + { + $result = unpack('s', pack('S', $x & 0xFFFF)); + assert($result !== false); + return $result[1]; + } + + public static function i64Extend32S(int $x): int + { + $result = unpack('l', pack('L', $x & 0xFFFFFFFF)); + assert($result !== false); + return $result[1]; + } + + public static function i64Extend8S(int $x): int + { + $result = unpack('c', pack('C', $x & 0xFF)); + assert($result !== false); + return $result[1]; + } + + public static function i64ExtendI32S(int $x): int + { + return $x; + } + + public static function i64ExtendI32U(int $x): int + { + $x = self::convertS32ToU32($x); + return $x & 0xFFFFFFFF; + } + + public static function i64GeS(int $x, int $y): bool + { + return $x >= $y; + } + + public static function i64GeU(int $x, int $y): bool + { + $yPacked = pack('J', $y); + $xPacked = pack('J', $x); + return $xPacked >= $yPacked; + } + + public static function i64GtS(int $x, int $y): bool + { + return $x > $y; + } + + public static function i64GtU(int $x, int $y): bool + { + $yPacked = pack('J', $y); + $xPacked = pack('J', $x); + return $xPacked > $yPacked; + } + + public static function i64LeS(int $x, int $y): bool + { + return $x <= $y; + } + + public static function i64LeU(int $x, int $y): bool + { + $yPacked = pack('J', $y); + $xPacked = pack('J', $x); + return $xPacked <= $yPacked; + } + + public static function i64LtS(int $x, int $y): bool + { + return $x < $y; + } + + public static function i64LtU(int $x, int $y): bool + { + $yPacked = pack('J', $y); + $xPacked = pack('J', $x); + return $xPacked < $yPacked; + } + + public static function i64Mul(int $x, int $y): int + { + return self::bigIntToPhpInt(bcmul((string)$x, (string)$y)); + } + + public static function i64Ne(int $x, int $y): bool + { + return $x !== $y; + } + + public static function i64Or(int $x, int $y): int + { + return $x | $y; + } + + public static function i64Popcnt(int $x): int + { + $ones = 0; + for ($i = 0; $i < 64; $i++) { + if (($x & (1 << $i)) !== 0) { + $ones++; + } + } + return $ones; + } + + public static function i64ReinterpretF32(float $x): int + { + return self::reinterpretF32AsI64($x); + } + + public static function i64ReinterpretF64(float $x): int + { + return self::reinterpretF64AsI64($x); + } + + public static function i64RemS(int $x, int $y): int|TrapKind + { + if ($y === 0) { + return TrapKind::DivideByZero; + } + return $x % $y; + } + + public static function i64RemU(int $x, int $y): int|TrapKind + { + $y = self::convertS64ToBigUInt($y); + $x = self::convertS64ToBigUInt($x); + if ($y === '0') { + return TrapKind::DivideByZero; + } + return self::bigIntToPhpInt(bcmod($x, $y, 0)); + } + + public static function i64RotL(int $x, int $y): int + { + $y = self::convertS64ToBigUInt($y); + $k = (int)bcmod($y, '64'); + $left = $x << $k; + $right = $x; + for ($i = 0; $i < 64 - $k; $i++) { + $right = ($right >> 1) & 0x7FFFFFFFFFFFFFFF; + } + return $left | $right; + } + + public static function i64RotR(int $x, int $y): int + { + $y = self::convertS64ToBigUInt($y); + $k = (int)bcmod($y, '64'); + $left = $x; + for ($i = 0; $i < $k; $i++) { + $left = ($left >> 1) & 0x7FFFFFFFFFFFFFFF; + } + $right = $x << (64 - $k); + return $left | $right; + } + + public static function i64Shl(int $x, int $y): int + { + $y = self::convertS64ToBigUInt($y); + $k = (int)bcmod($y, '64'); + return $x << $k; + } + + public static function i64ShrS(int $x, int $y): int + { + $y = self::convertS64ToBigUInt($y); + $k = (int)bcmod($y, '64'); + return $x >> $k; + } + + public static function i64ShrU(int $x, int $y): int + { + $y = self::convertS64ToBigUInt($y); + $k = (int)bcmod($y, '64'); + if ($k === 0) { + return $x; + } + // Perform shr_u by string-based manipulation because PHP does not + // support shr_u operation. + $result = bindec(substr(decbin($x), 0, -$k)); + assert(is_int($result)); + return $result; + } + + public static function i64Sub(int $x, int $y): int + { + $result = self::bigIntToPhpInt(bcsub((string)$x, (string)$y)); + return $result; + } + + public static function i64TruncF32S(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -9223372036854775809.0 || 9223372036854775808.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i64TruncF32U(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -1.0 || 18446744073709551616.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i64TruncF64S(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -9223372036854775809.0 || 9223372036854775808.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i64TruncF64U(float $x): int|TrapKind + { + if (is_nan($x)) { + return TrapKind::InvalidConversionToInteger; + } + if (is_infinite($x)) { + return TrapKind::IntegerOverflow; + } + if ($x <= -1.0 || 18446744073709551616.0 <= $x) { + return TrapKind::IntegerOverflow; + } + return (int) $x; + } + + public static function i64TruncSatF32S(float $x): int + { + if ($x < -9223372036854775808.0) { + return PHP_INT_MIN; + } elseif (9223372036854775807.0 < $x) { + return PHP_INT_MAX; + } else { + return (int) $x; + } + } + + public static function i64TruncSatF32U(float $x): int + { + if ($x < 0.0) { + return 0; + } elseif (9223372036854775807.0 < $x) { + // @todo + return (int) $x; + } else { + return (int) $x; + } + } + + public static function i64TruncSatF64S(float $x): int + { + if ($x < -9223372036854775808.0) { + return PHP_INT_MIN; + } elseif (9223372036854775807.0 < $x) { + return PHP_INT_MAX; + } else { + return (int) $x; + } + } + + public static function i64TruncSatF64U(float $x): int + { + if ($x < 0.0) { + return 0; + } elseif (9223372036854775807.0 < $x) { + // @todo + return (int) $x; + } else { + return (int) $x; + } + } + + public static function i64Xor(int $x, int $y): int + { + return $x ^ $y; + } + public static function reinterpretI32AsF32(int $x): float { return self::deserializeF32FromBytes(self::serializeI32ToBytes($x)); diff --git a/src/WebAssembly/Execution/Runtime.php b/src/WebAssembly/Execution/Runtime.php index c4a78c8..f6dce48 100644 --- a/src/WebAssembly/Execution/Runtime.php +++ b/src/WebAssembly/Execution/Runtime.php @@ -16,28 +16,14 @@ use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\Limits; use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\TableType; use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\ValType; use RuntimeException; -use function abs; use function array_map; use function array_merge; use function array_reverse; use function array_slice; use function assert; -use function ceil; use function count; -use function fdiv; -use function floor; -use function intdiv; use function is_array; -use function is_infinite; use function is_int; -use function is_nan; -use function max; -use function min; -use function pack; -use function round; -use function sqrt; -use function unpack; -use const PHP_INT_MIN; final class Runtime implements ExporterInterface { @@ -573,21 +559,21 @@ final class Runtime implements ExporterInterface private function execInstrNumericF32Abs(Instrs\Numeric\F32Abs $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(abs($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Abs($x)); } private function execInstrNumericF32Add(Instrs\Numeric\F32Add $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 + $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Add($x, $y)); } private function execInstrNumericF32Ceil(Instrs\Numeric\F32Ceil $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(ceil($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Ceil($x)); } private function execInstrNumericF32Const(Instrs\Numeric\F32Const $instr): void @@ -597,193 +583,177 @@ final class Runtime implements ExporterInterface private function execInstrNumericF32ConvertI32S(Instrs\Numeric\F32ConvertI32S $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ConvertI32S($x)); } private function execInstrNumericF32ConvertI32U(Instrs\Numeric\F32ConvertI32U $instr): void { - $v = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ConvertI32U($x)); } private function execInstrNumericF32ConvertI64S(Instrs\Numeric\F32ConvertI64S $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ConvertI64S($x)); } private function execInstrNumericF32ConvertI64U(Instrs\Numeric\F32ConvertI64U $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ConvertI64U($x)); } private function execInstrNumericF32CopySign(Instrs\Numeric\F32CopySign $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $c1Sign = NumericOps::getFloatSign($c1); - $c2Sign = NumericOps::getFloatSign($c2); - $this->stack->pushValue($c1Sign === $c2Sign ? $c1 : -$c1); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32CopySign($x, $y)); } private function execInstrNumericF32DemoteF64(Instrs\Numeric\F32DemoteF64 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue($v); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32DemoteF64($x)); } private function execInstrNumericF32Div(Instrs\Numeric\F32Div $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::truncateF64ToF32(fdiv($c1, $c2))); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Div($x, $y)); } private function execInstrNumericF32Eq(Instrs\Numeric\F32Eq $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 === $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Eq($x, $y)); } private function execInstrNumericF32Floor(Instrs\Numeric\F32Floor $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(floor($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Floor($x)); } private function execInstrNumericF32Ge(Instrs\Numeric\F32Ge $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 >= $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Ge($x, $y)); } private function execInstrNumericF32Gt(Instrs\Numeric\F32Gt $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 > $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Gt($x, $y)); } private function execInstrNumericF32Le(Instrs\Numeric\F32Le $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 <= $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Le($x, $y)); } private function execInstrNumericF32Lt(Instrs\Numeric\F32Lt $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 < $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Lt($x, $y)); } private function execInstrNumericF32Max(Instrs\Numeric\F32Max $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - if (is_nan($c1) || is_nan($c2)) { - // PHP's standard max() handles NaNs in diffrent way than WebAssembly spec does. - $this->stack->pushValue(NAN); - return; - } - $this->stack->pushValue(max($c1, $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Max($x, $y)); } private function execInstrNumericF32Min(Instrs\Numeric\F32Min $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - if (is_nan($c1) || is_nan($c2)) { - // PHP's standard min() handles NaNs in diffrent way than WebAssembly spec does. - $this->stack->pushValue(NAN); - return; - } - $this->stack->pushValue(min($c1, $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Min($x, $y)); } private function execInstrNumericF32Mul(Instrs\Numeric\F32Mul $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 * $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Mul($x, $y)); } private function execInstrNumericF32Ne(Instrs\Numeric\F32Ne $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 !== $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f32Ne($x, $y)); } private function execInstrNumericF32Nearest(Instrs\Numeric\F32Nearest $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(round($v, mode: PHP_ROUND_HALF_EVEN)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Nearest($x)); } private function execInstrNumericF32Neg(Instrs\Numeric\F32Neg $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(-$c1); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Neg($x)); } private function execInstrNumericF32ReinterpretI32(Instrs\Numeric\F32ReinterpretI32 $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::reinterpretI32AsF32($v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ReinterpretI32($x)); } private function execInstrNumericF32ReinterpretI64(Instrs\Numeric\F32ReinterpretI64 $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::reinterpretI64AsF32($v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f32ReinterpretI64($x)); } private function execInstrNumericF32Sqrt(Instrs\Numeric\F32Sqrt $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::truncateF64ToF32(sqrt($c1))); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Sqrt($x)); } private function execInstrNumericF32Sub(Instrs\Numeric\F32Sub $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 - $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Sub($x, $y)); } private function execInstrNumericF32Trunc(Instrs\Numeric\F32Trunc $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0) { - $this->stack->pushValue(NumericOps::truncateF64ToF32(ceil($v))); - } else { - $this->stack->pushValue(NumericOps::truncateF64ToF32(floor($v))); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f32Trunc($x)); } private function execInstrNumericF64Abs(Instrs\Numeric\F64Abs $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(abs($c1)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Abs($x)); } private function execInstrNumericF64Add(Instrs\Numeric\F64Add $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue($c1 + $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Add($x, $y)); } private function execInstrNumericF64Ceil(Instrs\Numeric\F64Ceil $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(ceil($c1)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Ceil($x)); } private function execInstrNumericF64Const(Instrs\Numeric\F64Const $instr): void @@ -793,202 +763,178 @@ final class Runtime implements ExporterInterface private function execInstrNumericF64ConvertI32S(Instrs\Numeric\F64ConvertI32S $instr): void { - $c = $this->stack->popInt(); - $this->stack->pushValue((float) $c); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ConvertI32S($x)); } private function execInstrNumericF64ConvertI32U(Instrs\Numeric\F64ConvertI32U $instr): void { - $c = $this->stack->popInt(); - $this->stack->pushValue((float) $c); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ConvertI32U($x)); } private function execInstrNumericF64ConvertI64S(Instrs\Numeric\F64ConvertI64S $instr): void { - $c = $this->stack->popInt(); - $this->stack->pushValue((float) $c); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ConvertI64S($x)); } private function execInstrNumericF64ConvertI64U(Instrs\Numeric\F64ConvertI64U $instr): void { - $c = $this->stack->popInt(); - $this->stack->pushValue((float) $c); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ConvertI64U($x)); } private function execInstrNumericF64CopySign(Instrs\Numeric\F64CopySign $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $c1Sign = NumericOps::getFloatSign($c1); - $c2Sign = NumericOps::getFloatSign($c2); - $this->stack->pushValue($c1Sign === $c2Sign ? $c1 : -$c1); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64CopySign($x, $y)); } private function execInstrNumericF64Div(Instrs\Numeric\F64Div $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(fdiv($c1, $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Div($x, $y)); } private function execInstrNumericF64Eq(Instrs\Numeric\F64Eq $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 === $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Eq($x, $y)); } private function execInstrNumericF64Floor(Instrs\Numeric\F64Floor $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(floor($c1)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Floor($x)); } private function execInstrNumericF64Ge(Instrs\Numeric\F64Ge $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 >= $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Ge($x, $y)); } private function execInstrNumericF64Gt(Instrs\Numeric\F64Gt $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 > $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Gt($x, $y)); } private function execInstrNumericF64Le(Instrs\Numeric\F64Le $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 <= $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Le($x, $y)); } private function execInstrNumericF64Lt(Instrs\Numeric\F64Lt $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 < $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Lt($x, $y)); } private function execInstrNumericF64Max(Instrs\Numeric\F64Max $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - if (is_nan($c1) || is_nan($c2)) { - // PHP's standard max() handles NaNs in diffrent way than WebAssembly spec does. - $this->stack->pushValue(NAN); - return; - } - $this->stack->pushValue(max($c1, $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Max($x, $y)); } private function execInstrNumericF64Min(Instrs\Numeric\F64Min $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - if (is_nan($c1) || is_nan($c2)) { - // PHP's standard min() handles NaNs in diffrent way than WebAssembly spec does. - $this->stack->pushValue(NAN); - return; - } - $this->stack->pushValue(min($c1, $c2)); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Min($x, $y)); } private function execInstrNumericF64Mul(Instrs\Numeric\F64Mul $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue($c1 * $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Mul($x, $y)); } private function execInstrNumericF64Ne(Instrs\Numeric\F64Ne $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushBool($c1 !== $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushBool(NumericOps::f64Ne($x, $y)); } private function execInstrNumericF64Nearest(Instrs\Numeric\F64Nearest $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(round($v, mode: PHP_ROUND_HALF_EVEN)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Nearest($x)); } private function execInstrNumericF64Neg(Instrs\Numeric\F64Neg $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(-$c1); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Neg($x)); } private function execInstrNumericF64PromoteF32(Instrs\Numeric\F64PromoteF32 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue($v); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64PromoteF32($x)); } private function execInstrNumericF64ReinterpretI32(Instrs\Numeric\F64ReinterpretI32 $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::reinterpretI32AsF64($v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ReinterpretI32($x)); } private function execInstrNumericF64ReinterpretI64(Instrs\Numeric\F64ReinterpretI64 $instr): void { - $v = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::reinterpretI64AsF64($v)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::f64ReinterpretI64($x)); } private function execInstrNumericF64Sqrt(Instrs\Numeric\F64Sqrt $instr): void { - $c1 = $this->stack->popFloat(); - $this->stack->pushValue(sqrt($c1)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Sqrt($x)); } private function execInstrNumericF64Sub(Instrs\Numeric\F64Sub $instr): void { - $c2 = $this->stack->popFloat(); - $c1 = $this->stack->popFloat(); - $this->stack->pushValue($c1 - $c2); + $y = $this->stack->popFloat(); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Sub($x, $y)); } private function execInstrNumericF64Trunc(Instrs\Numeric\F64Trunc $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0) { - $this->stack->pushValue(ceil($v)); - } else { - $this->stack->pushValue(floor($v)); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::f64Trunc($x)); } private function execInstrNumericI32Add(Instrs\Numeric\I32Add $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 + $c2) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Add($x, $y)); } private function execInstrNumericI32And(Instrs\Numeric\I32And $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 & $c2) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32And($x, $y)); } private function execInstrNumericI32Clz(Instrs\Numeric\I32Clz $instr): void { - $i = NumericOps::convertS32ToU32($this->stack->popInt()); - $leadingZeros = 0; - for ($j = 31; 0 <= $j; $j--) { - if (($i & (1 << $j)) === 0) { - $leadingZeros++; - } else { - break; - } - } - $this->stack->pushValue($leadingZeros); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Clz($x)); } private function execInstrNumericI32Const(Instrs\Numeric\I32Const $instr): void @@ -998,406 +944,311 @@ final class Runtime implements ExporterInterface private function execInstrNumericI32Ctz(Instrs\Numeric\I32Ctz $instr): void { - $i = NumericOps::convertS32ToU32($this->stack->popInt()); - $trailingZeros = 0; - for ($j = 0; $j < 32; $j++) { - if (($i & (1 << $j)) === 0) { - $trailingZeros++; - } else { - break; - } - } - $this->stack->pushValue($trailingZeros); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Ctz($x)); } private function execInstrNumericI32DivS(Instrs\Numeric\I32DivS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - if ($c2 === 0) { - throw new TrapException("i32.div_s: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i32DivS($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if ($c1 === -2147483648 && $c2 === -1) { - throw new TrapException("i32.div_s: overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue(intdiv($c1, $c2)); + $this->stack->pushValue($result); } private function execInstrNumericI32DivU(Instrs\Numeric\I32DivU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - if ($c2 === 0) { - throw new TrapException("i32.div_u: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i32DivU($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue(NumericOps::convertU32ToS32(intdiv($c1, $c2))); + $this->stack->pushValue($result); } private function execInstrNumericI32Eq(Instrs\Numeric\I32Eq $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 === $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32Eq($x, $y)); } private function execInstrNumericI32Eqz(Instrs\Numeric\I32Eqz $instr): void { - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 === 0); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32Eqz($x)); } private function execInstrNumericI32Extend16S(Instrs\Numeric\I32Extend16S $instr): void { - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c2 = $c1 & 0xFFFF; - $result = unpack('s', pack('S', $c2)); - assert($result !== false); - $this->stack->pushValue($result[1]); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Extend16S($x)); } private function execInstrNumericI32Extend8S(Instrs\Numeric\I32Extend8S $instr): void { - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c2 = $c1 & 0xFF; - $result = unpack('c', pack('C', $c2)); - assert($result !== false); - $this->stack->pushValue($result[1]); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Extend8S($x)); } private function execInstrNumericI32GeS(Instrs\Numeric\I32GeS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 >= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32GeS($x, $y)); } private function execInstrNumericI32GeU(Instrs\Numeric\I32GeU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushBool($c1 >= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32GeU($x, $y)); } private function execInstrNumericI32GtS(Instrs\Numeric\I32GtS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 > $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32GtS($x, $y)); } private function execInstrNumericI32GtU(Instrs\Numeric\I32GtU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushBool($c1 > $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32GtU($x, $y)); } private function execInstrNumericI32LeS(Instrs\Numeric\I32LeS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 <= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32LeS($x, $y)); } private function execInstrNumericI32LeU(Instrs\Numeric\I32LeU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushBool($c1 <= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32LeU($x, $y)); } private function execInstrNumericI32LtS(Instrs\Numeric\I32LtS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 < $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32LtS($x, $y)); } private function execInstrNumericI32LtU(Instrs\Numeric\I32LtU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushBool($c1 < $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32LtU($x, $y)); } private function execInstrNumericI32Mul(Instrs\Numeric\I32Mul $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 * $c2) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Mul($x, $y)); } private function execInstrNumericI32Ne(Instrs\Numeric\I32Ne $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 !== $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i32Ne($x, $y)); } private function execInstrNumericI32Or(Instrs\Numeric\I32Or $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 | $c2) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Or($x, $y)); } private function execInstrNumericI32Popcnt(Instrs\Numeric\I32Popcnt $instr): void { - $i = NumericOps::convertS32ToU32($this->stack->popInt()); - $popcnt = 0; - for ($j = 0; $j < 32; $j++) { - if (($i & (1 << $j)) !== 0) { - $popcnt++; - } - } - $this->stack->pushValue($popcnt); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Popcnt($x)); } private function execInstrNumericI32ReinterpretF32(Instrs\Numeric\I32ReinterpretF32 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::reinterpretF32AsI32($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32ReinterpretF32($x)); } private function execInstrNumericI32ReinterpretF64(Instrs\Numeric\I32ReinterpretF64 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::reinterpretF64AsI32($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32ReinterpretF64($x)); } private function execInstrNumericI32RemS(Instrs\Numeric\I32RemS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - if ($c2 === 0) { - throw new TrapException("i32.rem_s: divide by zero or overflow", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i32RemS($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue($c1 % $c2); + $this->stack->pushValue($result); } private function execInstrNumericI32RemU(Instrs\Numeric\I32RemU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - if ($c2 === 0) { - throw new TrapException("i32.rem_u: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i32RemU($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue(NumericOps::convertU32ToS32($c1 % $c2)); + $this->stack->pushValue($result); } private function execInstrNumericI32RotL(Instrs\Numeric\I32RotL $instr): void { - $i2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $i1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $k = $i2 % 32; - $this->stack->pushValue(NumericOps::convertU32ToS32((($i1 << $k) | ($i1 >> (32 - $k))) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32RotL($x, $y)); } private function execInstrNumericI32RotR(Instrs\Numeric\I32RotR $instr): void { - $i2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $i1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $k = $i2 % 32; - $this->stack->pushValue(NumericOps::convertU32ToS32((($i1 >> $k) | ($i1 << (32 - $k))) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32RotR($x, $y)); } private function execInstrNumericI32Shl(Instrs\Numeric\I32Shl $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $k = $c2 % 32; - $c1 = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 << $k) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Shl($x, $y)); } private function execInstrNumericI32ShrS(Instrs\Numeric\I32ShrS $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $k = $c2 % 32; - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $signed = $c1 & 0x80000000; - if ($signed !== 0) { - $result = $c1; - for ($i = 0; $i < $k; $i++) { - $result = ($result >> 1) | 0x80000000; - } - $this->stack->pushValue(NumericOps::convertU32ToS32($result)); - } else { - $this->stack->pushValue($c1 >> $k); - } + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32ShrS($x, $y)); } private function execInstrNumericI32ShrU(Instrs\Numeric\I32ShrU $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $k = $c2 % 32; - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushValue(NumericOps::convertU32ToS32($c1 >> $k)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32ShrU($x, $y)); } private function execInstrNumericI32Sub(Instrs\Numeric\I32Sub $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c2Neg = ((~$c2 & 0xFFFFFFFF) + 1) & 0xFFFFFFFF; - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 + $c2Neg) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Sub($x, $y)); } private function execInstrNumericI32TruncF32S(Instrs\Numeric\I32TruncF32S $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); - } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + $x = $this->stack->popFloat(); + $result = NumericOps::i32TruncF32S($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if ($v <= -2147483649.0 || 2147483648.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI32TruncF32U(Instrs\Numeric\I32TruncF32U $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); - } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + $x = $this->stack->popFloat(); + $result = NumericOps::i32TruncF32U($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if ($v <= -1.0 || 4294967296.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue(NumericOps::convertU32ToS32((int) $v)); + $this->stack->pushValue($result); } private function execInstrNumericI32TruncF64S(Instrs\Numeric\I32TruncF64S $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); - } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + $x = $this->stack->popFloat(); + $result = NumericOps::i32TruncF64S($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if ($v <= -2147483649.0 || 2147483648.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI32TruncF64U(Instrs\Numeric\I32TruncF64U $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); - } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + $x = $this->stack->popFloat(); + $result = NumericOps::i32TruncF64U($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if ($v <= -1.0 || 4294967296.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue(NumericOps::convertU32ToS32((int) $v)); + $this->stack->pushValue($result); } private function execInstrNumericI32TruncSatF32S(Instrs\Numeric\I32TruncSatF32S $instr): void { - $v = $this->stack->popFloat(); - if ($v < -2147483648.0) { - $this->stack->pushValue(-2147483648); - } elseif ($v > 2147483647.0) { - $this->stack->pushValue(2147483647); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32TruncSatF32S($x)); } private function execInstrNumericI32TruncSatF32U(Instrs\Numeric\I32TruncSatF32U $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0.0) { - $this->stack->pushValue(0); - } elseif ($v > 4294967295.0) { - $this->stack->pushValue(4294967295); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32TruncSatF32U($x)); } private function execInstrNumericI32TruncSatF64S(Instrs\Numeric\I32TruncSatF64S $instr): void { - $v = $this->stack->popFloat(); - if ($v < -2147483648.0) { - $this->stack->pushValue(-2147483648); - } elseif ($v > 2147483647.0) { - $this->stack->pushValue(2147483647); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32TruncSatF64S($x)); } private function execInstrNumericI32TruncSatF64U(Instrs\Numeric\I32TruncSatF64U $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0.0) { - $this->stack->pushValue(0); - } elseif ($v > 4294967295.0) { - $this->stack->pushValue(4294967295); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i32TruncSatF64U($x)); } private function execInstrNumericI32WrapI64(Instrs\Numeric\I32WrapI64 $instr): void { - $c1 = $this->stack->popInt(); - $this->stack->pushValue(NumericOps::convertU32ToS32($c1 & 0xFFFFFFFF)); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32WrapI64($x)); } private function execInstrNumericI32Xor(Instrs\Numeric\I32Xor $instr): void { - $c2 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 ^ $c2) & 0xFFFFFFFF)); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i32Xor($x, $y)); } private function execInstrNumericI64Add(Instrs\Numeric\I64Add $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $result = NumericOps::bigIntToPhpInt(bcadd((string)$c1, (string)$c2)); - $this->stack->pushValue($result); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Add($x, $y)); } private function execInstrNumericI64And(Instrs\Numeric\I64And $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1 & $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64And($x, $y)); } private function execInstrNumericI64Clz(Instrs\Numeric\I64Clz $instr): void { - $i = $this->stack->popInt(); - $leadingZeros = 0; - for ($j = 63; 0 <= $j; $j--) { - if ($j === 63) { - if ($i < 0) { - break; - } else { - $leadingZeros++; - } - } else { - if (($i & (1 << $j)) === 0) { - $leadingZeros++; - } else { - break; - } - } - } - $this->stack->pushValue($leadingZeros); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Clz($x)); } private function execInstrNumericI64Const(Instrs\Numeric\I64Const $instr): void @@ -1407,406 +1258,303 @@ final class Runtime implements ExporterInterface private function execInstrNumericI64Ctz(Instrs\Numeric\I64Ctz $instr): void { - $i = $this->stack->popInt(); - $trailingZeros = 0; - for ($j = 0; $j < 64; $j++) { - if ($j === 63) { - if ($i >= 0) { - $trailingZeros++; - } - } else { - if (($i & (1 << $j)) === 0) { - $trailingZeros++; - } else { - break; - } - } - } - $this->stack->pushValue($trailingZeros); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Ctz($x)); } private function execInstrNumericI64DivS(Instrs\Numeric\I64DivS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - if ($c2 === 0) { - throw new TrapException("i64.div_s: divide by zero", trapKind: TrapKind::DivideByZero); - } - if ($c1 === PHP_INT_MIN && $c2 === -1) { - throw new TrapException("i64.div_s: overflow", trapKind: TrapKind::IntegerOverflow); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i64DivS($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue(intdiv($c1, $c2)); + $this->stack->pushValue($result); } private function execInstrNumericI64DivU(Instrs\Numeric\I64DivU $instr): void { - $c2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $c1 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - if ($c2 === '0') { - throw new TrapException("i64.div_u: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i64DivU($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue(NumericOps::bigIntToPhpInt(bcdiv($c1, $c2, 0))); + $this->stack->pushValue($result); } private function execInstrNumericI64Eq(Instrs\Numeric\I64Eq $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 === $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64Eq($x, $y)); } private function execInstrNumericI64Eqz(Instrs\Numeric\I64Eqz $instr): void { - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 === 0); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64Eqz($x)); } private function execInstrNumericI64Extend16S(Instrs\Numeric\I64Extend16S $instr): void { - $c1 = $this->stack->popInt(); - $c2 = $c1 & 0xFFFF; - $result = unpack('s', pack('S', $c2)); - assert($result !== false); - $this->stack->pushValue($result[1]); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Extend16S($x)); } private function execInstrNumericI64Extend32S(Instrs\Numeric\I64Extend32S $instr): void { - $c1 = $this->stack->popInt(); - $c2 = $c1 & 0xFFFFFFFF; - $result = unpack('l', pack('L', $c2)); - assert($result !== false); - $this->stack->pushValue($result[1]); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Extend32S($x)); } private function execInstrNumericI64Extend8S(Instrs\Numeric\I64Extend8S $instr): void { - $c1 = $this->stack->popInt(); - $c2 = $c1 & 0xFF; - $result = unpack('c', pack('C', $c2)); - assert($result !== false); - $this->stack->pushValue($result[1]); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Extend8S($x)); } private function execInstrNumericI64ExtendI32S(Instrs\Numeric\I64ExtendI32S $instr): void { - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64ExtendI32S($x)); } private function execInstrNumericI64ExtendI32U(Instrs\Numeric\I64ExtendI32U $instr): void { - $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); - $c2 = $c1 & 0xFFFFFFFF; - $this->stack->pushValue($c2); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64ExtendI32U($x)); } private function execInstrNumericI64GeS(Instrs\Numeric\I64GeS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 >= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64GeS($x, $y)); } private function execInstrNumericI64GeU(Instrs\Numeric\I64GeU $instr): void { - $c2 = $this->stack->popInt(); - $c2Packed = pack('J', $c2); - $c1 = $this->stack->popInt(); - $c1Packed = pack('J', $c1); - $this->stack->pushBool($c1Packed >= $c2Packed); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64GeU($x, $y)); } private function execInstrNumericI64GtS(Instrs\Numeric\I64GtS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 > $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64GtS($x, $y)); } private function execInstrNumericI64GtU(Instrs\Numeric\I64GtU $instr): void { - $c2 = $this->stack->popInt(); - $c2Packed = pack('J', $c2); - $c1 = $this->stack->popInt(); - $c1Packed = pack('J', $c1); - $this->stack->pushBool($c1Packed > $c2Packed); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64GtU($x, $y)); } private function execInstrNumericI64LeS(Instrs\Numeric\I64LeS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 <= $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64LeS($x, $y)); } private function execInstrNumericI64LeU(Instrs\Numeric\I64LeU $instr): void { - $c2 = $this->stack->popInt(); - $c2Packed = pack('J', $c2); - $c1 = $this->stack->popInt(); - $c1Packed = pack('J', $c1); - $this->stack->pushBool($c1Packed <= $c2Packed); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64LeU($x, $y)); } private function execInstrNumericI64LtS(Instrs\Numeric\I64LtS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 < $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64LtS($x, $y)); } private function execInstrNumericI64LtU(Instrs\Numeric\I64LtU $instr): void { - $c2 = $this->stack->popInt(); - $c2Packed = pack('J', $c2); - $c1 = $this->stack->popInt(); - $c1Packed = pack('J', $c1); - $this->stack->pushBool($c1Packed < $c2Packed); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64LtU($x, $y)); } private function execInstrNumericI64Mul(Instrs\Numeric\I64Mul $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $result = NumericOps::bigIntToPhpInt(bcmul((string)$c1, (string)$c2)); - $this->stack->pushValue($result); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Mul($x, $y)); } private function execInstrNumericI64Ne(Instrs\Numeric\I64Ne $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushBool($c1 !== $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushBool(NumericOps::i64Ne($x, $y)); } private function execInstrNumericI64Or(Instrs\Numeric\I64Or $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1 | $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Or($x, $y)); } private function execInstrNumericI64Popcnt(Instrs\Numeric\I64Popcnt $instr): void { - $i = $this->stack->popInt(); - $popcnt = 0; - for ($j = 0; $j < 64; $j++) { - if (($i & (1 << $j)) !== 0) { - $popcnt++; - } - } - $this->stack->pushValue($popcnt); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Popcnt($x)); } private function execInstrNumericI64ReinterpretF32(Instrs\Numeric\I64ReinterpretF32 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::reinterpretF32AsI64($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64ReinterpretF32($x)); } private function execInstrNumericI64ReinterpretF64(Instrs\Numeric\I64ReinterpretF64 $instr): void { - $v = $this->stack->popFloat(); - $this->stack->pushValue(NumericOps::reinterpretF64AsI64($v)); + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64ReinterpretF64($x)); } private function execInstrNumericI64RemS(Instrs\Numeric\I64RemS $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - if ($c2 === 0) { - throw new TrapException("i64.rem_s: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i64RemS($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue($c1 % $c2); + $this->stack->pushValue($result); } private function execInstrNumericI64RemU(Instrs\Numeric\I64RemU $instr): void { - $c2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $c1 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - if ($c2 === '0') { - throw new TrapException("i64.rem_u: divide by zero", trapKind: TrapKind::DivideByZero); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $result = NumericOps::i64RemU($x, $y); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - $this->stack->pushValue(NumericOps::bigIntToPhpInt(bcmod($c1, $c2, 0))); + $this->stack->pushValue($result); } private function execInstrNumericI64RotL(Instrs\Numeric\I64RotL $instr): void { - $i2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $k = (int)bcmod($i2, '64'); - $i1 = $this->stack->popInt(); - $left = $i1 << $k; - $right = $i1; - for ($i = 0; $i < 64 - $k; $i++) { - $right = ($right >> 1) & 0x7FFFFFFFFFFFFFFF; - } - $this->stack->pushValue($left | $right); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64RotL($x, $y)); } private function execInstrNumericI64RotR(Instrs\Numeric\I64RotR $instr): void { - $i2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $k = (int)bcmod($i2, '64'); - $i1 = $this->stack->popInt(); - $left = $i1; - for ($i = 0; $i < $k; $i++) { - $left = ($left >> 1) & 0x7FFFFFFFFFFFFFFF; - } - $right = $i1 << (64 - $k); - $this->stack->pushValue($left | $right); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64RotR($x, $y)); } private function execInstrNumericI64Shl(Instrs\Numeric\I64Shl $instr): void { - $c2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $k = (int)bcmod($c2, '64'); - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1 << $k); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Shl($x, $y)); } private function execInstrNumericI64ShrS(Instrs\Numeric\I64ShrS $instr): void { - $c2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $k = (int)bcmod($c2, '64'); - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1 >> $k); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64ShrS($x, $y)); } private function execInstrNumericI64ShrU(Instrs\Numeric\I64ShrU $instr): void { - $c2 = NumericOps::convertS64ToBigUInt($this->stack->popInt()); - $k = (int)bcmod($c2, '64'); - if ($k === 0) { - return; - } - // Perform shr_u based on string manipulation because PHP does not - // support shr_u operation. - $c1 = $this->stack->popInt(); - $this->stack->pushValue(bindec(substr(decbin($c1), 0, -$k))); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64ShrU($x, $y)); } private function execInstrNumericI64Sub(Instrs\Numeric\I64Sub $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $result = NumericOps::bigIntToPhpInt(bcsub((string)$c1, (string)$c2)); - $this->stack->pushValue($result); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Sub($x, $y)); } private function execInstrNumericI64TruncF32S(Instrs\Numeric\I64TruncF32S $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion ($v)", trapKind: TrapKind::InvalidConversionToInteger); + $x = $this->stack->popFloat(); + $result = NumericOps::i64TruncF32S($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow ($v)", trapKind: TrapKind::IntegerOverflow); - } - if ($v <= -9223372036854775809.0 || 9223372036854775808.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow ($v)", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI64TruncF32U(Instrs\Numeric\I64TruncF32U $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); + $x = $this->stack->popFloat(); + $result = NumericOps::i64TruncF32U($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - if ($v <= -1.0 || 18446744073709551616.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI64TruncF64S(Instrs\Numeric\I64TruncF64S $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); + $x = $this->stack->popFloat(); + $result = NumericOps::i64TruncF64S($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - if ($v <= -9223372036854775809.0 || 9223372036854775808.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI64TruncF64U(Instrs\Numeric\I64TruncF64U $instr): void { - $v = $this->stack->popFloat(); - if (is_nan($v)) { - throw new TrapException($instr::opName() . ": invalid conversion", trapKind: TrapKind::InvalidConversionToInteger); + $x = $this->stack->popFloat(); + $result = NumericOps::i64TruncF64U($x); + if (!is_int($result)) { + throw new TrapException($instr::opName() . 'invalid operation', trapKind: $result); } - if (is_infinite($v)) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - if ($v <= -1.0 || 18446744073709551616.0 <= $v) { - throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); - } - $this->stack->pushValue((int) $v); + $this->stack->pushValue($result); } private function execInstrNumericI64TruncSatF32S(Instrs\Numeric\I64TruncSatF32S $instr): void { - $v = $this->stack->popFloat(); - if ($v < -9223372036854775808.0) { - $this->stack->pushValue(-9223372036854775808); - } elseif ($v > 9223372036854775807.0) { - $this->stack->pushValue(9223372036854775807); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64TruncSatF32S($x)); } private function execInstrNumericI64TruncSatF32U(Instrs\Numeric\I64TruncSatF32U $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0.0) { - $this->stack->pushValue(0); - } elseif ($v > 18446744073709551615.0) { - $this->stack->pushValue(18446744073709551615); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64TruncSatF32U($x)); } private function execInstrNumericI64TruncSatF64S(Instrs\Numeric\I64TruncSatF64S $instr): void { - $v = $this->stack->popFloat(); - if ($v < -9223372036854775808.0) { - $this->stack->pushValue(-9223372036854775808); - } elseif ($v > 9223372036854775807.0) { - $this->stack->pushValue(9223372036854775807); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64TruncSatF64S($x)); } private function execInstrNumericI64TruncSatF64U(Instrs\Numeric\I64TruncSatF64U $instr): void { - $v = $this->stack->popFloat(); - if ($v < 0.0) { - $this->stack->pushValue(0); - } elseif ($v > 18446744073709551615.0) { - $this->stack->pushValue(18446744073709551615); - } else { - $this->stack->pushValue((int) $v); - } + $x = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::i64TruncSatF64U($x)); } private function execInstrNumericI64Xor(Instrs\Numeric\I64Xor $instr): void { - $c2 = $this->stack->popInt(); - $c1 = $this->stack->popInt(); - $this->stack->pushValue($c1 ^ $c2); + $y = $this->stack->popInt(); + $x = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::i64Xor($x, $y)); } private function execInstrReferenceRefFunc(Instrs\Reference\RefFunc $instr): void |
