aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-04-07 00:26:55 +0900
committernsfisis <nsfisis@gmail.com>2025-04-07 01:52:27 +0900
commit28b496687c0eb36a035a8686794fa45434957e28 (patch)
treef660f257eb72a040f693882a502adb1416e42257
parent7bc9e73c59e6ec7e4f9feef785e08dc56fbb09eb (diff)
downloadphp-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.tar.gz
php-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.tar.zst
php-waddiwasi-28b496687c0eb36a035a8686794fa45434957e28.zip
feat: do not use FFI to cast integers to f32
-rw-r--r--TODO2
-rw-r--r--src/BitOps/BinaryConversion.php73
-rw-r--r--src/BitOps/FloatTraits.php2
-rw-r--r--src/WebAssembly/Execution/NumericOps.php26
4 files changed, 80 insertions, 23 deletions
diff --git a/TODO b/TODO
index bd28bcb..171eb28 100644
--- a/TODO
+++ b/TODO
@@ -4,8 +4,6 @@
* Provide sane bindings to PHP
* Write PHPDoc for public APIs
* Provide high-level APIs
-* Eliminate use of FFI, except for optimization
- * strtof
NOTE:
diff --git a/src/BitOps/BinaryConversion.php b/src/BitOps/BinaryConversion.php
index 14b33c9..ae4e403 100644
--- a/src/BitOps/BinaryConversion.php
+++ b/src/BitOps/BinaryConversion.php
@@ -196,6 +196,16 @@ final readonly class BinaryConversion
}
/**
+ * @param numeric-string $x
+ */
+ public static function convertBigIntToF32(string $x): float
+ {
+ // PHP's (float) cast is through f64, so we have to convert it to f32 directly.
+ // i64 => f32 is not equal to i64 => f64 => f32.
+ return self::reinterpretI32AsF32(self::convertBigIntToF32Bits($x));
+ }
+
+ /**
* @return non-empty-string
*/
private static function packInt(PackIntSpecifiers $spec, int $x): string
@@ -237,6 +247,69 @@ final readonly class BinaryConversion
return $result[1];
}
+ /**
+ * @param numeric-string $value
+ */
+ private static function convertBigIntToF32Bits(string $value): int
+ {
+ if ($value === '0') {
+ return 0;
+ }
+
+ // Sign
+ if (bccomp($value, '0') < 0) {
+ $sign = Signedness::Signed;
+ $value = bcsub('0', $value);
+ } else {
+ $sign = Signedness::Unsigned;
+ }
+
+ // Exponent
+ if (bccomp($value, "9223372036854775807") <= 0) {
+ $e = strlen(decbin((int)$value)) - 1;
+ } else {
+ for ($i = 63; ; $i++) {
+ if (bccomp($value, bcpow('2', (string)$i)) < 0) {
+ $e = $i - 1;
+ break;
+ }
+ }
+ assert(isset($e));
+ }
+
+ // Infinity
+ if ($e >= 128) {
+ return FloatTraits::getF32SignBit($sign) | FloatTraits::F32_INFINITY_BITS;
+ }
+
+ // Mantissa
+ $p = bcpow('2', (string)$e); // p = 2^e
+ $numerator = bcmul(bcsub($value, $p), (string)(1 << 23)); // (value - p) * 2^23
+ $quotient = (int)bcdiv($numerator, $p, scale: 0);
+ $remainder = bcmod($numerator, $p);
+
+ // Round
+ $half = bcdiv($p, '2', scale: 0);
+ if (bccomp($remainder, $half) > 0) {
+ $quotient += 1;
+ } elseif ($remainder === $half) {
+ // Half to even
+ if ($quotient % 2 === 1) {
+ $quotient += 1;
+ }
+ }
+ if ($quotient >= (1 << 23)) {
+ $quotient -= (1 << 23);
+ $e += 1;
+ // Infinity
+ if ($e >= 128) {
+ return FloatTraits::getF32SignBit($sign) | FloatTraits::F32_INFINITY_BITS;
+ }
+ }
+
+ return FloatTraits::getF32SignBit($sign) | (($e + 127) << FloatTraits::F32_MANTISSA_BITS) | $quotient;
+ }
+
private static function isLittleEndian(): bool
{
return pack("s", ord("a"))[0] === "a";
diff --git a/src/BitOps/FloatTraits.php b/src/BitOps/FloatTraits.php
index dca9e18..75eebcb 100644
--- a/src/BitOps/FloatTraits.php
+++ b/src/BitOps/FloatTraits.php
@@ -17,6 +17,8 @@ final readonly class FloatTraits
public const int F32_SIGN_SIGNED = 0b10000000_00000000_00000000_00000000;
public const int F32_EXPONENT_NAN = 0b01111111_10000000_00000000_00000000;
+ public const int F32_INFINITY_BITS = 0b01111111_10000000_00000000_00000000;
+
public const int F64_EXPONENT_BITS = 11;
public const int F64_MANTISSA_BITS = 52;
diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php
index f7a1e89..7d5e57c 100644
--- a/src/WebAssembly/Execution/NumericOps.php
+++ b/src/WebAssembly/Execution/NumericOps.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Nsfisis\Waddiwasi\WebAssembly\Execution;
-use FFI;
use Nsfisis\Waddiwasi\BitOps\BinaryConversion;
use Nsfisis\Waddiwasi\BitOps\FloatOps;
use RoundingMode;
@@ -70,13 +69,13 @@ final readonly class NumericOps
public static function f32ConvertI64S(int $x): float
{
- return self::castBigIntToF32((string)$x);
+ return BinaryConversion::convertBigIntToF32((string)$x);
}
public static function f32ConvertI64U(int $x): float
{
$x = self::convertS64ToBigUInt($x);
- return self::castBigIntToF32($x);
+ return BinaryConversion::convertBigIntToF32($x);
}
public static function f32CopySign(float $x, float $y): float
@@ -1165,6 +1164,9 @@ final readonly class NumericOps
}
}
+ /**
+ * @return numeric-string
+ */
public static function convertS64ToBigUInt(int $x): string
{
if ($x < 0) {
@@ -1190,22 +1192,4 @@ final readonly class NumericOps
}
return (int)$result;
}
-
-
- private static function castBigIntToF32(string $x): float
- {
- // @phpstan-ignore-next-line
- return self::ffi()->strtof($x, null);
- }
-
- private static function ffi(): FFI
- {
- static $ffi;
- if (!$ffi) {
- $ffi = FFI::cdef(
- 'float strtof(const char *restrict nptr, char **restrict endptr);',
- );
- }
- return $ffi;
- }
}