1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
<?php
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
{
/**
* @var array<string, array<string, ExternVal>>
*/
private array $symbols = [];
public function __construct(
public readonly Store $store,
) {
}
public function register(string $namespace, string $name, Extern $extern): void
{
$externVal = $this->store->register($extern);
$this->symbols[$namespace][$name] = $externVal;
}
public function registerNamespace(string $namespace, ExporterInterface $exporter): void
{
foreach ($exporter->exports() as $export) {
$this->symbols[$namespace][$export->name] = $export->value;
}
}
/**
* @return list<ExternVal>
*/
public function resolve(Module $module): array
{
$externVals = [];
foreach ($module->imports as $import) {
$externVal = $this->symbols[$import->module][$import->name] ?? null;
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);
}
}
|