watr 3.1.0 → 3.2.0
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 +395 -239
- 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.
|
|
3
|
+
"version": "3.2.0",
|
|
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,25 +36,39 @@ 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
|
+
ctx._ = {} // implicit types
|
|
43
|
+
|
|
44
|
+
let subc // current subtype count
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
// prepare/normalize nodes
|
|
47
|
+
while (nodes.length) {
|
|
48
|
+
let [kind, ...node] = nodes.shift()
|
|
44
49
|
let imported // if node needs to be imported
|
|
50
|
+
let rec // number of subtypes under rec type
|
|
51
|
+
|
|
52
|
+
// (rec (type $a (sub final? $sup* (func ...))...) (type $b ...)) -> save subtypes
|
|
53
|
+
if (kind === 'rec') {
|
|
54
|
+
// node contains a list of subtypes, (type ...) or (type (sub final? ...))
|
|
55
|
+
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
56
|
+
// add rest of subtypes as regular type nodes with subtype flag
|
|
57
|
+
if (node.length > 1) rec = subc = node.length, nodes.unshift(...node), node = nodes.shift(), kind = node.shift()
|
|
58
|
+
else kind = (node = node[0]).shift()
|
|
59
|
+
}
|
|
45
60
|
|
|
46
61
|
// import abbr
|
|
47
62
|
// (import m n (table|memory|global|func id? type)) -> (table|memory|global|func id? (import m n) type)
|
|
48
|
-
if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
63
|
+
else if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
49
64
|
|
|
50
65
|
// index, alias
|
|
51
|
-
let
|
|
52
|
-
|
|
66
|
+
let items = ctx[kind];
|
|
67
|
+
let name = alias(node, items)
|
|
53
68
|
|
|
54
69
|
// export abbr
|
|
55
70
|
// (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')
|
|
71
|
+
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items.length]])
|
|
57
72
|
|
|
58
73
|
// for import nodes - redirect output to import
|
|
59
74
|
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
@@ -64,7 +79,7 @@ export default function watr(nodes) {
|
|
|
64
79
|
if (node[1]?.[0] === 'elem') {
|
|
65
80
|
let [reftype, [, ...els]] = node
|
|
66
81
|
node = [els.length, els.length, reftype]
|
|
67
|
-
|
|
82
|
+
ctx.elem.push([['table', name || items.length], ['i32.const', '0'], reftype, ...els])
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
|
|
@@ -72,39 +87,64 @@ export default function watr(nodes) {
|
|
|
72
87
|
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
73
88
|
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
74
89
|
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
75
|
-
|
|
90
|
+
ctx.data.push([['memory', items.length], ['i32.const', 0], ...data])
|
|
76
91
|
node = [m, m]
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// keep start name
|
|
80
|
-
else if (kind === 'start') name && node.push(name)
|
|
95
|
+
else if (kind === 'start') name && node.push(name)
|
|
96
|
+
|
|
97
|
+
// normalize type definition to (func|array|struct dfn) form
|
|
98
|
+
// (type (func param* result*))
|
|
99
|
+
// (type (array (mut i8)))
|
|
100
|
+
// (type (struct (field a)*)
|
|
101
|
+
// (type (sub final? $nm* (struct|array|func ...)))
|
|
102
|
+
else if (kind === 'type') {
|
|
103
|
+
let [dfn] = node
|
|
104
|
+
let issub = subc-- > 0
|
|
105
|
+
let subkind = issub && 'subfinal', supertypes = []
|
|
106
|
+
if (dfn[0] === 'sub') {
|
|
107
|
+
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
108
|
+
dfn = (supertypes = dfn).pop() // last item is definition
|
|
109
|
+
}
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
let ckind = dfn.shift() // composite type kind
|
|
112
|
+
if (ckind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
113
|
+
else if (ckind === 'struct') dfn = fieldseq(dfn, 'field', true)
|
|
114
|
+
else if (ckind === 'array') dfn = dfn.shift()
|
|
115
|
+
|
|
116
|
+
node = [ckind, dfn, subkind, supertypes, rec ? [ctx.type.length, rec] : issub]
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
// dupe to code section, save implicit type
|
|
86
120
|
else if (kind === 'func') {
|
|
87
|
-
let [idx, param, result] = typeuse(node,
|
|
88
|
-
idx ?? (
|
|
89
|
-
|
|
121
|
+
let [idx, param, result] = typeuse(node, ctx);
|
|
122
|
+
idx ?? (ctx._[idx = '$' + param + '>' + result] = [param, result]);
|
|
123
|
+
// we save idx because type can be defined after
|
|
124
|
+
!imported && nodes.push(['code', [idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
90
125
|
node.unshift(['type', idx])
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
// import writes to import section amd adds placeholder for (kind) section
|
|
94
|
-
if (imported)
|
|
129
|
+
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
95
130
|
|
|
96
|
-
|
|
131
|
+
items.push(node)
|
|
97
132
|
}
|
|
98
133
|
|
|
99
134
|
// add implicit types - main types receive aliases, implicit types are added if no explicit types exist
|
|
100
|
-
for (let n in
|
|
135
|
+
for (let n in ctx._) ctx.type[n] ??= (ctx.type.push(['func', ctx._[n]]) - 1)
|
|
101
136
|
|
|
102
137
|
// patch datacount if data === 0
|
|
103
|
-
|
|
138
|
+
// FIXME: let's try to return empty in datacount builder, since we filter after builder as well
|
|
139
|
+
// if (!ctx.data.length) ctx.datacount.length = 0
|
|
104
140
|
|
|
105
141
|
// convert nodes to bytes
|
|
106
142
|
const bin = (kind, count = true) => {
|
|
107
|
-
|
|
143
|
+
const items = ctx[kind]
|
|
144
|
+
.filter(Boolean) // filter out (type, imported) placeholders
|
|
145
|
+
.map(item => build[kind](item, ctx))
|
|
146
|
+
.filter(Boolean) // filter out unrenderable things (subtype or data.length)
|
|
147
|
+
|
|
108
148
|
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
109
149
|
}
|
|
110
150
|
|
|
@@ -128,114 +168,105 @@ export default function watr(nodes) {
|
|
|
128
168
|
])
|
|
129
169
|
}
|
|
130
170
|
|
|
171
|
+
// consume name eg. $t ...
|
|
172
|
+
const alias = (node, list) => {
|
|
173
|
+
let name = (node[0]?.[0] === '$' || node[0]?.[0] == null) && node.shift();
|
|
174
|
+
if (name) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
175
|
+
return name
|
|
176
|
+
}
|
|
177
|
+
|
|
131
178
|
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
132
179
|
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
133
180
|
const plain = (nodes, ctx) => {
|
|
134
|
-
let out = []
|
|
181
|
+
let out = [], stack = [], label
|
|
135
182
|
|
|
136
183
|
while (nodes.length) {
|
|
137
184
|
let node = nodes.shift()
|
|
138
185
|
|
|
186
|
+
// lookup is slower than sequence of known ifs
|
|
139
187
|
if (typeof node === 'string') {
|
|
140
188
|
out.push(node)
|
|
141
189
|
|
|
142
|
-
|
|
190
|
+
// block typeuse?
|
|
191
|
+
if (node === 'block' || node === 'if' || node === 'loop') {
|
|
192
|
+
// (loop $l?)
|
|
193
|
+
if (nodes[0]?.[0] === '$') label = nodes.shift(), out.push(label), stack.push(label)
|
|
194
|
+
|
|
195
|
+
out.push(blocktype(nodes, ctx))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// else $label
|
|
199
|
+
// end $label - make sure it matches block label
|
|
200
|
+
else if (node === 'else' || node === 'end') {
|
|
201
|
+
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched label ${label}`)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// select (result i32 i32 i32)?
|
|
205
|
+
else if (node === 'select') {
|
|
206
|
+
out.push(paramres(nodes, 0)[1])
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// call_indirect $table? $typeidx
|
|
210
|
+
// return_call_indirect $table? $typeidx
|
|
211
|
+
else if (node.endsWith('call_indirect')) {
|
|
212
|
+
let tableidx = nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0
|
|
213
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
214
|
+
out.push(tableidx, ['type', idx ?? (ctx._[idx = '$' + param + '>' + result] = [param, result], idx)])
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// mark datacount section as required
|
|
218
|
+
else if (node === 'memory.init' || node === 'data.drop' || node === 'array.new_data' || node === 'array.init_data') {
|
|
219
|
+
ctx.datacount[0] = true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// table.init tableidx? elemidx -> table.init tableidx elemidx
|
|
223
|
+
else if (node === 'table.init') out.push((nodes[1][0] === '$' || !isNaN(nodes[1])) ? nodes.shift() : 0, nodes.shift())
|
|
224
|
+
|
|
225
|
+
// table.* tableidx?
|
|
226
|
+
else if (node.startsWith('table.')) {
|
|
227
|
+
out.push(nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0)
|
|
228
|
+
|
|
229
|
+
// table.copy tableidx? tableidx?
|
|
230
|
+
if (node === 'table.copy') out.push(nodes[0][0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0)
|
|
231
|
+
}
|
|
143
232
|
}
|
|
144
233
|
|
|
145
|
-
// FIXME: try to move this to abbr
|
|
146
234
|
else {
|
|
147
|
-
node = plain(node, ctx)
|
|
148
|
-
|
|
149
235
|
// (block ...) -> block ... end
|
|
150
236
|
if (node[0] === 'block' || node[0] === 'loop') {
|
|
151
|
-
out.push(...node, 'end')
|
|
237
|
+
out.push(...plain(node, ctx), 'end')
|
|
152
238
|
}
|
|
153
239
|
|
|
154
240
|
// (if ...) -> if ... end
|
|
155
241
|
else if (node[0] === 'if') {
|
|
156
|
-
let
|
|
242
|
+
let then = [], els = [], immed = [node.shift()]
|
|
157
243
|
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
158
244
|
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
159
|
-
if (node[node.length - 1]?.[0] === 'else')
|
|
160
|
-
|
|
245
|
+
if (node[node.length - 1]?.[0] === 'else') {
|
|
246
|
+
els = plain(node.pop(), ctx)
|
|
247
|
+
// ignore empty else
|
|
248
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
249
|
+
if (els.length === 1) els.length = 0
|
|
250
|
+
}
|
|
251
|
+
if (node[node.length - 1]?.[0] === 'then') then = plain(node.pop(), ctx)
|
|
161
252
|
|
|
162
253
|
// label?
|
|
163
|
-
if (node[0]?.[0] === '$')
|
|
254
|
+
if (node[0]?.[0] === '$') immed.push(node.shift())
|
|
164
255
|
|
|
165
|
-
// blocktype?
|
|
166
|
-
|
|
256
|
+
// blocktype?
|
|
257
|
+
immed.push(blocktype(node, ctx))
|
|
167
258
|
|
|
168
|
-
|
|
169
|
-
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
170
|
-
if (thenelse[thenelse.length - 1] === 'else') thenelse.pop()
|
|
259
|
+
if (typeof node[0] === 'string') err('Unfolded condition')
|
|
171
260
|
|
|
172
|
-
out.push(...node, ...
|
|
261
|
+
out.push(...plain(node, ctx), ...immed, ...then, ...els, 'end')
|
|
173
262
|
}
|
|
174
|
-
else out.push(node)
|
|
263
|
+
else out.push(plain(node, ctx))
|
|
175
264
|
}
|
|
176
265
|
}
|
|
177
266
|
|
|
178
267
|
return out
|
|
179
268
|
}
|
|
180
269
|
|
|
181
|
-
const abbr = {
|
|
182
|
-
// block typeuse?
|
|
183
|
-
block: (nodes, ctx) => {
|
|
184
|
-
let out = []
|
|
185
|
-
|
|
186
|
-
// (loop $l?)
|
|
187
|
-
if (nodes[0]?.[0] === '$') out.push(nodes.shift())
|
|
188
|
-
|
|
189
|
-
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
190
|
-
|
|
191
|
-
// direct idx (no params/result needed)
|
|
192
|
-
if (idx != null) out.push(['type', idx])
|
|
193
|
-
// get type - can be either idx or valtype (numtype | reftype)
|
|
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])
|
|
199
|
-
|
|
200
|
-
return out
|
|
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],
|
|
237
|
-
}
|
|
238
|
-
|
|
239
270
|
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
240
271
|
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
241
272
|
const typeuse = (nodes, ctx, names) => {
|
|
@@ -248,7 +279,7 @@ const typeuse = (nodes, ctx, names) => {
|
|
|
248
279
|
|
|
249
280
|
// check type consistency (excludes forward refs)
|
|
250
281
|
if ((param.length || result.length) && idx in ctx.type)
|
|
251
|
-
if (ctx.type[id(idx, ctx.type)].join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
282
|
+
if (ctx.type[id(idx, ctx.type)][1].join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
252
283
|
|
|
253
284
|
return [idx]
|
|
254
285
|
}
|
|
@@ -261,54 +292,111 @@ const typeuse = (nodes, ctx, names) => {
|
|
|
261
292
|
|
|
262
293
|
// consume (param t+)* (result t+)* sequence
|
|
263
294
|
const paramres = (nodes, names = true) => {
|
|
264
|
-
let param = [], result = []
|
|
295
|
+
// let param = [], result = []
|
|
265
296
|
|
|
266
297
|
// collect param (param i32 i64) (param $x? i32)
|
|
267
|
-
|
|
298
|
+
let param = fieldseq(nodes, 'param', names)
|
|
299
|
+
|
|
300
|
+
// collect result eg. (result f64 f32)(result i32)
|
|
301
|
+
let result = fieldseq(nodes, 'result')
|
|
302
|
+
|
|
303
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
304
|
+
|
|
305
|
+
return [param, result]
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
309
|
+
// optionally allow or not names
|
|
310
|
+
const fieldseq = (nodes, field, names = false) => {
|
|
311
|
+
let seq = []
|
|
312
|
+
// collect field eg. (field f64 f32)(field i32)
|
|
313
|
+
while (nodes[0]?.[0] === field) {
|
|
268
314
|
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
315
|
let name = args[0]?.[0] === '$' && args.shift()
|
|
271
316
|
// expose name refs, if allowed
|
|
272
|
-
if (name)
|
|
273
|
-
|
|
317
|
+
if (name) {
|
|
318
|
+
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
319
|
+
else err(`Unexpected ${field} name ${name}`)
|
|
320
|
+
}
|
|
321
|
+
seq.push(...args)
|
|
274
322
|
}
|
|
323
|
+
return seq
|
|
324
|
+
}
|
|
275
325
|
|
|
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
|
-
}
|
|
326
|
+
// consume blocktype - makes sure either type or single result is returned
|
|
327
|
+
const blocktype = (nodes, ctx) => {
|
|
328
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
282
329
|
|
|
283
|
-
|
|
330
|
+
// direct idx (no params/result needed)
|
|
331
|
+
if (idx != null) return ['type', idx]
|
|
284
332
|
|
|
285
|
-
|
|
333
|
+
// get type - can be either idx or valtype (numtype | reftype)
|
|
334
|
+
if (!param.length && !result.length) return
|
|
335
|
+
|
|
336
|
+
// (result i32) - doesn't require registering type
|
|
337
|
+
if (!param.length && result.length === 1) return ['result', ...result]
|
|
338
|
+
|
|
339
|
+
// (param i32 i32)? (result i32 i32) - implicit type
|
|
340
|
+
ctx._[idx = '$' + param + '>' + result] = [param, result]
|
|
341
|
+
return ['type', idx]
|
|
286
342
|
}
|
|
287
343
|
|
|
344
|
+
|
|
288
345
|
// build section binary [by section codes] (non consuming)
|
|
289
346
|
const build = [,
|
|
290
|
-
//
|
|
291
|
-
|
|
347
|
+
// type kinds
|
|
348
|
+
// (func params result)
|
|
349
|
+
// (array i8)
|
|
350
|
+
// (struct ...fields)
|
|
351
|
+
([kind, fields, subkind, supertypes, rec], ctx) => {
|
|
352
|
+
if (rec === true) return // ignore rec subtypes cept for 1st one
|
|
353
|
+
|
|
354
|
+
let details
|
|
355
|
+
// (rec (sub ...)*)
|
|
356
|
+
if (rec) {
|
|
357
|
+
// FIXME: rec of one type
|
|
358
|
+
kind = 'rec'
|
|
359
|
+
let [from, length] = rec, subtypes = Array.from({ length }, (_, i) => build[SECTION.type](ctx.type[from + i].slice(0, 4), ctx))
|
|
360
|
+
details = vec(subtypes)
|
|
361
|
+
}
|
|
362
|
+
// (sub final? sups* (type...))
|
|
363
|
+
else if (subkind === 'sub' || supertypes?.length) {
|
|
364
|
+
details = [...vec(supertypes.map(n => id(n, ctx.type))), ...build[SECTION.type]([kind, fields], ctx)]
|
|
365
|
+
kind = subkind
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
else if (kind === 'func') {
|
|
369
|
+
details = [...vec(fields[0].map(t => reftype(t, ctx))), ...vec(fields[1].map(t => reftype(t, ctx)))]
|
|
370
|
+
}
|
|
371
|
+
else if (kind === 'array') {
|
|
372
|
+
details = fieldtype(fields, ctx)
|
|
373
|
+
}
|
|
374
|
+
else if (kind === 'struct') {
|
|
375
|
+
details = vec(fields.map(t => fieldtype(t, ctx)))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return [DEFTYPE[kind], ...details]
|
|
379
|
+
},
|
|
292
380
|
|
|
293
381
|
// (import "math" "add" (func|table|global|memory typedef?))
|
|
294
382
|
([mod, field, [kind, ...dfn]], ctx) => {
|
|
295
383
|
let details
|
|
296
384
|
|
|
297
|
-
if (kind
|
|
385
|
+
if (kind === 'func') {
|
|
298
386
|
// we track imported funcs in func section to share namespace, and skip them on final build
|
|
299
387
|
let [[, typeidx]] = dfn
|
|
300
388
|
details = uleb(id(typeidx, ctx.type))
|
|
301
389
|
}
|
|
302
|
-
else if (kind
|
|
390
|
+
else if (kind === 'memory') {
|
|
303
391
|
details = limits(dfn)
|
|
304
392
|
}
|
|
305
|
-
else if (kind
|
|
306
|
-
|
|
307
|
-
details = [...type(mut ? t[1] : t, ctx), mut]
|
|
393
|
+
else if (kind === 'global') {
|
|
394
|
+
details = fieldtype(dfn[0], ctx)
|
|
308
395
|
}
|
|
309
|
-
else if (kind
|
|
310
|
-
details = [...
|
|
396
|
+
else if (kind === 'table') {
|
|
397
|
+
details = [...reftype(dfn.pop(), ctx), ...limits(dfn)]
|
|
311
398
|
}
|
|
399
|
+
else err(`Unknown kind ${kind}`)
|
|
312
400
|
|
|
313
401
|
return ([...vec(str(mod.slice(1, -1))), ...vec(str(field.slice(1, -1))), KIND[kind], ...details])
|
|
314
402
|
},
|
|
@@ -318,20 +406,15 @@ const build = [,
|
|
|
318
406
|
|
|
319
407
|
// (table 1 2 funcref)
|
|
320
408
|
(node, ctx) => {
|
|
321
|
-
let lims = limits(node), t =
|
|
322
|
-
return init ? [0x40, 0x00, ...t, ...lims, ...expr(init, ctx)]
|
|
409
|
+
let lims = limits(node), t = reftype(node.shift(), ctx), [init] = node
|
|
410
|
+
return init ? [0x40, 0x00, ...t, ...lims, ...expr(init, ctx)] : [...t, ...lims]
|
|
323
411
|
},
|
|
324
412
|
|
|
325
413
|
// (memory id? export* min max shared)
|
|
326
414
|
(node, ctx) => limits(node),
|
|
327
415
|
|
|
328
416
|
// (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
|
-
},
|
|
417
|
+
([t, init], ctx) => [...fieldtype(t, ctx), ...expr(init, ctx)],
|
|
335
418
|
|
|
336
419
|
// (export "name" (func|table|mem $name|idx))
|
|
337
420
|
([nm, [kind, l]], ctx) => ([...vec(str(nm.slice(1, -1))), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
@@ -342,74 +425,77 @@ const build = [,
|
|
|
342
425
|
// (elem elem*) - passive
|
|
343
426
|
// (elem declare elem*) - declarative
|
|
344
427
|
// (elem (table idx)? (offset expr)|(expr) elem*) - active
|
|
345
|
-
// elems := funcref|externref (item expr)|expr (item expr)|expr
|
|
346
|
-
// idxs := func? $id0 $id1
|
|
347
428
|
// ref: https://webassembly.github.io/spec/core/binary/modules.html#element-section
|
|
348
429
|
(parts, ctx) => {
|
|
349
|
-
let
|
|
430
|
+
let passive = 0, declare = 0, elexpr = 0, nofunc = 0, tabidx, offset, rt
|
|
350
431
|
|
|
351
432
|
// declare?
|
|
352
|
-
if (parts[0] === 'declare') parts.shift(),
|
|
433
|
+
if (parts[0] === 'declare') parts.shift(), declare = 1
|
|
353
434
|
|
|
354
435
|
// table?
|
|
355
436
|
if (parts[0][0] === 'table') {
|
|
356
437
|
[, tabidx] = parts.shift()
|
|
357
438
|
tabidx = id(tabidx, ctx.table)
|
|
358
|
-
// ignore table=0
|
|
359
|
-
if (tabidx) mode |= 0b010
|
|
360
439
|
}
|
|
361
440
|
|
|
362
441
|
// (offset expr)|expr
|
|
363
442
|
if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
|
|
364
443
|
offset = parts.shift()
|
|
365
444
|
if (offset[0] === 'offset') [, offset] = offset
|
|
445
|
+
offset = expr(offset, ctx)
|
|
366
446
|
}
|
|
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()
|
|
447
|
+
// no offset = passive
|
|
448
|
+
else if (!declare) passive = 1
|
|
373
449
|
|
|
374
|
-
//
|
|
375
|
-
if (
|
|
450
|
+
// funcref|externref|(ref ...)
|
|
451
|
+
if (REFTYPE[parts[0]] || parts[0]?.[0] === 'ref') rt = reftype(parts.shift(), ctx)
|
|
452
|
+
// func ... abbr https://webassembly.github.io/function-references/core/text/modules.html#id7
|
|
453
|
+
else if (parts[0] === 'func') rt = [HEAPTYPE[parts.shift()]]
|
|
454
|
+
// or anything else
|
|
455
|
+
else rt = [HEAPTYPE.func]
|
|
376
456
|
|
|
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
|
|
457
|
+
// deabbr els sequence, detect expr usage
|
|
383
458
|
parts = parts.map(el => {
|
|
384
459
|
if (el[0] === 'item') [, ...el] = el
|
|
385
460
|
if (el[0] === 'ref.func') [, el] = el
|
|
386
|
-
// (ref.null func) and other expressions turn expr
|
|
387
|
-
if (typeof el !== 'string')
|
|
461
|
+
// (ref.null func) and other expressions turn expr els mode
|
|
462
|
+
if (typeof el !== 'string') elexpr = 1
|
|
388
463
|
return el
|
|
389
464
|
})
|
|
390
465
|
|
|
466
|
+
// reftype other than (ref null? func) forces table index via nofunc flag
|
|
467
|
+
// also it forces elexpr
|
|
468
|
+
if (rt[0] !== REFTYPE.funcref) nofunc = 1, elexpr = 1
|
|
469
|
+
|
|
470
|
+
// mode:
|
|
471
|
+
// bit 0 indicates a passive or declarative segment
|
|
472
|
+
// bit 1 indicates the presence of an explicit table index for an active segment
|
|
473
|
+
// and otherwise distinguishes passive from declarative segments
|
|
474
|
+
// bit 2 indicates the use of element type and element expressions instead of elemkind=0x00 and element indices.
|
|
475
|
+
let mode = (elexpr << 2) | ((passive || declare ? declare : (!!tabidx || nofunc)) << 1) | (passive || declare);
|
|
476
|
+
|
|
391
477
|
return ([
|
|
392
478
|
mode,
|
|
393
479
|
...(
|
|
394
|
-
// 0b000 e:expr y*:vec(funcidx) | type=
|
|
395
|
-
mode === 0b000 ?
|
|
480
|
+
// 0b000 e:expr y*:vec(funcidx) | type=(ref func), init ((ref.func y)end)*, active (table=0,offset=e)
|
|
481
|
+
mode === 0b000 ? offset :
|
|
396
482
|
// 0b001 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive
|
|
397
483
|
mode === 0b001 ? [0x00] :
|
|
398
484
|
// 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), ...
|
|
485
|
+
mode === 0b010 ? [...uleb(tabidx || 0), ...offset, 0x00] :
|
|
400
486
|
// 0b011 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive declare
|
|
401
487
|
mode === 0b011 ? [0x00] :
|
|
402
|
-
// 0b100 e:expr el*:vec(expr) | type=
|
|
403
|
-
mode === 0b100 ?
|
|
488
|
+
// 0b100 e:expr el*:vec(expr) | type=(ref null func), init el*, active (table=0, offset=e)
|
|
489
|
+
mode === 0b100 ? offset :
|
|
404
490
|
// 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
|
|
405
|
-
mode === 0b101 ?
|
|
491
|
+
mode === 0b101 ? rt :
|
|
406
492
|
// 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), ...
|
|
493
|
+
mode === 0b110 ? [...uleb(tabidx || 0), ...offset, ...rt] :
|
|
408
494
|
// 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare
|
|
409
|
-
|
|
495
|
+
rt
|
|
410
496
|
),
|
|
411
497
|
...vec(
|
|
412
|
-
parts.map(
|
|
498
|
+
parts.map(elexpr ?
|
|
413
499
|
// ((ref.func y)end)*
|
|
414
500
|
el => expr(typeof el === 'string' ? ['ref.func', el] : el, ctx) :
|
|
415
501
|
// el*
|
|
@@ -422,7 +508,7 @@ const build = [,
|
|
|
422
508
|
// (code)
|
|
423
509
|
(body, ctx) => {
|
|
424
510
|
let [typeidx, param] = body.shift()
|
|
425
|
-
if (!param) [param] = ctx.type[id(typeidx, ctx.type)]
|
|
511
|
+
if (!param) [, [param]] = ctx.type[id(typeidx, ctx.type)]
|
|
426
512
|
|
|
427
513
|
// provide param/local in ctx
|
|
428
514
|
ctx.local = Object.create(param) // list of local variables - some of them are params
|
|
@@ -435,7 +521,11 @@ const build = [,
|
|
|
435
521
|
// collect locals
|
|
436
522
|
while (body[0]?.[0] === 'local') {
|
|
437
523
|
let [, ...types] = body.shift()
|
|
438
|
-
if (types[0]?.[0] === '$')
|
|
524
|
+
if (types[0]?.[0] === '$') {
|
|
525
|
+
let name = types.shift()
|
|
526
|
+
if (name in ctx.local) err(`Duplicate local ${name}`)
|
|
527
|
+
else ctx.local[name] = ctx.local.length
|
|
528
|
+
}
|
|
439
529
|
ctx.local.push(...types)
|
|
440
530
|
}
|
|
441
531
|
|
|
@@ -451,7 +541,7 @@ const build = [,
|
|
|
451
541
|
ctx.local = ctx.block = null
|
|
452
542
|
|
|
453
543
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
454
|
-
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...
|
|
544
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...bytes])
|
|
455
545
|
},
|
|
456
546
|
|
|
457
547
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
@@ -490,10 +580,20 @@ const build = [,
|
|
|
490
580
|
(nodes, ctx) => uleb(ctx.data.length)
|
|
491
581
|
]
|
|
492
582
|
|
|
493
|
-
//
|
|
494
|
-
const
|
|
495
|
-
t[0] === 'ref' ?
|
|
496
|
-
|
|
583
|
+
// build reftype, either direct absheaptype or wrapped heaptype https://webassembly.github.io/gc/core/binary/types.html#reference-types
|
|
584
|
+
const reftype = (t, ctx) => (
|
|
585
|
+
t[0] === 'ref' ?
|
|
586
|
+
t[1] == 'null' ?
|
|
587
|
+
HEAPTYPE[t[2]] ? [HEAPTYPE[t[2]]] : [REFTYPE.refnull, ...uleb(id(t[t.length - 1], ctx.type))] :
|
|
588
|
+
[TYPE.ref, ...uleb(HEAPTYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))] :
|
|
589
|
+
// abbrs
|
|
590
|
+
[TYPE[t] ?? err(`Unknown type ${t}`)]
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// build type with mutable flag (mut t) or t
|
|
594
|
+
const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[1] : t, ctx), mut];
|
|
595
|
+
|
|
596
|
+
|
|
497
597
|
|
|
498
598
|
// consume one instruction from nodes sequence
|
|
499
599
|
const instr = (nodes, ctx) => {
|
|
@@ -512,50 +612,43 @@ const instr = (nodes, ctx) => {
|
|
|
512
612
|
[...immed] = isNaN(op[0]) && INSTR[op] || err(`Unknown instruction ${op}`)
|
|
513
613
|
code = immed[0]
|
|
514
614
|
|
|
515
|
-
//
|
|
516
|
-
// https://github.
|
|
517
|
-
if (code ===
|
|
615
|
+
// gc-related
|
|
616
|
+
// https://webassembly.github.io/gc/core/binary/instructions.html#reference-instructions
|
|
617
|
+
if (code === 0x0fb) {
|
|
518
618
|
[, code] = immed
|
|
519
|
-
|
|
520
|
-
//
|
|
521
|
-
if (code <=
|
|
522
|
-
|
|
523
|
-
immed.push(...uleb(
|
|
619
|
+
|
|
620
|
+
// struct.new $t ... array.set $t
|
|
621
|
+
if ((code >= 0 && code <= 14) || (code >= 16 && code <= 19)) {
|
|
622
|
+
let tidx = id(nodes.shift(), ctx.type)
|
|
623
|
+
immed.push(...uleb(tidx))
|
|
624
|
+
|
|
625
|
+
// struct.get|set* $t $f - read field by index from struct definition (ctx.type[structidx][dfnidx])
|
|
626
|
+
if (code >= 2 && code <= 5) immed.push(...uleb(id(nodes.shift(), ctx.type[tidx][1])))
|
|
627
|
+
// array.new_fixed $t n
|
|
628
|
+
else if (code === 8) immed.push(...uleb(nodes.shift()))
|
|
629
|
+
// array.new_data|init_data $t $d
|
|
630
|
+
else if (code === 9 || code === 18) immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
631
|
+
// array.new_elem|init_elem $t $e
|
|
632
|
+
else if (code === 10 || code === 19) immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
633
|
+
// array.copy $t $t
|
|
634
|
+
else if (code === 17) immed.push(...uleb(id(nodes.shift(), ctx.type)))
|
|
524
635
|
}
|
|
525
|
-
//
|
|
526
|
-
else if (code >=
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (
|
|
636
|
+
// ref.test|cast (ref null? $t|heaptype)
|
|
637
|
+
else if (code >= 20 && code <= 23) {
|
|
638
|
+
// FIXME: normalizer is supposed to resolve this
|
|
639
|
+
let ht = reftype(nodes.shift(), ctx)
|
|
640
|
+
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop()+1) // ref.test|cast (ref null $t) is next op
|
|
641
|
+
if (ht.length > 1) ht.shift() // pop ref
|
|
642
|
+
immed.push(...ht)
|
|
531
643
|
}
|
|
532
|
-
// (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
n = +n
|
|
541
|
-
// i8, i16, i32 - bypass the encoding
|
|
542
|
-
if (t[0] === 'i') {
|
|
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
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// (i8x16.extract_lane_s 0 ...)
|
|
557
|
-
else if (code >= 0x15 && code <= 0x22) {
|
|
558
|
-
immed.push(...uleb(nodes.shift()))
|
|
644
|
+
// br_on_cast[_fail] $l? (ref null? ht1) (ref null? ht2)
|
|
645
|
+
// FIXME: normalizer should resolve anyref|etc to (ref null any|etc)
|
|
646
|
+
else if (code === 24 || code === 25) {
|
|
647
|
+
let i = blockid(nodes.shift(), ctx.block),
|
|
648
|
+
ht1 = reftype(nodes.shift(), ctx),
|
|
649
|
+
ht2 = reftype(nodes.shift(), ctx),
|
|
650
|
+
castflags = ((ht2[0] !== REFTYPE.ref) << 1) | (ht1[0] !== REFTYPE.ref)
|
|
651
|
+
immed.push(castflags, ...uleb(i), ht1.pop(), ht2.pop()) // we take only abstype or
|
|
559
652
|
}
|
|
560
653
|
}
|
|
561
654
|
|
|
@@ -592,24 +685,82 @@ const instr = (nodes, ctx) => {
|
|
|
592
685
|
}
|
|
593
686
|
}
|
|
594
687
|
|
|
688
|
+
// v128s: (v128.load x) etc
|
|
689
|
+
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
|
|
690
|
+
else if (code === 0xfd) {
|
|
691
|
+
[, code] = immed
|
|
692
|
+
immed = [0xfd, ...uleb(code)]
|
|
693
|
+
// (v128.load offset? align?)
|
|
694
|
+
if (code <= 0x0b) {
|
|
695
|
+
const [a, o] = memarg(nodes)
|
|
696
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
697
|
+
}
|
|
698
|
+
// (v128.load_lane offset? align? idx)
|
|
699
|
+
else if (code >= 0x54 && code <= 0x5d) {
|
|
700
|
+
const [a, o] = memarg(nodes)
|
|
701
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
702
|
+
// (v128.load_lane_zero)
|
|
703
|
+
if (code <= 0x5b) immed.push(...uleb(nodes.shift()))
|
|
704
|
+
}
|
|
705
|
+
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
706
|
+
else if (code === 0x0d) {
|
|
707
|
+
// i8, i16, i32 - bypass the encoding
|
|
708
|
+
for (let i = 0; i < 16; i++) immed.push(parseUint(nodes.shift(), 32))
|
|
709
|
+
}
|
|
710
|
+
// (v128.const i32x4 1 2 3 4)
|
|
711
|
+
else if (code === 0x0c) {
|
|
712
|
+
let [t, n] = nodes.shift().split('x'),
|
|
713
|
+
bits = +t.slice(1),
|
|
714
|
+
stride = bits >>> 3 // i16 -> 2, f32 -> 4
|
|
715
|
+
n = +n
|
|
716
|
+
// i8, i16, i32 - bypass the encoding
|
|
717
|
+
if (t[0] === 'i') {
|
|
718
|
+
let arr = n === 16 ? new Uint8Array(16) : n === 8 ? new Uint16Array(8) : n === 4 ? new Uint32Array(4) : new BigUint64Array(2)
|
|
719
|
+
for (let i = 0; i < n; i++) {
|
|
720
|
+
let s = nodes.shift(), v = encode[t].parse(s)
|
|
721
|
+
arr[i] = v
|
|
722
|
+
}
|
|
723
|
+
immed.push(...(new Uint8Array(arr.buffer)))
|
|
724
|
+
}
|
|
725
|
+
// f32, f64 - encode
|
|
726
|
+
else {
|
|
727
|
+
let arr = new Uint8Array(16)
|
|
728
|
+
for (let i = 0; i < n; i++) {
|
|
729
|
+
let s = nodes.shift(), v = encode[t](s)
|
|
730
|
+
arr.set(v, i * stride)
|
|
731
|
+
}
|
|
732
|
+
immed.push(...arr)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// (i8x16.extract_lane_s 0 ...)
|
|
736
|
+
else if (code >= 0x15 && code <= 0x22) {
|
|
737
|
+
immed.push(...uleb(parseUint(nodes.shift())))
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
595
741
|
// control block abbrs
|
|
596
742
|
// block ..., loop ..., if ...
|
|
597
743
|
else if (code === 2 || code === 3 || code === 4) {
|
|
598
744
|
ctx.block.push(code)
|
|
599
745
|
|
|
600
|
-
// (block $x) (loop $y)
|
|
746
|
+
// (block $x) (loop $y) - save label pointer
|
|
747
|
+
// FIXME: do in normalizer
|
|
601
748
|
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
602
749
|
|
|
603
|
-
let
|
|
604
|
-
|
|
605
|
-
let [param, result] = typeidx !== false ? ctx.type[typeidx] : nodes[0]?.[0] === 'result' ? [, [nodes.shift()[1]]] : []
|
|
750
|
+
let t = nodes.shift();
|
|
606
751
|
|
|
607
752
|
// void
|
|
608
|
-
if (!
|
|
753
|
+
if (!t) immed.push(TYPE.void)
|
|
609
754
|
// (result i32) - doesn't require registering type
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
else
|
|
755
|
+
// FIXME: Make sure it is signed positive integer (leb, not uleb) https://webassembly.github.io/gc/core/binary/instructions.html#control-instructions
|
|
756
|
+
else if (t[0] === 'result') immed.push(...reftype(t[1], ctx))
|
|
757
|
+
else {
|
|
758
|
+
let typeidx = id(t[1], ctx.type), [param, result] = ctx.type[typeidx][1]
|
|
759
|
+
// (type $idx (func (result i32)))
|
|
760
|
+
if (!param?.length && result.length === 1) immed.push(...reftype(result[0], ctx))
|
|
761
|
+
// (type idx)
|
|
762
|
+
else immed.push(...uleb(typeidx))
|
|
763
|
+
}
|
|
613
764
|
}
|
|
614
765
|
// else
|
|
615
766
|
else if (code === 5) { }
|
|
@@ -638,7 +789,8 @@ const instr = (nodes, ctx) => {
|
|
|
638
789
|
immed.push(
|
|
639
790
|
...uleb(id(nodes[1][1], ctx.type)),
|
|
640
791
|
...uleb(id(nodes.shift(), ctx.table))
|
|
641
|
-
)
|
|
792
|
+
)
|
|
793
|
+
nodes.shift()
|
|
642
794
|
}
|
|
643
795
|
|
|
644
796
|
// call_ref $type
|
|
@@ -654,19 +806,14 @@ const instr = (nodes, ctx) => {
|
|
|
654
806
|
// br_if $label cond result?
|
|
655
807
|
// br_on_null $l, br_on_non_null $l
|
|
656
808
|
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))
|
|
809
|
+
immed.push(...uleb(blockid(nodes.shift(), ctx.block)))
|
|
661
810
|
}
|
|
662
811
|
|
|
663
812
|
// br_table 1 2 3 4 0 selector result?
|
|
664
813
|
else if (code == 0x0e) {
|
|
665
814
|
let args = []
|
|
666
815
|
while (nodes[0] && (!isNaN(nodes[0]) || nodes[0][0] === '$')) {
|
|
667
|
-
|
|
668
|
-
i <= ctx.block.length || err(`Bad label ${l}`)
|
|
669
|
-
args.push(...uleb(i))
|
|
816
|
+
args.push(...uleb(blockid(nodes.shift(), ctx.block)))
|
|
670
817
|
}
|
|
671
818
|
args.unshift(...uleb(args.length - 1))
|
|
672
819
|
immed.push(...args)
|
|
@@ -676,7 +823,7 @@ const instr = (nodes, ctx) => {
|
|
|
676
823
|
else if (code == 0x1b) {
|
|
677
824
|
let result = nodes.shift()
|
|
678
825
|
// 0x1b -> 0x1c
|
|
679
|
-
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t =>
|
|
826
|
+
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t => reftype(t, ctx))))
|
|
680
827
|
}
|
|
681
828
|
|
|
682
829
|
// ref.func $id
|
|
@@ -687,7 +834,7 @@ const instr = (nodes, ctx) => {
|
|
|
687
834
|
// ref.null func
|
|
688
835
|
else if (code == 0xd0) {
|
|
689
836
|
let t = nodes.shift()
|
|
690
|
-
immed.push(HEAPTYPE[t]
|
|
837
|
+
immed.push(...(HEAPTYPE[t] ? [HEAPTYPE[t]] : uleb(id(t, ctx.type)))) // func->funcref, extern->externref
|
|
691
838
|
}
|
|
692
839
|
|
|
693
840
|
// binary/unary (i32.add a b) - no immed
|
|
@@ -710,7 +857,7 @@ const instr = (nodes, ctx) => {
|
|
|
710
857
|
immed.push(0)
|
|
711
858
|
}
|
|
712
859
|
|
|
713
|
-
// table.get $id
|
|
860
|
+
// table.get|set $id
|
|
714
861
|
else if (code == 0x25 || code == 0x26) {
|
|
715
862
|
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
716
863
|
}
|
|
@@ -723,6 +870,16 @@ const instr = (nodes, ctx) => {
|
|
|
723
870
|
// instantiation time value initializer (consuming) - we redirect to instr
|
|
724
871
|
const expr = (node, ctx) => [...instr([node], ctx), 0x0b]
|
|
725
872
|
|
|
873
|
+
// deref id node to numeric idx
|
|
874
|
+
const id = (nm, list, n) => (n = nm[0] === '$' ? list[nm] : +nm, n in list ? n : err(`Unknown ${list.name} ${nm}`))
|
|
875
|
+
|
|
876
|
+
// block id - same as id but for block
|
|
877
|
+
// index indicates how many block items to pop
|
|
878
|
+
const blockid = (nm, block, i) => (
|
|
879
|
+
i = nm?.[0] === '$' ? block.length - block[nm] : +nm,
|
|
880
|
+
isNaN(i) || i > block.length ? err(`Bad label ${nm}`) : i
|
|
881
|
+
)
|
|
882
|
+
|
|
726
883
|
// consume align/offset params
|
|
727
884
|
const memarg = (args) => {
|
|
728
885
|
let align, offset, k, v
|
|
@@ -734,10 +891,6 @@ const memarg = (args) => {
|
|
|
734
891
|
return [align, offset]
|
|
735
892
|
}
|
|
736
893
|
|
|
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
894
|
// const ALIGN = {
|
|
742
895
|
// 'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
743
896
|
// 'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
@@ -754,7 +907,14 @@ const align = (op) => {
|
|
|
754
907
|
}
|
|
755
908
|
|
|
756
909
|
// build limits sequence (consuming)
|
|
757
|
-
const limits = (node) =>
|
|
910
|
+
const limits = (node) => (
|
|
911
|
+
isNaN(parseInt(node[1])) ? [0, ...uleb(parseUint(node.shift()))] : [node[2] === 'shared' ? 3 : 1, ...uleb(parseUint(node.shift())), ...uleb(parseUint(node.shift()))]
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
// check if node is valid int in a range
|
|
915
|
+
// we put extra condition for index ints for tests complacency
|
|
916
|
+
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
|
|
917
|
+
|
|
758
918
|
|
|
759
919
|
// escape codes
|
|
760
920
|
const escape = { n: 10, r: 13, t: 9, v: 1, '"': 34, "'": 39, '\\': 92 }
|
|
@@ -772,7 +932,3 @@ const str = str => {
|
|
|
772
932
|
|
|
773
933
|
// serialize binary array
|
|
774
934
|
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