aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/WebAssembly
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-04-06 02:23:01 +0900
committernsfisis <nsfisis@gmail.com>2025-04-06 02:23:01 +0900
commitfa9ad79209d85b0677b00ca1d41d070105fec09f (patch)
tree95a81c0909e761e9cecb3cd7333201cb4ac62161 /src/WebAssembly
parenta0f17ee6807f9a0605261a11a8ba46c57a9849a0 (diff)
parentde116dceae7ea654df28caab3fd2f3aefdffe188 (diff)
downloadphp-waddiwasi-fa9ad79209d85b0677b00ca1d41d070105fec09f.tar.gz
php-waddiwasi-fa9ad79209d85b0677b00ca1d41d070105fec09f.tar.zst
php-waddiwasi-fa9ad79209d85b0677b00ca1d41d070105fec09f.zip
Merge branch 'fix/float-handling'
Diffstat (limited to 'src/WebAssembly')
-rw-r--r--src/WebAssembly/BinaryFormat/Decoder.php18
-rw-r--r--src/WebAssembly/Execution/MemInst.php29
-rw-r--r--src/WebAssembly/Execution/NumericOps.php231
3 files changed, 53 insertions, 225 deletions
diff --git a/src/WebAssembly/BinaryFormat/Decoder.php b/src/WebAssembly/BinaryFormat/Decoder.php
index e64fac8..f0a5c29 100644
--- a/src/WebAssembly/BinaryFormat/Decoder.php
+++ b/src/WebAssembly/BinaryFormat/Decoder.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Nsfisis\Waddiwasi\WebAssembly\BinaryFormat;
+use Nsfisis\Waddiwasi\BitOps\BinaryConversion;
use Nsfisis\Waddiwasi\Stream\StreamInterface;
use Nsfisis\Waddiwasi\Stream\UnexpectedEofException;
use Nsfisis\Waddiwasi\WebAssembly\BinaryFormat\Internal\Code;
@@ -39,7 +40,6 @@ use function assert;
use function count;
use function get_class;
use function in_array;
-use function is_float;
use function is_int;
use function ord;
use function sprintf;
@@ -1090,13 +1090,7 @@ final class Decoder
*/
private function decodeF32(): float
{
- $buf = $this->stream->read(4);
- $result = unpack('g', $buf);
- if ($result === false) {
- throw new InvalidBinaryFormatException("f32");
- }
- assert(isset($result[1]) && is_float($result[1]));
- return $result[1];
+ return BinaryConversion::deserializeF32($this->stream->read(4));
}
/**
@@ -1104,13 +1098,7 @@ final class Decoder
*/
private function decodeF64(): float
{
- $buf = $this->stream->read(8);
- $result = unpack('e', $buf);
- if ($result === false) {
- throw new InvalidBinaryFormatException("f64");
- }
- assert(isset($result[1]) && is_float($result[1]));
- return $result[1];
+ return BinaryConversion::deserializeF64($this->stream->read(8));
}
/**
diff --git a/src/WebAssembly/Execution/MemInst.php b/src/WebAssembly/Execution/MemInst.php
index 94d57cf..9ced0a3 100644
--- a/src/WebAssembly/Execution/MemInst.php
+++ b/src/WebAssembly/Execution/MemInst.php
@@ -44,11 +44,6 @@ final class MemInst
private CData $dataS64_6;
private CData $dataS64_7;
- private CData $dataF32_0;
- private CData $dataF32_1;
- private CData $dataF32_2;
- private CData $dataF32_3;
-
private CData $dataF64_0;
private CData $dataF64_1;
private CData $dataF64_2;
@@ -155,11 +150,6 @@ final class MemInst
$this->dataS64_6 = $castInt(64, true, 6);
$this->dataS64_7 = $castInt(64, true, 7);
- $this->dataF32_0 = $castFloat(32, 0);
- $this->dataF32_1 = $castFloat(32, 1);
- $this->dataF32_2 = $castFloat(32, 2);
- $this->dataF32_3 = $castFloat(32, 3);
-
$this->dataF64_0 = $castFloat(64, 0);
$this->dataF64_1 = $castFloat(64, 1);
$this->dataF64_2 = $castFloat(64, 2);
@@ -380,7 +370,10 @@ final class MemInst
if ($this->size() < $ptr + 4) {
return null;
}
- return $this->dataF32($ptr)[$ptr >> 2];
+ // f32 cannot be loaded directly from memory because PHP handles NaN
+ // differently than WebAssembly spec defines.
+ $i = $this->dataU32($ptr)[$ptr >> 2];
+ return NumericOps::f32ReinterpretI32($i);
}
/**
@@ -517,7 +510,9 @@ final class MemInst
if ($this->size() < $ptr + 4) {
return false;
}
- $this->dataF32($ptr)[$ptr >> 2] = $c;
+ // f32 cannot be stored directly in memory because PHP handles NaN
+ // differently than WebAssembly spec defines.
+ $this->dataU32($ptr)[$ptr >> 2] = NumericOps::i32ReinterpretF32($c);
return true;
}
@@ -578,16 +573,6 @@ final class MemInst
};
}
- private function dataF32(int $ptr): CData
- {
- return match ($ptr & 3) {
- 0 => $this->dataF32_0,
- 1 => $this->dataF32_1,
- 2 => $this->dataF32_2,
- 3 => $this->dataF32_3,
- };
- }
-
private function dataF64(int $ptr): CData
{
return match ($ptr & 7) {
diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php
index a24791f..2b48679 100644
--- a/src/WebAssembly/Execution/NumericOps.php
+++ b/src/WebAssembly/Execution/NumericOps.php
@@ -5,7 +5,8 @@ declare(strict_types=1);
namespace Nsfisis\Waddiwasi\WebAssembly\Execution;
use FFI;
-use InvalidArgumentException;
+use Nsfisis\Waddiwasi\BitOps\BinaryConversion;
+use Nsfisis\Waddiwasi\BitOps\FloatOps;
use RoundingMode;
use function abs;
use function assert;
@@ -25,12 +26,10 @@ use function is_int;
use function is_nan;
use function max;
use function min;
-use function pack;
use function round;
use function sprintf;
use function sqrt;
use function substr;
-use function unpack;
use const PHP_INT_MAX;
use const PHP_INT_MIN;
@@ -82,14 +81,18 @@ final readonly class NumericOps
public static function f32CopySign(float $x, float $y): float
{
- $xSign = self::getFloatSign($x);
- $ySign = self::getFloatSign($y);
+ $xSign = FloatOps::getSignedness($x);
+ $ySign = FloatOps::getSignedness($y);
return $xSign === $ySign ? $x : self::f32Neg($x);
}
public static function f32DemoteF64(float $x): float
{
- return self::truncateF64ToF32($x);
+ if (is_nan($x)) {
+ return NAN;
+ } else {
+ return self::truncateF64ToF32($x);
+ }
}
public static function f32Div(float $x, float $y): float
@@ -170,7 +173,7 @@ final readonly class NumericOps
{
if (is_nan($x)) {
// Negate operator does not work for NaN in PHP.
- return self::constructNan(-self::getFloatSign($x), $x);
+ return FloatOps::constructNan(FloatOps::getSignedness($x)->negated(), $x);
} else {
return -$x;
}
@@ -178,12 +181,12 @@ final readonly class NumericOps
public static function f32ReinterpretI32(int $x): float
{
- return self::reinterpretI32AsF32($x);
+ return BinaryConversion::reinterpretI32AsF32($x);
}
public static function f32ReinterpretI64(int $x): float
{
- return self::reinterpretI64AsF32($x);
+ return BinaryConversion::reinterpretI64AsF32($x);
}
public static function f32Sqrt(float $x): float
@@ -250,8 +253,8 @@ final readonly class NumericOps
public static function f64CopySign(float $x, float $y): float
{
- $xSign = self::getFloatSign($x);
- $ySign = self::getFloatSign($y);
+ $xSign = FloatOps::getSignedness($x);
+ $ySign = FloatOps::getSignedness($y);
return $xSign === $ySign ? $x : self::f64Neg($x);
}
@@ -333,7 +336,7 @@ final readonly class NumericOps
{
if (is_nan($x)) {
// Negate operator does not work for NaN in PHP.
- return self::constructNan(-self::getFloatSign($x), $x);
+ return FloatOps::constructNan(FloatOps::getSignedness($x)->negated(), $x);
} else {
return -$x;
}
@@ -341,17 +344,21 @@ final readonly class NumericOps
public static function f64PromoteF32(float $x): float
{
- return $x;
+ if (is_nan($x)) {
+ return NAN;
+ } else {
+ return $x;
+ }
}
public static function f64ReinterpretI32(int $x): float
{
- return self::reinterpretI32AsF64($x);
+ return BinaryConversion::reinterpretI32AsF64($x);
}
public static function f64ReinterpretI64(int $x): float
{
- return self::reinterpretI64AsF64($x);
+ return BinaryConversion::reinterpretI64AsF64($x);
}
public static function f64Sqrt(float $x): float
@@ -450,17 +457,13 @@ final readonly class NumericOps
public static function i32Extend16S(int $x): int
{
$x = self::convertS32ToU32($x);
- $result = unpack('s', pack('S', $x & 0xFFFF));
- assert($result !== false);
- return $result[1];
+ return BinaryConversion::deserializeS16(BinaryConversion::serializeI16($x & 0xFFFF));
}
public static function i32Extend8S(int $x): int
{
$x = self::convertS32ToU32($x);
- $result = unpack('c', pack('C', $x & 0xFF));
- assert($result !== false);
- return $result[1];
+ return BinaryConversion::deserializeS8(BinaryConversion::serializeI8($x & 0xFF));
}
public static function i32GeS(int $x, int $y): bool
@@ -542,12 +545,12 @@ final readonly class NumericOps
public static function i32ReinterpretF32(float $x): int
{
- return self::reinterpretF32AsI32($x);
+ return BinaryConversion::reinterpretF32AsI32($x);
}
public static function i32ReinterpretF64(float $x): int
{
- return self::reinterpretF64AsI32($x);
+ return BinaryConversion::reinterpretF64AsI32($x);
}
public static function i32RemS(int $x, int $y): int|TrapKind
@@ -831,23 +834,17 @@ final readonly class NumericOps
public static function i64Extend16S(int $x): int
{
- $result = unpack('s', pack('S', $x & 0xFFFF));
- assert($result !== false);
- return $result[1];
+ return BinaryConversion::deserializeS16(BinaryConversion::serializeI16($x & 0xFFFF));
}
public static function i64Extend32S(int $x): int
{
- $result = unpack('l', pack('L', $x & 0xFFFFFFFF));
- assert($result !== false);
- return $result[1];
+ return BinaryConversion::deserializeS32(BinaryConversion::serializeI32($x & 0xFFFFFFFF));
}
public static function i64Extend8S(int $x): int
{
- $result = unpack('c', pack('C', $x & 0xFF));
- assert($result !== false);
- return $result[1];
+ return BinaryConversion::deserializeS8(BinaryConversion::serializeI8($x & 0xFF));
}
public static function i64ExtendI32S(int $x): int
@@ -868,8 +865,8 @@ final readonly class NumericOps
public static function i64GeU(int $x, int $y): bool
{
- $yPacked = pack('J', $y);
- $xPacked = pack('J', $x);
+ $yPacked = BinaryConversion::serializeI64InBigEndian($y);
+ $xPacked = BinaryConversion::serializeI64InBigEndian($x);
return $xPacked >= $yPacked;
}
@@ -880,8 +877,8 @@ final readonly class NumericOps
public static function i64GtU(int $x, int $y): bool
{
- $yPacked = pack('J', $y);
- $xPacked = pack('J', $x);
+ $yPacked = BinaryConversion::serializeI64InBigEndian($y);
+ $xPacked = BinaryConversion::serializeI64InBigEndian($x);
return $xPacked > $yPacked;
}
@@ -892,8 +889,8 @@ final readonly class NumericOps
public static function i64LeU(int $x, int $y): bool
{
- $yPacked = pack('J', $y);
- $xPacked = pack('J', $x);
+ $yPacked = BinaryConversion::serializeI64InBigEndian($y);
+ $xPacked = BinaryConversion::serializeI64InBigEndian($x);
return $xPacked <= $yPacked;
}
@@ -904,8 +901,8 @@ final readonly class NumericOps
public static function i64LtU(int $x, int $y): bool
{
- $yPacked = pack('J', $y);
- $xPacked = pack('J', $x);
+ $yPacked = BinaryConversion::serializeI64InBigEndian($y);
+ $xPacked = BinaryConversion::serializeI64InBigEndian($x);
return $xPacked < $yPacked;
}
@@ -937,12 +934,12 @@ final readonly class NumericOps
public static function i64ReinterpretF32(float $x): int
{
- return self::reinterpretF32AsI64($x);
+ return BinaryConversion::reinterpretF32AsI64($x);
}
public static function i64ReinterpretF64(float $x): int
{
- return self::reinterpretF64AsI64($x);
+ return BinaryConversion::reinterpretF64AsI64($x);
}
public static function i64RemS(int $x, int $y): int|TrapKind
@@ -1142,116 +1139,17 @@ final readonly class NumericOps
return $x ^ $y;
}
- public static function reinterpretI32AsF32(int $x): float
- {
- return self::deserializeF32FromBytes(self::serializeI32ToBytes($x));
- }
-
- public static function reinterpretI64AsF32(int $x): float
- {
- return self::deserializeF32FromBytes(self::serializeI64ToBytes($x));
- }
-
- public static function reinterpretI32AsF64(int $x): float
- {
- return self::deserializeF64FromBytes(self::serializeI32ToBytes($x));
- }
-
- public static function reinterpretI64AsF64(int $x): float
- {
- return self::deserializeF64FromBytes(self::serializeI64ToBytes($x));
- }
-
- public static function reinterpretF32AsI32(float $x): int
- {
- return self::deserializeI32FromBytes(self::serializeF32ToBytes($x));
- }
-
- public static function reinterpretF64AsI32(float $x): int
- {
- return self::deserializeI32FromBytes(self::serializeF64ToBytes($x));
- }
-
- public static function reinterpretF32AsI64(float $x): int
- {
- return self::deserializeI64FromBytes(self::serializeF32ToBytes($x));
- }
-
- public static function reinterpretF64AsI64(float $x): int
- {
- return self::deserializeI64FromBytes(self::serializeF64ToBytes($x));
- }
-
public static function truncateF64ToF32(float $x): float
{
- return self::deserializeF32FromBytes(self::serializeF32ToBytes($x));
+ return BinaryConversion::deserializeF32(BinaryConversion::serializeF32($x));
}
- public static function serializeI32ToBytes(int $x): string
- {
- return pack('l', $x);
- }
-
- public static function deserializeI32FromBytes(string $s): int
- {
- $result = unpack('l', $s);
- if ($result === false) {
- throw new InvalidArgumentException("Failed to unpack i32: $s");
- }
- return $result[1];
- }
-
- public static function serializeI64ToBytes(int $x): string
- {
- return pack('q', $x);
- }
-
- public static function deserializeI64FromBytes(string $s): int
- {
- $result = unpack('q', $s);
- if ($result === false) {
- throw new InvalidArgumentException("Failed to unpack i64: $s");
- }
- return $result[1];
- }
-
- public static function serializeF32ToBytes(float $x): string
- {
- return pack('f', $x);
- }
-
- public static function deserializeF32FromBytes(string $s): float
- {
- $result = unpack('f', $s);
- if ($result === false) {
- throw new InvalidArgumentException("Failed to unpack f32: $s");
- }
- return $result[1];
- }
-
- public static function serializeF64ToBytes(float $x): string
- {
- return pack('d', $x);
- }
-
- public static function deserializeF64FromBytes(string $s): float
- {
- $result = unpack('d', $s);
- if ($result === false) {
- throw new InvalidArgumentException("Failed to unpack f64: $s");
- }
- return $result[1];
- }
public static function convertS32ToU32(int $x): int
{
- // assert(-0x80000000 <= $x && $x <= 0x7FFFFFFF, "convertS32ToU32: out of range $x");
+ assert(-0x80000000 <= $x && $x <= 0x7FFFFFFF);
if ($x < 0) {
- $buf = pack('l', $x);
- $result = unpack('L', $buf);
- assert($result !== false);
- assert(0x00000000 <= $result[1] && $result[1] <= 0xFFFFFFFF, "convertS32ToU32: out of range $result[1]");
- return $result[1];
+ return $x + 0x100000000;
} else {
return $x;
}
@@ -1261,11 +1159,7 @@ final readonly class NumericOps
{
assert(0x00000000 <= $x && $x <= 0xFFFFFFFF);
if (($x & 0x80000000) !== 0) {
- $buf = pack('L', $x);
- $result = unpack('l', $buf);
- assert($result !== false);
- assert(-0x80000000 <= $result[1] && $result[1] <= 0x7FFFFFFF, "convertU32ToS32: out of range $result[1]");
- return $result[1];
+ return $x - 0x100000000;
} else {
return $x;
}
@@ -1297,45 +1191,6 @@ final readonly class NumericOps
return (int)$result;
}
- /**
- * @return 1|-1
- */
- public static function getFloatSign(float $p): int
- {
- if (is_nan($p)) {
- $n = self::reinterpretF64AsI64($p);
- // The MSB is the sign bit.
- return (($n >> 63) & 1) === 1 ? -1 : 1;
- } elseif ($p !== 0.0) {
- return $p < 0.0 ? -1 : 1;
- } else {
- // Comparison with 0 does not work for -0.0.
- return fdiv(1, $p) < 0.0 ? -1 : 1;
- }
- }
-
- /**
- * @param -1|1 $sign
- */
- private static function constructNan(int $sign, float $x): float
- {
- [$_, $_, $payload] = self::destructFloat($x);
- $i = ($sign === 1 ? 0 : PHP_INT_MIN) | 0b01111111_11110000_00000000_00000000_00000000_00000000_00000000_00000000 | $payload;
- return self::reinterpretI64AsF64($i);
- }
-
- /**
- * @return array{int, int, int}
- */
- private static function destructFloat(float $x): array
- {
- $i = self::reinterpretF64AsI64($x);
- return [
- $i & PHP_INT_MIN,
- $i & 0b01111111_11110000_00000000_00000000_00000000_00000000_00000000_00000000,
- $i & 0b00000000_00001111_11111111_11111111_11111111_11111111_11111111_11111111,
- ];
- }
private static function castBitIntToCFloat(string $x): float
{