aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WebAssembly/Execution/Linker.php104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/WebAssembly/Execution/Linker.php b/src/WebAssembly/Execution/Linker.php
index 89aaaba..232d924 100644
--- a/src/WebAssembly/Execution/Linker.php
+++ b/src/WebAssembly/Execution/Linker.php
@@ -4,7 +4,14 @@ declare(strict_types=1);
namespace Nsfisis\Waddiwasi\WebAssembly\Execution;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Import;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\ImportDescs;
use Nsfisis\Waddiwasi\WebAssembly\Structure\Modules\Module;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\FuncType;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\GlobalType;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\Limits;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\MemType;
+use Nsfisis\Waddiwasi\WebAssembly\Structure\Types\TableType;
final class Linker
{
@@ -42,8 +49,105 @@ final class Linker
if ($externVal === null) {
throw new LinkErrorException("import not found: {$import->module}::{$import->name}");
}
+
+ $this->validateImportTypeCompatibility($module, $import, $externVal);
+
$externVals[] = $externVal;
}
return $externVals;
}
+
+ private function validateImportTypeCompatibility(Module $module, Import $import, ExternVal $externVal): void
+ {
+ switch ($import->desc::class) {
+ case ImportDescs\Func::class:
+ if (!$externVal instanceof ExternVals\Func) {
+ throw new LinkErrorException("incompatible import type: expected function, got " . $externVal::class);
+ }
+ $expectedType = $module->types[$import->desc->func];
+ $actualFunc = $this->store->funcs[$externVal->addr];
+ $actualType = match (true) {
+ $actualFunc instanceof FuncInsts\Wasm => $actualFunc->type,
+ $actualFunc instanceof FuncInsts\Host => $actualFunc->type,
+ default => throw new LinkErrorException("unknown function instance type"),
+ };
+ if (!$this->functionsTypesMatch($expectedType, $actualType)) {
+ throw new LinkErrorException("incompatible import type: function signature mismatch");
+ }
+ break;
+
+ case ImportDescs\Global_::class:
+ if (!$externVal instanceof ExternVals\Global_) {
+ throw new LinkErrorException("incompatible import type: expected global, got " . $externVal::class);
+ }
+ $expectedType = $import->desc->global;
+ $actualGlobal = $this->store->globals[$externVal->addr];
+ if (!$this->globalTypesMatch($expectedType, $actualGlobal->type)) {
+ throw new LinkErrorException("incompatible import type: global type mismatch");
+ }
+ break;
+
+ case ImportDescs\Table::class:
+ if (!$externVal instanceof ExternVals\Table) {
+ throw new LinkErrorException("incompatible import type: expected table, got " . $externVal::class);
+ }
+ $expectedType = $import->desc->table;
+ $actualTable = $this->store->tables[$externVal->addr];
+ if (!$this->tableTypesMatch($expectedType, $actualTable->type)) {
+ throw new LinkErrorException("incompatible import type: table type mismatch");
+ }
+ break;
+
+ case ImportDescs\Mem::class:
+ if (!$externVal instanceof ExternVals\Mem) {
+ throw new LinkErrorException("incompatible import type: expected memory, got " . $externVal::class);
+ }
+ $expectedType = $import->desc->mem;
+ $actualMem = $this->store->mems[$externVal->addr];
+ if (!$this->memTypesMatch($expectedType, $actualMem->type)) {
+ throw new LinkErrorException("incompatible import type: memory type mismatch");
+ }
+ break;
+ }
+ }
+
+ private function functionsTypesMatch(FuncType $expectedType, FuncType $actualType): bool
+ {
+ return $expectedType->params === $actualType->params
+ && $expectedType->results === $actualType->results;
+ }
+
+ private function globalTypesMatch(GlobalType $expectedType, GlobalType $actualType): bool
+ {
+ return $expectedType->mut === $actualType->mut
+ && $expectedType->valType === $actualType->valType;
+ }
+
+ private function tableTypesMatch(TableType $expectedType, TableType $actualType): bool
+ {
+ return $expectedType->refType === $actualType->refType
+ && $this->limitsMatch($expectedType->limits, $actualType->limits);
+ }
+
+ private function memTypesMatch(MemType $expectedType, MemType $actualType): bool
+ {
+ return $this->limitsMatch($expectedType->limits, $actualType->limits);
+ }
+
+ private function limitsMatch(Limits $expectedLimits, Limits $actualLimits): bool
+ {
+ // Limits subtyping:
+ // For limits [n1, m1?] <: [n2, m2?]:
+ // - n1 >= n2
+ // - either:
+ // - m2 is empty
+ // - or:
+ // - both m1 and m2 are non-empty
+ // - m1 <= m2
+ $n1 = $actualLimits->min;
+ $m1 = $actualLimits->max;
+ $n2 = $expectedLimits->min;
+ $m2 = $expectedLimits->max;
+ return $n1 >= $n2 && ($m2 === null || $m1 !== null && $m1 <= $m2);
+ }
}