watr 3.3.0 → 4.0.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/LICENSE +26 -0
- package/bin/watr.js +83 -0
- package/dist/watr.js +1815 -0
- package/dist/watr.min.js +5 -0
- package/package.json +61 -14
- package/readme.md +42 -89
- package/src/compile.js +512 -590
- package/src/const.js +142 -56
- package/src/encode.js +89 -35
- package/src/parse.js +41 -32
- package/src/print.js +73 -12
- package/src/util.js +67 -35
- package/types/src/compile.d.ts +8 -0
- package/types/src/compile.d.ts.map +1 -0
- package/types/src/const.d.ts +87 -0
- package/types/src/const.d.ts.map +1 -0
- package/types/src/encode.d.ts +58 -0
- package/types/src/encode.d.ts.map +1 -0
- package/types/src/parse.d.ts +3 -0
- package/types/src/parse.d.ts.map +1 -0
- package/types/src/print.d.ts +16 -0
- package/types/src/print.d.ts.map +1 -0
- package/types/src/util.d.ts +18 -0
- package/types/src/util.d.ts.map +1 -0
- package/types/watr.d.ts +34 -0
- package/types/watr.d.ts.map +1 -0
- package/watr.js +233 -3
package/src/compile.js
CHANGED
|
@@ -1,60 +1,87 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb, i32, i64 } from './encode.js'
|
|
3
|
-
import { SECTION, TYPE, KIND, INSTR,
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, DEFTYPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
-
import {
|
|
5
|
+
import { err, unescape, str } from './util.js'
|
|
6
6
|
|
|
7
|
-
// build instructions index
|
|
8
|
-
INSTR.forEach((op, i) => INSTR[op] = i >= 0x133 ? [0xfd, i - 0x133] : i >= 0x11b ? [0xfc, i - 0x11b] : i >= 0xfb ? [0xfb, i - 0xfb] : [i]);
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Clean up AST: remove comments, normalize quoted ids, convert strings to bytes.
|
|
10
|
+
* Preserves @custom and @metadata.code.* annotations. Preserves .i for error reporting.
|
|
11
|
+
*
|
|
12
|
+
* @param {any} node - AST node
|
|
13
|
+
* @param {Array} [result] - Internal accumulator
|
|
14
|
+
* @returns {any} Cleaned node
|
|
15
|
+
*/
|
|
16
|
+
const cleanup = (node, result) => !Array.isArray(node) ? (
|
|
17
|
+
typeof node !== 'string' ? node :
|
|
18
|
+
// skip comments: ;; ... or (; ... ;)
|
|
19
|
+
node[0] === ';' || node[1] === ';' ? null :
|
|
20
|
+
// normalize quoted ids: $"name" -> $name (if no escapes), else $unescaped
|
|
21
|
+
node[0] === '$' && node[1] === '"' ? (node.includes('\\') ? '$' + unescape(node.slice(1)) : '$' + node.slice(2, -1)) :
|
|
22
|
+
// convert string literals to byte arrays with valueOf
|
|
23
|
+
node[0] === '"' ? str(node) :
|
|
24
|
+
node
|
|
25
|
+
) :
|
|
26
|
+
// remove annotations like (@name ...) except @custom and @metadata.code.*
|
|
27
|
+
node[0]?.[0] === '@' && node[0] !== '@custom' && !node[0]?.startsWith?.('@metadata.code.') ? null :
|
|
28
|
+
// unwrap single-element array containing module (after removing comments), preserve .i
|
|
29
|
+
(result = node.map(cleanup).filter(n => n != null), result.i = node.i, result.length === 1 && result[0]?.[0] === 'module' ? result[0] : result)
|
|
30
|
+
|
|
12
31
|
|
|
13
32
|
/**
|
|
14
33
|
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
15
34
|
*
|
|
16
35
|
* @param {string|Array} nodes - The WAT tree or string to be compiled to WASM binary.
|
|
17
|
-
* @param {Object} opt - opt.fullSize for fixed-width uleb encoding
|
|
18
36
|
* @returns {Uint8Array} The compiled WASM binary data.
|
|
19
37
|
*/
|
|
20
|
-
export default function
|
|
38
|
+
export default function compile(nodes) {
|
|
21
39
|
// normalize to (module ...) form
|
|
22
|
-
if (typeof nodes === 'string') nodes = parse(nodes)
|
|
23
|
-
else
|
|
40
|
+
if (typeof nodes === 'string') err.src = nodes, nodes = parse(nodes) || []
|
|
41
|
+
else err.src = '' // clear source if AST passed directly
|
|
42
|
+
err.i = 0
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
nodes = cleanup(nodes) || []
|
|
45
|
+
|
|
46
|
+
let idx = 0
|
|
27
47
|
|
|
28
48
|
// module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
|
|
29
|
-
if (nodes[0] === 'module')
|
|
49
|
+
if (nodes[0] === 'module') idx++, isId(nodes[idx]) && idx++
|
|
30
50
|
// single node, not module
|
|
31
51
|
else if (typeof nodes[0] === 'string') nodes = [nodes]
|
|
32
52
|
|
|
33
53
|
// binary abbr "\00" "\0x61" ...
|
|
34
|
-
if (nodes[
|
|
35
|
-
|
|
36
|
-
return Uint8Array.from(str(...nodes))
|
|
37
|
-
}
|
|
54
|
+
if (nodes[idx] === 'binary') return Uint8Array.from(nodes.slice(++idx).flat())
|
|
55
|
+
|
|
38
56
|
// quote "a" "b"
|
|
39
|
-
|
|
40
|
-
nodes.shift()
|
|
41
|
-
return watr(nodes.map(s => s.slice(1, -1)).join(''))
|
|
42
|
-
}
|
|
57
|
+
if (nodes[idx] === 'quote') return compile(nodes.slice(++idx).map(v => v.valueOf().slice(1, -1)).flat().join(''))
|
|
43
58
|
|
|
44
59
|
// scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
|
|
45
60
|
const ctx = []
|
|
46
61
|
for (let kind in SECTION) (ctx[SECTION[kind]] = ctx[kind] = []).name = kind
|
|
62
|
+
ctx.metadata = {} // code metadata storage: { type: [[funcIdx, [[pos, data]...]]] }
|
|
47
63
|
|
|
48
64
|
// initialize types
|
|
49
|
-
nodes.filter((
|
|
65
|
+
nodes.slice(idx).filter((n) => {
|
|
66
|
+
if (!Array.isArray(n)) {
|
|
67
|
+
let pos = err.src?.indexOf(n, err.i)
|
|
68
|
+
if (pos >= 0) err.i = pos
|
|
69
|
+
err(`Unexpected token ${n}`)
|
|
70
|
+
}
|
|
71
|
+
let [kind, ...node] = n
|
|
72
|
+
err.i = n.i // track position for errors
|
|
73
|
+
// (@custom "name" placement? data) - custom section support
|
|
74
|
+
if (kind === '@custom') {
|
|
75
|
+
ctx.custom.push(node)
|
|
76
|
+
}
|
|
50
77
|
// (rec (type $a (sub final? $sup* (func ...))...) (type $b ...)) -> save subtypes
|
|
51
|
-
if (kind === 'rec') {
|
|
78
|
+
else if (kind === 'rec') {
|
|
52
79
|
// node contains a list of subtypes, (type ...) or (type (sub final? ...))
|
|
53
80
|
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
54
81
|
// add rest of subtypes as regular type nodes with subtype flag
|
|
55
82
|
for (let i = 0; i < node.length; i++) {
|
|
56
83
|
let [, ...subnode] = node[i]
|
|
57
|
-
|
|
84
|
+
name(subnode, ctx.type);
|
|
58
85
|
(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
|
|
59
86
|
ctx.type.push(subnode)
|
|
60
87
|
}
|
|
@@ -64,13 +91,9 @@ export default function watr(nodes) {
|
|
|
64
91
|
// (type (struct (field a)*)
|
|
65
92
|
// (type (sub final? $nm* (struct|array|func ...)))
|
|
66
93
|
else if (kind === 'type') {
|
|
67
|
-
|
|
94
|
+
name(node, ctx.type);
|
|
68
95
|
ctx.type.push(typedef(node, ctx));
|
|
69
96
|
}
|
|
70
|
-
// (@custom "name" placement? data)
|
|
71
|
-
else if (kind === '@custom') {
|
|
72
|
-
ctx.custom.push(node) // node is just the arguments, not including @custom
|
|
73
|
-
}
|
|
74
97
|
// other sections may have id
|
|
75
98
|
else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
|
|
76
99
|
|
|
@@ -78,7 +101,9 @@ export default function watr(nodes) {
|
|
|
78
101
|
})
|
|
79
102
|
|
|
80
103
|
// prepare/normalize nodes
|
|
81
|
-
.forEach((
|
|
104
|
+
.forEach((n) => {
|
|
105
|
+
let [kind, ...node] = n
|
|
106
|
+
err.i = n.i // track position for errors
|
|
82
107
|
let imported // if node needs to be imported
|
|
83
108
|
|
|
84
109
|
// import abbr
|
|
@@ -87,31 +112,34 @@ export default function watr(nodes) {
|
|
|
87
112
|
|
|
88
113
|
// index, alias
|
|
89
114
|
let items = ctx[kind];
|
|
90
|
-
|
|
115
|
+
if (!items) err(`Unknown section ${kind}`)
|
|
116
|
+
name(node, items);
|
|
91
117
|
|
|
92
118
|
// export abbr
|
|
93
|
-
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
119
|
+
// (table|memory|global|func|tag id? (export n)* ...) -> (table|memory|global|func|tag id ...) (export n (table|memory|global|func id))
|
|
94
120
|
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items?.length]])
|
|
95
121
|
|
|
96
122
|
// for import nodes - redirect output to import
|
|
97
123
|
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
98
124
|
|
|
99
|
-
// table abbr
|
|
125
|
+
// table abbr: (table id? i64? reftype (elem ...)) -> (table id? i64? n n reftype) + (elem ...)
|
|
100
126
|
if (kind === 'table') {
|
|
101
|
-
|
|
102
|
-
if (node[1]?.[0] === 'elem') {
|
|
103
|
-
let [reftype, [, ...els]] = node
|
|
104
|
-
node = [els.length, els.length, reftype]
|
|
105
|
-
ctx.elem.push([['table',
|
|
127
|
+
const is64 = node[0] === 'i64', idx = is64 ? 1 : 0
|
|
128
|
+
if (node[idx + 1]?.[0] === 'elem') {
|
|
129
|
+
let [reftype, [, ...els]] = [node[idx], node[idx + 1]]
|
|
130
|
+
node = is64 ? ['i64', els.length, els.length, reftype] : [els.length, els.length, reftype]
|
|
131
|
+
ctx.elem.push([['table', items.length], ['offset', [is64 ? 'i64.const' : 'i32.const', is64 ? 0n : 0]], reftype, ...els])
|
|
106
132
|
}
|
|
107
133
|
}
|
|
108
134
|
|
|
109
|
-
// data abbr
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
// data abbr: (memory id? i64? (data str)) -> (memory id? i64? n n) + (data ...)
|
|
136
|
+
else if (kind === 'memory') {
|
|
137
|
+
const is64 = node[0] === 'i64', idx = is64 ? 1 : 0
|
|
138
|
+
if (node[idx]?.[0] === 'data') {
|
|
139
|
+
let [, ...data] = node.splice(idx, 1)[0], m = '' + Math.ceil(data.reduce((s, d) => s + d.length, 0) / 65536)
|
|
140
|
+
ctx.data.push([['memory', items.length], [is64 ? 'i64.const' : 'i32.const', is64 ? 0n : 0], ...data])
|
|
141
|
+
node = is64 ? ['i64', m, m] : [m, m]
|
|
142
|
+
}
|
|
115
143
|
}
|
|
116
144
|
|
|
117
145
|
// dupe to code section, save implicit type
|
|
@@ -119,22 +147,22 @@ export default function watr(nodes) {
|
|
|
119
147
|
let [idx, param, result] = typeuse(node, ctx);
|
|
120
148
|
idx ??= regtype(param, result, ctx)
|
|
121
149
|
|
|
122
|
-
//
|
|
123
|
-
!imported && ctx.code.push([[idx, param, result], ...
|
|
124
|
-
node
|
|
150
|
+
// flatten + normalize function body
|
|
151
|
+
!imported && ctx.code.push([[idx, param, result], ...normalize(node, ctx)])
|
|
152
|
+
node = [['type', idx]]
|
|
125
153
|
}
|
|
126
154
|
|
|
127
155
|
// tag has a type similar to func
|
|
128
156
|
else if (kind === 'tag') {
|
|
129
157
|
let [idx, param] = typeuse(node, ctx);
|
|
130
158
|
idx ??= regtype(param, [], ctx)
|
|
131
|
-
node
|
|
159
|
+
node = [['type', idx]]
|
|
132
160
|
}
|
|
133
161
|
|
|
134
162
|
// import writes to import section amd adds placeholder for (kind) section
|
|
135
163
|
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
136
164
|
|
|
137
|
-
items
|
|
165
|
+
items.push(node)
|
|
138
166
|
})
|
|
139
167
|
|
|
140
168
|
// convert nodes to bytes
|
|
@@ -150,8 +178,22 @@ export default function watr(nodes) {
|
|
|
150
178
|
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
151
179
|
}
|
|
152
180
|
|
|
181
|
+
// Generate metadata custom sections
|
|
182
|
+
const binMeta = () => {
|
|
183
|
+
const sections = []
|
|
184
|
+
for (const type in ctx.metadata) {
|
|
185
|
+
const name = vec(str(`"metadata.code.${type}"`))
|
|
186
|
+
const content = vec(ctx.metadata[type].map(([funcIdx, instances]) =>
|
|
187
|
+
[...uleb(funcIdx), ...vec(instances.map(([pos, data]) => [...uleb(pos), ...vec(data)]))]
|
|
188
|
+
))
|
|
189
|
+
sections.push(0, ...vec([...name, ...content]))
|
|
190
|
+
}
|
|
191
|
+
return sections
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
153
195
|
// build final binary
|
|
154
|
-
|
|
196
|
+
return Uint8Array.from([
|
|
155
197
|
0x00, 0x61, 0x73, 0x6d, // magic
|
|
156
198
|
0x01, 0x00, 0x00, 0x00, // version
|
|
157
199
|
...bin(SECTION.custom),
|
|
@@ -166,263 +208,243 @@ export default function watr(nodes) {
|
|
|
166
208
|
...bin(SECTION.start, false),
|
|
167
209
|
...bin(SECTION.elem),
|
|
168
210
|
...bin(SECTION.datacount, false),
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Build code metadata custom sections: metadata.code.<type>
|
|
175
|
-
for (const type in ctx.meta) {
|
|
176
|
-
const name = vec(str(`"metadata.code.${type}"`))
|
|
177
|
-
const content = vec(ctx.meta[type].map(([funcIdx, instances]) =>
|
|
178
|
-
[...uleb(funcIdx), ...vec(instances.map(([pos, data]) => [...uleb(pos), ...vec(str(data))]))]
|
|
179
|
-
))
|
|
180
|
-
out.push(0, ...vec([...name, ...content]))
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
out.push(...codeSection, ...bin(SECTION.data))
|
|
184
|
-
|
|
185
|
-
return Uint8Array.from(out)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// consume name eg. $t ...
|
|
189
|
-
const alias = (node, list) => {
|
|
190
|
-
let name = (node[0]?.[0] === '$' || node[0]?.[0] == null) && node.shift();
|
|
191
|
-
if (name && list) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
192
|
-
return name
|
|
211
|
+
...bin(SECTION.code),
|
|
212
|
+
...binMeta(),
|
|
213
|
+
...bin(SECTION.data)
|
|
214
|
+
])
|
|
193
215
|
}
|
|
194
216
|
|
|
195
|
-
// (type $id? (func param* result*))
|
|
196
|
-
// (type $id? (array (mut i8)))
|
|
197
|
-
// (type $id? (struct (field a)*)
|
|
198
|
-
// (type $id? (sub final? $nm* (struct|array|func ...)))
|
|
199
|
-
const typedef = ([dfn], ctx) => {
|
|
200
|
-
let subkind = 'subfinal', supertypes = [], compkind
|
|
201
|
-
if (dfn[0] === 'sub') {
|
|
202
|
-
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
203
|
-
dfn = (supertypes = dfn).pop() // last item is definition
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
[compkind, ...dfn] = dfn // composite type kind
|
|
207
217
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// register (implicit) type
|
|
216
|
-
const regtype = (param, result, ctx, idx = '$' + param + '>' + result) => (
|
|
217
|
-
(ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1),
|
|
218
|
-
idx
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
222
|
-
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
223
|
-
const typeuse = (nodes, ctx, names) => {
|
|
224
|
-
let idx, param, result
|
|
225
|
-
|
|
226
|
-
// explicit type (type 0|$name)
|
|
227
|
-
if (nodes[0]?.[0] === 'type') {
|
|
228
|
-
[, idx] = nodes.shift();
|
|
229
|
-
[param, result] = paramres(nodes, names);
|
|
230
|
-
|
|
231
|
-
const [, srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
232
|
-
|
|
233
|
-
// check type consistency (excludes forward refs)
|
|
234
|
-
if ((param.length || result.length) && srcParamRes.join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
235
|
-
|
|
236
|
-
return [idx, ...srcParamRes]
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// implicit type (param i32 i32)(result i32)
|
|
240
|
-
return [idx, ...paramres(nodes, names)]
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// consume (param t+)* (result t+)* sequence
|
|
244
|
-
const paramres = (nodes, names = true) => {
|
|
245
|
-
// let param = [], result = []
|
|
246
|
-
|
|
247
|
-
// collect param (param i32 i64) (param $x? i32)
|
|
248
|
-
let param = fieldseq(nodes, 'param', names)
|
|
249
|
-
|
|
250
|
-
// collect result eg. (result f64 f32)(result i32)
|
|
251
|
-
let result = fieldseq(nodes, 'result')
|
|
252
|
-
|
|
253
|
-
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
254
|
-
|
|
255
|
-
return [param, result]
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
259
|
-
// optionally allow or not names
|
|
260
|
-
const fieldseq = (nodes, field, names = false) => {
|
|
261
|
-
let seq = []
|
|
262
|
-
// collect field eg. (field f64 f32)(field i32)
|
|
263
|
-
while (nodes[0]?.[0] === field) {
|
|
264
|
-
let [, ...args] = nodes.shift()
|
|
265
|
-
let name = args[0]?.[0] === '$' && args.shift()
|
|
266
|
-
// expose name refs, if allowed
|
|
267
|
-
if (name) {
|
|
268
|
-
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
269
|
-
else err(`Unexpected ${field} name ${name}`)
|
|
270
|
-
}
|
|
271
|
-
seq.push(...args)
|
|
272
|
-
}
|
|
273
|
-
return seq
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// consume blocktype - makes sure either type or single result is returned
|
|
277
|
-
const blocktype = (nodes, ctx) => {
|
|
278
|
-
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
279
|
-
|
|
280
|
-
// get type - can be either idx or valtype (numtype | reftype)
|
|
281
|
-
if (!param.length && !result.length) return
|
|
282
|
-
|
|
283
|
-
// (result i32) - doesn't require registering type
|
|
284
|
-
if (!param.length && result.length === 1) return ['result', ...result]
|
|
285
|
-
|
|
286
|
-
// register implicit type
|
|
287
|
-
idx ??= regtype(param, result, ctx)
|
|
288
|
-
|
|
289
|
-
return ['type', idx]
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
293
|
-
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
294
|
-
const plain = (nodes, ctx) => {
|
|
295
|
-
let out = [], stack = [], label
|
|
296
|
-
// helper: check if node is immediate (not array operand)
|
|
297
|
-
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
218
|
+
/** Check if node is a valid index reference ($name or number) */
|
|
219
|
+
const isIdx = n => n?.[0] === '$' || !isNaN(n)
|
|
220
|
+
/** Check if node is an identifier (starts with $) */
|
|
221
|
+
const isId = n => n?.[0] === '$'
|
|
222
|
+
/** Check if node is align/offset memory parameter */
|
|
223
|
+
const isMemParam = n => n?.[0] === 'a' || n?.[0] === 'o'
|
|
298
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Normalize and flatten function body to stack form.
|
|
227
|
+
* Converts folded S-expressions to linear instruction sequence.
|
|
228
|
+
* Handles blocks, if/then/else, try_table, and metadata annotations.
|
|
229
|
+
*
|
|
230
|
+
* @param {Array} nodes - Function body nodes
|
|
231
|
+
* @param {Object} ctx - Compilation context with type info
|
|
232
|
+
* @returns {Array} Flattened instruction sequence
|
|
233
|
+
*/
|
|
234
|
+
function normalize(nodes, ctx) {
|
|
235
|
+
const out = []
|
|
236
|
+
nodes = [...nodes]
|
|
299
237
|
while (nodes.length) {
|
|
300
238
|
let node = nodes.shift()
|
|
301
|
-
|
|
302
|
-
// code metadata annotations - pass through as marker with metadata type and data
|
|
303
|
-
// (@metadata.code.<type> data:str)
|
|
304
|
-
if (Array.isArray(node) && node[0]?.startsWith?.('@metadata.code.')) {
|
|
305
|
-
let type = node[0].slice(15) // remove '@metadata.code.' prefix
|
|
306
|
-
out.push(['@metadata', type, node[1]])
|
|
307
|
-
continue
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// lookup is slower than sequence of known ifs
|
|
311
239
|
if (typeof node === 'string') {
|
|
312
240
|
out.push(node)
|
|
313
|
-
|
|
314
|
-
// block typeuse?
|
|
315
241
|
if (node === 'block' || node === 'if' || node === 'loop') {
|
|
316
|
-
|
|
317
|
-
if (nodes[0]?.[0] === '$') label = nodes.shift(), out.push(label), stack.push(label)
|
|
318
|
-
|
|
242
|
+
if (isId(nodes[0])) out.push(nodes.shift())
|
|
319
243
|
out.push(blocktype(nodes, ctx))
|
|
320
244
|
}
|
|
321
|
-
|
|
322
|
-
// else $label
|
|
323
|
-
// end $label - make sure it matches block label
|
|
324
245
|
else if (node === 'else' || node === 'end') {
|
|
325
|
-
if (nodes[0]
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// select (result i32 i32 i32)?
|
|
329
|
-
else if (node === 'select') {
|
|
330
|
-
out.push(paramres(nodes, 0)[1])
|
|
246
|
+
if (isId(nodes[0])) nodes.shift()
|
|
331
247
|
}
|
|
332
|
-
|
|
333
|
-
// call_indirect $table? $typeidx
|
|
334
|
-
// return_call_indirect $table? $typeidx
|
|
248
|
+
else if (node === 'select') out.push(paramres(nodes)[1])
|
|
335
249
|
else if (node.endsWith('call_indirect')) {
|
|
336
|
-
let tableidx =
|
|
337
|
-
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
250
|
+
let tableidx = isIdx(nodes[0]) ? nodes.shift() : 0, [idx, param, result] = typeuse(nodes, ctx)
|
|
338
251
|
out.push(tableidx, ['type', idx ?? regtype(param, result, ctx)])
|
|
339
252
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
else if (node
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
253
|
+
else if (node === 'table.init') out.push(isIdx(nodes[1]) ? nodes.shift() : 0, nodes.shift())
|
|
254
|
+
else if (node === 'table.copy' || node === 'memory.copy') out.push(isIdx(nodes[0]) ? nodes.shift() : 0, isIdx(nodes[0]) ? nodes.shift() : 0)
|
|
255
|
+
else if (node.startsWith('table.')) out.push(isIdx(nodes[0]) ? nodes.shift() : 0)
|
|
256
|
+
else if (node === 'memory.init') {
|
|
257
|
+
out.push(...(isIdx(nodes[1]) ? [nodes.shift(), nodes.shift()].reverse() : [nodes.shift(), 0]))
|
|
258
|
+
ctx.datacount && (ctx.datacount[0] = true)
|
|
346
259
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
260
|
+
else if (node === 'data.drop' || node === 'array.new_data' || node === 'array.init_data') {
|
|
261
|
+
node === 'data.drop' && out.push(nodes.shift())
|
|
262
|
+
ctx.datacount && (ctx.datacount[0] = true)
|
|
263
|
+
}
|
|
264
|
+
// memory.* instructions and load/store with optional memory index
|
|
265
|
+
else if ((node.startsWith('memory.') || node.endsWith('load') || node.endsWith('store')) && isIdx(nodes[0])) out.push(nodes.shift())
|
|
266
|
+
}
|
|
267
|
+
else if (Array.isArray(node)) {
|
|
268
|
+
const op = node[0]
|
|
269
|
+
node.i != null && (err.i = node.i) // track position for errors
|
|
270
|
+
|
|
271
|
+
// code metadata annotations - pass through as marker with metadata type and data
|
|
272
|
+
// (@metadata.code.<type> data:str)
|
|
273
|
+
if (op?.startsWith?.('@metadata.code.')) {
|
|
274
|
+
let type = op.slice(15) // remove '@metadata.code.' prefix
|
|
275
|
+
out.push(['@metadata', type, node[1]])
|
|
276
|
+
continue
|
|
351
277
|
}
|
|
352
278
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
279
|
+
// Check if node is a valid instruction (string with opcode in INSTR)
|
|
280
|
+
if (typeof op !== 'string' || !Array.isArray(INSTR[op])) { out.push(node); continue }
|
|
281
|
+
const parts = node.slice(1)
|
|
282
|
+
if (op === 'block' || op === 'loop') {
|
|
283
|
+
out.push(op)
|
|
284
|
+
if (isId(parts[0])) out.push(parts.shift())
|
|
285
|
+
out.push(blocktype(parts, ctx), ...normalize(parts, ctx), 'end')
|
|
286
|
+
}
|
|
287
|
+
else if (op === 'if') {
|
|
288
|
+
let then = [], els = []
|
|
289
|
+
if (parts.at(-1)?.[0] === 'else') els = normalize(parts.pop().slice(1), ctx)
|
|
290
|
+
if (parts.at(-1)?.[0] === 'then') then = normalize(parts.pop().slice(1), ctx)
|
|
291
|
+
let immed = [op]
|
|
292
|
+
if (isId(parts[0])) immed.push(parts.shift())
|
|
293
|
+
immed.push(blocktype(parts, ctx))
|
|
294
|
+
out.push(...normalize(parts, ctx), ...immed, ...then)
|
|
295
|
+
els.length && out.push('else', ...els)
|
|
296
|
+
out.push('end')
|
|
297
|
+
}
|
|
298
|
+
else if (op === 'try_table') {
|
|
299
|
+
out.push(op)
|
|
300
|
+
if (isId(parts[0])) out.push(parts.shift())
|
|
301
|
+
out.push(blocktype(parts, ctx))
|
|
302
|
+
// Collect catch clauses
|
|
303
|
+
while (parts[0]?.[0] === 'catch' || parts[0]?.[0] === 'catch_ref' || parts[0]?.[0] === 'catch_all' || parts[0]?.[0] === 'catch_all_ref') {
|
|
304
|
+
out.push(parts.shift())
|
|
305
|
+
}
|
|
306
|
+
out.push(...normalize(parts, ctx), 'end')
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
const imm = []
|
|
310
|
+
// Collect immediate operands (non-arrays or special forms like type/param/result/ref)
|
|
311
|
+
while (parts.length && (!Array.isArray(parts[0]) || 'type,param,result,ref'.includes(parts[0][0]))) imm.push(parts.shift())
|
|
312
|
+
out.push(...normalize(parts, ctx), op, ...imm)
|
|
313
|
+
nodes.unshift(...out.splice(out.length - 1 - imm.length))
|
|
356
314
|
}
|
|
315
|
+
} else out.push(node)
|
|
316
|
+
}
|
|
317
|
+
return out
|
|
318
|
+
}
|
|
357
319
|
|
|
358
|
-
|
|
359
|
-
|
|
320
|
+
/**
|
|
321
|
+
* Register implicit function type, return type index.
|
|
322
|
+
* Creates canonical name like '$i32,i32>i32' for deduplication.
|
|
323
|
+
*
|
|
324
|
+
* @param {string[]} param - Parameter types
|
|
325
|
+
* @param {string[]} result - Result types
|
|
326
|
+
* @param {Object} ctx - Compilation context
|
|
327
|
+
* @param {string} [idx] - Type identifier
|
|
328
|
+
* @returns {string} Type index/identifier
|
|
329
|
+
*/
|
|
330
|
+
const regtype = (param, result, ctx, idx = '$' + param + '>' + result) => (ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1, idx)
|
|
360
331
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Collect field sequence: (field a) (field b c) → [a, b, c].
|
|
334
|
+
* Tracks named fields for index lookup.
|
|
335
|
+
*
|
|
336
|
+
* @param {Array} nodes - Nodes to consume from
|
|
337
|
+
* @param {string} field - Field keyword ('param', 'result', 'field')
|
|
338
|
+
* @returns {Array} Collected values with named indices
|
|
339
|
+
*/
|
|
340
|
+
const fieldseq = (nodes, field) => {
|
|
341
|
+
let seq = []
|
|
342
|
+
while (nodes[0]?.[0] === field) {
|
|
343
|
+
let [, ...args] = nodes.shift(), nm = isId(args[0]) && args.shift()
|
|
344
|
+
if (nm) nm in seq ? (() => { throw Error(`Duplicate ${field} ${nm}`) })() : seq[nm] = seq.length
|
|
345
|
+
seq.push(...args)
|
|
346
|
+
}
|
|
347
|
+
return seq
|
|
348
|
+
}
|
|
364
349
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Consume (param ...)* (result ...)* from nodes.
|
|
352
|
+
*
|
|
353
|
+
* @param {Array} nodes - Nodes to consume from
|
|
354
|
+
* @returns {[string[], string[]]} [params, results]
|
|
355
|
+
*/
|
|
356
|
+
const paramres = (nodes) => {
|
|
357
|
+
let param = fieldseq(nodes, 'param'), result = fieldseq(nodes, 'result')
|
|
358
|
+
if (nodes[0]?.[0] === 'param') throw Error('Unexpected param')
|
|
359
|
+
return [param, result]
|
|
360
|
+
}
|
|
369
361
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Consume typeuse: (type idx)? (param ...)* (result ...)*.
|
|
364
|
+
* Resolves type reference or returns inline signature.
|
|
365
|
+
*
|
|
366
|
+
* @param {Array} nodes - Nodes to consume from
|
|
367
|
+
* @param {Object} ctx - Compilation context with type table
|
|
368
|
+
* @returns {[string|undefined, string[], string[]]} [typeIdx, params, results]
|
|
369
|
+
*/
|
|
370
|
+
const typeuse = (nodes, ctx) => {
|
|
371
|
+
if (nodes[0]?.[0] !== 'type') return [, ...paramres(nodes)]
|
|
372
|
+
let [, idx] = nodes.shift(), [param, result] = paramres(nodes)
|
|
373
|
+
const entry = ctx.type[(typeof idx === 'string' && isNaN(idx)) ? ctx.type[idx] : +idx]
|
|
374
|
+
if (!entry) throw Error(`Unknown type ${idx}`)
|
|
375
|
+
if ((param.length || result.length) && entry[1].join('>') !== param + '>' + result) throw Error(`Type ${idx} mismatch`)
|
|
376
|
+
return [idx, ...entry[1]]
|
|
377
|
+
}
|
|
375
378
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (node[node.length - 1]?.[0] === 'then') then = plain(node.pop(), ctx)
|
|
379
|
+
/**
|
|
380
|
+
* Resolve blocktype: void | (result t) | (type idx).
|
|
381
|
+
* Returns abbreviated form when possible.
|
|
382
|
+
*
|
|
383
|
+
* @param {Array} nodes - Nodes to consume from
|
|
384
|
+
* @param {Object} ctx - Compilation context
|
|
385
|
+
* @returns {Array|undefined} Blocktype node or undefined for void
|
|
386
|
+
*/
|
|
387
|
+
const blocktype = (nodes, ctx) => {
|
|
388
|
+
let [idx, param, result] = typeuse(nodes, ctx)
|
|
389
|
+
if (!param.length && !result.length) return
|
|
390
|
+
if (!param.length && result.length === 1) return ['result', ...result]
|
|
391
|
+
return ['type', idx ?? regtype(param, result, ctx)]
|
|
392
|
+
}
|
|
391
393
|
|
|
392
|
-
// label?
|
|
393
|
-
if (node[0]?.[0] === '$') immed.push(node.shift())
|
|
394
394
|
|
|
395
|
-
// blocktype?
|
|
396
|
-
immed.push(blocktype(node, ctx))
|
|
397
395
|
|
|
398
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Consume and register section item name (e.g., $foo).
|
|
398
|
+
* Stores alias in list for later index resolution.
|
|
399
|
+
*
|
|
400
|
+
* @param {Array} node - Node array (mutated)
|
|
401
|
+
* @param {Array} list - Section list with name property
|
|
402
|
+
* @returns {string|false} Name if found, false otherwise
|
|
403
|
+
*/
|
|
404
|
+
const name = (node, list) => {
|
|
405
|
+
let nm = isId(node[0]) && node.shift();
|
|
406
|
+
if (nm) nm in list ? err(`Duplicate ${list.name} ${nm}`) : list[nm] = list.length; // save alias
|
|
407
|
+
return nm
|
|
408
|
+
}
|
|
399
409
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Parse type definition: func, array, struct, or sub(type).
|
|
412
|
+
* Handles recursive types and subtyping.
|
|
413
|
+
*
|
|
414
|
+
* @param {Array} node - [definition] where definition is func/array/struct/sub
|
|
415
|
+
* @param {Object} ctx - Compilation context
|
|
416
|
+
* @returns {[string, any, string, string[]]} [kind, fields, subkind, supertypes]
|
|
417
|
+
*/
|
|
418
|
+
const typedef = ([dfn], ctx) => {
|
|
419
|
+
let subkind = 'subfinal', supertypes = [], compkind
|
|
420
|
+
if (dfn[0] === 'sub') {
|
|
421
|
+
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
422
|
+
dfn = (supertypes = dfn).pop() // last item is definition
|
|
405
423
|
}
|
|
406
424
|
|
|
407
|
-
|
|
425
|
+
[compkind, ...dfn] = dfn // composite type kind
|
|
426
|
+
|
|
427
|
+
if (compkind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
428
|
+
else if (compkind === 'struct') dfn = fieldseq(dfn, 'field')
|
|
429
|
+
else if (compkind === 'array') [dfn] = dfn
|
|
430
|
+
|
|
431
|
+
return [compkind, dfn, subkind, supertypes]
|
|
408
432
|
}
|
|
409
433
|
|
|
410
434
|
|
|
411
435
|
// build section binary [by section codes] (non consuming)
|
|
412
436
|
const build = [
|
|
413
|
-
// (@custom "name" placement? data)
|
|
414
|
-
// placement is optional: (before|after section) or (before first)|(after last)
|
|
415
|
-
// For now we ignore placement and just output the custom section
|
|
437
|
+
// (@custom "name" placement? data) - custom section builder
|
|
416
438
|
([name, ...rest], ctx) => {
|
|
417
|
-
// Check if second arg is placement directive
|
|
439
|
+
// Check if second arg is placement directive (before|after section)
|
|
418
440
|
let data = rest
|
|
419
441
|
if (rest[0]?.[0] === 'before' || rest[0]?.[0] === 'after') {
|
|
420
442
|
// Skip placement for now - would need more complex section ordering
|
|
421
443
|
data = rest.slice(1)
|
|
422
444
|
}
|
|
423
|
-
|
|
424
445
|
// Custom section format: name (vec string) + raw content bytes
|
|
425
|
-
|
|
446
|
+
// parse already returns strings as byte arrays, so just vec them
|
|
447
|
+
return [...vec(name), ...data.flat()]
|
|
426
448
|
},
|
|
427
449
|
// type kinds
|
|
428
450
|
// (func params result)
|
|
@@ -457,7 +479,7 @@ const build = [
|
|
|
457
479
|
return [DEFTYPE[kind], ...details]
|
|
458
480
|
},
|
|
459
481
|
|
|
460
|
-
// (import "math" "add" (func|table|global|memory dfn?))
|
|
482
|
+
// (import "math" "add" (func|table|global|memory|tag dfn?))
|
|
461
483
|
([mod, field, [kind, ...dfn]], ctx) => {
|
|
462
484
|
let details
|
|
463
485
|
|
|
@@ -481,7 +503,7 @@ const build = [
|
|
|
481
503
|
}
|
|
482
504
|
else err(`Unknown kind ${kind}`)
|
|
483
505
|
|
|
484
|
-
return ([...vec(
|
|
506
|
+
return ([...vec(mod), ...vec(field), KIND[kind], ...details])
|
|
485
507
|
},
|
|
486
508
|
|
|
487
509
|
// (func $name? ...params result ...body)
|
|
@@ -499,8 +521,8 @@ const build = [
|
|
|
499
521
|
// (global $id? (mut i32) (i32.const 42))
|
|
500
522
|
([t, init], ctx) => [...fieldtype(t, ctx), ...expr(init, ctx)],
|
|
501
523
|
|
|
502
|
-
//
|
|
503
|
-
([nm, [kind, l]], ctx) => ([...vec(
|
|
524
|
+
// (export "name" (func|table|mem $name|idx))
|
|
525
|
+
([nm, [kind, l]], ctx) => ([...vec(nm), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
504
526
|
|
|
505
527
|
// (start $main)
|
|
506
528
|
([l], ctx) => uleb(id(l, ctx.func)),
|
|
@@ -516,10 +538,15 @@ const build = [
|
|
|
516
538
|
if (parts[0] === 'declare') parts.shift(), declare = 1
|
|
517
539
|
|
|
518
540
|
// table?
|
|
519
|
-
if (parts[0][0] === 'table') {
|
|
541
|
+
if (parts[0]?.[0] === 'table') {
|
|
520
542
|
[, tabidx] = parts.shift()
|
|
521
543
|
tabidx = id(tabidx, ctx.table)
|
|
522
544
|
}
|
|
545
|
+
// Handle abbreviated form: (elem tableidx (offset ...) ...) where tableidx is directly a number/identifier
|
|
546
|
+
else if ((typeof parts[0] === 'string' || typeof parts[0] === 'number') &&
|
|
547
|
+
(parts[1]?.[0] === 'offset' || (Array.isArray(parts[1]) && parts[1][0] !== 'item' && !parts[1][0]?.startsWith('ref')))) {
|
|
548
|
+
tabidx = id(parts.shift(), ctx.table)
|
|
549
|
+
}
|
|
523
550
|
|
|
524
551
|
// (offset expr)|expr
|
|
525
552
|
if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
|
|
@@ -531,15 +558,17 @@ const build = [
|
|
|
531
558
|
else if (!declare) passive = 1
|
|
532
559
|
|
|
533
560
|
// funcref|externref|(ref ...)
|
|
534
|
-
if (
|
|
561
|
+
if (TYPE[parts[0]] || parts[0]?.[0] === 'ref') rt = reftype(parts.shift(), ctx)
|
|
535
562
|
// func ... abbr https://webassembly.github.io/function-references/core/text/modules.html#id7
|
|
536
|
-
else if (parts[0] === 'func') rt = [
|
|
563
|
+
else if (parts[0] === 'func') rt = [TYPE[parts.shift()]]
|
|
537
564
|
// or anything else
|
|
538
|
-
else rt = [
|
|
565
|
+
else rt = [TYPE.func]
|
|
539
566
|
|
|
540
567
|
// deabbr els sequence, detect expr usage
|
|
541
568
|
parts = parts.map(el => {
|
|
542
|
-
|
|
569
|
+
// (item ref.func $f) or (item (ref.func $f)) → $f
|
|
570
|
+
if (el[0] === 'item') el = el.length === 3 && el[1] === 'ref.func' ? el[2] : el[1]
|
|
571
|
+
// (ref.func $f) → $f
|
|
543
572
|
if (el[0] === 'ref.func') [, el] = el
|
|
544
573
|
// (ref.null func) and other expressions turn expr els mode
|
|
545
574
|
if (typeof el !== 'string') elexpr = 1
|
|
@@ -548,7 +577,7 @@ const build = [
|
|
|
548
577
|
|
|
549
578
|
// reftype other than (ref null? func) forces table index via nofunc flag
|
|
550
579
|
// also it forces elexpr
|
|
551
|
-
if (rt[0] !==
|
|
580
|
+
if (rt[0] !== TYPE.funcref) nofunc = 1, elexpr = 1
|
|
552
581
|
|
|
553
582
|
// mode:
|
|
554
583
|
// bit 0 indicates a passive or declarative segment
|
|
@@ -608,38 +637,31 @@ const build = [
|
|
|
608
637
|
// collect locals
|
|
609
638
|
while (body[0]?.[0] === 'local') {
|
|
610
639
|
let [, ...types] = body.shift()
|
|
611
|
-
if (types[0]
|
|
612
|
-
let
|
|
613
|
-
if (
|
|
614
|
-
else ctx.local[
|
|
640
|
+
if (isId(types[0])) {
|
|
641
|
+
let nm = types.shift()
|
|
642
|
+
if (nm in ctx.local) err(`Duplicate local ${nm}`)
|
|
643
|
+
else ctx.local[nm] = ctx.local.length
|
|
615
644
|
}
|
|
616
645
|
ctx.local.push(...types)
|
|
617
646
|
}
|
|
618
647
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
bytes.push(0x0b)
|
|
623
|
-
|
|
624
|
-
// Extract metadata placeholders (arrays), group by type
|
|
625
|
-
const metaByType = {}, cleanBytes = []
|
|
626
|
-
for (const b of bytes)
|
|
627
|
-
if (Array.isArray(b)) for (const [type, data] of b) (metaByType[type] ??= []).push([cleanBytes.length, data])
|
|
628
|
-
else cleanBytes.push(b)
|
|
648
|
+
// Setup metadata tracking for this function
|
|
649
|
+
ctx.meta = {}
|
|
650
|
+
const bytes = instr(body, ctx)
|
|
629
651
|
|
|
630
|
-
// Store metadata for this function
|
|
652
|
+
// Store collected metadata for this function
|
|
631
653
|
const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
|
|
632
|
-
for (const type in
|
|
654
|
+
for (const type in ctx.meta) ((ctx.metadata ??= {})[type] ??= []).push([funcIdx, ctx.meta[type]])
|
|
633
655
|
|
|
634
656
|
// squash locals into (n:u32 t:valtype)*, n is number and t is type
|
|
635
657
|
// we skip locals provided by params
|
|
636
658
|
let loctypes = ctx.local.slice(param.length).reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
|
|
637
659
|
|
|
638
660
|
// cleanup tmp state
|
|
639
|
-
ctx.local = ctx.block = null
|
|
661
|
+
ctx.local = ctx.block = ctx.meta = null
|
|
640
662
|
|
|
641
663
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
642
|
-
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...
|
|
664
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...bytes])
|
|
643
665
|
},
|
|
644
666
|
|
|
645
667
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
@@ -653,12 +675,17 @@ const build = [
|
|
|
653
675
|
[, memidx] = inits.shift()
|
|
654
676
|
memidx = id(memidx, ctx.memory)
|
|
655
677
|
}
|
|
678
|
+
// Handle abbreviated form: (data memidx (offset ...) ...) where memidx is directly a number/identifier
|
|
679
|
+
else if ((typeof inits[0] === 'string' || typeof inits[0] === 'number') &&
|
|
680
|
+
(inits[1]?.[0] === 'offset' || (Array.isArray(inits[1]) && typeof inits[1][0] === 'string'))) {
|
|
681
|
+
memidx = id(inits.shift(), ctx.memory)
|
|
682
|
+
}
|
|
656
683
|
|
|
657
684
|
// (offset (i32.const 0)) or (i32.const 0)
|
|
658
|
-
if (typeof inits[0]
|
|
685
|
+
if (Array.isArray(inits[0]) && typeof inits[0]?.[0] === 'string') {
|
|
659
686
|
offset = inits.shift()
|
|
660
|
-
if (offset
|
|
661
|
-
|
|
687
|
+
if (offset[0] === 'offset') [, offset] = offset
|
|
688
|
+
offset ?? err('Bad offset', offset)
|
|
662
689
|
}
|
|
663
690
|
|
|
664
691
|
return ([
|
|
@@ -670,23 +697,24 @@ const build = [
|
|
|
670
697
|
// passive: 1
|
|
671
698
|
[1]
|
|
672
699
|
),
|
|
673
|
-
...vec(
|
|
700
|
+
...vec(inits.flat())
|
|
674
701
|
])
|
|
675
702
|
},
|
|
676
703
|
|
|
677
704
|
// datacount
|
|
678
|
-
(nodes, ctx) => uleb(ctx.data.length)
|
|
679
|
-
]
|
|
705
|
+
(nodes, ctx) => uleb(ctx.data.length),
|
|
680
706
|
|
|
681
|
-
// (tag $
|
|
682
|
-
|
|
707
|
+
// (tag $name? (type idx))
|
|
708
|
+
([[, typeidx]], ctx) => [0x00, ...uleb(id(typeidx, ctx.type))]
|
|
709
|
+
]
|
|
683
710
|
|
|
684
|
-
//
|
|
711
|
+
// Build reference type encoding (ref/refnull forms, not related to regtype which handles func types)
|
|
712
|
+
// https://webassembly.github.io/gc/core/binary/types.html#reference-types
|
|
685
713
|
const reftype = (t, ctx) => (
|
|
686
714
|
t[0] === 'ref' ?
|
|
687
715
|
t[1] == 'null' ?
|
|
688
|
-
|
|
689
|
-
[TYPE.ref, ...uleb(
|
|
716
|
+
TYPE[t[2]] ? [TYPE[t[2]]] : [TYPE.refnull, ...uleb(id(t[t.length - 1], ctx.type))] :
|
|
717
|
+
[TYPE.ref, ...uleb(TYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))] :
|
|
690
718
|
// abbrs
|
|
691
719
|
[TYPE[t] ?? err(`Unknown type ${t}`)]
|
|
692
720
|
);
|
|
@@ -695,320 +723,191 @@ const reftype = (t, ctx) => (
|
|
|
695
723
|
const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[1] : t, ctx), mut];
|
|
696
724
|
|
|
697
725
|
|
|
698
|
-
// consume one instruction from nodes sequence
|
|
699
|
-
const instr = (nodes, ctx) => {
|
|
700
|
-
if (!nodes?.length) return []
|
|
701
726
|
|
|
702
|
-
let out = [], op = nodes.shift(), immed, code
|
|
703
|
-
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
704
727
|
|
|
705
|
-
// Handle code metadata marker - store for next instruction
|
|
706
|
-
// ['@metadata', type, data]
|
|
707
|
-
if (op?.[0] === '@metadata') {
|
|
708
|
-
;(ctx._meta ??= []).push(op.slice(1))
|
|
709
|
-
return nodes.length ? instr(nodes, ctx) : []
|
|
710
|
-
}
|
|
711
728
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
729
|
+
// Pre-defined instruction handlers
|
|
730
|
+
const IMM = {
|
|
731
|
+
null: () => [],
|
|
732
|
+
reversed: (n, c) => { let t = n.shift(), e = n.shift(); return [...uleb(id(e, c.elem)), ...uleb(id(t, c.table))] },
|
|
733
|
+
block: (n, c) => {
|
|
734
|
+
c.block.push(1)
|
|
735
|
+
isId(n[0]) && (c.block[n.shift()] = c.block.length)
|
|
736
|
+
let t = n.shift()
|
|
737
|
+
return !t ? [TYPE.void] : t[0] === 'result' ? reftype(t[1], c) : uleb(id(t[1], c.type))
|
|
738
|
+
},
|
|
739
|
+
try_table: (n, c) => {
|
|
740
|
+
isId(n[0]) && (c.block[n.shift()] = c.block.length + 1)
|
|
741
|
+
let blocktype = n.shift()
|
|
742
|
+
let result = !blocktype ? [TYPE.void] : blocktype[0] === 'result' ? reftype(blocktype[1], c) : uleb(id(blocktype[1], c.type))
|
|
743
|
+
// Collect catch clauses BEFORE pushing try_table to block stack (catch labels are relative to outer blocks)
|
|
744
|
+
let catches = [], count = 0
|
|
745
|
+
while (n[0]?.[0] === 'catch' || n[0]?.[0] === 'catch_ref' || n[0]?.[0] === 'catch_all' || n[0]?.[0] === 'catch_all_ref') {
|
|
746
|
+
let clause = n.shift()
|
|
747
|
+
let kind = clause[0] === 'catch' ? 0x00 : clause[0] === 'catch_ref' ? 0x01 : clause[0] === 'catch_all' ? 0x02 : 0x03
|
|
748
|
+
if (kind <= 0x01) catches.push(kind, ...uleb(id(clause[1], c.tag)), ...uleb(blockid(clause[2], c.block)))
|
|
749
|
+
else catches.push(kind, ...uleb(blockid(clause[1], c.block)))
|
|
750
|
+
count++
|
|
751
|
+
}
|
|
752
|
+
c.block.push(1) // NOW push try_table to block stack after processing catches
|
|
753
|
+
return [...result, ...uleb(count), ...catches]
|
|
754
|
+
},
|
|
755
|
+
end: (_n, c) => (c.block.pop(), []),
|
|
756
|
+
call_indirect: (n, c) => { let t = n.shift(), [, idx] = n.shift(); return [...uleb(id(idx, c.type)), ...uleb(id(t, c.table))] },
|
|
757
|
+
br_table: (n, c) => {
|
|
758
|
+
let labels = [], count = 0
|
|
759
|
+
while (n[0] && (!isNaN(n[0]) || isId(n[0]))) (labels.push(...uleb(blockid(n.shift(), c.block))), count++)
|
|
760
|
+
return [...uleb(count - 1), ...labels]
|
|
761
|
+
},
|
|
762
|
+
select: (n, c) => { let r = n.shift() || []; return r.length ? vec(r.map(t => reftype(t, c))) : [] },
|
|
763
|
+
ref_null: (n, c) => { let t = n.shift(); return TYPE[t] ? [TYPE[t]] : uleb(id(t, c.type)) },
|
|
764
|
+
memarg: (n, c, op) => memargEnc(n, op, isIdx(n[0]) && !isMemParam(n[0]) ? id(n.shift(), c.memory) : 0),
|
|
765
|
+
opt_memory: (n, c) => uleb(id(isIdx(n[0]) ? n.shift() : 0, c.memory)),
|
|
766
|
+
reftype: (n, c) => { let ht = reftype(n.shift(), c); return ht.length > 1 ? ht.slice(1) : ht },
|
|
767
|
+
reftype2: (n, c) => { let b = blockid(n.shift(), c.block), h1 = reftype(n.shift(), c), h2 = reftype(n.shift(), c); return [((h2[0] !== TYPE.ref) << 1) | (h1[0] !== TYPE.ref), ...uleb(b), h1.pop(), h2.pop()] },
|
|
768
|
+
v128const: (n) => {
|
|
769
|
+
let [t, num] = n.shift().split('x'), bits = +t.slice(1), stride = bits >>> 3; num = +num
|
|
770
|
+
if (t[0] === 'i') {
|
|
771
|
+
let arr = num === 16 ? new Uint8Array(16) : num === 8 ? new Uint16Array(8) : num === 4 ? new Uint32Array(4) : new BigUint64Array(2)
|
|
772
|
+
for (let j = 0; j < num; j++) arr[j] = encode[t].parse(n.shift())
|
|
773
|
+
return [...new Uint8Array(arr.buffer)]
|
|
774
|
+
}
|
|
775
|
+
let arr = new Uint8Array(16)
|
|
776
|
+
for (let j = 0; j < num; j++) arr.set(encode[t](n.shift()), j * stride)
|
|
777
|
+
return [...arr]
|
|
778
|
+
},
|
|
779
|
+
shuffle: (n) => {
|
|
780
|
+
let result = []
|
|
781
|
+
for (let j = 0; j < 16; j++) result.push(parseUint(n.shift(), 32))
|
|
782
|
+
if (typeof n[0] === 'string' && !isNaN(n[0])) err(`invalid lane length`)
|
|
783
|
+
return result
|
|
784
|
+
},
|
|
785
|
+
memlane: (n, c, op) => {
|
|
786
|
+
// SIMD lane: [memidx?] [offset/align]* laneidx - memidx present if isId OR (isIdx AND (next is memParam OR isIdx))
|
|
787
|
+
const memIdx = isId(n[0]) || (isIdx(n[0]) && (isMemParam(n[1]) || isIdx(n[1]))) ? id(n.shift(), c.memory) : 0
|
|
788
|
+
return [...memargEnc(n, op, memIdx), ...uleb(parseUint(n.shift()))]
|
|
789
|
+
},
|
|
790
|
+
'*': (n) => uleb(n.shift()),
|
|
791
|
+
|
|
792
|
+
// *idx types
|
|
793
|
+
labelidx: (n, c) => uleb(blockid(n.shift(), c.block)),
|
|
794
|
+
laneidx: (n) => [parseUint(n.shift(), 0xff)],
|
|
795
|
+
funcidx: (n, c) => uleb(id(n.shift(), c.func)),
|
|
796
|
+
typeidx: (n, c) => uleb(id(n.shift(), c.type)),
|
|
797
|
+
tableidx: (n, c) => uleb(id(n.shift(), c.table)),
|
|
798
|
+
memoryidx: (n, c) => uleb(id(n.shift(), c.memory)),
|
|
799
|
+
globalidx: (n, c) => uleb(id(n.shift(), c.global)),
|
|
800
|
+
localidx: (n, c) => uleb(id(n.shift(), c.local)),
|
|
801
|
+
dataidx: (n, c) => uleb(id(n.shift(), c.data)),
|
|
802
|
+
elemidx: (n, c) => uleb(id(n.shift(), c.elem)),
|
|
803
|
+
tagidx: (n, c) => uleb(id(n.shift(), c.tag)),
|
|
804
|
+
'memoryidx?': (n, c) => uleb(id(isIdx(n[0]) ? n.shift() : 0, c.memory)),
|
|
805
|
+
|
|
806
|
+
// Value type
|
|
807
|
+
i32: (n) => encode.i32(n.shift()),
|
|
808
|
+
i64: (n) => encode.i64(n.shift()),
|
|
809
|
+
f32: (n) => encode.f32(n.shift()),
|
|
810
|
+
f64: (n) => encode.f64(n.shift()),
|
|
811
|
+
v128: (n) => encode.v128(n.shift()),
|
|
812
|
+
|
|
813
|
+
// Combinations
|
|
814
|
+
typeidx_field: (n, c) => { let typeId = id(n.shift(), c.type); return [...uleb(typeId), ...uleb(id(n.shift(), c.type[typeId][1]))] },
|
|
815
|
+
typeidx_multi: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(n.shift())],
|
|
816
|
+
typeidx_dataidx: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.data))],
|
|
817
|
+
typeidx_elemidx: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.elem))],
|
|
818
|
+
typeidx_typeidx: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.type))],
|
|
819
|
+
dataidx_memoryidx: (n, c) => [...uleb(id(n.shift(), c.data)), ...uleb(id(n.shift(), c.memory))],
|
|
820
|
+
memoryidx_memoryidx: (n, c) => [...uleb(id(n.shift(), c.memory)), ...uleb(id(n.shift(), c.memory))],
|
|
821
|
+
tableidx_tableidx: (n, c) => [...uleb(id(n.shift(), c.table)), ...uleb(id(n.shift(), c.table))]
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
// per-op imm handlers
|
|
825
|
+
const HANDLER = {};
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
// Populate INSTR and IMM
|
|
829
|
+
(function populate(items, pre) {
|
|
830
|
+
for (let op = 0, item, nm, imm; op < items.length; op++) if (item = items[op]) {
|
|
831
|
+
// Nested array (0xfb, 0xfc, 0xfd opcodes)
|
|
832
|
+
if (Array.isArray(item)) populate(item, op)
|
|
833
|
+
else [nm, imm] = item.split(' '), INSTR[nm] = pre ? [pre, ...uleb(op)] : [op], imm && (HANDLER[nm] = IMM[imm])
|
|
720
834
|
}
|
|
835
|
+
})(INSTR);
|
|
721
836
|
|
|
722
|
-
[...immed] = isNaN(op[0]) && INSTR[op] || err(`Unknown instruction ${op}`)
|
|
723
|
-
code = immed[0]
|
|
724
|
-
|
|
725
|
-
// gc-related
|
|
726
|
-
// https://webassembly.github.io/gc/core/binary/instructions.html#reference-instructions
|
|
727
|
-
if (code === 0x0fb) {
|
|
728
|
-
[, code] = immed
|
|
729
|
-
|
|
730
|
-
// struct.new $t ... array.set $t
|
|
731
|
-
if ((code >= 0 && code <= 14) || (code >= 16 && code <= 19)) {
|
|
732
|
-
let tidx = id(nodes.shift(), ctx.type)
|
|
733
|
-
immed.push(...uleb(tidx))
|
|
734
|
-
|
|
735
|
-
// struct.get|set* $t $f - read field by index from struct definition (ctx.type[structidx][dfnidx])
|
|
736
|
-
if (code >= 2 && code <= 5) immed.push(...uleb(id(nodes.shift(), ctx.type[tidx][1])))
|
|
737
|
-
// array.new_fixed $t n
|
|
738
|
-
else if (code === 8) immed.push(...uleb(nodes.shift()))
|
|
739
|
-
// array.new_data|init_data $t $d
|
|
740
|
-
else if (code === 9 || code === 18) {
|
|
741
|
-
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.data)))
|
|
742
|
-
}
|
|
743
|
-
// array.new_elem|init_elem $t $e
|
|
744
|
-
else if (code === 10 || code === 19) immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
745
|
-
// array.copy $t $t
|
|
746
|
-
else if (code === 17) immed.push(...uleb(id(nodes.shift(), ctx.type)))
|
|
747
|
-
}
|
|
748
|
-
// ref.test|cast (ref null? $t|heaptype)
|
|
749
|
-
else if (code >= 20 && code <= 23) {
|
|
750
|
-
let ht = reftype(nodes.shift(), ctx)
|
|
751
|
-
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop() + 1) // ref.test|cast (ref null $t) is next op
|
|
752
|
-
if (ht.length > 1) ht.shift() // pop ref
|
|
753
|
-
immed.push(...ht)
|
|
754
|
-
}
|
|
755
|
-
// br_on_cast[_fail] $l? (ref null? ht1) (ref null? ht2)
|
|
756
|
-
else if (code === 24 || code === 25) {
|
|
757
|
-
let i = blockid(nodes.shift(), ctx.block),
|
|
758
|
-
ht1 = reftype(nodes.shift(), ctx),
|
|
759
|
-
ht2 = reftype(nodes.shift(), ctx),
|
|
760
|
-
castflags = ((ht2[0] !== REFTYPE.ref) << 1) | (ht1[0] !== REFTYPE.ref)
|
|
761
|
-
immed.push(castflags, ...uleb(i), ht1.pop(), ht2.pop()) // we take only abstype or
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
837
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
else if (code == 0xfc) {
|
|
769
|
-
[, code] = immed
|
|
838
|
+
// instruction encoder
|
|
839
|
+
const instr = (nodes, ctx) => {
|
|
840
|
+
let out = [], meta = []
|
|
770
841
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
let m = isImm(nodes[0]) ? nodes.shift() : 0, d = isImm(nodes[0]) ? nodes.shift() : 0
|
|
774
|
-
immed.push(...uleb(id(d, ctx.data)), ...uleb(id(m, ctx.memory)))
|
|
775
|
-
}
|
|
776
|
-
// data.drop idx
|
|
777
|
-
else if (code === 0x09) {
|
|
778
|
-
immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
779
|
-
}
|
|
780
|
-
// memory.copy dstmem srcmem
|
|
781
|
-
else if (code === 0x0a) {
|
|
782
|
-
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)), ...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
783
|
-
}
|
|
784
|
-
// memory.fill memidx
|
|
785
|
-
else if (code === 0x0b) {
|
|
786
|
-
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
787
|
-
}
|
|
842
|
+
while (nodes?.length) {
|
|
843
|
+
let op = nodes.shift()
|
|
788
844
|
|
|
789
|
-
//
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
else if (code === 0x0c) {
|
|
795
|
-
immed.push(...uleb(id(nodes[1], ctx.elem)), ...uleb(id(nodes.shift(), ctx.table)))
|
|
796
|
-
nodes.shift()
|
|
797
|
-
}
|
|
798
|
-
// table.* tableidx?
|
|
799
|
-
// abbrs https://webassembly.github.io/spec/core/text/instructions.html#id1
|
|
800
|
-
else if (code >= 0x0c && code < 0x13) {
|
|
801
|
-
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
802
|
-
// table.copy tableidx? tableidx?
|
|
803
|
-
if (code === 0x0e) immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
845
|
+
// Handle code metadata marker - store for next instruction
|
|
846
|
+
// ['@metadata', type, data]
|
|
847
|
+
if (op?.[0] === '@metadata') {
|
|
848
|
+
meta.push(op.slice(1))
|
|
849
|
+
continue
|
|
804
850
|
}
|
|
805
|
-
}
|
|
806
851
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
immed = [0xfd, ...uleb(code)]
|
|
812
|
-
// (v128.load offset? align?)
|
|
813
|
-
if (code <= 0x0b) {
|
|
814
|
-
const [a, o] = memarg(nodes)
|
|
815
|
-
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
816
|
-
}
|
|
817
|
-
// (v128.load_lane offset? align? idx)
|
|
818
|
-
else if (code >= 0x54 && code <= 0x5d) {
|
|
819
|
-
const [a, o] = memarg(nodes)
|
|
820
|
-
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
821
|
-
// (v128.load_lane_zero)
|
|
822
|
-
if (code <= 0x5b) immed.push(...uleb(nodes.shift()))
|
|
823
|
-
}
|
|
824
|
-
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
825
|
-
else if (code === 0x0d) {
|
|
826
|
-
// i8, i16, i32 - bypass the encoding
|
|
827
|
-
for (let i = 0; i < 16; i++) immed.push(parseUint(nodes.shift(), 32))
|
|
828
|
-
}
|
|
829
|
-
// (v128.const i32x4 1 2 3 4)
|
|
830
|
-
else if (code === 0x0c) {
|
|
831
|
-
let [t, n] = nodes.shift().split('x'),
|
|
832
|
-
bits = +t.slice(1),
|
|
833
|
-
stride = bits >>> 3 // i16 -> 2, f32 -> 4
|
|
834
|
-
n = +n
|
|
835
|
-
// i8, i16, i32 - bypass the encoding
|
|
836
|
-
if (t[0] === 'i') {
|
|
837
|
-
let arr = n === 16 ? new Uint8Array(16) : n === 8 ? new Uint16Array(8) : n === 4 ? new Uint32Array(4) : new BigUint64Array(2)
|
|
838
|
-
for (let i = 0; i < n; i++) {
|
|
839
|
-
let s = nodes.shift(), v = encode[t].parse(s)
|
|
840
|
-
arr[i] = v
|
|
841
|
-
}
|
|
842
|
-
immed.push(...(new Uint8Array(arr.buffer)))
|
|
843
|
-
}
|
|
844
|
-
// f32, f64 - encode
|
|
845
|
-
else {
|
|
846
|
-
let arr = new Uint8Array(16)
|
|
847
|
-
for (let i = 0; i < n; i++) {
|
|
848
|
-
let s = nodes.shift(), v = encode[t](s)
|
|
849
|
-
arr.set(v, i * stride)
|
|
850
|
-
}
|
|
851
|
-
immed.push(...arr)
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
// (i8x16.extract_lane_s 0 ...)
|
|
855
|
-
else if (code >= 0x15 && code <= 0x22) {
|
|
856
|
-
immed.push(...uleb(parseUint(nodes.shift())))
|
|
852
|
+
// Array = unknown instruction passed through from normalize
|
|
853
|
+
if (Array.isArray(op)) {
|
|
854
|
+
op.i != null && (err.i = op.i)
|
|
855
|
+
err(`Unknown instruction ${op[0]}`)
|
|
857
856
|
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// control block abbrs
|
|
861
|
-
// block ..., loop ..., if ...
|
|
862
|
-
else if (code === 2 || code === 3 || code === 4) {
|
|
863
|
-
ctx.block.push(code)
|
|
864
|
-
|
|
865
|
-
// (block $x) (loop $y) - save label pointer
|
|
866
|
-
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
867
|
-
|
|
868
|
-
let t = nodes.shift();
|
|
869
|
-
|
|
870
|
-
// void
|
|
871
|
-
if (!t) immed.push(TYPE.void)
|
|
872
|
-
// (result i32) - doesn't require registering type
|
|
873
|
-
// FIXME: Make sure it is signed positive integer (leb, not uleb) https://webassembly.github.io/gc/core/binary/instructions.html#control-instructions
|
|
874
|
-
else if (t[0] === 'result') immed.push(...reftype(t[1], ctx))
|
|
875
|
-
// (type idx)
|
|
876
|
-
else immed.push(...uleb(id(t[1], ctx.type)))
|
|
877
|
-
}
|
|
878
|
-
// else
|
|
879
|
-
else if (code === 5) { }
|
|
880
|
-
// then
|
|
881
|
-
else if (code === 6) immed = [] // ignore
|
|
882
|
-
|
|
883
|
-
// local.get $id, local.tee $id x
|
|
884
|
-
else if (code == 0x20 || code == 0x21 || code == 0x22) {
|
|
885
|
-
immed.push(...uleb(id(nodes.shift(), ctx.local)))
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// global.get $id, global.set $id
|
|
889
|
-
else if (code == 0x23 || code == 0x24) {
|
|
890
|
-
immed.push(...uleb(id(nodes.shift(), ctx.global)))
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// call $func ...nodes
|
|
894
|
-
// return_call $func
|
|
895
|
-
else if (code == 0x10 || code == 0x12) {
|
|
896
|
-
immed.push(...uleb(id(nodes.shift(), ctx.func)))
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// call_indirect $table (type $typeName) ...nodes
|
|
900
|
-
// return_call_indirect $table (type $typeName) ... nodes
|
|
901
|
-
else if (code == 0x11 || code == 0x13) {
|
|
902
|
-
immed.push(
|
|
903
|
-
...uleb(id(nodes[1][1], ctx.type)),
|
|
904
|
-
...uleb(id(nodes.shift(), ctx.table))
|
|
905
|
-
)
|
|
906
|
-
nodes.shift()
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// call_ref $type
|
|
910
|
-
// return_call_ref $type
|
|
911
|
-
else if (code == 0x14 || code == 0x15) {
|
|
912
|
-
immed.push(...uleb(id(nodes.shift(), ctx.type)))
|
|
913
|
-
}
|
|
914
857
|
|
|
915
|
-
|
|
916
|
-
else if (code == 0x0b) ctx.block.pop()
|
|
858
|
+
let [...bytes] = INSTR[op] || err(`Unknown instruction ${op}`)
|
|
917
859
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
let args = []
|
|
928
|
-
while (nodes[0] && (!isNaN(nodes[0]) || nodes[0][0] === '$')) {
|
|
929
|
-
args.push(...uleb(blockid(nodes.shift(), ctx.block)))
|
|
860
|
+
// special op handlers
|
|
861
|
+
if (HANDLER[op]) {
|
|
862
|
+
// select: becomes typed select (opcode+1) if next node is an array with result types
|
|
863
|
+
if (op === 'select' && nodes[0]?.length) bytes[0]++
|
|
864
|
+
// ref.type|cast: opcode+1 if type is nullable: (ref null $t) or (funcref, anyref, etc.)
|
|
865
|
+
else if (HANDLER[op] === IMM.reftype && (nodes[0][1] === 'null' || nodes[0][0] !== 'ref')) {
|
|
866
|
+
bytes[bytes.length - 1]++
|
|
867
|
+
}
|
|
868
|
+
bytes.push(...HANDLER[op](nodes, ctx, op))
|
|
930
869
|
}
|
|
931
|
-
args.unshift(...uleb(args.length - 1))
|
|
932
|
-
immed.push(...args)
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// select (result t+)
|
|
936
|
-
else if (code == 0x1b) {
|
|
937
|
-
let result = nodes.shift()
|
|
938
|
-
// 0x1b -> 0x1c
|
|
939
|
-
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t => reftype(t, ctx))))
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// ref.func $id
|
|
943
|
-
else if (code == 0xd2) {
|
|
944
|
-
immed.push(...uleb(id(nodes.shift(), ctx.func)))
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// ref.null func
|
|
948
|
-
else if (code == 0xd0) {
|
|
949
|
-
let t = nodes.shift()
|
|
950
|
-
immed.push(...(HEAPTYPE[t] ? [HEAPTYPE[t]] : uleb(id(t, ctx.type)))) // func->funcref, extern->externref
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
// binary/unary (i32.add a b) - no immed
|
|
954
|
-
else if (code >= 0x45) { }
|
|
955
|
-
|
|
956
|
-
// i32.store align=n offset=m
|
|
957
|
-
else if (code >= 0x28 && code <= 0x3e) {
|
|
958
|
-
let [a, o] = memarg(nodes)
|
|
959
|
-
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// i32.const 123, f32.const 123.45
|
|
963
|
-
else if (code >= 0x41 && code <= 0x44) {
|
|
964
|
-
immed.push(...encode[op.split('.')[0]](nodes.shift()))
|
|
965
|
-
}
|
|
966
870
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
else if (code == 0x3f || code == 0x40) {
|
|
970
|
-
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
971
|
-
}
|
|
871
|
+
// Record metadata at current byte position
|
|
872
|
+
for (const [type, data] of meta) ((ctx.meta[type] ??= []).push([out.length, data]))
|
|
972
873
|
|
|
973
|
-
|
|
974
|
-
else if (code == 0x25 || code == 0x26) {
|
|
975
|
-
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
874
|
+
out.push(...bytes)
|
|
976
875
|
}
|
|
977
876
|
|
|
978
|
-
|
|
979
|
-
if (ctx._meta) out.push(ctx._meta), ctx._meta = null
|
|
980
|
-
|
|
981
|
-
out.push(...immed)
|
|
982
|
-
|
|
983
|
-
return out
|
|
877
|
+
return out.push(0x0b), out
|
|
984
878
|
}
|
|
985
879
|
|
|
986
|
-
// instantiation time value initializer (consuming) -
|
|
987
|
-
const expr = (node, ctx) =>
|
|
880
|
+
// instantiation time value initializer (consuming) - normalize then encode + add end byte
|
|
881
|
+
const expr = (node, ctx) => instr(normalize([node], ctx), ctx)
|
|
988
882
|
|
|
989
883
|
// deref id node to numeric idx
|
|
990
|
-
const id = (nm, list, n) => (n = nm
|
|
884
|
+
const id = (nm, list, n) => (n = isId(nm) ? list[nm] : +nm, n in list ? n : err(`Unknown ${list.name} ${nm}`))
|
|
991
885
|
|
|
992
886
|
// block id - same as id but for block
|
|
993
887
|
// index indicates how many block items to pop
|
|
994
888
|
const blockid = (nm, block, i) => (
|
|
995
|
-
i = nm
|
|
889
|
+
i = isId(nm) ? block.length - block[nm] : +nm,
|
|
996
890
|
isNaN(i) || i > block.length ? err(`Bad label ${nm}`) : i
|
|
997
891
|
)
|
|
998
892
|
|
|
999
893
|
// consume align/offset params
|
|
1000
894
|
const memarg = (args) => {
|
|
1001
895
|
let align, offset, k, v
|
|
1002
|
-
while (args[0]
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
if ((offset < 0 || offset > 0xffffffff)) err(`Bad offset ${offset}`)
|
|
1007
|
-
if ((align <= 0 || align > 0xffffffff)) err(`Bad align ${align}`)
|
|
896
|
+
while (isMemParam(args[0])) [k, v] = args.shift().split('='), k === 'offset' ? offset = +v : k === 'align' ? align = +v : err(`Unknown param ${k}=${v}`)
|
|
897
|
+
|
|
898
|
+
if (offset < 0 || offset > 0xffffffff) err(`Bad offset ${offset}`)
|
|
899
|
+
if (align <= 0 || align > 0xffffffff) err(`Bad align ${align}`)
|
|
1008
900
|
if (align) ((align = Math.log2(align)) % 1) && err(`Bad align ${align}`)
|
|
1009
901
|
return [align, offset]
|
|
1010
902
|
}
|
|
1011
903
|
|
|
904
|
+
// Encode memarg (align + offset) with default values based on instruction
|
|
905
|
+
// If memIdx is non-zero, set bit 6 in alignment flags and insert memIdx after align
|
|
906
|
+
const memargEnc = (nodes, op, memIdx = 0) => {
|
|
907
|
+
const [a, o] = memarg(nodes), alignVal = (a ?? align(op)) | (memIdx && 0x40)
|
|
908
|
+
return memIdx ? [...uleb(alignVal), ...uleb(memIdx), ...uleb(o ?? 0)] : [...uleb(alignVal), ...uleb(o ?? 0)]
|
|
909
|
+
}
|
|
910
|
+
|
|
1012
911
|
// const ALIGN = {
|
|
1013
912
|
// 'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
1014
913
|
// 'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
@@ -1018,20 +917,43 @@ const memarg = (args) => {
|
|
|
1018
917
|
// 'v128.load': 16, 'v128.load8_lane': 1, 'v128.load16_lane': 2, 'v128.load32_lane': 4, 'v128.load64_lane': 8, 'v128.store8_lane': 1, 'v128.store16_lane': 2, 'v128.store32_lane': 4, 'v128.store64_lane': 8, 'v128.load32_zero': 4, 'v128.load64_zero': 8
|
|
1019
918
|
// }
|
|
1020
919
|
const align = (op) => {
|
|
1021
|
-
let
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
920
|
+
let i = op.indexOf('.', 3) + 1, group = op.slice(1, op[0] === 'v' ? 4 : 3) // type: i32->32, v128->128
|
|
921
|
+
if (op[i] === 'a') i = op.indexOf('.', i) + 1 // skip 'atomic.'
|
|
922
|
+
if (op[0] === 'm') return op.includes('64') ? 3 : 2 // memory.*.wait64 vs wait32/notify
|
|
923
|
+
if (op[i] === 'r') { // rmw: extract size from rmw##
|
|
924
|
+
let m = op.slice(i, i + 6).match(/\d+/)
|
|
925
|
+
return m ? Math.log2(m[0] / 8) : Math.log2(+group / 8)
|
|
926
|
+
}
|
|
927
|
+
// load/store: extract size after operation name
|
|
928
|
+
let k = op[i] === 'l' ? i + 4 : i + 5, m = op.slice(k).match(/(\d+)(x|_|$)/)
|
|
929
|
+
return Math.log2(m ? (m[2] === 'x' ? 8 : m[1] / 8) : +group / 8)
|
|
1025
930
|
}
|
|
1026
931
|
|
|
1027
932
|
// build limits sequence (consuming)
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
)
|
|
933
|
+
// Memory64: i64 index type uses flags 0x04-0x07 (bit 2 = is_64)
|
|
934
|
+
const limits = (node) => {
|
|
935
|
+
const is64 = node[0] === 'i64' && node.shift()
|
|
936
|
+
const shared = node[node.length - 1] === 'shared' && node.pop()
|
|
937
|
+
const hasMax = !isNaN(parseInt(node[1]))
|
|
938
|
+
const flag = (is64 ? 4 : 0) | (shared ? 2 : 0) | (hasMax ? 1 : 0)
|
|
939
|
+
// For i64, parse as unsigned BigInt (limits are always unsigned)
|
|
940
|
+
const parse = is64 ? v => {
|
|
941
|
+
if (typeof v === 'bigint') return v
|
|
942
|
+
const str = typeof v === 'string' ? v.replaceAll('_', '') : String(v)
|
|
943
|
+
return BigInt(str)
|
|
944
|
+
} : parseUint
|
|
945
|
+
|
|
946
|
+
return hasMax
|
|
947
|
+
? [flag, ...uleb(parse(node.shift())), ...uleb(parse(node.shift()))]
|
|
948
|
+
: [flag, ...uleb(parse(node.shift()))]
|
|
949
|
+
}
|
|
1031
950
|
|
|
1032
951
|
// check if node is valid int in a range
|
|
1033
|
-
|
|
1034
|
-
const
|
|
952
|
+
const parseUint = (v, max = 0xFFFFFFFF) => {
|
|
953
|
+
const n = typeof v === 'string' && v[0] !== '+' ? i32.parse(v) : typeof v === 'number' ? v : err(`Bad int ${v}`)
|
|
954
|
+
return n > max ? err(`Value out of range ${v}`) : n
|
|
955
|
+
}
|
|
956
|
+
|
|
1035
957
|
|
|
1036
958
|
// serialize binary array
|
|
1037
959
|
const vec = a => [...uleb(a.length), ...a.flat()]
|