diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-07-11 02:57:23 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-07-11 02:57:23 +0900 |
| commit | 26f49b7e27076e689541b9e13a1b54f60a4ee5c2 (patch) | |
| tree | a3762813c34a384f21ddeed630ddf333a1cc1b05 /src/WebAssembly/Execution | |
| parent | 326273f20c2d7dfe3d866eb720d1bb914570e3a3 (diff) | |
| download | php-waddiwasi-26f49b7e27076e689541b9e13a1b54f60a4ee5c2.tar.gz php-waddiwasi-26f49b7e27076e689541b9e13a1b54f60a4ee5c2.tar.zst php-waddiwasi-26f49b7e27076e689541b9e13a1b54f60a4ee5c2.zip | |
feat: organize namespaces
Diffstat (limited to 'src/WebAssembly/Execution')
37 files changed, 4536 insertions, 0 deletions
diff --git a/src/WebAssembly/Execution/Allocator.php b/src/WebAssembly/Execution/Allocator.php new file mode 100644 index 0000000..67eb467 --- /dev/null +++ b/src/WebAssembly/Execution/Allocator.php @@ -0,0 +1,165 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\ExportDescs; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Func; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Module; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\GlobalType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\MemType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\RefType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\TableType; +use RuntimeException; +use function count; + +final readonly class Allocator +{ + public function __construct( + private readonly Store $store, + ) { + } + + /** + * @param list<ExternVal> $externVals + */ + public function allocPreInitModule( + Module $module, + array $externVals, + ): ModuleInst { + $m = new ModuleInst($module->types, [], [], [], [], [], [], []); + + foreach ($externVals as $externVal) { + match ($externVal::class) { + ExternVals\Func::class => $m->funcAddrs[] = $externVal->addr, + ExternVals\Global_::class => $m->globalAddrs[] = $externVal->addr, + default => null, + }; + } + foreach ($module->funcs as $func) { + $m->funcAddrs[] = $this->allocFunc($func, $m); + } + + return $m; + } + + /** + * @param list<ExternVal> $externVals + * @param list<int|float|Ref> $vals + * @param list<list<Ref>> $refsList + * @param list<int> $preAllocatedFuncs + */ + public function allocModule( + Module $module, + array $externVals, + array $vals, + array $refsList, + array $preAllocatedFuncs, + ): ModuleInst { + $m = new ModuleInst($module->types, [], [], [], [], [], [], []); + + foreach ($externVals as $externVal) { + match ($externVal::class) { + ExternVals\Func::class => null, // handled below. + ExternVals\Table::class => $m->tableAddrs[] = $externVal->addr, + ExternVals\Mem::class => $m->memAddrs[] = $externVal->addr, + ExternVals\Global_::class => $m->globalAddrs[] = $externVal->addr, + default => throw new RuntimeException("unreachable"), + }; + } + + foreach ($preAllocatedFuncs as $funcAddr) { + $m->funcAddrs[] = $funcAddr; + $funcInst = $this->store->funcs[$funcAddr]; + if ($funcInst instanceof FuncInsts\Wasm) { + // @phpstan-ignore-next-line + $this->store->funcs[$funcAddr] = FuncInst::Wasm( + $funcInst->type, + $m, + $funcInst->code, + ); + } + } + foreach ($module->tables as $table) { + $m->tableAddrs[] = $this->allocTable($table->type, Ref::RefNull($table->type->refType)); + } + foreach ($module->mems as $mem) { + $m->memAddrs[] = $this->allocMem($mem->type); + } + foreach ($module->datas as $data) { + $m->dataAddrs[] = $this->allocData($data->init); + } + + foreach ($module->globals as $i => $global) { + $m->globalAddrs[] = $this->allocGlobal($global->type, $vals[$i]); + } + foreach ($module->elems as $i => $elem) { + $m->elemAddrs[] = $this->allocElem($elem->type, $refsList[$i]); + } + + foreach ($module->exports as $export) { + $value = match ($export->desc::class) { + ExportDescs\Func::class => ExternVal::Func($m->funcAddrs[$export->desc->func]), + ExportDescs\Table::class => ExternVal::Table($m->tableAddrs[$export->desc->table]), + ExportDescs\Mem::class => ExternVal::Mem($m->memAddrs[$export->desc->mem]), + ExportDescs\Global_::class => ExternVal::Global_($m->globalAddrs[$export->desc->global]), + default => throw new RuntimeException("unreachable"), + }; + $m->exports[] = new ExportInst($export->name, $value); + } + + return $m; + } + + private function allocFunc(Func $func, ModuleInst $moduleInst): int + { + $funcType = $moduleInst->types[$func->type]; + $funcInst = FuncInst::Wasm($funcType, $moduleInst, $func); + $this->store->funcs[] = $funcInst; + return count($this->store->funcs) - 1; + } + + private function allocTable(TableType $tableType, Ref $ref): int + { + $minSize = $tableType->limits->min; + $elem = array_fill(0, $minSize, $ref); + $tableInst = new TableInst($tableType, $elem); + $this->store->tables[] = $tableInst; + return count($this->store->tables) - 1; + } + + private function allocMem(MemType $memType): int + { + $memInst = new MemInst($memType); + $this->store->mems[] = $memInst; + return count($this->store->mems) - 1; + } + + private function allocGlobal(GlobalType $globalType, int|float|Ref $val): int + { + $globalInst = new GlobalInst($globalType, $val); + $this->store->globals[] = $globalInst; + return count($this->store->globals) - 1; + } + + /** + * @param list<Ref> $elem + */ + private function allocElem(RefType $refType, array $elem): int + { + $elemInst = new ElemInst($refType, $elem); + $this->store->elems[] = $elemInst; + return count($this->store->elems) - 1; + } + + /** + * @param list<Byte> $data + */ + private function allocData(array $data): int + { + $dataInst = new DataInst($data); + $this->store->datas[] = $dataInst; + return count($this->store->datas) - 1; + } +} diff --git a/src/WebAssembly/Execution/DataInst.php b/src/WebAssembly/Execution/DataInst.php new file mode 100644 index 0000000..f6918c6 --- /dev/null +++ b/src/WebAssembly/Execution/DataInst.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +final readonly class DataInst +{ + /** + * @param list<Byte> $data + */ + public function __construct( + public array $data, + ) { + } +} diff --git a/src/WebAssembly/Execution/ElemInst.php b/src/WebAssembly/Execution/ElemInst.php new file mode 100644 index 0000000..422cd62 --- /dev/null +++ b/src/WebAssembly/Execution/ElemInst.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\RefType; + +final readonly class ElemInst +{ + /** + * @param list<Ref> $elem + */ + public function __construct( + public RefType $type, + public array $elem, + ) { + } +} diff --git a/src/WebAssembly/Execution/ExportInst.php b/src/WebAssembly/Execution/ExportInst.php new file mode 100644 index 0000000..7fe04cd --- /dev/null +++ b/src/WebAssembly/Execution/ExportInst.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +final readonly class ExportInst +{ + /** + * @param Name $name + */ + public function __construct( + public string $name, + public ExternVal $value, + ) { + } +} diff --git a/src/WebAssembly/Execution/Extern.php b/src/WebAssembly/Execution/Extern.php new file mode 100644 index 0000000..dacfad1 --- /dev/null +++ b/src/WebAssembly/Execution/Extern.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +abstract readonly class Extern +{ + final public static function Func(FuncInst $func): Externs\Func + { + return new Externs\Func($func); + } + + final public static function Table(TableInst $table): Externs\Table + { + return new Externs\Table($table); + } + + final public static function Mem(MemInst $mem): Externs\Mem + { + return new Externs\Mem($mem); + } + + final public static function Global_(GlobalInst $global): Externs\Global_ + { + return new Externs\Global_($global); + } +} diff --git a/src/WebAssembly/Execution/ExternVal.php b/src/WebAssembly/Execution/ExternVal.php new file mode 100644 index 0000000..116ce53 --- /dev/null +++ b/src/WebAssembly/Execution/ExternVal.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +abstract readonly class ExternVal +{ + final public static function Func(int $addr): ExternVals\Func + { + return new ExternVals\Func($addr); + } + + final public static function Table(int $addr): ExternVals\Table + { + return new ExternVals\Table($addr); + } + + final public static function Mem(int $addr): ExternVals\Mem + { + return new ExternVals\Mem($addr); + } + + final public static function Global_(int $addr): ExternVals\Global_ + { + return new ExternVals\Global_($addr); + } +} diff --git a/src/WebAssembly/Execution/ExternVals/Func.php b/src/WebAssembly/Execution/ExternVals/Func.php new file mode 100644 index 0000000..cc3c82a --- /dev/null +++ b/src/WebAssembly/Execution/ExternVals/Func.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVals; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVal; + +final readonly class Func extends ExternVal +{ + protected function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/ExternVals/Global_.php b/src/WebAssembly/Execution/ExternVals/Global_.php new file mode 100644 index 0000000..cc28f26 --- /dev/null +++ b/src/WebAssembly/Execution/ExternVals/Global_.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVals; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVal; + +final readonly class Global_ extends ExternVal +{ + protected function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/ExternVals/Mem.php b/src/WebAssembly/Execution/ExternVals/Mem.php new file mode 100644 index 0000000..1b76d3f --- /dev/null +++ b/src/WebAssembly/Execution/ExternVals/Mem.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVals; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVal; + +final readonly class Mem extends ExternVal +{ + protected function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/ExternVals/Table.php b/src/WebAssembly/Execution/ExternVals/Table.php new file mode 100644 index 0000000..787adb9 --- /dev/null +++ b/src/WebAssembly/Execution/ExternVals/Table.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVals; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\ExternVal; + +final readonly class Table extends ExternVal +{ + protected function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/Externs/Func.php b/src/WebAssembly/Execution/Externs/Func.php new file mode 100644 index 0000000..3bfc116 --- /dev/null +++ b/src/WebAssembly/Execution/Externs/Func.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Externs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Extern; +use Nsfisis\Waddiwasi\WebAssembly\Execution\FuncInst; + +final readonly class Func extends Extern +{ + protected function __construct( + public FuncInst $func, + ) { + } +} diff --git a/src/WebAssembly/Execution/Externs/Global_.php b/src/WebAssembly/Execution/Externs/Global_.php new file mode 100644 index 0000000..562e934 --- /dev/null +++ b/src/WebAssembly/Execution/Externs/Global_.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Externs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Extern; +use Nsfisis\Waddiwasi\WebAssembly\Execution\GlobalInst; + +final readonly class Global_ extends Extern +{ + protected function __construct( + public GlobalInst $global, + ) { + } +} diff --git a/src/WebAssembly/Execution/Externs/Mem.php b/src/WebAssembly/Execution/Externs/Mem.php new file mode 100644 index 0000000..8924e00 --- /dev/null +++ b/src/WebAssembly/Execution/Externs/Mem.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Externs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Extern; +use Nsfisis\Waddiwasi\WebAssembly\Execution\MemInst; + +final readonly class Mem extends Extern +{ + protected function __construct( + public MemInst $mem, + ) { + } +} diff --git a/src/WebAssembly/Execution/Externs/Table.php b/src/WebAssembly/Execution/Externs/Table.php new file mode 100644 index 0000000..65a833f --- /dev/null +++ b/src/WebAssembly/Execution/Externs/Table.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Externs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Extern; +use Nsfisis\Waddiwasi\WebAssembly\Execution\TableInst; + +final readonly class Table extends Extern +{ + protected function __construct( + public TableInst $table, + ) { + } +} diff --git a/src/WebAssembly/Execution/Frame.php b/src/WebAssembly/Execution/Frame.php new file mode 100644 index 0000000..331b43a --- /dev/null +++ b/src/WebAssembly/Execution/Frame.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +final class Frame +{ + /** + * @param int<0, max> $arity + * @param list<int|float|Ref> $locals + */ + public function __construct( + public readonly int $arity, + public array $locals, + public readonly ModuleInst $module, + public string $debugName, + ) { + } +} diff --git a/src/WebAssembly/Execution/FuncInst.php b/src/WebAssembly/Execution/FuncInst.php new file mode 100644 index 0000000..dda32cf --- /dev/null +++ b/src/WebAssembly/Execution/FuncInst.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Func; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType; + +abstract readonly class FuncInst +{ + final public static function Wasm(FuncType $type, ModuleInst $module, Func $code): FuncInsts\Wasm + { + return new FuncInsts\Wasm($type, $module, $code); + } + + final public static function Host(FuncType $type, callable $callback): FuncInsts\Host + { + return new FuncInsts\Host($type, $callback); + } +} diff --git a/src/WebAssembly/Execution/FuncInsts/Host.php b/src/WebAssembly/Execution/FuncInsts/Host.php new file mode 100644 index 0000000..6a66325 --- /dev/null +++ b/src/WebAssembly/Execution/FuncInsts/Host.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\FuncInsts; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\FuncInst; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType; + +final readonly class Host extends FuncInst +{ + /** + * @param callable $callback + */ + public function __construct( + public FuncType $type, + public mixed $callback, + ) { + } +} diff --git a/src/WebAssembly/Execution/FuncInsts/Wasm.php b/src/WebAssembly/Execution/FuncInsts/Wasm.php new file mode 100644 index 0000000..d0780ab --- /dev/null +++ b/src/WebAssembly/Execution/FuncInsts/Wasm.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\FuncInsts; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\FuncInst; +use Nsfisis\Waddiwasi\WebAssembly\Execution\ModuleInst; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Func; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType; + +final readonly class Wasm extends FuncInst +{ + public function __construct( + public FuncType $type, + public ModuleInst $module, + public Func $code, + ) { + } +} diff --git a/src/WebAssembly/Execution/GlobalInst.php b/src/WebAssembly/Execution/GlobalInst.php new file mode 100644 index 0000000..75f764f --- /dev/null +++ b/src/WebAssembly/Execution/GlobalInst.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\GlobalType; + +final class GlobalInst +{ + public function __construct( + public readonly GlobalType $type, + public int|float|Ref $value, + ) { + } +} diff --git a/src/WebAssembly/Execution/Label.php b/src/WebAssembly/Execution/Label.php new file mode 100644 index 0000000..85e90a7 --- /dev/null +++ b/src/WebAssembly/Execution/Label.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +final readonly class Label +{ + /** + * @param int<0, max> $arity + */ + public function __construct( + public int $arity, + ) { + } +} diff --git a/src/WebAssembly/Execution/MemInst.php b/src/WebAssembly/Execution/MemInst.php new file mode 100644 index 0000000..802f334 --- /dev/null +++ b/src/WebAssembly/Execution/MemInst.php @@ -0,0 +1,662 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use FFI; +use FFI\CData; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\Limits; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\MemType; +use function assert; +use function count; + +final class MemInst +{ + private const PAGE_SIZE = 64 * 1024; + + private const MAX_PAGES = 0x10000; + + private CData $dataU8; + private CData $dataS8; + + private CData $dataU16_0; + private CData $dataU16_1; + private CData $dataS16_0; + private CData $dataS16_1; + + private CData $dataU32_0; + private CData $dataU32_1; + private CData $dataU32_2; + private CData $dataU32_3; + private CData $dataS32_0; + private CData $dataS32_1; + private CData $dataS32_2; + private CData $dataS32_3; + + private CData $dataS64_0; + private CData $dataS64_1; + private CData $dataS64_2; + private CData $dataS64_3; + private CData $dataS64_4; + private CData $dataS64_5; + 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; + private CData $dataF64_3; + private CData $dataF64_4; + private CData $dataF64_5; + private CData $dataF64_6; + private CData $dataF64_7; + + private int $dataSize; + + private readonly FFI $ffi; + + public function __construct( + public MemType $type, + ) { + $this->ffi = FFI::cdef(); + $this->initInternalMemory($type->limits->min); + } + + public function size(): int + { + return $this->dataSize; + } + + public function nPages(): int + { + return $this->size() / self::PAGE_SIZE; + } + + /** + * @return int + * Returns the original size of the memory in pages or -1 if failed. + */ + public function grow(int $n): int + { + $sz = $this->nPages(); + $len = $sz + $n; + if (self::MAX_PAGES < $len) { + return -1; + } + + $limits = $this->type->limits; + $limits_ = new Limits($len, $limits->max); + if (!$limits_->isValid()) { + return -1; + } + + $originalSize = $this->size(); + // @phpstan-ignore-next-line + $originalData = $this->ffi->new("uint8_t[$originalSize+8]"); + assert($originalData !== null); + FFI::memcpy($originalData, $this->dataU8, $originalSize); + + $this->initInternalMemory($len); + + FFI::memcpy($this->dataU8, $originalData, $originalSize); + + return $sz; + } + + private function initInternalMemory(int $n): void + { + $this->dataSize = $n * self::PAGE_SIZE; + + // @phpstan-ignore-next-line + $this->dataU8 = $this->ffi->new("uint8_t[$this->dataSize+8]"); + // @phpstan-ignore-next-line + $this->dataS8 = $this->ffi->cast("int8_t[$this->dataSize]", $this->dataU8); + + // @phpstan-ignore-next-line + $castInt = fn ($n, $signed, $offset) => $this->ffi->cast( + sprintf("%sint%d_t[$this->dataSize/%d]", $signed ? "" : "u", $n, $n / 8), + // @phpstan-ignore-next-line + $this->ffi->cast("uint8_t[$this->dataSize+8]", $this->dataU8 + $offset), + ); + + // @phpstan-ignore-next-line + $castFloat = fn ($n, $offset) => $this->ffi->cast( + sprintf("%s[$this->dataSize/%d]", $n === 32 ? "float" : "double", $n / 8), + // @phpstan-ignore-next-line + $this->ffi->cast("uint8_t[$this->dataSize+8]", $this->dataU8 + $offset), + ); + + // @phpstan-ignore-next-line + $this->dataU16_0 = $castInt(16, false, 0); + // @phpstan-ignore-next-line + $this->dataU16_1 = $castInt(16, false, 1); + // @phpstan-ignore-next-line + $this->dataS16_0 = $castInt(16, true, 0); + // @phpstan-ignore-next-line + $this->dataS16_1 = $castInt(16, true, 1); + + // @phpstan-ignore-next-line + $this->dataU32_0 = $castInt(32, false, 0); + // @phpstan-ignore-next-line + $this->dataU32_1 = $castInt(32, false, 1); + // @phpstan-ignore-next-line + $this->dataU32_2 = $castInt(32, false, 2); + // @phpstan-ignore-next-line + $this->dataU32_3 = $castInt(32, false, 3); + // @phpstan-ignore-next-line + $this->dataS32_0 = $castInt(32, true, 0); + // @phpstan-ignore-next-line + $this->dataS32_1 = $castInt(32, true, 1); + // @phpstan-ignore-next-line + $this->dataS32_2 = $castInt(32, true, 2); + // @phpstan-ignore-next-line + $this->dataS32_3 = $castInt(32, true, 3); + + // @phpstan-ignore-next-line + $this->dataS64_0 = $castInt(64, true, 0); + // @phpstan-ignore-next-line + $this->dataS64_1 = $castInt(64, true, 1); + // @phpstan-ignore-next-line + $this->dataS64_2 = $castInt(64, true, 2); + // @phpstan-ignore-next-line + $this->dataS64_3 = $castInt(64, true, 3); + // @phpstan-ignore-next-line + $this->dataS64_4 = $castInt(64, true, 4); + // @phpstan-ignore-next-line + $this->dataS64_5 = $castInt(64, true, 5); + // @phpstan-ignore-next-line + $this->dataS64_6 = $castInt(64, true, 6); + // @phpstan-ignore-next-line + $this->dataS64_7 = $castInt(64, true, 7); + + // @phpstan-ignore-next-line + $this->dataF32_0 = $castFloat(32, 0); + // @phpstan-ignore-next-line + $this->dataF32_1 = $castFloat(32, 1); + // @phpstan-ignore-next-line + $this->dataF32_2 = $castFloat(32, 2); + // @phpstan-ignore-next-line + $this->dataF32_3 = $castFloat(32, 3); + + // @phpstan-ignore-next-line + $this->dataF64_0 = $castFloat(64, 0); + // @phpstan-ignore-next-line + $this->dataF64_1 = $castFloat(64, 1); + // @phpstan-ignore-next-line + $this->dataF64_2 = $castFloat(64, 2); + // @phpstan-ignore-next-line + $this->dataF64_3 = $castFloat(64, 3); + // @phpstan-ignore-next-line + $this->dataF64_4 = $castFloat(64, 4); + // @phpstan-ignore-next-line + $this->dataF64_5 = $castFloat(64, 5); + // @phpstan-ignore-next-line + $this->dataF64_6 = $castFloat(64, 6); + // @phpstan-ignore-next-line + $this->dataF64_7 = $castFloat(64, 7); + + // @phpstan-ignore-next-line + FFI::memset($this->dataU8, 0, $this->dataSize); + } + + /** + * @param list<int> $data + */ + public function copyData(array $data, int $src, int $dst, int $len): void + { + assert(0 <= $len); + assert(0 <= $src); + assert(0 <= $dst); + assert($src + $len <= count($data)); + assert($dst + $len <= $this->size()); + for ($i = 0; $i < $len; $i++) { + $this->storeByte($dst + $i, $data[$src + $i]); + } + } + + public function memcpy(int $dst, int $src, int $len): void + { + assert(0 <= $len); + assert(0 <= $src); + assert(0 <= $dst); + assert($src + $len <= $this->size()); + assert($dst + $len <= $this->size()); + if ($src === $dst || $len === 0) { + return; + } + for ($i = 0; $i < $len; $i++) { + $s = ($dst < $src) ? ($src + $i) : ($src + $len - 1 - $i); + $d = ($dst < $src) ? ($dst + $i) : ($dst + $len - 1 - $i); + $x = $this->loadByte($s); + assert($x !== null); + $this->storeByte($d, $x); + } + } + + public function memset(int $dst, int $c, int $len): void + { + assert(0 <= $len); + assert(0 <= $dst); + assert($dst + $len <= $this->size()); + for ($i = 0; $i < $len; $i++) { + $this->storeI32_s8($dst + $i, $c); + } + } + + /** + * @return ?S32 + */ + public function loadI32_s8(int $ptr): ?int + { + if ($this->size() < $ptr + 1) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS8[$ptr]; + assert(-0x80 <= $c && $c <= 0x7F, "$c"); + return $c; + } + + /** + * @return ?S32 + */ + public function loadI32_u8(int $ptr): ?int + { + if ($this->size() < $ptr + 1) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataU8[$ptr]; + assert(0 <= $c && $c <= 0xFF, "$c"); + return $c; + } + + /** + * @return ?S32 + */ + public function loadI32_s16(int $ptr): ?int + { + if ($this->size() < $ptr + 2) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS16($ptr)[$ptr >> 1]; + assert(-0x8000 <= $c && $c <= 0x7FFF, "$c"); + return $c; + } + + /** + * @return ?S32 + */ + public function loadI32_u16(int $ptr): ?int + { + if ($this->size() < $ptr + 2) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataU16($ptr)[$ptr >> 1]; + assert(0 <= $c && $c <= 0xFFFF, "$c"); + return $c; + } + + /** + * @return ?S32 + */ + public function loadI32_s32(int $ptr): ?int + { + if ($this->size() < $ptr + 4) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS32($ptr)[$ptr >> 2]; + assert(-0x80000000 <= $c && $c <= 0x7FFFFFFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_s8(int $ptr): ?int + { + if ($this->size() < $ptr + 1) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS8[$ptr]; + assert(-0x80 <= $c && $c <= 0x7F, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_u8(int $ptr): ?int + { + if ($this->size() < $ptr + 1) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataU8[$ptr]; + assert(0 <= $c && $c <= 0xFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_s16(int $ptr): ?int + { + if ($this->size() < $ptr + 2) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS16($ptr)[$ptr >> 1]; + assert(-0x8000 <= $c && $c <= 0x7FFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_u16(int $ptr): ?int + { + if ($this->size() < $ptr + 2) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataU16($ptr)[$ptr >> 1]; + assert(0 <= $c && $c <= 0xFFFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_s32(int $ptr): ?int + { + if ($this->size() < $ptr + 4) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS32($ptr)[$ptr >> 2]; + assert(-0x80000000 <= $c && $c <= 0x7FFFFFFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_u32(int $ptr): ?int + { + if ($this->size() < $ptr + 4) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataU32($ptr)[$ptr >> 2]; + assert(0 <= $c && $c <= 0xFFFFFFFF, "$c"); + return $c; + } + + /** + * @return ?S64 + */ + public function loadI64_s64(int $ptr): ?int + { + if ($this->size() < $ptr + 8) { + return null; + } + // @phpstan-ignore-next-line + $c = $this->dataS64($ptr)[$ptr >> 3]; + assert(-0x8000000000000000 <= $c && $c <= 0x7FFFFFFFFFFFFFFF, "$c"); + return $c; + } + + /** + * @return ?F32 + */ + public function loadF32(int $ptr): ?float + { + if ($this->size() < $ptr + 4) { + return null; + } + // @phpstan-ignore-next-line + return $this->dataF32($ptr)[$ptr >> 2]; + } + + /** + * @return ?F64 + */ + public function loadF64(int $ptr): ?float + { + if ($this->size() < $ptr + 8) { + return null; + } + // @phpstan-ignore-next-line + return $this->dataF64($ptr)[$ptr >> 3]; + } + + /** + * @return ?int + */ + public function loadByte(int $ptr): ?int + { + if ($this->size() < $ptr + 1) { + return null; + } + // @phpstan-ignore-next-line + return $this->dataU8[$ptr]; + } + + /** + * @return bool + */ + public function storeByte(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 1) { + return false; + } + // @phpstan-ignore-next-line + $this->dataU8[$ptr] = $c; + return true; + } + + /** + * @param S32 $c + * @return bool + */ + public function storeI32_s8(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 1) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS8[$ptr] = $c; + return true; + } + + /** + * @param S32 $c + * @return bool + */ + public function storeI32_s16(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 2) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS16($ptr)[$ptr >> 1] = $c; + return true; + } + + /** + * @param S32 $c + * @return bool + */ + public function storeI32_s32(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 4) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS32($ptr)[$ptr >> 2] = $c; + return true; + } + + /** + * @param S64 $c + * @return bool + */ + public function storeI64_s8(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 1) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS8[$ptr] = $c; + return true; + } + + /** + * @param S64 $c + * @return bool + */ + public function storeI64_s16(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 2) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS16($ptr)[$ptr >> 1] = $c; + return true; + } + + /** + * @param S64 $c + * @return bool + */ + public function storeI64_s32(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 4) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS32($ptr)[$ptr >> 2] = $c; + return true; + } + + /** + * @param S64 $c + * @return bool + */ + public function storeI64_s64(int $ptr, int $c): bool + { + if ($this->size() < $ptr + 8) { + return false; + } + // @phpstan-ignore-next-line + $this->dataS64($ptr)[$ptr >> 3] = $c; + return true; + } + + /** + * @param F32 $c + * @return bool + */ + public function storeF32(int $ptr, float $c): bool + { + if ($this->size() < $ptr + 4) { + return false; + } + // @phpstan-ignore-next-line + $this->dataF32($ptr)[$ptr >> 2] = $c; + return true; + } + + /** + * @param F64 $c + * @return bool + */ + public function storeF64(int $ptr, float $c): bool + { + if ($this->size() < $ptr + 8) { + return false; + } + // @phpstan-ignore-next-line + $this->dataF64($ptr)[$ptr >> 3] = $c; + return true; + } + + private function dataU16(int $ptr): CData + { + return ($ptr & 1) !== 0 ? $this->dataU16_1 : $this->dataU16_0; + } + + private function dataS16(int $ptr): CData + { + return ($ptr & 1) !== 0 ? $this->dataS16_1 : $this->dataS16_0; + } + + private function dataU32(int $ptr): CData + { + return match ($ptr & 3) { + 0 => $this->dataU32_0, + 1 => $this->dataU32_1, + 2 => $this->dataU32_2, + 3 => $this->dataU32_3, + }; + } + + private function dataS32(int $ptr): CData + { + return match ($ptr & 3) { + 0 => $this->dataS32_0, + 1 => $this->dataS32_1, + 2 => $this->dataS32_2, + 3 => $this->dataS32_3, + }; + } + + private function dataS64(int $ptr): CData + { + return match ($ptr & 7) { + 0 => $this->dataS64_0, + 1 => $this->dataS64_1, + 2 => $this->dataS64_2, + 3 => $this->dataS64_3, + 4 => $this->dataS64_4, + 5 => $this->dataS64_5, + 6 => $this->dataS64_6, + 7 => $this->dataS64_7, + }; + } + + 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) { + 0 => $this->dataF64_0, + 1 => $this->dataF64_1, + 2 => $this->dataF64_2, + 3 => $this->dataF64_3, + 4 => $this->dataF64_4, + 5 => $this->dataF64_5, + 6 => $this->dataF64_6, + 7 => $this->dataF64_7, + }; + } +} diff --git a/src/WebAssembly/Execution/ModuleInst.php b/src/WebAssembly/Execution/ModuleInst.php new file mode 100644 index 0000000..37bbcfd --- /dev/null +++ b/src/WebAssembly/Execution/ModuleInst.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType; + +final class ModuleInst +{ + /** + * @param list<FuncType> $types + * @param list<int> $funcAddrs + * @param list<int> $tableAddrs + * @param list<int> $memAddrs + * @param list<int> $globalAddrs + * @param list<int> $elemAddrs + * @param list<int> $dataAddrs + * @param list<ExportInst> $exports + */ + public function __construct( + public array $types, + public array $funcAddrs, + public array $tableAddrs, + public array $memAddrs, + public array $globalAddrs, + public array $elemAddrs, + public array $dataAddrs, + public array $exports, + ) { + } +} diff --git a/src/WebAssembly/Execution/NumericOps.php b/src/WebAssembly/Execution/NumericOps.php new file mode 100644 index 0000000..895b8c2 --- /dev/null +++ b/src/WebAssembly/Execution/NumericOps.php @@ -0,0 +1,193 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use InvalidArgumentException; +use function assert; +use function bcadd; +use function bccomp; +use function bcmod; +use function bcpow; +use function bcsub; +use function fdiv; +use function is_nan; +use function pack; +use function unpack; + +final readonly class NumericOps +{ + private function __construct() + { + } + + 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)); + } + + 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"); + 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]; + } else { + return $x; + } + } + + public static function convertU32ToS32(int $x): int + { + 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]; + } else { + return $x; + } + } + + public static function convertS64ToBigUInt(int $x): string + { + if ($x < 0) { + return bcadd((string)$x, '18446744073709551616'); + } else { + return (string)$x; + } + } + + public static function bigIntToPhpInt(string $s): int + { + $result = bcmod($s, bcpow('2', '64')); + if ($result[0] === '-') { + // 2^63 <= -$result + if (bccomp(bcpow('2', '63'), bcsub('0', $result)) <= 0) { + $result = bcadd($result, bcpow('2', '64')); + } + } else { + // 2^63-1 < $result + if (bccomp(bcsub(bcpow('2', '63'), '1'), $result) < 0) { + $result = bcsub($result, bcpow('2', '64')); + } + } + return (int)$result; + } + + 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; + } + } +} diff --git a/src/WebAssembly/Execution/Ref.php b/src/WebAssembly/Execution/Ref.php new file mode 100644 index 0000000..f7b6760 --- /dev/null +++ b/src/WebAssembly/Execution/Ref.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\RefType; + +abstract readonly class Ref +{ + final public static function RefNull(RefType $type): Refs\RefNull + { + return new Refs\RefNull($type); + } + + final public static function RefFunc(int $addr): Refs\RefFunc + { + return new Refs\RefFunc($addr); + } + + final public static function RefExtern(int $addr): Refs\RefExtern + { + return new Refs\RefExtern($addr); + } +} diff --git a/src/WebAssembly/Execution/Refs/RefExtern.php b/src/WebAssembly/Execution/Refs/RefExtern.php new file mode 100644 index 0000000..41f3acd --- /dev/null +++ b/src/WebAssembly/Execution/Refs/RefExtern.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Refs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Ref; + +final readonly class RefExtern extends Ref +{ + public function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/Refs/RefFunc.php b/src/WebAssembly/Execution/Refs/RefFunc.php new file mode 100644 index 0000000..3eabf4b --- /dev/null +++ b/src/WebAssembly/Execution/Refs/RefFunc.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Refs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Ref; + +final readonly class RefFunc extends Ref +{ + public function __construct( + public int $addr, + ) { + } +} diff --git a/src/WebAssembly/Execution/Refs/RefNull.php b/src/WebAssembly/Execution/Refs/RefNull.php new file mode 100644 index 0000000..438ed9e --- /dev/null +++ b/src/WebAssembly/Execution/Refs/RefNull.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Refs; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Ref; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\RefType; + +final readonly class RefNull extends Ref +{ + public function __construct( + public RefType $type, + ) { + } +} diff --git a/src/WebAssembly/Execution/Result.php b/src/WebAssembly/Execution/Result.php new file mode 100644 index 0000000..50e543c --- /dev/null +++ b/src/WebAssembly/Execution/Result.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +abstract readonly class Result +{ + /** + * @param list<int|float|Ref> $values + */ + final public static function Values(array $values): Results\Values + { + return new Results\Values($values); + } + + final public static function Trap(): Results\Trap + { + return new Results\Trap(); + } +} diff --git a/src/WebAssembly/Execution/Results/Trap.php b/src/WebAssembly/Execution/Results/Trap.php new file mode 100644 index 0000000..e0a9cfd --- /dev/null +++ b/src/WebAssembly/Execution/Results/Trap.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Results; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Result; + +final readonly class Trap extends Result +{ +} diff --git a/src/WebAssembly/Execution/Results/Values.php b/src/WebAssembly/Execution/Results/Values.php new file mode 100644 index 0000000..9391a2c --- /dev/null +++ b/src/WebAssembly/Execution/Results/Values.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution\Results; + +use Nsfisis\Waddiwasi\WebAssembly\Execution\Ref; +use Nsfisis\Waddiwasi\WebAssembly\Execution\Result; + +final readonly class Values extends Result +{ + /** + * @param list<int|float|Ref> $values + */ + protected function __construct( + public array $values, + ) { + } +} diff --git a/src/WebAssembly/Execution/Runtime.php b/src/WebAssembly/Execution/Runtime.php new file mode 100644 index 0000000..dbefea2 --- /dev/null +++ b/src/WebAssembly/Execution/Runtime.php @@ -0,0 +1,2668 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Instructions\Instr; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Instructions\Instrs; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Instructions\Instrs\Control\BlockType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Instructions\Instrs\Control\BlockTypes; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\DataModes; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\ElemModes; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Module; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\Limits; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\NumType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\ResultType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\TableType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\ValType; +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\ValTypes; +use RuntimeException; +use function abs; +use function array_map; +use function array_merge; +use function array_reverse; +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 +{ + /** + * @var array<string, array{int, int}> + */ + private array $instrMetrics = []; + + private function __construct( + public readonly Store $store, + public readonly Stack $stack, + public readonly ModuleInst $module, + ) { + } + + /** + * @param array<string, array<string, Extern>> $imports + */ + public static function instantiate( + Store $store, + Module $module, + array $imports, + ): self { + $externVals = []; + foreach ($module->imports as $import) { + $extern = $imports[$import->module][$import->name] ?? null; + if ($extern === null) { + throw new RuntimeException("instantiate: import not found: {$import->module}::{$import->name}"); + } + $externVals[] = $store->register($extern); + } + return self::doInstantiate($store, $module, $externVals); + } + + /** + * @param list<ExternVal> $externVals + */ + private static function doInstantiate( + Store $store, + Module $module, + array $externVals, + ): self { + $allocator = new Allocator($store); + + $moduleInstInit = $allocator->allocPreInitModule($module, $externVals); + + $stack = new Stack(); + $frameInit = new Frame(0, [], $moduleInstInit, 'preinit'); + $stack->pushFrame($frameInit); + + $runtimeInit = new self($store, $stack, $moduleInstInit); + + $vals = []; + foreach ($module->globals as $global) { + $vals[] = $runtimeInit->evalInstrsForInit($global->init); + assert($stack->top() === $frameInit); + } + + $refsList = []; + foreach ($module->elems as $elem) { + $refs = []; + foreach ($elem->init as $expr) { + $result = $runtimeInit->evalInstrsForInit($expr); + assert($result instanceof Ref); + $refs[] = $result; + } + $refsList[] = $refs; + } + + assert($stack->top() === $frameInit); + $stack->popFrame(); + + $moduleInst = $allocator->allocModule( + $module, + $externVals, + $vals, + $refsList, + $moduleInstInit->funcAddrs, + ); + + $runtime = new self($store, $stack, $moduleInst); + + $frame = new Frame(0, [], $moduleInst, 'init'); + $stack->pushFrame($frame); + + foreach ($module->elems as $i => $elem) { + if ($elem->mode instanceof ElemModes\Active) { + $n = count($elem->init); + $instrs = $elem->mode->offset; + $instrs[] = Instr::I32Const(0); + $instrs[] = Instr::I32Const($n); + $instrs[] = Instr::TableInit($elem->mode->table, $i); + $instrs[] = Instr::ElemDrop($i); + $runtime->execInstrsForInit($instrs); + } elseif ($elem->mode instanceof ElemModes\Declarative) { + $runtime->execInstrsForInit([Instr::ElemDrop($i)]); + } + } + foreach ($module->datas as $i => $data) { + if ($data->mode instanceof DataModes\Active) { + assert($data->mode->memory === 0); + $n = count($data->init); + $instrs = $data->mode->offset; + $instrs[] = Instr::I32Const(0); + $instrs[] = Instr::I32Const($n); + $instrs[] = Instr::MemoryInit($i); + $instrs[] = Instr::DataDrop($i); + $runtime->execInstrsForInit($instrs); + } + } + + if ($module->start !== null) { + $runtime->execInstrsForInit([Instr::Call($module->start->func)]); + } + + assert($stack->top() === $frame); + $stack->popFrame(); + + return new self($store, $stack, $moduleInst); + } + + public function getExport(string $name): ?ExternVal + { + foreach ($this->module->exports as $export) { + if ($name === $export->name) { + return $export->value; + } + } + return null; + } + + public function getExportedTable(string $name): ?TableInst + { + $export = $this->getExport($name); + if ($export instanceof ExternVals\Table) { + return $this->store->tables[$export->addr]; + } + return null; + } + + public function getExportedMemory(string $name): ?MemInst + { + $export = $this->getExport($name); + if ($export instanceof ExternVals\Mem) { + return $this->store->mems[$export->addr]; + } + return null; + } + + /** + * @return array<string, array{int, int}> + */ + public function getInstrMetrics(): array + { + asort($this->instrMetrics); + return $this->instrMetrics; + } + + /** + * @param list<int|float|Ref> $vals + * @return list<int|float|Ref> + */ + public function invoke(string $name, array $vals): array + { + try { + $export = $this->getExport($name); + assert($export instanceof ExternVals\Func); + $funcAddr = $export->addr; + + $funcInst = $this->store->funcs[$funcAddr]; + assert($funcInst instanceof FuncInsts\Wasm); + $paramTypes = $funcInst->type->params->types; + $resultTypes = $funcInst->type->results->types; + if (count($paramTypes) !== count($vals)) { + throw new RuntimeException("invoke($name) invalid function arity: expected " . count($paramTypes) . ", got " . count($vals)); + } + $f = new Frame(0, [], new ModuleInst([], [], [], [], [], [], [], []), "export: $name"); + $this->stack->pushFrame($f); + foreach ($vals as $val) { + $this->stack->pushValue($val); + } + $this->doInvokeFunc($funcAddr); + $results = $this->stack->popNValues(count($resultTypes)); + $this->stack->popFrame(); + return array_reverse($results); + } catch (RuntimeException $e) { + // @todo more reliable way to clear the stack + $this->stack->clear(); + throw $e; + } + } + + public function invokeByFuncAddr(int $funcAddr): void + { + $this->doInvokeFunc($funcAddr); + } + + private function doInvokeFunc(int $funcAddr): void + { + $fn = $this->store->funcs[$funcAddr]; + if ($fn instanceof FuncInsts\Wasm) { + $this->doInvokeWasmFunc($fn, $funcAddr); + } elseif ($fn instanceof FuncInsts\Host) { + $this->doInvokeHostFunc($fn); + } else { + throw new RuntimeException("doInvokeFunc: unreachable"); + } + } + + private function doInvokeWasmFunc(FuncInsts\Wasm $fn, int $funcAddr): void + { + $paramTypes = $fn->type->params->types; + $n = count($paramTypes); + $resultTypes = $fn->type->results->types; + $m = count($resultTypes); + $ts = $fn->code->locals; + $vals = $this->stack->popNValues($n); + $f = new Frame( + $m, + array_merge( + array_reverse($vals), + array_map(fn ($local) => self::defaultValueFromValType($local->type), $ts), + ), + $fn->module, + "wasm: $funcAddr", + ); + $this->activateFrame($f); + $l = new Label($m); + $this->execInstrs($fn->code->body, $l, null); + $this->deactivateFrame($m); + } + + private function activateFrame(Frame $f): void + { + $this->stack->pushFrame($f); + } + + private function deactivateFrame(int $arity): void + { + $vals = $this->stack->popNValues($arity); + $this->stack->popEntriesToCurrentFrame(); + for ($i = $arity - 1; 0 <= $i; $i--) { + $this->stack->pushValue($vals[$i]); + } + } + + private function activateLabel(Label $l): void + { + $this->stack->pushLabel($l); + } + + private function deactivateLabel(?int $arity): void + { + if ($arity === null) { + $vals = $this->stack->popValuesToLabel(); + } else { + $vals = $this->stack->popNValues($arity); + $this->stack->popValuesToLabel(); + } + for ($i = count($vals) - 1; 0 <= $i; $i--) { + $this->stack->pushValue($vals[$i]); + } + } + + private function doInvokeHostFunc(FuncInsts\Host $fn): void + { + $paramTypes = $fn->type->params->types; + $n = count($paramTypes); + $resultTypes = $fn->type->results->types; + $m = count($resultTypes); + + $params = array_reverse($this->stack->popNValues($n)); + $results = ($fn->callback)($this, ...$params); + if ($results === null) { + assert($m === 0); + return; + } + if (!is_array($results)) { + $results = [$results]; + } + assert($m === count($results)); + foreach ($results as $result) { + $this->stack->pushValue($result); + } + } + + /** + * @param list<Instr> $instrs + * @param ?list<int|float|Ref> $params + */ + private function execInstrs( + array $instrs, + Label $l, + ?array $params, + ): ?int { + $this->activateLabel($l); + if ($params !== null) { + foreach ($params as $param) { + $this->stack->pushValue($param); + } + } + + foreach ($instrs as $i => $instr) { + $result = $this->execInstr($instr); + if ($result !== null) { + return $result; + } + } + + $this->deactivateLabel(null); + return null; + } + + /** + * @param list<Instr> $instrs + */ + private function execInstrsForInit(array $instrs): void + { + foreach ($instrs as $i) { + $this->execInstr($i); + } + } + + /** + * @param list<Instr> $instrs + */ + private function evalInstrsForInit(array $instrs): int|float|Ref + { + $this->execInstrsForInit($instrs); + $result = $this->stack->popValue(); + return $result; + } + + private function execInstr(Instr $instr): ?int + { + return match ($instr::class) { + Instrs\Numeric\F32Abs::class => $this->execInstrNumericF32Abs($instr), + Instrs\Numeric\F32Add::class => $this->execInstrNumericF32Add($instr), + Instrs\Numeric\F32Ceil::class => $this->execInstrNumericF32Ceil($instr), + Instrs\Numeric\F32Const::class => $this->execInstrNumericF32Const($instr), + Instrs\Numeric\F32ConvertI32S::class => $this->execInstrNumericF32ConvertI32S($instr), + Instrs\Numeric\F32ConvertI32U::class => $this->execInstrNumericF32ConvertI32U($instr), + Instrs\Numeric\F32ConvertI64S::class => $this->execInstrNumericF32ConvertI64S($instr), + Instrs\Numeric\F32ConvertI64U::class => $this->execInstrNumericF32ConvertI64U($instr), + Instrs\Numeric\F32CopySign::class => $this->execInstrNumericF32CopySign($instr), + Instrs\Numeric\F32DemoteF64::class => $this->execInstrNumericF32DemoteF64($instr), + Instrs\Numeric\F32Div::class => $this->execInstrNumericF32Div($instr), + Instrs\Numeric\F32Eq::class => $this->execInstrNumericF32Eq($instr), + Instrs\Numeric\F32Floor::class => $this->execInstrNumericF32Floor($instr), + Instrs\Numeric\F32Ge::class => $this->execInstrNumericF32Ge($instr), + Instrs\Numeric\F32Gt::class => $this->execInstrNumericF32Gt($instr), + Instrs\Numeric\F32Le::class => $this->execInstrNumericF32Le($instr), + Instrs\Numeric\F32Lt::class => $this->execInstrNumericF32Lt($instr), + Instrs\Numeric\F32Max::class => $this->execInstrNumericF32Max($instr), + Instrs\Numeric\F32Min::class => $this->execInstrNumericF32Min($instr), + Instrs\Numeric\F32Mul::class => $this->execInstrNumericF32Mul($instr), + Instrs\Numeric\F32Ne::class => $this->execInstrNumericF32Ne($instr), + Instrs\Numeric\F32Nearest::class => $this->execInstrNumericF32Nearest($instr), + Instrs\Numeric\F32Neg::class => $this->execInstrNumericF32Neg($instr), + Instrs\Numeric\F32ReinterpretI32::class => $this->execInstrNumericF32ReinterpretI32($instr), + Instrs\Numeric\F32ReinterpretI64::class => $this->execInstrNumericF32ReinterpretI64($instr), + Instrs\Numeric\F32Sqrt::class => $this->execInstrNumericF32Sqrt($instr), + Instrs\Numeric\F32Sub::class => $this->execInstrNumericF32Sub($instr), + Instrs\Numeric\F32Trunc::class => $this->execInstrNumericF32Trunc($instr), + Instrs\Numeric\F64Abs::class => $this->execInstrNumericF64Abs($instr), + Instrs\Numeric\F64Add::class => $this->execInstrNumericF64Add($instr), + Instrs\Numeric\F64Ceil::class => $this->execInstrNumericF64Ceil($instr), + Instrs\Numeric\F64Const::class => $this->execInstrNumericF64Const($instr), + Instrs\Numeric\F64ConvertI32S::class => $this->execInstrNumericF64ConvertI32S($instr), + Instrs\Numeric\F64ConvertI32U::class => $this->execInstrNumericF64ConvertI32U($instr), + Instrs\Numeric\F64ConvertI64S::class => $this->execInstrNumericF64ConvertI64S($instr), + Instrs\Numeric\F64ConvertI64U::class => $this->execInstrNumericF64ConvertI64U($instr), + Instrs\Numeric\F64CopySign::class => $this->execInstrNumericF64CopySign($instr), + Instrs\Numeric\F64Div::class => $this->execInstrNumericF64Div($instr), + Instrs\Numeric\F64Eq::class => $this->execInstrNumericF64Eq($instr), + Instrs\Numeric\F64Floor::class => $this->execInstrNumericF64Floor($instr), + Instrs\Numeric\F64Ge::class => $this->execInstrNumericF64Ge($instr), + Instrs\Numeric\F64Gt::class => $this->execInstrNumericF64Gt($instr), + Instrs\Numeric\F64Le::class => $this->execInstrNumericF64Le($instr), + Instrs\Numeric\F64Lt::class => $this->execInstrNumericF64Lt($instr), + Instrs\Numeric\F64Max::class => $this->execInstrNumericF64Max($instr), + Instrs\Numeric\F64Min::class => $this->execInstrNumericF64Min($instr), + Instrs\Numeric\F64Mul::class => $this->execInstrNumericF64Mul($instr), + Instrs\Numeric\F64Ne::class => $this->execInstrNumericF64Ne($instr), + Instrs\Numeric\F64Nearest::class => $this->execInstrNumericF64Nearest($instr), + Instrs\Numeric\F64Neg::class => $this->execInstrNumericF64Neg($instr), + Instrs\Numeric\F64PromoteF32::class => $this->execInstrNumericF64PromoteF32($instr), + Instrs\Numeric\F64ReinterpretI32::class => $this->execInstrNumericF64ReinterpretI32($instr), + Instrs\Numeric\F64ReinterpretI64::class => $this->execInstrNumericF64ReinterpretI64($instr), + Instrs\Numeric\F64Sqrt::class => $this->execInstrNumericF64Sqrt($instr), + Instrs\Numeric\F64Sub::class => $this->execInstrNumericF64Sub($instr), + Instrs\Numeric\F64Trunc::class => $this->execInstrNumericF64Trunc($instr), + Instrs\Numeric\I32Add::class => $this->execInstrNumericI32Add($instr), + Instrs\Numeric\I32And::class => $this->execInstrNumericI32And($instr), + Instrs\Numeric\I32Clz::class => $this->execInstrNumericI32Clz($instr), + Instrs\Numeric\I32Const::class => $this->execInstrNumericI32Const($instr), + Instrs\Numeric\I32Ctz::class => $this->execInstrNumericI32Ctz($instr), + Instrs\Numeric\I32DivS::class => $this->execInstrNumericI32DivS($instr), + Instrs\Numeric\I32DivU::class => $this->execInstrNumericI32DivU($instr), + Instrs\Numeric\I32Eq::class => $this->execInstrNumericI32Eq($instr), + Instrs\Numeric\I32Eqz::class => $this->execInstrNumericI32Eqz($instr), + Instrs\Numeric\I32Extend16S::class => $this->execInstrNumericI32Extend16S($instr), + Instrs\Numeric\I32Extend8S::class => $this->execInstrNumericI32Extend8S($instr), + Instrs\Numeric\I32GeS::class => $this->execInstrNumericI32GeS($instr), + Instrs\Numeric\I32GeU::class => $this->execInstrNumericI32GeU($instr), + Instrs\Numeric\I32GtS::class => $this->execInstrNumericI32GtS($instr), + Instrs\Numeric\I32GtU::class => $this->execInstrNumericI32GtU($instr), + Instrs\Numeric\I32LeS::class => $this->execInstrNumericI32LeS($instr), + Instrs\Numeric\I32LeU::class => $this->execInstrNumericI32LeU($instr), + Instrs\Numeric\I32LtS::class => $this->execInstrNumericI32LtS($instr), + Instrs\Numeric\I32LtU::class => $this->execInstrNumericI32LtU($instr), + Instrs\Numeric\I32Mul::class => $this->execInstrNumericI32Mul($instr), + Instrs\Numeric\I32Ne::class => $this->execInstrNumericI32Ne($instr), + Instrs\Numeric\I32Or::class => $this->execInstrNumericI32Or($instr), + Instrs\Numeric\I32Popcnt::class => $this->execInstrNumericI32Popcnt($instr), + Instrs\Numeric\I32ReinterpretF32::class => $this->execInstrNumericI32ReinterpretF32($instr), + Instrs\Numeric\I32ReinterpretF64::class => $this->execInstrNumericI32ReinterpretF64($instr), + Instrs\Numeric\I32RemS::class => $this->execInstrNumericI32RemS($instr), + Instrs\Numeric\I32RemU::class => $this->execInstrNumericI32RemU($instr), + Instrs\Numeric\I32RotL::class => $this->execInstrNumericI32RotL($instr), + Instrs\Numeric\I32RotR::class => $this->execInstrNumericI32RotR($instr), + Instrs\Numeric\I32Shl::class => $this->execInstrNumericI32Shl($instr), + Instrs\Numeric\I32ShrS::class => $this->execInstrNumericI32ShrS($instr), + Instrs\Numeric\I32ShrU::class => $this->execInstrNumericI32ShrU($instr), + Instrs\Numeric\I32Sub::class => $this->execInstrNumericI32Sub($instr), + Instrs\Numeric\I32TruncF32S::class => $this->execInstrNumericI32TruncF32S($instr), + Instrs\Numeric\I32TruncF32U::class => $this->execInstrNumericI32TruncF32U($instr), + Instrs\Numeric\I32TruncF64S::class => $this->execInstrNumericI32TruncF64S($instr), + Instrs\Numeric\I32TruncF64U::class => $this->execInstrNumericI32TruncF64U($instr), + Instrs\Numeric\I32TruncSatF32S::class => $this->execInstrNumericI32TruncSatF32S($instr), + Instrs\Numeric\I32TruncSatF32U::class => $this->execInstrNumericI32TruncSatF32U($instr), + Instrs\Numeric\I32TruncSatF64S::class => $this->execInstrNumericI32TruncSatF64S($instr), + Instrs\Numeric\I32TruncSatF64U::class => $this->execInstrNumericI32TruncSatF64U($instr), + Instrs\Numeric\I32WrapI64::class => $this->execInstrNumericI32WrapI64($instr), + Instrs\Numeric\I32Xor::class => $this->execInstrNumericI32Xor($instr), + Instrs\Numeric\I64Add::class => $this->execInstrNumericI64Add($instr), + Instrs\Numeric\I64And::class => $this->execInstrNumericI64And($instr), + Instrs\Numeric\I64Clz::class => $this->execInstrNumericI64Clz($instr), + Instrs\Numeric\I64Const::class => $this->execInstrNumericI64Const($instr), + Instrs\Numeric\I64Ctz::class => $this->execInstrNumericI64Ctz($instr), + Instrs\Numeric\I64DivS::class => $this->execInstrNumericI64DivS($instr), + Instrs\Numeric\I64DivU::class => $this->execInstrNumericI64DivU($instr), + Instrs\Numeric\I64Eq::class => $this->execInstrNumericI64Eq($instr), + Instrs\Numeric\I64Eqz::class => $this->execInstrNumericI64Eqz($instr), + Instrs\Numeric\I64Extend16S::class => $this->execInstrNumericI64Extend16S($instr), + Instrs\Numeric\I64Extend32S::class => $this->execInstrNumericI64Extend32S($instr), + Instrs\Numeric\I64Extend8S::class => $this->execInstrNumericI64Extend8S($instr), + Instrs\Numeric\I64ExtendI32S::class => $this->execInstrNumericI64ExtendI32S($instr), + Instrs\Numeric\I64ExtendI32U::class => $this->execInstrNumericI64ExtendI32U($instr), + Instrs\Numeric\I64GeS::class => $this->execInstrNumericI64GeS($instr), + Instrs\Numeric\I64GeU::class => $this->execInstrNumericI64GeU($instr), + Instrs\Numeric\I64GtS::class => $this->execInstrNumericI64GtS($instr), + Instrs\Numeric\I64GtU::class => $this->execInstrNumericI64GtU($instr), + Instrs\Numeric\I64LeS::class => $this->execInstrNumericI64LeS($instr), + Instrs\Numeric\I64LeU::class => $this->execInstrNumericI64LeU($instr), + Instrs\Numeric\I64LtS::class => $this->execInstrNumericI64LtS($instr), + Instrs\Numeric\I64LtU::class => $this->execInstrNumericI64LtU($instr), + Instrs\Numeric\I64Mul::class => $this->execInstrNumericI64Mul($instr), + Instrs\Numeric\I64Ne::class => $this->execInstrNumericI64Ne($instr), + Instrs\Numeric\I64Or::class => $this->execInstrNumericI64Or($instr), + Instrs\Numeric\I64Popcnt::class => $this->execInstrNumericI64Popcnt($instr), + Instrs\Numeric\I64ReinterpretF32::class => $this->execInstrNumericI64ReinterpretF32($instr), + Instrs\Numeric\I64ReinterpretF64::class => $this->execInstrNumericI64ReinterpretF64($instr), + Instrs\Numeric\I64RemS::class => $this->execInstrNumericI64RemS($instr), + Instrs\Numeric\I64RemU::class => $this->execInstrNumericI64RemU($instr), + Instrs\Numeric\I64RotL::class => $this->execInstrNumericI64RotL($instr), + Instrs\Numeric\I64RotR::class => $this->execInstrNumericI64RotR($instr), + Instrs\Numeric\I64Shl::class => $this->execInstrNumericI64Shl($instr), + Instrs\Numeric\I64ShrS::class => $this->execInstrNumericI64ShrS($instr), + Instrs\Numeric\I64ShrU::class => $this->execInstrNumericI64ShrU($instr), + Instrs\Numeric\I64Sub::class => $this->execInstrNumericI64Sub($instr), + Instrs\Numeric\I64TruncF32S::class => $this->execInstrNumericI64TruncF32S($instr), + Instrs\Numeric\I64TruncF32U::class => $this->execInstrNumericI64TruncF32U($instr), + Instrs\Numeric\I64TruncF64S::class => $this->execInstrNumericI64TruncF64S($instr), + Instrs\Numeric\I64TruncF64U::class => $this->execInstrNumericI64TruncF64U($instr), + Instrs\Numeric\I64TruncSatF32S::class => $this->execInstrNumericI64TruncSatF32S($instr), + Instrs\Numeric\I64TruncSatF32U::class => $this->execInstrNumericI64TruncSatF32U($instr), + Instrs\Numeric\I64TruncSatF64S::class => $this->execInstrNumericI64TruncSatF64S($instr), + Instrs\Numeric\I64TruncSatF64U::class => $this->execInstrNumericI64TruncSatF64U($instr), + Instrs\Numeric\I64Xor::class => $this->execInstrNumericI64Xor($instr), + Instrs\Reference\RefFunc::class => $this->execInstrReferenceRefFunc($instr), + Instrs\Reference\RefIsNull::class => $this->execInstrReferenceRefIsNull($instr), + Instrs\Reference\RefNull::class => $this->execInstrReferenceRefNull($instr), + Instrs\Parametric\Drop::class => $this->execInstrParametricDrop($instr), + Instrs\Parametric\Select::class => $this->execInstrParametricSelect($instr), + Instrs\Variable\GlobalGet::class => $this->execInstrVariableGlobalGet($instr), + Instrs\Variable\GlobalSet::class => $this->execInstrVariableGlobalSet($instr), + Instrs\Variable\LocalGet::class => $this->execInstrVariableLocalGet($instr), + Instrs\Variable\LocalSet::class => $this->execInstrVariableLocalSet($instr), + Instrs\Variable\LocalTee::class => $this->execInstrVariableLocalTee($instr), + Instrs\Table\ElemDrop::class => $this->execInstrTableElemDrop($instr), + Instrs\Table\TableCopy::class => $this->execInstrTableTableCopy($instr), + Instrs\Table\TableFill::class => $this->execInstrTableTableFill($instr), + Instrs\Table\TableGet::class => $this->execInstrTableTableGet($instr), + Instrs\Table\TableGrow::class => $this->execInstrTableTableGrow($instr), + Instrs\Table\TableInit::class => $this->execInstrTableTableInit($instr), + Instrs\Table\TableSet::class => $this->execInstrTableTableSet($instr), + Instrs\Table\TableSize::class => $this->execInstrTableTableSize($instr), + Instrs\Memory\DataDrop::class => $this->execInstrMemoryDataDrop($instr), + Instrs\Memory\F32Load::class => $this->execInstrMemoryF32Load($instr), + Instrs\Memory\F32Store::class => $this->execInstrMemoryF32Store($instr), + Instrs\Memory\F64Load::class => $this->execInstrMemoryF64Load($instr), + Instrs\Memory\F64Store::class => $this->execInstrMemoryF64Store($instr), + Instrs\Memory\I32Load::class => $this->execInstrMemoryI32Load($instr), + Instrs\Memory\I32Load16S::class => $this->execInstrMemoryI32Load16S($instr), + Instrs\Memory\I32Load16U::class => $this->execInstrMemoryI32Load16U($instr), + Instrs\Memory\I32Load8S::class => $this->execInstrMemoryI32Load8S($instr), + Instrs\Memory\I32Load8U::class => $this->execInstrMemoryI32Load8U($instr), + Instrs\Memory\I32Store::class => $this->execInstrMemoryI32Store($instr), + Instrs\Memory\I32Store16::class => $this->execInstrMemoryI32Store16($instr), + Instrs\Memory\I32Store8::class => $this->execInstrMemoryI32Store8($instr), + Instrs\Memory\I64Load::class => $this->execInstrMemoryI64Load($instr), + Instrs\Memory\I64Load16S::class => $this->execInstrMemoryI64Load16S($instr), + Instrs\Memory\I64Load16U::class => $this->execInstrMemoryI64Load16U($instr), + Instrs\Memory\I64Load32S::class => $this->execInstrMemoryI64Load32S($instr), + Instrs\Memory\I64Load32U::class => $this->execInstrMemoryI64Load32U($instr), + Instrs\Memory\I64Load8S::class => $this->execInstrMemoryI64Load8S($instr), + Instrs\Memory\I64Load8U::class => $this->execInstrMemoryI64Load8U($instr), + Instrs\Memory\I64Store::class => $this->execInstrMemoryI64Store($instr), + Instrs\Memory\I64Store16::class => $this->execInstrMemoryI64Store16($instr), + Instrs\Memory\I64Store32::class => $this->execInstrMemoryI64Store32($instr), + Instrs\Memory\I64Store8::class => $this->execInstrMemoryI64Store8($instr), + Instrs\Memory\MemoryCopy::class => $this->execInstrMemoryMemoryCopy($instr), + Instrs\Memory\MemoryFill::class => $this->execInstrMemoryMemoryFill($instr), + Instrs\Memory\MemoryGrow::class => $this->execInstrMemoryMemoryGrow($instr), + Instrs\Memory\MemoryInit::class => $this->execInstrMemoryMemoryInit($instr), + Instrs\Memory\MemorySize::class => $this->execInstrMemoryMemorySize($instr), + Instrs\Control\Block::class => $this->execInstrControlBlock($instr), + Instrs\Control\Br::class => $this->execInstrControlBr($instr), + Instrs\Control\BrIf::class => $this->execInstrControlBrIf($instr), + Instrs\Control\BrTable::class => $this->execInstrControlBrTable($instr), + Instrs\Control\Call::class => $this->execInstrControlCall($instr), + Instrs\Control\CallIndirect::class => $this->execInstrControlCallIndirect($instr), + Instrs\Control\Else_::class => $this->execInstrControlElse_($instr), + Instrs\Control\End::class => $this->execInstrControlEnd($instr), + Instrs\Control\If_::class => $this->execInstrControlIf_($instr), + Instrs\Control\Loop::class => $this->execInstrControlLoop($instr), + Instrs\Control\Nop::class => $this->execInstrControlNop($instr), + Instrs\Control\Return_::class => $this->execInstrControlReturn_($instr), + Instrs\Control\Unreachable::class => $this->execInstrControlUnreachable($instr), + default => throw new RuntimeException("invalid instruction"), + }; + } + + private function execInstrNumericF32Abs(Instrs\Numeric\F32Abs $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(abs($v)); + } + + private function execInstrNumericF32Add(Instrs\Numeric\F32Add $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 + $c2)); + } + + private function execInstrNumericF32Ceil(Instrs\Numeric\F32Ceil $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(ceil($v)); + } + + private function execInstrNumericF32Const(Instrs\Numeric\F32Const $instr): void + { + $this->stack->pushValue($instr->value); + } + + private function execInstrNumericF32ConvertI32S(Instrs\Numeric\F32ConvertI32S $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + } + + private function execInstrNumericF32ConvertI32U(Instrs\Numeric\F32ConvertI32U $instr): void + { + $v = NumericOps::convertS32ToU32($this->stack->popInt()); + $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + } + + private function execInstrNumericF32ConvertI64S(Instrs\Numeric\F32ConvertI64S $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + } + + private function execInstrNumericF32ConvertI64U(Instrs\Numeric\F32ConvertI64U $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::truncateF64ToF32((float) $v)); + } + + 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); + } + + private function execInstrNumericF32DemoteF64(Instrs\Numeric\F32DemoteF64 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue($v); + } + + private function execInstrNumericF32Div(Instrs\Numeric\F32Div $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::truncateF64ToF32(fdiv($c1, $c2))); + } + + private function execInstrNumericF32Eq(Instrs\Numeric\F32Eq $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 === $c2); + } + + private function execInstrNumericF32Floor(Instrs\Numeric\F32Floor $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(floor($v)); + } + + private function execInstrNumericF32Ge(Instrs\Numeric\F32Ge $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 >= $c2); + } + + private function execInstrNumericF32Gt(Instrs\Numeric\F32Gt $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 > $c2); + } + + private function execInstrNumericF32Le(Instrs\Numeric\F32Le $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 <= $c2); + } + + private function execInstrNumericF32Lt(Instrs\Numeric\F32Lt $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 < $c2); + } + + 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)); + } + + 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)); + } + + private function execInstrNumericF32Mul(Instrs\Numeric\F32Mul $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 * $c2)); + } + + private function execInstrNumericF32Ne(Instrs\Numeric\F32Ne $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 !== $c2); + } + + private function execInstrNumericF32Nearest(Instrs\Numeric\F32Nearest $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(round($v, mode: PHP_ROUND_HALF_EVEN)); + } + + private function execInstrNumericF32Neg(Instrs\Numeric\F32Neg $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(-$c1); + } + + private function execInstrNumericF32ReinterpretI32(Instrs\Numeric\F32ReinterpretI32 $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::reinterpretI32AsF32($v)); + } + + private function execInstrNumericF32ReinterpretI64(Instrs\Numeric\F32ReinterpretI64 $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::reinterpretI64AsF32($v)); + } + + private function execInstrNumericF32Sqrt(Instrs\Numeric\F32Sqrt $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::truncateF64ToF32(sqrt($c1))); + } + + private function execInstrNumericF32Sub(Instrs\Numeric\F32Sub $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::truncateF64ToF32($c1 - $c2)); + } + + 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))); + } + } + + private function execInstrNumericF64Abs(Instrs\Numeric\F64Abs $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(abs($c1)); + } + + private function execInstrNumericF64Add(Instrs\Numeric\F64Add $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue($c1 + $c2); + } + + private function execInstrNumericF64Ceil(Instrs\Numeric\F64Ceil $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(ceil($c1)); + } + + private function execInstrNumericF64Const(Instrs\Numeric\F64Const $instr): void + { + $this->stack->pushValue($instr->value); + } + + private function execInstrNumericF64ConvertI32S(Instrs\Numeric\F64ConvertI32S $instr): void + { + $c = $this->stack->popInt(); + $this->stack->pushValue((float) $c); + } + + private function execInstrNumericF64ConvertI32U(Instrs\Numeric\F64ConvertI32U $instr): void + { + $c = $this->stack->popInt(); + $this->stack->pushValue((float) $c); + } + + private function execInstrNumericF64ConvertI64S(Instrs\Numeric\F64ConvertI64S $instr): void + { + $c = $this->stack->popInt(); + $this->stack->pushValue((float) $c); + } + + private function execInstrNumericF64ConvertI64U(Instrs\Numeric\F64ConvertI64U $instr): void + { + $c = $this->stack->popInt(); + $this->stack->pushValue((float) $c); + } + + 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); + } + + private function execInstrNumericF64Div(Instrs\Numeric\F64Div $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(fdiv($c1, $c2)); + } + + private function execInstrNumericF64Eq(Instrs\Numeric\F64Eq $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 === $c2); + } + + private function execInstrNumericF64Floor(Instrs\Numeric\F64Floor $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(floor($c1)); + } + + private function execInstrNumericF64Ge(Instrs\Numeric\F64Ge $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 >= $c2); + } + + private function execInstrNumericF64Gt(Instrs\Numeric\F64Gt $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 > $c2); + } + + private function execInstrNumericF64Le(Instrs\Numeric\F64Le $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 <= $c2); + } + + private function execInstrNumericF64Lt(Instrs\Numeric\F64Lt $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 < $c2); + } + + 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)); + } + + 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)); + } + + private function execInstrNumericF64Mul(Instrs\Numeric\F64Mul $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue($c1 * $c2); + } + + private function execInstrNumericF64Ne(Instrs\Numeric\F64Ne $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushBool($c1 !== $c2); + } + + private function execInstrNumericF64Nearest(Instrs\Numeric\F64Nearest $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(round($v, mode: PHP_ROUND_HALF_EVEN)); + } + + private function execInstrNumericF64Neg(Instrs\Numeric\F64Neg $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(-$c1); + } + + private function execInstrNumericF64PromoteF32(Instrs\Numeric\F64PromoteF32 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue($v); + } + + private function execInstrNumericF64ReinterpretI32(Instrs\Numeric\F64ReinterpretI32 $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::reinterpretI32AsF64($v)); + } + + private function execInstrNumericF64ReinterpretI64(Instrs\Numeric\F64ReinterpretI64 $instr): void + { + $v = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::reinterpretI64AsF64($v)); + } + + private function execInstrNumericF64Sqrt(Instrs\Numeric\F64Sqrt $instr): void + { + $c1 = $this->stack->popFloat(); + $this->stack->pushValue(sqrt($c1)); + } + + private function execInstrNumericF64Sub(Instrs\Numeric\F64Sub $instr): void + { + $c2 = $this->stack->popFloat(); + $c1 = $this->stack->popFloat(); + $this->stack->pushValue($c1 - $c2); + } + + 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)); + } + } + + private function execInstrNumericI32Add(Instrs\Numeric\I32Add $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 + $c2) & 0xFFFFFFFF)); + } + + 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)); + } + + 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); + } + + private function execInstrNumericI32Const(Instrs\Numeric\I32Const $instr): void + { + $this->stack->pushValue($instr->value); + } + + 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); + } + + 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); + } + if ($c1 === -2147483648 && $c2 === -1) { + throw new TrapException("i32.div_s: overflow", trapKind: TrapKind::IntegerOverflow); + } + $this->stack->pushValue(intdiv($c1, $c2)); + } + + 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); + } + $this->stack->pushValue(NumericOps::convertU32ToS32(intdiv($c1, $c2))); + } + + private function execInstrNumericI32Eq(Instrs\Numeric\I32Eq $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 === $c2); + } + + private function execInstrNumericI32Eqz(Instrs\Numeric\I32Eqz $instr): void + { + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 === 0); + } + + 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]); + } + + 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]); + } + + private function execInstrNumericI32GeS(Instrs\Numeric\I32GeS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 >= $c2); + } + + 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); + } + + private function execInstrNumericI32GtS(Instrs\Numeric\I32GtS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 > $c2); + } + + 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); + } + + private function execInstrNumericI32LeS(Instrs\Numeric\I32LeS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 <= $c2); + } + + 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); + } + + private function execInstrNumericI32LtS(Instrs\Numeric\I32LtS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 < $c2); + } + + 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); + } + + private function execInstrNumericI32Mul(Instrs\Numeric\I32Mul $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::convertU32ToS32(($c1 * $c2) & 0xFFFFFFFF)); + } + + private function execInstrNumericI32Ne(Instrs\Numeric\I32Ne $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 !== $c2); + } + + 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)); + } + + 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); + } + + private function execInstrNumericI32ReinterpretF32(Instrs\Numeric\I32ReinterpretF32 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::reinterpretF32AsI32($v)); + } + + private function execInstrNumericI32ReinterpretF64(Instrs\Numeric\I32ReinterpretF64 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::reinterpretF64AsI32($v)); + } + + 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); + } + $this->stack->pushValue($c1 % $c2); + } + + 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); + } + $this->stack->pushValue(NumericOps::convertU32ToS32($c1 % $c2)); + } + + 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)); + } + + 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)); + } + + 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)); + } + + 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); + } + } + + 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)); + } + + 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)); + } + + 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); + } + if ($v <= -2147483649.0 || 2147483648.0 <= $v) { + throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + } + $this->stack->pushValue((int) $v); + } + + 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); + } + if ($v <= -1.0 || 4294967296.0 <= $v) { + throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + } + $this->stack->pushValue(NumericOps::convertU32ToS32((int) $v)); + } + + 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); + } + if ($v <= -2147483649.0 || 2147483648.0 <= $v) { + throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + } + $this->stack->pushValue((int) $v); + } + + 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); + } + if ($v <= -1.0 || 4294967296.0 <= $v) { + throw new TrapException($instr::opName() . ": overflow", trapKind: TrapKind::IntegerOverflow); + } + $this->stack->pushValue(NumericOps::convertU32ToS32((int) $v)); + } + + 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); + } + } + + 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); + } + } + + 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); + } + } + + 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); + } + } + + private function execInstrNumericI32WrapI64(Instrs\Numeric\I32WrapI64 $instr): void + { + $c1 = $this->stack->popInt(); + $this->stack->pushValue(NumericOps::convertU32ToS32($c1 & 0xFFFFFFFF)); + } + + 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)); + } + + 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); + } + + private function execInstrNumericI64And(Instrs\Numeric\I64And $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushValue($c1 & $c2); + } + + 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); + } + + private function execInstrNumericI64Const(Instrs\Numeric\I64Const $instr): void + { + $this->stack->pushValue($instr->value); + } + + 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); + } + + 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); + } + $this->stack->pushValue(intdiv($c1, $c2)); + } + + 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); + } + $this->stack->pushValue(NumericOps::bigIntToPhpInt(bcdiv($c1, $c2, 0))); + } + + private function execInstrNumericI64Eq(Instrs\Numeric\I64Eq $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 === $c2); + } + + private function execInstrNumericI64Eqz(Instrs\Numeric\I64Eqz $instr): void + { + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 === 0); + } + + 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]); + } + + 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]); + } + + 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]); + } + + private function execInstrNumericI64ExtendI32S(Instrs\Numeric\I64ExtendI32S $instr): void + { + $c1 = $this->stack->popInt(); + $this->stack->pushValue($c1); + } + + private function execInstrNumericI64ExtendI32U(Instrs\Numeric\I64ExtendI32U $instr): void + { + $c1 = NumericOps::convertS32ToU32($this->stack->popInt()); + $c2 = $c1 & 0xFFFFFFFF; + $this->stack->pushValue($c2); + } + + private function execInstrNumericI64GeS(Instrs\Numeric\I64GeS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 >= $c2); + } + + 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); + } + + private function execInstrNumericI64GtS(Instrs\Numeric\I64GtS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 > $c2); + } + + 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); + } + + private function execInstrNumericI64LeS(Instrs\Numeric\I64LeS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 <= $c2); + } + + 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); + } + + private function execInstrNumericI64LtS(Instrs\Numeric\I64LtS $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 < $c2); + } + + 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); + } + + 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); + } + + private function execInstrNumericI64Ne(Instrs\Numeric\I64Ne $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushBool($c1 !== $c2); + } + + private function execInstrNumericI64Or(Instrs\Numeric\I64Or $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushValue($c1 | $c2); + } + + 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); + } + + private function execInstrNumericI64ReinterpretF32(Instrs\Numeric\I64ReinterpretF32 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::reinterpretF32AsI64($v)); + } + + private function execInstrNumericI64ReinterpretF64(Instrs\Numeric\I64ReinterpretF64 $instr): void + { + $v = $this->stack->popFloat(); + $this->stack->pushValue(NumericOps::reinterpretF64AsI64($v)); + } + + 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); + } + $this->stack->pushValue($c1 % $c2); + } + + 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); + } + $this->stack->pushValue(NumericOps::bigIntToPhpInt(bcmod($c1, $c2, 0))); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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))); + } + + 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); + } + + 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); + } + 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); + } + + 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); + } + 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); + } + + 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); + } + 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); + } + + 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); + } + 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); + } + + 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); + } + } + + 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); + } + } + + 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); + } + } + + 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); + } + } + + private function execInstrNumericI64Xor(Instrs\Numeric\I64Xor $instr): void + { + $c2 = $this->stack->popInt(); + $c1 = $this->stack->popInt(); + $this->stack->pushValue($c1 ^ $c2); + } + + private function execInstrReferenceRefFunc(Instrs\Reference\RefFunc $instr): void + { + $x = $instr->func; + $f = $this->stack->currentFrame(); + $a = $f->module->funcAddrs[$x]; + $this->stack->pushRefFunc($a); + } + + private function execInstrReferenceRefIsNull(Instrs\Reference\RefIsNull $instr): void + { + $val = $this->stack->popRef(); + $this->stack->pushBool($val instanceof Refs\RefNull); + } + + private function execInstrReferenceRefNull(Instrs\Reference\RefNull $instr): void + { + $t = $instr->type; + $this->stack->pushRefNull($t); + } + + private function execInstrParametricDrop(Instrs\Parametric\Drop $instr): void + { + $this->stack->popValue(); + } + + private function execInstrParametricSelect(Instrs\Parametric\Select $instr): void + { + $c = $this->stack->popInt(); + $val2 = $this->stack->popValue(); + $val1 = $this->stack->popValue(); + if ($c !== 0) { + $this->stack->pushValue($val1); + } else { + $this->stack->pushValue($val2); + } + } + + private function execInstrVariableGlobalGet(Instrs\Variable\GlobalGet $instr): void + { + $x = $instr->var; + $f = $this->stack->currentFrame(); + $a = $f->module->globalAddrs[$x]; + $glob = $this->store->globals[$a]; + $val = $glob->value; + $this->stack->pushValue($val); + } + + private function execInstrVariableGlobalSet(Instrs\Variable\GlobalSet $instr): void + { + $x = $instr->var; + $f = $this->stack->currentFrame(); + $a = $f->module->globalAddrs[$x]; + $glob = $this->store->globals[$a]; + $val = $this->stack->popValue(); + $glob->value = $val; + } + + private function execInstrVariableLocalGet(Instrs\Variable\LocalGet $instr): void + { + $x = $instr->var; + $f = $this->stack->currentFrame(); + $val = $f->locals[$x] ?? null; + if ($val === null) { + throw new RuntimeException("local.get: local $x not found in [$f->debugName]"); + } + $this->stack->pushValue($val); + } + + private function execInstrVariableLocalSet(Instrs\Variable\LocalSet $instr): void + { + $x = $instr->var; + $f = $this->stack->currentFrame(); + $val = $this->stack->popValue(); + // @phpstan-ignore-next-line + $f->locals[$x] = $val; + } + + private function execInstrVariableLocalTee(Instrs\Variable\LocalTee $instr): void + { + $x = $instr->var; + $f = $this->stack->currentFrame(); + $val = $this->stack->popValue(); + // @phpstan-ignore-next-line + $f->locals[$x] = $val; + $this->stack->pushValue($val); + } + + private function execInstrTableElemDrop(Instrs\Table\ElemDrop $instr): void + { + $x = $instr->elem; + $f = $this->stack->currentFrame(); + $a = $f->module->elemAddrs[$x]; + $elem = $this->store->elems[$a]; + // @phpstan-ignore-next-line + $this->store->elems[$a] = new ElemInst($elem->type, []); + } + + private function execInstrTableTableCopy(Instrs\Table\TableCopy $instr): void + { + $x = $instr->to; + $y = $instr->from; + $f = $this->stack->currentFrame(); + $taX = $f->module->tableAddrs[$x]; + $tabX = $this->store->tables[$taX]; + $taY = $f->module->tableAddrs[$y]; + $tabY = $this->store->tables[$taY]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $s = NumericOps::convertS32ToU32($this->stack->popInt()); + $d = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($tabX->elem) < $d + $n || count($tabY->elem) < $s + $n) { + throw new TrapException("table.copy: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + if ($n === 0 || ($x === $y && $d === $s)) { + return; + } + for ($i = 0; $i < $n; $i++) { + $s_ = ($d < $s) ? ($s + $i) : ($s + $n - 1 - $i); + $d_ = ($d < $s) ? ($d + $i) : ($d + $n - 1 - $i); + // @phpstan-ignore-next-line + $tabX->elem[$d_] = $tabY->elem[$s_]; + } + } + + private function execInstrTableTableFill(Instrs\Table\TableFill $instr): void + { + $x = $instr->table; + $f = $this->stack->currentFrame(); + $ta = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$ta]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $val = $this->stack->popRef(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($tab->elem) < $i + $n) { + throw new TrapException("table.fill: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + for ($k = 0; $k < $n; $k++) { + // @phpstan-ignore-next-line + $tab->elem[$i + $k] = $val; + } + } + + private function execInstrTableTableGet(Instrs\Table\TableGet $instr): void + { + $x = $instr->table; + $f = $this->stack->currentFrame(); + $a = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($tab->elem) <= $i) { + throw new TrapException("table.get: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + $val = $tab->elem[$i]; + $this->stack->pushValue($val); + } + + private function execInstrTableTableGrow(Instrs\Table\TableGrow $instr): void + { + $x = $instr->table; + $f = $this->stack->currentFrame(); + $a = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$a]; + $sz = count($tab->elem); + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $val = $this->stack->popRef(); + + $len = $sz + $n; + if ((1 << 32) <= $len) { + $this->stack->pushValue(-1); + return; + } + + $limits = $tab->type->limits; + $limits_ = new Limits($len, $limits->max); + if (!$limits_->isValid()) { + $this->stack->pushValue(-1); + return; + } + + for ($i = 0; $i < $n; $i++) { + $tab->elem[] = $val; + } + $tab->type = new TableType($limits_, $tab->type->refType); + + $this->stack->pushValue($sz); + } + + private function execInstrTableTableInit(Instrs\Table\TableInit $instr): void + { + $x = $instr->to; + $y = $instr->from; + $f = $this->stack->currentFrame(); + $ta = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$ta]; + $ea = $f->module->elemAddrs[$y]; + $elem = $this->store->elems[$ea]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $s = NumericOps::convertS32ToU32($this->stack->popInt()); + $d = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($elem->elem) < $s + $n) { + throw new TrapException("table.init: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + if (count($tab->elem) < $d + $n) { + throw new TrapException("table.init: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + for ($i = 0; $i < $n; $i++) { + // @phpstan-ignore-next-line + $tab->elem[$d + $i] = $elem->elem[$s + $i]; + } + } + + private function execInstrTableTableSet(Instrs\Table\TableSet $instr): void + { + $x = $instr->table; + $f = $this->stack->currentFrame(); + $a = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$a]; + $val = $this->stack->popRef(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($tab->elem) <= $i) { + throw new TrapException("table.set: out of bounds", trapKind: TrapKind::OutOfBoundsTableAccess); + } + // @phpstan-ignore-next-line + $tab->elem[$i] = $val; + } + + private function execInstrTableTableSize(Instrs\Table\TableSize $instr): void + { + $x = $instr->table; + $f = $this->stack->currentFrame(); + $a = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$a]; + $sz = count($tab->elem); + $this->stack->pushValue($sz); + } + + private function execInstrMemoryDataDrop(Instrs\Memory\DataDrop $instr): void + { + $x = $instr->data; + $f = $this->stack->currentFrame(); + $a = $f->module->dataAddrs[$x]; + // @phpstan-ignore-next-line + $this->store->datas[$a] = new DataInst([]); + } + + private function execInstrMemoryF32Load(Instrs\Memory\F32Load $instr): void + { + $this->doLoadF32($instr->offset, $instr::opName()); + } + + private function execInstrMemoryF32Store(Instrs\Memory\F32Store $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popFloat(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeF32($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryF64Load(Instrs\Memory\F64Load $instr): void + { + $this->doLoadF64($instr->offset, $instr::opName()); + } + + private function execInstrMemoryF64Store(Instrs\Memory\F64Store $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popFloat(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeF64($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI32Load(Instrs\Memory\I32Load $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI32_s32($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI32Load16S(Instrs\Memory\I32Load16S $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI32_s16($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI32Load16U(Instrs\Memory\I32Load16U $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI32_u16($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI32Load8S(Instrs\Memory\I32Load8S $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI32_s8($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI32Load8U(Instrs\Memory\I32Load8U $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI32_u8($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI32Store(Instrs\Memory\I32Store $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI32_s32($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI32Store16(Instrs\Memory\I32Store16 $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI32_s16($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI32Store8(Instrs\Memory\I32Store8 $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI32_s8($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI64Load(Instrs\Memory\I64Load $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_s64($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load16S(Instrs\Memory\I64Load16S $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_s16($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load16U(Instrs\Memory\I64Load16U $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_u16($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load32S(Instrs\Memory\I64Load32S $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_s32($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load32U(Instrs\Memory\I64Load32U $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_u32($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load8S(Instrs\Memory\I64Load8S $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_s8($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Load8U(Instrs\Memory\I64Load8U $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadI64_u8($ea); + if ($c === null) { + throw new TrapException($instr::opName() . ": out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function execInstrMemoryI64Store(Instrs\Memory\I64Store $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI64_s64($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds: $ea >= " . $mem->size(), trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI64Store16(Instrs\Memory\I64Store16 $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI64_s16($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds: $ea >= " . $mem->size(), trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI64Store32(Instrs\Memory\I64Store32 $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI64_s32($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds: $ea >= " . $mem->size(), trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryI64Store8(Instrs\Memory\I64Store8 $instr): void + { + $offset = $instr->offset; + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $c = $this->stack->popInt(); + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $ok = $mem->storeI64_s8($ea, $c); + if (!$ok) { + throw new TrapException($instr::opName() . ": out of bounds: $ea >= " . $mem->size(), trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + } + + private function execInstrMemoryMemoryCopy(Instrs\Memory\MemoryCopy $instr): void + { + $f = $this->stack->currentFrame(); + $ma = $f->module->memAddrs[0]; + $mem = $this->store->mems[$ma]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $s = NumericOps::convertS32ToU32($this->stack->popInt()); + $d = NumericOps::convertS32ToU32($this->stack->popInt()); + if ($mem->size() < $s + $n || $mem->size() < $d + $n) { + throw new TrapException("memory.copy: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $mem->memcpy($d, $s, $n); + } + + private function execInstrMemoryMemoryFill(Instrs\Memory\MemoryFill $instr): void + { + $f = $this->stack->currentFrame(); + $ma = $f->module->memAddrs[0]; + $mem = $this->store->mems[$ma]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $val = $this->stack->popInt(); + $d = NumericOps::convertS32ToU32($this->stack->popInt()); + if ($mem->size() < $d + $n) { + throw new TrapException("memory.fill: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $mem->memset($d, $val, $n); + } + + private function execInstrMemoryMemoryGrow(Instrs\Memory\MemoryGrow $instr): void + { + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $result = $mem->grow($n); + $this->stack->pushValue($result); + } + + private function execInstrMemoryMemoryInit(Instrs\Memory\MemoryInit $instr): void + { + $x = $instr->data; + $f = $this->stack->currentFrame(); + $ma = $f->module->memAddrs[0]; + $mem = $this->store->mems[$ma]; + $da = $f->module->dataAddrs[$x]; + $data = $this->store->datas[$da]; + $n = NumericOps::convertS32ToU32($this->stack->popInt()); + $s = NumericOps::convertS32ToU32($this->stack->popInt()); + $d = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($data->data) < $s + $n) { + throw new TrapException("memory.init: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + if ($mem->size() < $d + $n) { + throw new TrapException("memory.init: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $mem->copyData($data->data, $s, $d, $n); + } + + private function execInstrMemoryMemorySize(Instrs\Memory\MemorySize $instr): void + { + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $szInByte = $mem->size(); + assert(is_int($szInByte / (64 * 1024))); + $sz = $szInByte / (64 * 1024); + $this->stack->pushValue($sz); + } + + private function execInstrControlBlock(Instrs\Control\Block $instr): ?int + { + $blockType = $instr->type; + $instrs = $instr->body; + $f = $this->stack->currentFrame(); + $bt = self::expandBlockType($blockType, $f->module); + $params = array_reverse($this->stack->popNValues(count($bt->params->types))); + $n = count($bt->results->types); + $l = new Label($n); + $result = $this->execInstrs($instrs, $l, $params); + if ($result === null) { + // Do nothing. + } elseif ($result === -1) { + return -1; + } elseif ($result === 0) { + $this->deactivateLabel($n); + } else { + $this->deactivateLabel(null); + return $result - 1; + } + return null; + } + + private function execInstrControlBr(Instrs\Control\Br $instr): int + { + return $instr->label; + } + + private function execInstrControlBrIf(Instrs\Control\BrIf $instr): ?int + { + $l = $instr->label; + $c = $this->stack->popInt(); + if ($c !== 0) { + return $l; + } else { + return null; + } + } + + private function execInstrControlBrTable(Instrs\Control\BrTable $instr): int + { + $ls = $instr->labelTable; + $ln = $instr->defaultLabel; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + if ($i < count($ls)) { + return $ls[$i]; + } else { + return $ln; + } + } + + private function execInstrControlCall(Instrs\Control\Call $instr): void + { + $x = $instr->func; + $f = $this->stack->currentFrame(); + $a = $f->module->funcAddrs[$x]; + $this->doInvokeFunc($a); + } + + private function execInstrControlCallIndirect(Instrs\Control\CallIndirect $instr): void + { + $x = $instr->funcTable; + $y = $instr->type; + $f = $this->stack->currentFrame(); + $ta = $f->module->tableAddrs[$x]; + $tab = $this->store->tables[$ta]; + $ftExpect = $f->module->types[$y]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + if (count($tab->elem) <= $i) { + throw new TrapException("call_indirect: out of bounds", trapKind: TrapKind::UndefinedElement); + } + $r = $tab->elem[$i]; + if ($r instanceof Refs\RefNull) { + throw new TrapException("call_indirect: ref.null", trapKind: TrapKind::UninitializedElement); + } + assert($r instanceof Refs\RefFunc); + $a = $r->addr; + $fn = $this->store->funcs[$a]; + assert($fn instanceof FuncInsts\Wasm || $fn instanceof FuncInsts\Host); + $ftActual = $fn->type; + if (!$ftExpect->equals($ftActual)) { + throw new TrapException("call_indirect: type mismatch", trapKind: TrapKind::IndirectCallTypeMismatch); + } + $this->doInvokeFunc($a); + } + + private function execInstrControlElse_(Instrs\Control\Else_ $instr): void + { + // Do nothing. + } + + private function execInstrControlEnd(Instrs\Control\End $instr): void + { + // Do nothing. + } + + private function execInstrControlIf_(Instrs\Control\If_ $instr): ?int + { + $blockType = $instr->type; + $instrs1 = $instr->thenBody; + $instrs2 = $instr->elseBody; + $c = $this->stack->popInt(); + if ($c !== 0) { + return $this->execInstr(Instr::Block($blockType, $instrs1)); + } else { + return $this->execInstr(Instr::Block($blockType, $instrs2)); + } + } + + private function execInstrControlLoop(Instrs\Control\Loop $instr): ?int + { + $blockType = $instr->type; + $instrs = $instr->body; + $f = $this->stack->currentFrame(); + $bt = self::expandBlockType($blockType, $f->module); + $m = count($bt->params->types); + $l = new Label($m); + while (true) { + $params = array_reverse($this->stack->popNValues($m)); + $result = $this->execInstrs($instrs, $l, $params); + if ($result === null) { + return null; + } elseif ($result === -1) { + return -1; + } elseif ($result === 0) { + $this->deactivateLabel($m); + continue; + } else { + $this->deactivateLabel(null); + return $result - 1; + } + } + } + + private function execInstrControlNop(Instrs\Control\Nop $instr): void + { + // Do nothing. + } + + private function execInstrControlReturn_(Instrs\Control\Return_ $instr): int + { + return -1; + } + + private function execInstrControlUnreachable(Instrs\Control\Unreachable $instr): void + { + throw new TrapException("unreachable", trapKind: TrapKind::Unreachable); + } + + private function doLoadF32(int $offset, string $instrOpName): void + { + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadF32($ea); + if ($c === null) { + throw new TrapException("$instrOpName: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private function doLoadF64(int $offset, string $instrOpName): void + { + $f = $this->stack->currentFrame(); + $a = $f->module->memAddrs[0]; + $mem = $this->store->mems[$a]; + $i = NumericOps::convertS32ToU32($this->stack->popInt()); + $ea = $i + $offset; + $c = $mem->loadF64($ea); + if ($c === null) { + throw new TrapException("$instrOpName: out of bounds", trapKind: TrapKind::OutOfBoundsMemoryAccess); + } + $this->stack->pushValue($c); + } + + private static function defaultValueFromValType(ValType $type): int|float|Ref + { + return match ($type::class) { + ValTypes\NumType::class => match ($type->inner) { + NumType::I32 => 0, + NumType::I64 => 0, + NumType::F32 => 0.0, + NumType::F64 => 0.0, + }, + ValTypes\RefType::class => Ref::RefNull($type->inner), + default => throw new RuntimeException("unreachable"), + }; + } + + private static function expandBlockType(BlockType $bt, ModuleInst $module): FuncType + { + if ($bt instanceof BlockTypes\TypeIdx) { + return $module->types[$bt->inner]; + } elseif ($bt instanceof BlockTypes\ValType) { + $t = $bt->inner; + return new FuncType( + new ResultType([]), + new ResultType($t === null ? [] : [$t]), + ); + } else { + throw new RuntimeException("expand(): invalid blocktype"); + } + } +} diff --git a/src/WebAssembly/Execution/Stack.php b/src/WebAssembly/Execution/Stack.php new file mode 100644 index 0000000..4181dcd --- /dev/null +++ b/src/WebAssembly/Execution/Stack.php @@ -0,0 +1,212 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\RefType; +use function assert; +use function count; +use function is_float; +use function is_int; +use function is_null; + +final class Stack +{ + /** + * @var list<Frame> + */ + private array $frames = []; + + private ?Frame $currentFrame = null; + + /** + * @var list<int|float|Ref|Frame|Label> + */ + private array $entries; + + public function __construct() + { + } + + public function pushFrame(Frame $frame): void + { + if ($this->getCallStackLimit() <= count($this->frames)) { + throw new StackOverflowException(); + } + $this->push($frame); + $this->frames[] = $frame; + $this->currentFrame = $frame; + } + + public function pushLabel(Label $label): void + { + $this->push($label); + } + + public function pushValue(int|float|Ref $val): void + { + $this->push($val); + } + + public function pushBool(bool $value): void + { + $this->pushValue((int)$value); + } + + public function pushRefNull(RefType $type): void + { + $this->pushValue(Ref::RefNull($type)); + } + + public function pushRefFunc(int $addr): void + { + $this->pushValue(Ref::RefFunc($addr)); + } + + public function pushRefExtern(int $addr): void + { + $this->pushValue(Ref::RefExtern($addr)); + } + + public function clear(): void + { + $this->frames = []; + $this->currentFrame = null; + $this->entries = []; + } + + public function popFrame(): Frame + { + $result = $this->pop(); + assert($result instanceof Frame); + array_pop($this->frames); + if (count($this->frames) === 0) { + $this->currentFrame = null; + } else { + $this->currentFrame = end($this->frames); + } + return $result; + } + + public function popValue(): int|float|Ref + { + $result = $this->pop(); + assert( + is_int($result) || is_float($result) || $result instanceof Ref, + 'Expected a value on the stack, but got ' . print_r($result, true), + ); + return $result; + } + + /** + * @return list<int|float|Ref> + */ + public function popNValues(int $n): array + { + $results = []; + for ($i = 0; $i < $n; $i++) { + $results[] = $this->popValue(); + } + return $results; + } + + public function popInt(): int + { + $v = $this->popValue(); + assert(is_int($v), "Expected an int on top of the stack, but got " . self::getValueTypeName($v)); + return $v; + } + + /** + * @return F32 + */ + public function popFloat(): float + { + $v = $this->popValue(); + assert(is_float($v), "Expected a float on top of the stack, but got " . self::getValueTypeName($v)); + return $v; + } + + public function popRef(): Ref + { + $v = $this->popValue(); + assert($v instanceof Ref, "Expected a Ref on top of the stack, but got " . self::getValueTypeName($v)); + return $v; + } + + /** + * @return list<int|float|Ref> + */ + public function popValuesToLabel(): array + { + $results = []; + while (!$this->isEmpty()) { + $top = $this->pop(); + if ($top instanceof Label) { + break; + } else { + assert(is_int($top) || is_float($top) || $top instanceof Ref); + $results[] = $top; + } + } + return $results; + } + + public function popEntriesToCurrentFrame(): void + { + while (!$this->isEmpty() && !$this->top() instanceof Frame) { + $this->pop(); + } + $this->popFrame(); + } + + public function top(): int|float|Ref|Frame|Label|null + { + $n = array_key_last($this->entries); + return $n === null ? null : $this->entries[$n]; + } + + public function count(): int + { + return count($this->entries); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } + + public function currentFrame(): Frame + { + assert($this->currentFrame !== null); + return $this->currentFrame; + } + + public function getCallStackLimit(): int + { + return 1024; + } + + private function push(int|float|Ref|Frame|Label $entry): void + { + $this->entries[] = $entry; + } + + private function pop(): int|float|Ref|Frame|Label|null + { + return array_pop($this->entries); + } + + private static function getValueTypeName(int|float|Ref|Frame|Label|null $value): string + { + return match (true) { + is_null($value) => 'null', + is_int($value) => 'int', + is_float($value) => 'float', + $value instanceof Ref => 'Ref', + $value instanceof Frame => 'Frame', + $value instanceof Label => 'Label', + }; + } +} diff --git a/src/WebAssembly/Execution/StackOverflowException.php b/src/WebAssembly/Execution/StackOverflowException.php new file mode 100644 index 0000000..c5ff3a4 --- /dev/null +++ b/src/WebAssembly/Execution/StackOverflowException.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use RuntimeException; +use Throwable; + +final class StackOverflowException extends RuntimeException +{ + public function __construct( + string $message = 'Stack overflow', + int $code = 0, + Throwable $previous = null, + ) { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/WebAssembly/Execution/Store.php b/src/WebAssembly/Execution/Store.php new file mode 100644 index 0000000..5bef648 --- /dev/null +++ b/src/WebAssembly/Execution/Store.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use RuntimeException; +use function count; + +final class Store +{ + /** + * @param list<FuncInst> $funcs + * @param list<TableInst> $tables + * @param list<MemInst> $mems + * @param list<GlobalInst> $globals + * @param list<ElemInst> $elems + * @param list<DataInst> $datas + */ + public function __construct( + public array $funcs, + public array $tables, + public array $mems, + public array $globals, + public array $elems, + public array $datas, + ) { + } + + public static function empty(): self + { + return new self([], [], [], [], [], []); + } + + public function register(Extern $extern): ExternVal + { + match ($extern::class) { + Externs\Func::class => $this->funcs[] = $extern->func, + Externs\Table::class => $this->tables[] = $extern->table, + Externs\Mem::class => $this->mems[] = $extern->mem, + Externs\Global_::class => $this->globals[] = $extern->global, + default => throw new RuntimeException("unreachable"), + }; + return match ($extern::class) { + Externs\Func::class => ExternVal::Func(count($this->funcs) - 1), + Externs\Table::class => ExternVal::Table(count($this->tables) - 1), + Externs\Mem::class => ExternVal::Mem(count($this->mems) - 1), + Externs\Global_::class => ExternVal::Global_(count($this->globals) - 1), + default => throw new RuntimeException("unreachable"), + }; + } +} diff --git a/src/WebAssembly/Execution/TableInst.php b/src/WebAssembly/Execution/TableInst.php new file mode 100644 index 0000000..9f4cbe7 --- /dev/null +++ b/src/WebAssembly/Execution/TableInst.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\TableType; + +final class TableInst +{ + /** + * @param list<Ref> $elem + */ + public function __construct( + public TableType $type, + public array $elem, + ) { + } +} diff --git a/src/WebAssembly/Execution/TrapException.php b/src/WebAssembly/Execution/TrapException.php new file mode 100644 index 0000000..449f9ca --- /dev/null +++ b/src/WebAssembly/Execution/TrapException.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +use RuntimeException; +use Throwable; + +class TrapException extends RuntimeException +{ + private readonly TrapKind $trapKind; + + public function __construct( + string $message = "", + int $code = 0, + ?Throwable $previous = null, + TrapKind $trapKind = TrapKind::Unknown, + ) { + parent::__construct($message, $code, $previous); + $this->trapKind = $trapKind; + } + + public function getTrapKind(): TrapKind + { + return $this->trapKind; + } +} diff --git a/src/WebAssembly/Execution/TrapKind.php b/src/WebAssembly/Execution/TrapKind.php new file mode 100644 index 0000000..35a4372 --- /dev/null +++ b/src/WebAssembly/Execution/TrapKind.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Nsfisis\Waddiwasi\WebAssembly\Execution; + +enum TrapKind +{ + case Unknown; + case Unreachable; + case OutOfBoundsMemoryAccess; + case OutOfBoundsTableAccess; + case UninitializedElement; + case IndirectCallTypeMismatch; + case UndefinedElement; + case DivideByZero; + case IntegerOverflow; + case InvalidConversionToInteger; +} |
