diff options
| -rw-r--r-- | BUGS | 3 | ||||
| -rw-r--r-- | TODO | 1 | ||||
| -rw-r--r-- | phpunit.xml | 3 | ||||
| -rw-r--r-- | src/WebAssembly/BinaryFormat/Decoder.php | 18 | ||||
| -rw-r--r-- | src/WebAssembly/Execution/NumericOps.php | 30 | ||||
| -rw-r--r-- | tests/src/SpecTestsuites/SpecTestsuiteBase.php | 14 |
6 files changed, 57 insertions, 12 deletions
@@ -2,9 +2,6 @@ ## Numeric -* ConversionsTest -* FloatExprsTest -* FloatLiteralsTest * FloatMemoryTest ## Validation @@ -1,7 +1,6 @@ * Support text format (.wat) * Implement validation * Fix known bugs (BUGS) - * We need to implement software-defined f32 in order to support floating-point number operations. * Implement NaN propagation * Provide sane bindings to PHP * Write PHPDoc for public APIs diff --git a/phpunit.xml b/phpunit.xml index 336683b..02e668c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -28,6 +28,7 @@ <file>tests/src/SpecTestsuites/Core/CallIndirectTest.php</file> <file>tests/src/SpecTestsuites/Core/CallTest.php</file> <file>tests/src/SpecTestsuites/Core/ConstTest.php</file> + <file>tests/src/SpecTestsuites/Core/ConversionsTest.php</file> <file>tests/src/SpecTestsuites/Core/DataTest.php</file> <file>tests/src/SpecTestsuites/Core/ElemTest.php</file> <file>tests/src/SpecTestsuites/Core/EndiannessTest.php</file> @@ -39,6 +40,8 @@ <file>tests/src/SpecTestsuites/Core/F64CmpTest.php</file> <file>tests/src/SpecTestsuites/Core/F64Test.php</file> <file>tests/src/SpecTestsuites/Core/FacTest.php</file> + <file>tests/src/SpecTestsuites/Core/FloatExprsTest.php</file> + <file>tests/src/SpecTestsuites/Core/FloatLiteralsTest.php</file> <file>tests/src/SpecTestsuites/Core/FloatMiscTest.php</file> <file>tests/src/SpecTestsuites/Core/ForwardTest.php</file> <file>tests/src/SpecTestsuites/Core/FuncPtrsTest.php</file> diff --git a/src/WebAssembly/BinaryFormat/Decoder.php b/src/WebAssembly/BinaryFormat/Decoder.php index e64fac8..cce3c5c 100644 --- a/src/WebAssembly/BinaryFormat/Decoder.php +++ b/src/WebAssembly/BinaryFormat/Decoder.php @@ -1091,12 +1091,24 @@ final class Decoder private function decodeF32(): float { $buf = $this->stream->read(4); - $result = unpack('g', $buf); + $result = unpack('V', $buf); if ($result === false) { throw new InvalidBinaryFormatException("f32"); } - assert(isset($result[1]) && is_float($result[1])); - return $result[1]; + assert(isset($result[1]) && is_int($result[1])); + $i = $result[1]; + if (($i & 0b01111111100000000000000000000000) === 0b01111111100000000000000000000000) { + $sign = ($i & 0b10000000000000000000000000000000) === 0 ? 1 : -1; + $payload = $i & 0b00000000011111111111111111111111; + $j = ($sign === 1 ? 0 : PHP_INT_MIN) | 0b0111111111110000000000000000000000000000000000000000000000000000 | ($payload << (52 - 23)); + $result = unpack('d', pack('q', $j)); + assert(isset($result[1]) && is_float($result[1])); + return $result[1]; + } else { + $result = unpack('g', $buf); + assert(isset($result[1]) && is_float($result[1])); + return $result[1]; + } } /** diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php index a24791f..8c061bd 100644 --- a/src/WebAssembly/Execution/NumericOps.php +++ b/src/WebAssembly/Execution/NumericOps.php @@ -168,6 +168,7 @@ final readonly class NumericOps public static function f32Neg(float $x): float { + self::reinterpretF32AsI32($x); if (is_nan($x)) { // Negate operator does not work for NaN in PHP. return self::constructNan(-self::getFloatSign($x), $x); @@ -341,7 +342,11 @@ 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 @@ -1144,7 +1149,15 @@ final readonly class NumericOps public static function reinterpretI32AsF32(int $x): float { - return self::deserializeF32FromBytes(self::serializeI32ToBytes($x)); + $y = self::convertS32ToU32($x); + if (($y & 0b01111111100000000000000000000000) === 0b01111111100000000000000000000000) { + $sign = ($y & 0b10000000000000000000000000000000) === 0 ? 1 : -1; + $payload = $y & 0b00000000011111111111111111111111; + $i = ($sign === 1 ? 0 : PHP_INT_MIN) | 0b0111111111110000000000000000000000000000000000000000000000000000 | ($payload << (52 - 23)); + return self::reinterpretI64AsF64($i); + } else { + return self::deserializeF32FromBytes(self::serializeI32ToBytes($x)); + } } public static function reinterpretI64AsF32(int $x): float @@ -1164,7 +1177,16 @@ final readonly class NumericOps public static function reinterpretF32AsI32(float $x): int { - return self::deserializeI32FromBytes(self::serializeF32ToBytes($x)); + if (is_nan($x)) { + [$sign, , $payload] = self::destructFloat($x); + $i = 0 + | ($sign === 0 ? 0 : 0b10000000000000000000000000000000) + | 0b01111111100000000000000000000000 + | ($payload >> (52 - 23)); + return self::convertU32ToS32($i); + } else { + return self::deserializeI32FromBytes(self::serializeF32ToBytes($x)); + } } public static function reinterpretF64AsI32(float $x): int @@ -1319,7 +1341,7 @@ final readonly class NumericOps */ private static function constructNan(int $sign, float $x): float { - [$_, $_, $payload] = self::destructFloat($x); + [, , $payload] = self::destructFloat($x); $i = ($sign === 1 ? 0 : PHP_INT_MIN) | 0b01111111_11110000_00000000_00000000_00000000_00000000_00000000_00000000 | $payload; return self::reinterpretI64AsF64($i); } diff --git a/tests/src/SpecTestsuites/SpecTestsuiteBase.php b/tests/src/SpecTestsuites/SpecTestsuiteBase.php index 6d92ed8..41afbf8 100644 --- a/tests/src/SpecTestsuites/SpecTestsuiteBase.php +++ b/tests/src/SpecTestsuites/SpecTestsuiteBase.php @@ -258,7 +258,7 @@ abstract class SpecTestsuiteBase extends TestCase 'i64' => unpack('q', self::convertInt64ToBinary($value))[1], 'f32' => match ($value) { 'nan:canonical', 'nan:arithmetic' => $value, - default => unpack('g', pack('V', (int)$value))[1], + default => self::i32ToF32((int)$value), }, 'f64' => match ($value) { 'nan:canonical', 'nan:arithmetic' => $value, @@ -270,6 +270,18 @@ abstract class SpecTestsuiteBase extends TestCase }; } + private static function i32ToF32(int $x): float + { + if (($x & 0b01111111100000000000000000000000) === 0b01111111100000000000000000000000) { + $sign = ($x & 0b10000000000000000000000000000000) === 0 ? 1 : -1; + $payload = $x & 0b00000000011111111111111111111111; + $i = ($sign === 1 ? 0 : PHP_INT_MIN) | 0b0111111111110000000000000000000000000000000000000000000000000000 | ($payload << (52 - 23)); + return unpack('e', pack('q', $i))[1]; + } else { + return unpack('g', pack('V', $x))[1]; + } + } + private function doAction( array $action, ): array { |
