watr 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -5
- package/readme.md +18 -24
- package/src/compile.js +430 -277
- package/src/const.js +29 -9
- package/src/encode.js +8 -4
- package/src/print.js +36 -48
- package/src/util.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watr",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Ligth & fast WAT compiler",
|
|
5
5
|
"main": "watr.js",
|
|
6
6
|
"exports": {
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/dy/watr#readme",
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"tst": "^
|
|
46
|
-
"wabt": "^1.0.28",
|
|
47
|
-
"wassemble": "^0.0.2",
|
|
48
|
-
"wat-compiler": "^1.0.0"
|
|
45
|
+
"tst": "^8.0.2"
|
|
49
46
|
}
|
|
50
47
|
}
|
package/readme.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
# watr [](https://github.com/audio-lab/watr/actions/workflows/test.js.yml) [](https://bundlephobia.com/package/watr) [](https://github.com/audio-lab/watr/actions/workflows/test.js.yml) [](https://bundlephobia.com/package/watr) [](https://npmjs.org/watr)
|
|
2
2
|
|
|
3
|
-
Light & fast
|
|
4
|
-
Useful for high-level languages or dynamic (in-browser) compilation.<br
|
|
3
|
+
Light & fast WAT compiler.<br/>
|
|
4
|
+
Useful for high-level languages or dynamic (in-browser) compilation.<br/>
|
|
5
5
|
Supports full [spec text syntax](https://webassembly.github.io/spec/core/text/index.html) and [official testsuite](https://github.com/WebAssembly/testsuite).
|
|
6
6
|
|
|
7
|
+
**[REPL](https://dy.github.io/watr/docs/repl)**
|
|
8
|
+
|
|
7
9
|
## Usage
|
|
8
10
|
|
|
9
11
|
### Compile
|
|
@@ -55,18 +57,19 @@ print(src, {
|
|
|
55
57
|
indent: ' ',
|
|
56
58
|
newline: '\n',
|
|
57
59
|
})
|
|
58
|
-
// (func
|
|
59
|
-
// (
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
60
|
+
// (func
|
|
61
|
+
// (export "double")
|
|
62
|
+
// (param f64)
|
|
63
|
+
// (result f64)
|
|
64
|
+
// (f64.mul (local.get 0) (f64.const 2))
|
|
65
|
+
// )
|
|
63
66
|
|
|
64
67
|
// minify
|
|
65
68
|
print(src, {
|
|
66
69
|
indent: false,
|
|
67
70
|
newline: false
|
|
68
71
|
})
|
|
69
|
-
// (func
|
|
72
|
+
// (func(export "double")(param f64)(result f64)(f64.mul(local.get 0)(f64.const 2)))
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
<!-- See [REPL](https://audio-lab.github.io/watr/repl.html).-->
|
|
@@ -74,24 +77,15 @@ print(src, {
|
|
|
74
77
|
## Status
|
|
75
78
|
|
|
76
79
|
* [x] core
|
|
77
|
-
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global)
|
|
78
|
-
* [x] [
|
|
79
|
-
* [x] [
|
|
80
|
-
* [x] [sign extension](https://github.com/WebAssembly/sign-extension-ops)
|
|
81
|
-
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md)
|
|
82
|
-
* [x] [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md)
|
|
83
|
-
* [x] [multiple memories](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md)
|
|
84
|
-
* [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
|
|
85
|
-
* [x] [relaxed simd](https://github.com/WebAssembly/relaxed-simd)
|
|
86
|
-
* [x] [fixed-width simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
|
|
80
|
+
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global), [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md), [nontrapping float to int](https://github.com/WebAssembly/nontrapping-float-to-int-conversions), [sign extension](https://github.com/WebAssembly/sign-extension-ops)
|
|
81
|
+
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md), [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md), [multiple memories](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md)
|
|
82
|
+
* [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md), [relaxed simd](https://github.com/WebAssembly/relaxed-simd), [fixed-width simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
|
|
87
83
|
* [x] [tail_call](https://github.com/WebAssembly/tail-call)
|
|
88
|
-
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md)
|
|
89
|
-
* [x] [
|
|
90
|
-
* [ ] [gc](https://github.com/WebAssembly/gc)
|
|
84
|
+
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
|
|
85
|
+
* [x] [gc](https://github.com/WebAssembly/gc)
|
|
91
86
|
* [ ] [exceptions](https://github.com/WebAssembly/exception-handling)
|
|
92
87
|
* [ ] [memory64](https://github.com/WebAssembly/memory64)
|
|
93
|
-
* [ ] [annotations](https://github.com/WebAssembly/annotations)
|
|
94
|
-
* [ ] [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
88
|
+
* [ ] [annotations](https://github.com/WebAssembly/annotations), [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
95
89
|
* [ ] [js strings](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md)
|
|
96
90
|
|
|
97
91
|
## Alternatives
|
package/src/compile.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
|
-
import { uleb } from './encode.js'
|
|
3
|
-
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, REFTYPE } from './const.js'
|
|
2
|
+
import { uleb, i32, i64 } from './encode.js'
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, DEFTYPE, RECTYPE, REFTYPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
+
import { clone, err } from './util.js'
|
|
5
6
|
|
|
6
7
|
// build instructions index
|
|
7
|
-
INSTR.forEach((op, i) => INSTR[op] = i >=
|
|
8
|
+
INSTR.forEach((op, i) => INSTR[op] = i >= 0x133 ? [0xfd, i - 0x133] : i >= 0x11b ? [0xfc, i - 0x11b] : i >= 0xfb ? [0xfb, i - 0xfb] : [i]);
|
|
9
|
+
|
|
8
10
|
|
|
9
|
-
// console.log(INSTR)
|
|
10
11
|
/**
|
|
11
12
|
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
12
13
|
*
|
|
@@ -35,12 +36,40 @@ export default function watr(nodes) {
|
|
|
35
36
|
return watr(nodes.map(i => i.slice(1, -1)).join(''))
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
for (let kind in SECTION) (
|
|
41
|
-
|
|
39
|
+
// scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
|
|
40
|
+
const ctx = []
|
|
41
|
+
for (let kind in SECTION) (ctx[SECTION[kind]] = ctx[kind] = []).name = kind
|
|
42
|
+
|
|
43
|
+
// initialize types
|
|
44
|
+
nodes.filter(([kind, ...node]) => {
|
|
45
|
+
// (rec (type $a (sub final? $sup* (func ...))...) (type $b ...)) -> save subtypes
|
|
46
|
+
if (kind === 'rec') {
|
|
47
|
+
// node contains a list of subtypes, (type ...) or (type (sub final? ...))
|
|
48
|
+
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
49
|
+
// add rest of subtypes as regular type nodes with subtype flag
|
|
50
|
+
for (let i = 0; i < node.length; i++) {
|
|
51
|
+
let [,...subnode] = node[i]
|
|
52
|
+
alias(subnode, ctx.type);
|
|
53
|
+
(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
|
|
54
|
+
ctx.type.push(subnode)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// (type (func param* result*))
|
|
58
|
+
// (type (array (mut i8)))
|
|
59
|
+
// (type (struct (field a)*)
|
|
60
|
+
// (type (sub final? $nm* (struct|array|func ...)))
|
|
61
|
+
else if (kind === 'type') {
|
|
62
|
+
alias(node, ctx.type);
|
|
63
|
+
ctx.type.push(typedef(node, ctx));
|
|
64
|
+
}
|
|
65
|
+
// other sections may have id
|
|
66
|
+
else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
|
|
67
|
+
|
|
68
|
+
else return true
|
|
69
|
+
})
|
|
42
70
|
|
|
43
|
-
|
|
71
|
+
// prepare/normalize nodes
|
|
72
|
+
.forEach(([kind, ...node]) => {
|
|
44
73
|
let imported // if node needs to be imported
|
|
45
74
|
|
|
46
75
|
// import abbr
|
|
@@ -48,12 +77,12 @@ export default function watr(nodes) {
|
|
|
48
77
|
if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
49
78
|
|
|
50
79
|
// index, alias
|
|
51
|
-
let
|
|
52
|
-
|
|
80
|
+
let items = ctx[kind];
|
|
81
|
+
let name = alias(node, items);
|
|
53
82
|
|
|
54
83
|
// export abbr
|
|
55
84
|
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
56
|
-
while (node[0]?.[0] === 'export')
|
|
85
|
+
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items.length]])
|
|
57
86
|
|
|
58
87
|
// for import nodes - redirect output to import
|
|
59
88
|
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
@@ -64,7 +93,7 @@ export default function watr(nodes) {
|
|
|
64
93
|
if (node[1]?.[0] === 'elem') {
|
|
65
94
|
let [reftype, [, ...els]] = node
|
|
66
95
|
node = [els.length, els.length, reftype]
|
|
67
|
-
|
|
96
|
+
ctx.elem.push([['table', name || items.length], ['i32.const', '0'], reftype, ...els])
|
|
68
97
|
}
|
|
69
98
|
}
|
|
70
99
|
|
|
@@ -72,39 +101,33 @@ export default function watr(nodes) {
|
|
|
72
101
|
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
73
102
|
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
74
103
|
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
75
|
-
|
|
104
|
+
ctx.data.push([['memory', items.length], ['i32.const', 0], ...data])
|
|
76
105
|
node = [m, m]
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
// keep start name
|
|
80
|
-
else if (kind === 'start') name && node.push(name);
|
|
81
|
-
|
|
82
|
-
// [func, [param, result]] -> [param, result], alias
|
|
83
|
-
else if (kind === 'type') node[0].shift(), node = paramres(node[0]), sections.type['$' + node.join('>')] ??= idx
|
|
84
|
-
|
|
85
108
|
// dupe to code section, save implicit type
|
|
86
109
|
else if (kind === 'func') {
|
|
87
|
-
let [idx, param, result] = typeuse(node,
|
|
88
|
-
idx
|
|
89
|
-
|
|
110
|
+
let [idx, param, result] = typeuse(node, ctx);
|
|
111
|
+
idx ??= regtype(param, result, ctx)
|
|
112
|
+
|
|
113
|
+
// we save idx because type can be defined after
|
|
114
|
+
!imported && ctx.code.push([[idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
90
115
|
node.unshift(['type', idx])
|
|
91
116
|
}
|
|
92
117
|
|
|
93
118
|
// import writes to import section amd adds placeholder for (kind) section
|
|
94
|
-
if (imported)
|
|
119
|
+
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
95
120
|
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// add implicit types - main types receive aliases, implicit types are added if no explicit types exist
|
|
100
|
-
for (let n in sections._) sections.type[n] ??= sections.type.push(sections._[n]) - 1
|
|
101
|
-
|
|
102
|
-
// patch datacount if data === 0
|
|
103
|
-
if (!sections.data.length) sections.datacount.length = 0
|
|
121
|
+
items.push(node)
|
|
122
|
+
})
|
|
104
123
|
|
|
105
124
|
// convert nodes to bytes
|
|
106
125
|
const bin = (kind, count = true) => {
|
|
107
|
-
|
|
126
|
+
const items = ctx[kind]
|
|
127
|
+
.filter(Boolean) // filter out (type, imported) placeholders
|
|
128
|
+
.map(item => build[kind](item, ctx))
|
|
129
|
+
.filter(Boolean) // filter out unrenderable things (subtype or data.length)
|
|
130
|
+
|
|
108
131
|
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
109
132
|
}
|
|
110
133
|
|
|
@@ -128,114 +151,39 @@ export default function watr(nodes) {
|
|
|
128
151
|
])
|
|
129
152
|
}
|
|
130
153
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
while (nodes.length) {
|
|
137
|
-
let node = nodes.shift()
|
|
138
|
-
|
|
139
|
-
if (typeof node === 'string') {
|
|
140
|
-
out.push(node)
|
|
141
|
-
|
|
142
|
-
if (abbr[node]) out.push(...abbr[node](nodes, ctx))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// FIXME: try to move this to abbr
|
|
146
|
-
else {
|
|
147
|
-
node = plain(node, ctx)
|
|
148
|
-
|
|
149
|
-
// (block ...) -> block ... end
|
|
150
|
-
if (node[0] === 'block' || node[0] === 'loop') {
|
|
151
|
-
out.push(...node, 'end')
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// (if ...) -> if ... end
|
|
155
|
-
else if (node[0] === 'if') {
|
|
156
|
-
let thenelse = [], blocktype = [node.shift()]
|
|
157
|
-
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
158
|
-
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
159
|
-
if (node[node.length - 1]?.[0] === 'else') thenelse.unshift(...node.pop())
|
|
160
|
-
if (node[node.length - 1]?.[0] === 'then') thenelse.unshift(...node.pop())
|
|
161
|
-
|
|
162
|
-
// label?
|
|
163
|
-
if (node[0]?.[0] === '$') blocktype.push(node.shift())
|
|
164
|
-
|
|
165
|
-
// blocktype? - (param) are removed already via plain
|
|
166
|
-
if (node[0]?.[0] === 'type' || node[0]?.[0] === 'result') blocktype.push(node.shift());
|
|
167
|
-
|
|
168
|
-
// ignore empty else
|
|
169
|
-
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
170
|
-
if (thenelse[thenelse.length - 1] === 'else') thenelse.pop()
|
|
171
|
-
|
|
172
|
-
out.push(...node, ...blocktype, ...thenelse, 'end')
|
|
173
|
-
}
|
|
174
|
-
else out.push(node)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return out
|
|
154
|
+
// consume name eg. $t ...
|
|
155
|
+
const alias = (node, list) => {
|
|
156
|
+
let name = (node[0]?.[0] === '$' || node[0]?.[0] == null) && node.shift();
|
|
157
|
+
if (name) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
158
|
+
return name
|
|
179
159
|
}
|
|
180
160
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
161
|
+
// (type $id? (func param* result*))
|
|
162
|
+
// (type $id? (array (mut i8)))
|
|
163
|
+
// (type $id? (struct (field a)*)
|
|
164
|
+
// (type $id? (sub final? $nm* (struct|array|func ...)))
|
|
165
|
+
const typedef = ([dfn], ctx) => {
|
|
166
|
+
let subkind = 'subfinal', supertypes = [], compkind
|
|
167
|
+
if (dfn[0] === 'sub') {
|
|
168
|
+
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
169
|
+
dfn = (supertypes = dfn).pop() // last item is definition
|
|
170
|
+
}
|
|
188
171
|
|
|
189
|
-
|
|
172
|
+
[compkind, ...dfn] = dfn // composite type kind
|
|
190
173
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
else if (!param.length && !result.length);
|
|
195
|
-
// (result i32) - doesn't require registering type
|
|
196
|
-
else if (!param.length && result.length === 1) out.push(['result', ...result])
|
|
197
|
-
// (param i32 i32)? (result i32 i32) - implicit type
|
|
198
|
-
else ctx._[idx = '$' + param + '>' + result] = [param, result], out.push(['type', idx])
|
|
174
|
+
if (compkind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
175
|
+
else if (compkind === 'struct') dfn = fieldseq(dfn, 'field', true)
|
|
176
|
+
else if (compkind === 'array') [dfn] = dfn
|
|
199
177
|
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
loop: (nodes, ctx) => abbr.block(nodes, ctx),
|
|
203
|
-
if: (nodes, ctx) => abbr.block(nodes, ctx),
|
|
204
|
-
|
|
205
|
-
// select (result i32 i32 i32)?
|
|
206
|
-
select: (nodes, ctx) => [paramres(nodes, 0)[1]],
|
|
207
|
-
|
|
208
|
-
// call_indirect $table? $typeidx
|
|
209
|
-
// return_call_indirect $table? $typeidx
|
|
210
|
-
call_indirect: (nodes, ctx) => {
|
|
211
|
-
let tableidx = nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0
|
|
212
|
-
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
213
|
-
return [tableidx, ['type', idx ?? (ctx._[idx = '$' + param + '>' + result] = [param, result], idx)]]
|
|
214
|
-
},
|
|
215
|
-
return_call_indirect: (nodes, ctx) => abbr.call_indirect(nodes, ctx),
|
|
216
|
-
|
|
217
|
-
// else $label
|
|
218
|
-
else: (nodes, ctx) => (nodes[0]?.[0] === '$' && nodes.shift(), []),
|
|
219
|
-
// end $label
|
|
220
|
-
end: (nodes, ctx) => (nodes[0]?.[0] === '$' && nodes.shift(), []),
|
|
221
|
-
|
|
222
|
-
// mark datacount section as required
|
|
223
|
-
'memory.init': (nodes, ctx) => (ctx.datacount[0] = true, []),
|
|
224
|
-
'data.drop': (nodes, ctx) => (ctx.datacount[0] = true, []),
|
|
225
|
-
|
|
226
|
-
// table.init tableidx? elemidx -> table.init tableidx elemidx
|
|
227
|
-
'table.init': (nodes, ctx) => [(nodes[1][0] === '$' || !isNaN(nodes[1])) ? nodes.shift() : 0, nodes.shift()],
|
|
228
|
-
|
|
229
|
-
// table.* tableidx?
|
|
230
|
-
'table.get': (nodes, ctx) => [nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0],
|
|
231
|
-
'table.set': (nodes, ctx) => abbr['table.get'](nodes, ctx),
|
|
232
|
-
'table.fill': (nodes, ctx) => abbr['table.get'](nodes, ctx),
|
|
233
|
-
'table.size': (nodes, ctx) => abbr['table.get'](nodes, ctx),
|
|
234
|
-
'table.grow': (nodes, ctx) => abbr['table.get'](nodes, ctx),
|
|
235
|
-
// table.copy tableidx? tableidx?
|
|
236
|
-
'table.copy': (nodes, ctx) => [...abbr['table.get'](nodes, ctx), nodes[0][0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0],
|
|
178
|
+
return [compkind, dfn, subkind, supertypes]
|
|
237
179
|
}
|
|
238
180
|
|
|
181
|
+
// register (implicit) type
|
|
182
|
+
const regtype = (param, result, ctx, idx='$' + param + '>' + result) => (
|
|
183
|
+
(ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1),
|
|
184
|
+
idx
|
|
185
|
+
)
|
|
186
|
+
|
|
239
187
|
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
240
188
|
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
241
189
|
const typeuse = (nodes, ctx, names) => {
|
|
@@ -246,69 +194,214 @@ const typeuse = (nodes, ctx, names) => {
|
|
|
246
194
|
[, idx] = nodes.shift();
|
|
247
195
|
[param, result] = paramres(nodes, names);
|
|
248
196
|
|
|
197
|
+
const [,srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
198
|
+
|
|
249
199
|
// check type consistency (excludes forward refs)
|
|
250
|
-
if ((param.length || result.length) &&
|
|
251
|
-
if (ctx.type[id(idx, ctx.type)].join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
200
|
+
if ((param.length || result.length) && srcParamRes.join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
252
201
|
|
|
253
|
-
return [idx]
|
|
202
|
+
return [idx, ...srcParamRes]
|
|
254
203
|
}
|
|
255
204
|
|
|
256
205
|
// implicit type (param i32 i32)(result i32)
|
|
257
|
-
[
|
|
258
|
-
|
|
259
|
-
return [, param, result]
|
|
206
|
+
return [idx, ...paramres(nodes, names)]
|
|
260
207
|
}
|
|
261
208
|
|
|
262
209
|
// consume (param t+)* (result t+)* sequence
|
|
263
210
|
const paramres = (nodes, names = true) => {
|
|
264
|
-
let param = [], result = []
|
|
211
|
+
// let param = [], result = []
|
|
265
212
|
|
|
266
213
|
// collect param (param i32 i64) (param $x? i32)
|
|
267
|
-
|
|
214
|
+
let param = fieldseq(nodes, 'param', names)
|
|
215
|
+
|
|
216
|
+
// collect result eg. (result f64 f32)(result i32)
|
|
217
|
+
let result = fieldseq(nodes, 'result')
|
|
218
|
+
|
|
219
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
220
|
+
|
|
221
|
+
return [param, result]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
225
|
+
// optionally allow or not names
|
|
226
|
+
const fieldseq = (nodes, field, names = false) => {
|
|
227
|
+
let seq = []
|
|
228
|
+
// collect field eg. (field f64 f32)(field i32)
|
|
229
|
+
while (nodes[0]?.[0] === field) {
|
|
268
230
|
let [, ...args] = nodes.shift()
|
|
269
|
-
args = args.map(t => t[0] === 'ref' && t[2] ? (HEAPTYPE[t[2]] ? (t[2]+t[0]) : t) : t); // deabbr
|
|
270
231
|
let name = args[0]?.[0] === '$' && args.shift()
|
|
271
232
|
// expose name refs, if allowed
|
|
272
|
-
if (name)
|
|
273
|
-
|
|
233
|
+
if (name) {
|
|
234
|
+
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
235
|
+
else err(`Unexpected ${field} name ${name}`)
|
|
236
|
+
}
|
|
237
|
+
seq.push(...args)
|
|
274
238
|
}
|
|
239
|
+
return seq
|
|
240
|
+
}
|
|
275
241
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
args = args.map(t => t[0] === 'ref' && t[2] ? (HEAPTYPE[t[2]] ? (t[2]+t[0]) : t) : t); // deabbr
|
|
280
|
-
result.push(...args)
|
|
281
|
-
}
|
|
242
|
+
// consume blocktype - makes sure either type or single result is returned
|
|
243
|
+
const blocktype = (nodes, ctx) => {
|
|
244
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
282
245
|
|
|
283
|
-
|
|
246
|
+
// get type - can be either idx or valtype (numtype | reftype)
|
|
247
|
+
if (!param.length && !result.length) return
|
|
284
248
|
|
|
285
|
-
|
|
249
|
+
// (result i32) - doesn't require registering type
|
|
250
|
+
if (!param.length && result.length === 1) return ['result', ...result]
|
|
251
|
+
|
|
252
|
+
// register implicit type
|
|
253
|
+
idx ??= regtype(param, result, ctx)
|
|
254
|
+
|
|
255
|
+
return ['type', idx]
|
|
286
256
|
}
|
|
287
257
|
|
|
258
|
+
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
259
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
260
|
+
const plain = (nodes, ctx) => {
|
|
261
|
+
let out = [], stack = [], label
|
|
262
|
+
|
|
263
|
+
while (nodes.length) {
|
|
264
|
+
let node = nodes.shift()
|
|
265
|
+
|
|
266
|
+
// lookup is slower than sequence of known ifs
|
|
267
|
+
if (typeof node === 'string') {
|
|
268
|
+
out.push(node)
|
|
269
|
+
|
|
270
|
+
// block typeuse?
|
|
271
|
+
if (node === 'block' || node === 'if' || node === 'loop') {
|
|
272
|
+
// (loop $l?)
|
|
273
|
+
if (nodes[0]?.[0] === '$') label = nodes.shift(), out.push(label), stack.push(label)
|
|
274
|
+
|
|
275
|
+
out.push(blocktype(nodes, ctx))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// else $label
|
|
279
|
+
// end $label - make sure it matches block label
|
|
280
|
+
else if (node === 'else' || node === 'end') {
|
|
281
|
+
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched label ${label}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// select (result i32 i32 i32)?
|
|
285
|
+
else if (node === 'select') {
|
|
286
|
+
out.push(paramres(nodes, 0)[1])
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// call_indirect $table? $typeidx
|
|
290
|
+
// return_call_indirect $table? $typeidx
|
|
291
|
+
else if (node.endsWith('call_indirect')) {
|
|
292
|
+
let tableidx = nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0
|
|
293
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
294
|
+
out.push(tableidx, ['type', idx ?? regtype(param, result, ctx)])
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// mark datacount section as required
|
|
298
|
+
else if (node === 'memory.init' || node === 'data.drop' || node === 'array.new_data' || node === 'array.init_data') {
|
|
299
|
+
ctx.datacount[0] = true
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// table.init tableidx? elemidx -> table.init tableidx elemidx
|
|
303
|
+
else if (node === 'table.init') out.push((nodes[1][0] === '$' || !isNaN(nodes[1])) ? nodes.shift() : 0, nodes.shift())
|
|
304
|
+
|
|
305
|
+
// table.* tableidx?
|
|
306
|
+
else if (node.startsWith('table.')) {
|
|
307
|
+
out.push(nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0)
|
|
308
|
+
|
|
309
|
+
// table.copy tableidx? tableidx?
|
|
310
|
+
if (node === 'table.copy') out.push(nodes[0][0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
else {
|
|
315
|
+
// (block ...) -> block ... end
|
|
316
|
+
if (node[0] === 'block' || node[0] === 'loop') {
|
|
317
|
+
out.push(...plain(node, ctx), 'end')
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// (if ...) -> if ... end
|
|
321
|
+
else if (node[0] === 'if') {
|
|
322
|
+
let then = [], els = [], immed = [node.shift()]
|
|
323
|
+
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
324
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
325
|
+
if (node[node.length - 1]?.[0] === 'else') {
|
|
326
|
+
els = plain(node.pop(), ctx)
|
|
327
|
+
// ignore empty else
|
|
328
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
329
|
+
if (els.length === 1) els.length = 0
|
|
330
|
+
}
|
|
331
|
+
if (node[node.length - 1]?.[0] === 'then') then = plain(node.pop(), ctx)
|
|
332
|
+
|
|
333
|
+
// label?
|
|
334
|
+
if (node[0]?.[0] === '$') immed.push(node.shift())
|
|
335
|
+
|
|
336
|
+
// blocktype?
|
|
337
|
+
immed.push(blocktype(node, ctx))
|
|
338
|
+
|
|
339
|
+
if (typeof node[0] === 'string') err('Unfolded condition')
|
|
340
|
+
|
|
341
|
+
out.push(...plain(node, ctx), ...immed, ...then, ...els, 'end')
|
|
342
|
+
}
|
|
343
|
+
else out.push(plain(node, ctx))
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return out
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
288
351
|
// build section binary [by section codes] (non consuming)
|
|
289
352
|
const build = [,
|
|
290
|
-
//
|
|
291
|
-
|
|
353
|
+
// type kinds
|
|
354
|
+
// (func params result)
|
|
355
|
+
// (array i8)
|
|
356
|
+
// (struct ...fields)
|
|
357
|
+
([kind, fields, subkind, supertypes, rec], ctx) => {
|
|
358
|
+
if (rec === true) return // ignore rec subtypes cept for 1st one
|
|
359
|
+
|
|
360
|
+
let details
|
|
361
|
+
// (rec (sub ...)*)
|
|
362
|
+
if (rec) {
|
|
363
|
+
kind = 'rec'
|
|
364
|
+
let [from, length] = rec, subtypes = Array.from({ length }, (_, i) => build[SECTION.type](ctx.type[from + i].slice(0, 4), ctx))
|
|
365
|
+
details = vec(subtypes)
|
|
366
|
+
}
|
|
367
|
+
// (sub final? sups* (type...))
|
|
368
|
+
else if (subkind === 'sub' || supertypes?.length) {
|
|
369
|
+
details = [...vec(supertypes.map(n => id(n, ctx.type))), ...build[SECTION.type]([kind, fields], ctx)]
|
|
370
|
+
kind = subkind
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
else if (kind === 'func') {
|
|
374
|
+
details = [...vec(fields[0].map(t => reftype(t, ctx))), ...vec(fields[1].map(t => reftype(t, ctx)))]
|
|
375
|
+
}
|
|
376
|
+
else if (kind === 'array') {
|
|
377
|
+
details = fieldtype(fields, ctx)
|
|
378
|
+
}
|
|
379
|
+
else if (kind === 'struct') {
|
|
380
|
+
details = vec(fields.map(t => fieldtype(t, ctx)))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return [DEFTYPE[kind], ...details]
|
|
384
|
+
},
|
|
292
385
|
|
|
293
|
-
// (import "math" "add" (func|table|global|memory
|
|
386
|
+
// (import "math" "add" (func|table|global|memory dfn?))
|
|
294
387
|
([mod, field, [kind, ...dfn]], ctx) => {
|
|
295
388
|
let details
|
|
296
389
|
|
|
297
|
-
if (kind
|
|
390
|
+
if (kind === 'func') {
|
|
298
391
|
// we track imported funcs in func section to share namespace, and skip them on final build
|
|
299
392
|
let [[, typeidx]] = dfn
|
|
300
393
|
details = uleb(id(typeidx, ctx.type))
|
|
301
394
|
}
|
|
302
|
-
else if (kind
|
|
395
|
+
else if (kind === 'memory') {
|
|
303
396
|
details = limits(dfn)
|
|
304
397
|
}
|
|
305
|
-
else if (kind
|
|
306
|
-
|
|
307
|
-
details = [...type(mut ? t[1] : t, ctx), mut]
|
|
398
|
+
else if (kind === 'global') {
|
|
399
|
+
details = fieldtype(dfn[0], ctx)
|
|
308
400
|
}
|
|
309
|
-
else if (kind
|
|
310
|
-
details = [...
|
|
401
|
+
else if (kind === 'table') {
|
|
402
|
+
details = [...reftype(dfn.pop(), ctx), ...limits(dfn)]
|
|
311
403
|
}
|
|
404
|
+
else err(`Unknown kind ${kind}`)
|
|
312
405
|
|
|
313
406
|
return ([...vec(str(mod.slice(1, -1))), ...vec(str(field.slice(1, -1))), KIND[kind], ...details])
|
|
314
407
|
},
|
|
@@ -318,20 +411,15 @@ const build = [,
|
|
|
318
411
|
|
|
319
412
|
// (table 1 2 funcref)
|
|
320
413
|
(node, ctx) => {
|
|
321
|
-
let lims = limits(node), t =
|
|
322
|
-
return init ? [0x40, 0x00, ...t, ...lims, ...expr(init, ctx)]
|
|
414
|
+
let lims = limits(node), t = reftype(node.shift(), ctx), [init] = node
|
|
415
|
+
return init ? [0x40, 0x00, ...t, ...lims, ...expr(init, ctx)] : [...t, ...lims]
|
|
323
416
|
},
|
|
324
417
|
|
|
325
418
|
// (memory id? export* min max shared)
|
|
326
419
|
(node, ctx) => limits(node),
|
|
327
420
|
|
|
328
421
|
// (global $id? (mut i32) (i32.const 42))
|
|
329
|
-
(
|
|
330
|
-
let [t] = node, mut = t[0] === 'mut' ? 1 : 0
|
|
331
|
-
|
|
332
|
-
let [, init] = node
|
|
333
|
-
return ([...type(mut ? t[1] : t, ctx), mut, ...expr(init, ctx)])
|
|
334
|
-
},
|
|
422
|
+
([t, init], ctx) => [...fieldtype(t, ctx), ...expr(init, ctx)],
|
|
335
423
|
|
|
336
424
|
// (export "name" (func|table|mem $name|idx))
|
|
337
425
|
([nm, [kind, l]], ctx) => ([...vec(str(nm.slice(1, -1))), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
@@ -342,74 +430,77 @@ const build = [,
|
|
|
342
430
|
// (elem elem*) - passive
|
|
343
431
|
// (elem declare elem*) - declarative
|
|
344
432
|
// (elem (table idx)? (offset expr)|(expr) elem*) - active
|
|
345
|
-
// elems := funcref|externref (item expr)|expr (item expr)|expr
|
|
346
|
-
// idxs := func? $id0 $id1
|
|
347
433
|
// ref: https://webassembly.github.io/spec/core/binary/modules.html#element-section
|
|
348
434
|
(parts, ctx) => {
|
|
349
|
-
let
|
|
435
|
+
let passive = 0, declare = 0, elexpr = 0, nofunc = 0, tabidx, offset, rt
|
|
350
436
|
|
|
351
437
|
// declare?
|
|
352
|
-
if (parts[0] === 'declare') parts.shift(),
|
|
438
|
+
if (parts[0] === 'declare') parts.shift(), declare = 1
|
|
353
439
|
|
|
354
440
|
// table?
|
|
355
441
|
if (parts[0][0] === 'table') {
|
|
356
442
|
[, tabidx] = parts.shift()
|
|
357
443
|
tabidx = id(tabidx, ctx.table)
|
|
358
|
-
// ignore table=0
|
|
359
|
-
if (tabidx) mode |= 0b010
|
|
360
444
|
}
|
|
361
445
|
|
|
362
446
|
// (offset expr)|expr
|
|
363
447
|
if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
|
|
364
448
|
offset = parts.shift()
|
|
365
449
|
if (offset[0] === 'offset') [, offset] = offset
|
|
450
|
+
offset = expr(offset, ctx)
|
|
366
451
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// func ... === funcref ..., https://webassembly.github.io/function-references/core/text/modules.html#id7
|
|
370
|
-
if (HEAPTYPE[parts[0]]) parts[0] += 'ref'
|
|
371
|
-
// reftype: funcref|externref|(ref ...)
|
|
372
|
-
if (parts[0] === 'funcref' || parts[0] === 'externref' || parts[0]?.[0] === 'ref') reftype = parts.shift()
|
|
452
|
+
// no offset = passive
|
|
453
|
+
else if (!declare) passive = 1
|
|
373
454
|
|
|
374
|
-
//
|
|
375
|
-
if (
|
|
455
|
+
// funcref|externref|(ref ...)
|
|
456
|
+
if (REFTYPE[parts[0]] || parts[0]?.[0] === 'ref') rt = reftype(parts.shift(), ctx)
|
|
457
|
+
// func ... abbr https://webassembly.github.io/function-references/core/text/modules.html#id7
|
|
458
|
+
else if (parts[0] === 'func') rt = [HEAPTYPE[parts.shift()]]
|
|
459
|
+
// or anything else
|
|
460
|
+
else rt = [HEAPTYPE.func]
|
|
376
461
|
|
|
377
|
-
//
|
|
378
|
-
if (reftype === 'externref' || reftype[0] === 'ref') offset ||= ['i32.const', 0], mode = 0b110
|
|
379
|
-
// reset to simplest mode if no actual elements
|
|
380
|
-
else if (!parts.length) mode &= 0b011
|
|
381
|
-
|
|
382
|
-
// simplify els sequence
|
|
462
|
+
// deabbr els sequence, detect expr usage
|
|
383
463
|
parts = parts.map(el => {
|
|
384
464
|
if (el[0] === 'item') [, ...el] = el
|
|
385
465
|
if (el[0] === 'ref.func') [, el] = el
|
|
386
|
-
// (ref.null func) and other expressions turn expr
|
|
387
|
-
if (typeof el !== 'string')
|
|
466
|
+
// (ref.null func) and other expressions turn expr els mode
|
|
467
|
+
if (typeof el !== 'string') elexpr = 1
|
|
388
468
|
return el
|
|
389
469
|
})
|
|
390
470
|
|
|
471
|
+
// reftype other than (ref null? func) forces table index via nofunc flag
|
|
472
|
+
// also it forces elexpr
|
|
473
|
+
if (rt[0] !== REFTYPE.funcref) nofunc = 1, elexpr = 1
|
|
474
|
+
|
|
475
|
+
// mode:
|
|
476
|
+
// bit 0 indicates a passive or declarative segment
|
|
477
|
+
// bit 1 indicates the presence of an explicit table index for an active segment
|
|
478
|
+
// and otherwise distinguishes passive from declarative segments
|
|
479
|
+
// bit 2 indicates the use of element type and element expressions instead of elemkind=0x00 and element indices.
|
|
480
|
+
let mode = (elexpr << 2) | ((passive || declare ? declare : (!!tabidx || nofunc)) << 1) | (passive || declare);
|
|
481
|
+
|
|
391
482
|
return ([
|
|
392
483
|
mode,
|
|
393
484
|
...(
|
|
394
|
-
// 0b000 e:expr y*:vec(funcidx) | type=
|
|
395
|
-
mode === 0b000 ?
|
|
485
|
+
// 0b000 e:expr y*:vec(funcidx) | type=(ref func), init ((ref.func y)end)*, active (table=0,offset=e)
|
|
486
|
+
mode === 0b000 ? offset :
|
|
396
487
|
// 0b001 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive
|
|
397
488
|
mode === 0b001 ? [0x00] :
|
|
398
489
|
// 0b010 x:tabidx e:expr et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, active (table=x,offset=e)
|
|
399
|
-
mode === 0b010 ? [...uleb(tabidx || 0), ...
|
|
490
|
+
mode === 0b010 ? [...uleb(tabidx || 0), ...offset, 0x00] :
|
|
400
491
|
// 0b011 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive declare
|
|
401
492
|
mode === 0b011 ? [0x00] :
|
|
402
|
-
// 0b100 e:expr el*:vec(expr) | type=
|
|
403
|
-
mode === 0b100 ?
|
|
493
|
+
// 0b100 e:expr el*:vec(expr) | type=(ref null func), init el*, active (table=0, offset=e)
|
|
494
|
+
mode === 0b100 ? offset :
|
|
404
495
|
// 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
|
|
405
|
-
mode === 0b101 ?
|
|
496
|
+
mode === 0b101 ? rt :
|
|
406
497
|
// 0b110 x:tabidx e:expr et:reftype el*:vec(expr) | type=et, init el*, active (table=x, offset=e)
|
|
407
|
-
mode === 0b110 ? [...uleb(tabidx || 0), ...
|
|
498
|
+
mode === 0b110 ? [...uleb(tabidx || 0), ...offset, ...rt] :
|
|
408
499
|
// 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare
|
|
409
|
-
|
|
500
|
+
rt
|
|
410
501
|
),
|
|
411
502
|
...vec(
|
|
412
|
-
parts.map(
|
|
503
|
+
parts.map(elexpr ?
|
|
413
504
|
// ((ref.func y)end)*
|
|
414
505
|
el => expr(typeof el === 'string' ? ['ref.func', el] : el, ctx) :
|
|
415
506
|
// el*
|
|
@@ -422,7 +513,7 @@ const build = [,
|
|
|
422
513
|
// (code)
|
|
423
514
|
(body, ctx) => {
|
|
424
515
|
let [typeidx, param] = body.shift()
|
|
425
|
-
if (!param) [param] = ctx.type[id(typeidx, ctx.type)]
|
|
516
|
+
if (!param) [, [param]] = ctx.type[id(typeidx, ctx.type)]
|
|
426
517
|
|
|
427
518
|
// provide param/local in ctx
|
|
428
519
|
ctx.local = Object.create(param) // list of local variables - some of them are params
|
|
@@ -435,7 +526,11 @@ const build = [,
|
|
|
435
526
|
// collect locals
|
|
436
527
|
while (body[0]?.[0] === 'local') {
|
|
437
528
|
let [, ...types] = body.shift()
|
|
438
|
-
if (types[0]?.[0] === '$')
|
|
529
|
+
if (types[0]?.[0] === '$') {
|
|
530
|
+
let name = types.shift()
|
|
531
|
+
if (name in ctx.local) err(`Duplicate local ${name}`)
|
|
532
|
+
else ctx.local[name] = ctx.local.length
|
|
533
|
+
}
|
|
439
534
|
ctx.local.push(...types)
|
|
440
535
|
}
|
|
441
536
|
|
|
@@ -451,7 +546,7 @@ const build = [,
|
|
|
451
546
|
ctx.local = ctx.block = null
|
|
452
547
|
|
|
453
548
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
454
|
-
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...
|
|
549
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...bytes])
|
|
455
550
|
},
|
|
456
551
|
|
|
457
552
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
@@ -490,10 +585,20 @@ const build = [,
|
|
|
490
585
|
(nodes, ctx) => uleb(ctx.data.length)
|
|
491
586
|
]
|
|
492
587
|
|
|
493
|
-
//
|
|
494
|
-
const
|
|
495
|
-
t[0] === 'ref' ?
|
|
496
|
-
|
|
588
|
+
// build reftype, either direct absheaptype or wrapped heaptype https://webassembly.github.io/gc/core/binary/types.html#reference-types
|
|
589
|
+
const reftype = (t, ctx) => (
|
|
590
|
+
t[0] === 'ref' ?
|
|
591
|
+
t[1] == 'null' ?
|
|
592
|
+
HEAPTYPE[t[2]] ? [HEAPTYPE[t[2]]] : [REFTYPE.refnull, ...uleb(id(t[t.length - 1], ctx.type))] :
|
|
593
|
+
[TYPE.ref, ...uleb(HEAPTYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))] :
|
|
594
|
+
// abbrs
|
|
595
|
+
[TYPE[t] ?? err(`Unknown type ${t}`)]
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
// build type with mutable flag (mut t) or t
|
|
599
|
+
const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[1] : t, ctx), mut];
|
|
600
|
+
|
|
601
|
+
|
|
497
602
|
|
|
498
603
|
// consume one instruction from nodes sequence
|
|
499
604
|
const instr = (nodes, ctx) => {
|
|
@@ -512,50 +617,41 @@ const instr = (nodes, ctx) => {
|
|
|
512
617
|
[...immed] = isNaN(op[0]) && INSTR[op] || err(`Unknown instruction ${op}`)
|
|
513
618
|
code = immed[0]
|
|
514
619
|
|
|
515
|
-
//
|
|
516
|
-
// https://github.
|
|
517
|
-
if (code ===
|
|
620
|
+
// gc-related
|
|
621
|
+
// https://webassembly.github.io/gc/core/binary/instructions.html#reference-instructions
|
|
622
|
+
if (code === 0x0fb) {
|
|
518
623
|
[, code] = immed
|
|
519
|
-
|
|
520
|
-
//
|
|
521
|
-
if (code <=
|
|
522
|
-
|
|
523
|
-
immed.push(...uleb(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
immed.push(...uleb((
|
|
529
|
-
//
|
|
530
|
-
if (code
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
for (let i = 0; i < 16; i++) immed.push(encode.i32.parse(nodes.shift()))
|
|
624
|
+
|
|
625
|
+
// struct.new $t ... array.set $t
|
|
626
|
+
if ((code >= 0 && code <= 14) || (code >= 16 && code <= 19)) {
|
|
627
|
+
let tidx = id(nodes.shift(), ctx.type)
|
|
628
|
+
immed.push(...uleb(tidx))
|
|
629
|
+
|
|
630
|
+
// struct.get|set* $t $f - read field by index from struct definition (ctx.type[structidx][dfnidx])
|
|
631
|
+
if (code >= 2 && code <= 5) immed.push(...uleb(id(nodes.shift(), ctx.type[tidx][1])))
|
|
632
|
+
// array.new_fixed $t n
|
|
633
|
+
else if (code === 8) immed.push(...uleb(nodes.shift()))
|
|
634
|
+
// array.new_data|init_data $t $d
|
|
635
|
+
else if (code === 9 || code === 18) immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
636
|
+
// array.new_elem|init_elem $t $e
|
|
637
|
+
else if (code === 10 || code === 19) immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
638
|
+
// array.copy $t $t
|
|
639
|
+
else if (code === 17) immed.push(...uleb(id(nodes.shift(), ctx.type)))
|
|
536
640
|
}
|
|
537
|
-
//
|
|
538
|
-
else if (code
|
|
539
|
-
let
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
let arr = n === 16 ? new Uint8Array(16) : n === 8 ? new Uint16Array(8) : n === 4 ? new Uint32Array(4) : new BigInt64Array(2)
|
|
544
|
-
for (let i = 0; i < n; i++) {
|
|
545
|
-
arr[i] = encode[t].parse(nodes.shift())
|
|
546
|
-
}
|
|
547
|
-
immed.push(...(new Uint8Array(arr.buffer)))
|
|
548
|
-
}
|
|
549
|
-
// f32, f64 - encode
|
|
550
|
-
else {
|
|
551
|
-
let arr = new Uint8Array(16)
|
|
552
|
-
for (let i = 0; i < n; i++) arr.set(encode[t](nodes.shift()), i * stride)
|
|
553
|
-
immed.push(...arr)
|
|
554
|
-
}
|
|
641
|
+
// ref.test|cast (ref null? $t|heaptype)
|
|
642
|
+
else if (code >= 20 && code <= 23) {
|
|
643
|
+
let ht = reftype(nodes.shift(), ctx)
|
|
644
|
+
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop()+1) // ref.test|cast (ref null $t) is next op
|
|
645
|
+
if (ht.length > 1) ht.shift() // pop ref
|
|
646
|
+
immed.push(...ht)
|
|
555
647
|
}
|
|
556
|
-
// (
|
|
557
|
-
else if (code
|
|
558
|
-
|
|
648
|
+
// br_on_cast[_fail] $l? (ref null? ht1) (ref null? ht2)
|
|
649
|
+
else if (code === 24 || code === 25) {
|
|
650
|
+
let i = blockid(nodes.shift(), ctx.block),
|
|
651
|
+
ht1 = reftype(nodes.shift(), ctx),
|
|
652
|
+
ht2 = reftype(nodes.shift(), ctx),
|
|
653
|
+
castflags = ((ht2[0] !== REFTYPE.ref) << 1) | (ht1[0] !== REFTYPE.ref)
|
|
654
|
+
immed.push(castflags, ...uleb(i), ht1.pop(), ht2.pop()) // we take only abstype or
|
|
559
655
|
}
|
|
560
656
|
}
|
|
561
657
|
|
|
@@ -592,24 +688,76 @@ const instr = (nodes, ctx) => {
|
|
|
592
688
|
}
|
|
593
689
|
}
|
|
594
690
|
|
|
691
|
+
// v128s: (v128.load x) etc
|
|
692
|
+
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
|
|
693
|
+
else if (code === 0xfd) {
|
|
694
|
+
[, code] = immed
|
|
695
|
+
immed = [0xfd, ...uleb(code)]
|
|
696
|
+
// (v128.load offset? align?)
|
|
697
|
+
if (code <= 0x0b) {
|
|
698
|
+
const [a, o] = memarg(nodes)
|
|
699
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
700
|
+
}
|
|
701
|
+
// (v128.load_lane offset? align? idx)
|
|
702
|
+
else if (code >= 0x54 && code <= 0x5d) {
|
|
703
|
+
const [a, o] = memarg(nodes)
|
|
704
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
705
|
+
// (v128.load_lane_zero)
|
|
706
|
+
if (code <= 0x5b) immed.push(...uleb(nodes.shift()))
|
|
707
|
+
}
|
|
708
|
+
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
709
|
+
else if (code === 0x0d) {
|
|
710
|
+
// i8, i16, i32 - bypass the encoding
|
|
711
|
+
for (let i = 0; i < 16; i++) immed.push(parseUint(nodes.shift(), 32))
|
|
712
|
+
}
|
|
713
|
+
// (v128.const i32x4 1 2 3 4)
|
|
714
|
+
else if (code === 0x0c) {
|
|
715
|
+
let [t, n] = nodes.shift().split('x'),
|
|
716
|
+
bits = +t.slice(1),
|
|
717
|
+
stride = bits >>> 3 // i16 -> 2, f32 -> 4
|
|
718
|
+
n = +n
|
|
719
|
+
// i8, i16, i32 - bypass the encoding
|
|
720
|
+
if (t[0] === 'i') {
|
|
721
|
+
let arr = n === 16 ? new Uint8Array(16) : n === 8 ? new Uint16Array(8) : n === 4 ? new Uint32Array(4) : new BigUint64Array(2)
|
|
722
|
+
for (let i = 0; i < n; i++) {
|
|
723
|
+
let s = nodes.shift(), v = encode[t].parse(s)
|
|
724
|
+
arr[i] = v
|
|
725
|
+
}
|
|
726
|
+
immed.push(...(new Uint8Array(arr.buffer)))
|
|
727
|
+
}
|
|
728
|
+
// f32, f64 - encode
|
|
729
|
+
else {
|
|
730
|
+
let arr = new Uint8Array(16)
|
|
731
|
+
for (let i = 0; i < n; i++) {
|
|
732
|
+
let s = nodes.shift(), v = encode[t](s)
|
|
733
|
+
arr.set(v, i * stride)
|
|
734
|
+
}
|
|
735
|
+
immed.push(...arr)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// (i8x16.extract_lane_s 0 ...)
|
|
739
|
+
else if (code >= 0x15 && code <= 0x22) {
|
|
740
|
+
immed.push(...uleb(parseUint(nodes.shift())))
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
595
744
|
// control block abbrs
|
|
596
745
|
// block ..., loop ..., if ...
|
|
597
746
|
else if (code === 2 || code === 3 || code === 4) {
|
|
598
747
|
ctx.block.push(code)
|
|
599
748
|
|
|
600
|
-
// (block $x) (loop $y)
|
|
749
|
+
// (block $x) (loop $y) - save label pointer
|
|
601
750
|
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
602
751
|
|
|
603
|
-
let
|
|
604
|
-
|
|
605
|
-
let [param, result] = typeidx !== false ? ctx.type[typeidx] : nodes[0]?.[0] === 'result' ? [, [nodes.shift()[1]]] : []
|
|
752
|
+
let t = nodes.shift();
|
|
606
753
|
|
|
607
754
|
// void
|
|
608
|
-
if (!
|
|
755
|
+
if (!t) immed.push(TYPE.void)
|
|
609
756
|
// (result i32) - doesn't require registering type
|
|
610
|
-
|
|
757
|
+
// FIXME: Make sure it is signed positive integer (leb, not uleb) https://webassembly.github.io/gc/core/binary/instructions.html#control-instructions
|
|
758
|
+
else if (t[0] === 'result') immed.push(...reftype(t[1], ctx))
|
|
611
759
|
// (type idx)
|
|
612
|
-
else immed.push(...uleb(
|
|
760
|
+
else immed.push(...uleb(id(t[1], ctx.type)))
|
|
613
761
|
}
|
|
614
762
|
// else
|
|
615
763
|
else if (code === 5) { }
|
|
@@ -638,7 +786,8 @@ const instr = (nodes, ctx) => {
|
|
|
638
786
|
immed.push(
|
|
639
787
|
...uleb(id(nodes[1][1], ctx.type)),
|
|
640
788
|
...uleb(id(nodes.shift(), ctx.table))
|
|
641
|
-
)
|
|
789
|
+
)
|
|
790
|
+
nodes.shift()
|
|
642
791
|
}
|
|
643
792
|
|
|
644
793
|
// call_ref $type
|
|
@@ -654,19 +803,14 @@ const instr = (nodes, ctx) => {
|
|
|
654
803
|
// br_if $label cond result?
|
|
655
804
|
// br_on_null $l, br_on_non_null $l
|
|
656
805
|
else if (code == 0x0c || code == 0x0d || code == 0xd5 || code == 0xd6) {
|
|
657
|
-
|
|
658
|
-
let l = nodes.shift(), i = l?.[0] === '$' ? ctx.block.length - ctx.block[l] : +l
|
|
659
|
-
i <= ctx.block.length || err(`Bad label ${l}`)
|
|
660
|
-
immed.push(...uleb(i))
|
|
806
|
+
immed.push(...uleb(blockid(nodes.shift(), ctx.block)))
|
|
661
807
|
}
|
|
662
808
|
|
|
663
809
|
// br_table 1 2 3 4 0 selector result?
|
|
664
810
|
else if (code == 0x0e) {
|
|
665
811
|
let args = []
|
|
666
812
|
while (nodes[0] && (!isNaN(nodes[0]) || nodes[0][0] === '$')) {
|
|
667
|
-
|
|
668
|
-
i <= ctx.block.length || err(`Bad label ${l}`)
|
|
669
|
-
args.push(...uleb(i))
|
|
813
|
+
args.push(...uleb(blockid(nodes.shift(), ctx.block)))
|
|
670
814
|
}
|
|
671
815
|
args.unshift(...uleb(args.length - 1))
|
|
672
816
|
immed.push(...args)
|
|
@@ -676,7 +820,7 @@ const instr = (nodes, ctx) => {
|
|
|
676
820
|
else if (code == 0x1b) {
|
|
677
821
|
let result = nodes.shift()
|
|
678
822
|
// 0x1b -> 0x1c
|
|
679
|
-
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t =>
|
|
823
|
+
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t => reftype(t, ctx))))
|
|
680
824
|
}
|
|
681
825
|
|
|
682
826
|
// ref.func $id
|
|
@@ -687,7 +831,7 @@ const instr = (nodes, ctx) => {
|
|
|
687
831
|
// ref.null func
|
|
688
832
|
else if (code == 0xd0) {
|
|
689
833
|
let t = nodes.shift()
|
|
690
|
-
immed.push(HEAPTYPE[t]
|
|
834
|
+
immed.push(...(HEAPTYPE[t] ? [HEAPTYPE[t]] : uleb(id(t, ctx.type)))) // func->funcref, extern->externref
|
|
691
835
|
}
|
|
692
836
|
|
|
693
837
|
// binary/unary (i32.add a b) - no immed
|
|
@@ -710,7 +854,7 @@ const instr = (nodes, ctx) => {
|
|
|
710
854
|
immed.push(0)
|
|
711
855
|
}
|
|
712
856
|
|
|
713
|
-
// table.get $id
|
|
857
|
+
// table.get|set $id
|
|
714
858
|
else if (code == 0x25 || code == 0x26) {
|
|
715
859
|
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
716
860
|
}
|
|
@@ -723,6 +867,16 @@ const instr = (nodes, ctx) => {
|
|
|
723
867
|
// instantiation time value initializer (consuming) - we redirect to instr
|
|
724
868
|
const expr = (node, ctx) => [...instr([node], ctx), 0x0b]
|
|
725
869
|
|
|
870
|
+
// deref id node to numeric idx
|
|
871
|
+
const id = (nm, list, n) => (n = nm[0] === '$' ? list[nm] : +nm, n in list ? n : err(`Unknown ${list.name} ${nm}`))
|
|
872
|
+
|
|
873
|
+
// block id - same as id but for block
|
|
874
|
+
// index indicates how many block items to pop
|
|
875
|
+
const blockid = (nm, block, i) => (
|
|
876
|
+
i = nm?.[0] === '$' ? block.length - block[nm] : +nm,
|
|
877
|
+
isNaN(i) || i > block.length ? err(`Bad label ${nm}`) : i
|
|
878
|
+
)
|
|
879
|
+
|
|
726
880
|
// consume align/offset params
|
|
727
881
|
const memarg = (args) => {
|
|
728
882
|
let align, offset, k, v
|
|
@@ -734,10 +888,6 @@ const memarg = (args) => {
|
|
|
734
888
|
return [align, offset]
|
|
735
889
|
}
|
|
736
890
|
|
|
737
|
-
// deref id node to idx
|
|
738
|
-
const id = (n, list) => (n = n[0] === '$' ? list[n] : !isNaN(n) && +n, n in list ? n : err(`Unknown ${list.name} ${n}`))
|
|
739
|
-
|
|
740
|
-
// ref:
|
|
741
891
|
// const ALIGN = {
|
|
742
892
|
// 'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
743
893
|
// 'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
@@ -754,7 +904,14 @@ const align = (op) => {
|
|
|
754
904
|
}
|
|
755
905
|
|
|
756
906
|
// build limits sequence (consuming)
|
|
757
|
-
const limits = (node) =>
|
|
907
|
+
const limits = (node) => (
|
|
908
|
+
isNaN(parseInt(node[1])) ? [0, ...uleb(parseUint(node.shift()))] : [node[2] === 'shared' ? 3 : 1, ...uleb(parseUint(node.shift())), ...uleb(parseUint(node.shift()))]
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
// check if node is valid int in a range
|
|
912
|
+
// we put extra condition for index ints for tests complacency
|
|
913
|
+
const parseUint = (v, max = 0xFFFFFFFF) => (typeof v === 'string' && v[0] !== '+' ? (typeof max === 'bigint' ? i64 : i32).parse(v) : typeof v === 'number' ? v : err(`Bad int ${v}`)) > max ? err(`Value out of range ${v}`) : v
|
|
914
|
+
|
|
758
915
|
|
|
759
916
|
// escape codes
|
|
760
917
|
const escape = { n: 10, r: 13, t: 9, v: 1, '"': 34, "'": 39, '\\': 92 }
|
|
@@ -772,7 +929,3 @@ const str = str => {
|
|
|
772
929
|
|
|
773
930
|
// serialize binary array
|
|
774
931
|
const vec = a => [...uleb(a.length), ...a.flat()]
|
|
775
|
-
|
|
776
|
-
const err = text => { throw Error(text) }
|
|
777
|
-
|
|
778
|
-
const clone = items => items.map(item => Array.isArray(item) ? clone(item) : item)
|
package/src/const.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// https://webassembly.github.io/spec/core/appendix/index-instructions.html
|
|
2
2
|
export const INSTR = [
|
|
3
3
|
'unreachable', 'nop', 'block', 'loop', 'if', 'else', 'then', , , , ,
|
|
4
|
-
'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', 'return_call', 'return_call_indirect','call_ref'
|
|
5
|
-
'drop', 'select', '
|
|
4
|
+
'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', 'return_call', 'return_call_indirect', 'call_ref', 'return_call_ref', , , , ,
|
|
5
|
+
'drop', 'select', '', , , ,
|
|
6
6
|
'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', 'table.get', 'table.set', ,
|
|
7
7
|
'i32.load', 'i64.load', 'f32.load', 'f64.load',
|
|
8
8
|
'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
|
|
@@ -26,14 +26,17 @@ export const INSTR = [
|
|
|
26
26
|
'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
|
|
27
27
|
'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
|
|
28
28
|
'i32.extend8_s', 'i32.extend16_s', 'i64.extend8_s', 'i64.extend16_s', 'i64.extend32_s', , , , , , , , , , , ,
|
|
29
|
-
'ref.null', 'ref.is_null', 'ref.func', ,'ref.as_non_null'
|
|
29
|
+
'ref.null', 'ref.is_null', 'ref.func', 'ref.eq', 'ref.as_non_null', 'br_on_null', 'br_on_non_null', , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// 0xFB 0xNN (0xFB shift)
|
|
32
|
+
'struct.new', 'struct.new_default', 'struct.get', 'struct.get_s', 'struct.get_u', 'struct.set', 'array.new', 'array.new_default', 'array.new_fixed', 'array.new_data', 'array.new_elem', 'array.get', 'array.get_s', 'array.get_u', 'array.set', 'array.len', 'array.fill', 'array.copy', 'array.init_data', 'array.init_elem', 'ref.test', '', 'ref.cast', '', 'br_on_cast', 'br_on_cast_fail', 'any.convert_extern', 'extern.convert_any', 'ref.i31', 'i31.get_s', 'i31.get_u', ,
|
|
33
|
+
|
|
34
|
+
// 0xFC 0xNN (0x11b shift)
|
|
32
35
|
'i32.trunc_sat_f32_s', 'i32.trunc_sat_f32_u', 'i32.trunc_sat_f64_s', 'i32.trunc_sat_f64_u', 'i64.trunc_sat_f32_s', 'i64.trunc_sat_f32_u', 'i64.trunc_sat_f64_s', 'i64.trunc_sat_f64_u',
|
|
33
36
|
'memory.init', 'data.drop', 'memory.copy', 'memory.fill', 'table.init', 'elem.drop', 'table.copy', 'table.grow', 'table.size', 'table.fill', ,
|
|
34
|
-
'i64.add128', 'i64.sub128', 'i64.mul_wide_s', 'i64.mul_wide_u', ,
|
|
37
|
+
'i64.add128', 'i64.sub128', 'i64.mul_wide_s', 'i64.mul_wide_u', ,
|
|
35
38
|
|
|
36
|
-
// 0xFD 0xNN (
|
|
39
|
+
// 0xFD 0xNN (0x133 shift)
|
|
37
40
|
'v128.load', 'v128.load8x8_s', 'v128.load8x8_u', 'v128.load16x4_s', 'v128.load16x4_u', 'v128.load32x2_s', 'v128.load32x2_u', 'v128.load8_splat', 'v128.load16_splat', 'v128.load32_splat', 'v128.load64_splat', 'v128.store', 'v128.const', 'i8x16.shuffle',
|
|
38
41
|
'i8x16.swizzle', 'i8x16.splat', 'i16x8.splat', 'i32x4.splat', 'i64x2.splat', 'f32x4.splat', 'f64x2.splat',
|
|
39
42
|
'i8x16.extract_lane_s', 'i8x16.extract_lane_u', 'i8x16.replace_lane', 'i16x8.extract_lane_s', 'i16x8.extract_lane_u', 'i16x8.replace_lane', 'i32x4.extract_lane', 'i32x4.replace_lane', 'i64x2.extract_lane', 'i64x2.replace_lane', 'f32x4.extract_lane', 'f32x4.replace_lane', 'f64x2.extract_lane', 'f64x2.replace_lane',
|
|
@@ -47,7 +50,24 @@ export const INSTR = [
|
|
|
47
50
|
'i8x16.relaxed_swizzle', 'i32x4.relaxed_trunc_f32x4_s', 'i32x4.relaxed_trunc_f32x4_u', 'i32x4.relaxed_trunc_f64x2_s_zero', 'i32x4.relaxed_trunc_f64x2_u_zero', 'f32x4.relaxed_madd', 'f32x4.relaxed_nmadd', 'f64x2.relaxed_madd', 'f64x2.relaxed_nmadd', 'i8x16.relaxed_laneselect', 'i16x8.relaxed_laneselect', 'i32x4.relaxed_laneselect', 'i64x2.relaxed_laneselect', 'f32x4.relaxed_min', 'f32x4.relaxed_max', 'f64x2.relaxed_min', 'f64x2.relaxed_max', 'i16x8.relaxed_q15mulr_s', 'i16x8.relaxed_dot_i8x16_i7x16_s', 'i32x4.relaxed_dot_i8x16_i7x16_add_s'
|
|
48
51
|
],
|
|
49
52
|
SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 },
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
RECTYPE = { sub: 0x50, subfinal: 0x4F, rec: 0x4E },
|
|
54
|
+
DEFTYPE = { func: 0x60, struct: 0x5F, array: 0x5E, ...RECTYPE },
|
|
55
|
+
HEAPTYPE = { nofunc: 0x73, noextern: 0x72, none: 0x71, func: 0x70, extern: 0x6F, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A },
|
|
56
|
+
REFTYPE = {
|
|
57
|
+
// absheaptype abbrs
|
|
58
|
+
nullfuncref: HEAPTYPE.nofunc,
|
|
59
|
+
nullexternref: HEAPTYPE.noextern,
|
|
60
|
+
nullref: HEAPTYPE.none,
|
|
61
|
+
funcref: HEAPTYPE.func,
|
|
62
|
+
externref: HEAPTYPE.extern,
|
|
63
|
+
anyref: HEAPTYPE.any,
|
|
64
|
+
eqref: HEAPTYPE.eq,
|
|
65
|
+
i31ref: HEAPTYPE.i31,
|
|
66
|
+
structref: HEAPTYPE.struct,
|
|
67
|
+
arrayref: HEAPTYPE.array,
|
|
68
|
+
|
|
69
|
+
// ref, refnull
|
|
70
|
+
ref: 0x64 /* -0x1c */, refnull: 0x63 /* -0x1d */
|
|
71
|
+
},
|
|
72
|
+
TYPE = { i8: 0x78, i16: 0x77, i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, v128: 0x7B, ...HEAPTYPE, ...REFTYPE },
|
|
53
73
|
KIND = { func: 0, table: 1, memory: 2, global: 3 }
|
package/src/encode.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { err, intRE, sepRE } from './util.js'
|
|
2
|
+
|
|
1
3
|
// encoding ref: https://github.com/j-s-n/WebBS/blob/master/compiler/byteCode.js
|
|
2
4
|
|
|
3
5
|
// uleb
|
|
@@ -48,11 +50,13 @@ export function i32(n, buffer = []) {
|
|
|
48
50
|
return buffer
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// for tests complacency we check format
|
|
54
|
+
const cleanInt = (v) => (!sepRE.test(v) && intRE.test(v=v.replaceAll('_',''))) ? v : err(`Bad int ${v}`)
|
|
51
55
|
|
|
52
56
|
// alias
|
|
53
57
|
export const i8 = i32, i16 = i32
|
|
54
58
|
|
|
55
|
-
i32.parse = n => parseInt(n
|
|
59
|
+
i32.parse = n => parseInt(cleanInt(n))
|
|
56
60
|
|
|
57
61
|
// bigleb
|
|
58
62
|
export function i64(n, buffer = []) {
|
|
@@ -70,10 +74,10 @@ export function i64(n, buffer = []) {
|
|
|
70
74
|
return buffer
|
|
71
75
|
}
|
|
72
76
|
i64.parse = n => {
|
|
73
|
-
n = n
|
|
74
|
-
n = n[0] === '-' ? -BigInt(n.slice(1)) : BigInt(n)
|
|
77
|
+
n = cleanInt(n)
|
|
78
|
+
n = n[0] === '-' ? -BigInt(n.slice(1)) : BigInt(n) // can be -0x123
|
|
75
79
|
byteView.setBigInt64(0, n)
|
|
76
|
-
return
|
|
80
|
+
return byteView.getBigInt64(0)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
const byteView = new DataView(new Float64Array(1).buffer)
|
package/src/print.js
CHANGED
|
@@ -1,69 +1,57 @@
|
|
|
1
1
|
import parse from './parse.js';
|
|
2
2
|
|
|
3
|
-
let indent = '', newline = '\n'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Formats a tree or a WAT string.
|
|
5
|
+
* Formats a tree or a WAT (WebAssembly Text) string into a readable format.
|
|
7
6
|
*
|
|
8
|
-
* @param {string | Array} tree - The code to print. If a string is provided, it will be parsed first.
|
|
9
|
-
* @param {Object} [options] -
|
|
10
|
-
* @param {string} [options.indent=' '] - The string used for one level of indentation.
|
|
11
|
-
* @param {string} [options.newline='\n'] - The string used for line breaks.
|
|
7
|
+
* @param {string | Array} tree - The code to print. If a string is provided, it will be parsed into a tree structure first.
|
|
8
|
+
* @param {Object} [options={}] - Optional settings for printing.
|
|
9
|
+
* @param {string} [options.indent=' '] - The string used for one level of indentation. Defaults to two spaces.
|
|
10
|
+
* @param {string} [options.newline='\n'] - The string used for line breaks. Defaults to a newline character.
|
|
12
11
|
* @returns {string} The formatted WAT string.
|
|
13
12
|
*/
|
|
14
13
|
export default function print(tree, options = {}) {
|
|
15
14
|
if (typeof tree === 'string') tree = parse(tree);
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
indent ||= '', newline ||= '';
|
|
16
|
+
let { indent=' ', newline='\n' } = options;
|
|
17
|
+
indent ||= '', newline ||= ''; // false -> str
|
|
19
18
|
|
|
20
19
|
return typeof tree[0] === 'string' ? printNode(tree) : tree.map(node => printNode(node)).join(newline)
|
|
21
|
-
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'local',
|
|
26
|
-
'drop',
|
|
27
|
-
'f32.const',
|
|
28
|
-
'f64.const',
|
|
29
|
-
'i32.const',
|
|
30
|
-
'i64.const',
|
|
31
|
-
'local.get',
|
|
32
|
-
'global.get',
|
|
33
|
-
'memory.size',
|
|
34
|
-
'result',
|
|
35
|
-
'export',
|
|
36
|
-
'unreachable',
|
|
37
|
-
'nop'
|
|
38
|
-
]
|
|
21
|
+
function printNode(node, level = 0) {
|
|
22
|
+
if (!Array.isArray(node)) return node
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
if (!Array.isArray(node)) return node + ''
|
|
24
|
+
let content = node[0]
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
// flat node (no deep subnodes), eg. (i32.const 1), (module (export "") 1)
|
|
27
|
+
let flat = !!newline && node.length < 4
|
|
28
|
+
let curIndent = indent.repeat(level + 1)
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
for (let i = 1; i < node.length; i++) {
|
|
31
|
+
// (<keyword> ...)
|
|
32
|
+
if (Array.isArray(node[i])) {
|
|
33
|
+
// check if it's still flat
|
|
34
|
+
if (flat) flat = node[i].every(subnode => !Array.isArray(subnode))
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
content += newline
|
|
58
|
-
|
|
36
|
+
// new line
|
|
37
|
+
content += newline + curIndent + printNode(node[i], level + 1)
|
|
38
|
+
}
|
|
39
|
+
// data chunks "\00..."
|
|
40
|
+
else if (node[0] === 'data') {
|
|
41
|
+
flat = false;
|
|
42
|
+
if (newline || content[content.length-1] !== ')') content += newline || ' '
|
|
43
|
+
content += curIndent + node[i]
|
|
44
|
+
}
|
|
45
|
+
// inline nodes
|
|
46
|
+
else {
|
|
47
|
+
if (newline || content[content.length-1] !== ')') content += ' '
|
|
48
|
+
content += node[i]
|
|
59
49
|
}
|
|
60
|
-
|
|
61
|
-
content += printNode(node[i], level + 1)
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
content += ` `
|
|
65
|
-
content += node[i]
|
|
66
50
|
}
|
|
51
|
+
|
|
52
|
+
// shrink unnecessary spaces
|
|
53
|
+
if (flat) return `(${content.replaceAll(newline + curIndent + '(', ' (')})`
|
|
54
|
+
|
|
55
|
+
return `(${content + newline + indent.repeat(level)})`
|
|
67
56
|
}
|
|
68
|
-
return `(${content})`
|
|
69
57
|
}
|
package/src/util.js
ADDED