watr 3.0.0 → 3.1.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 +1 -1
- package/readme.md +16 -32
- package/src/compile.js +578 -457
- package/src/const.js +25 -33
- package/src/encode.js +47 -12
- package/src/parse.js +15 -7
package/src/compile.js
CHANGED
|
@@ -1,437 +1,351 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb } from './encode.js'
|
|
3
|
-
import { SECTION,
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, REFTYPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
5
|
|
|
6
6
|
// build instructions index
|
|
7
|
-
INSTR.forEach((
|
|
8
|
-
let [op, ...imm] = instr.split(':'), a, b
|
|
9
|
-
|
|
10
|
-
// TODO
|
|
11
|
-
// wrap codes
|
|
12
|
-
// const code = i >= 0x10f ? [0xfd, i - 0x10f] : i >= 0xfc ? [0xfc, i - 0xfc] : i
|
|
13
|
-
INSTR[op] = i
|
|
14
|
-
|
|
15
|
-
// // handle immediates
|
|
16
|
-
// INSTR[op] = !imm.length ? () => code :
|
|
17
|
-
// imm.length === 1 ? (a = immedname(imm[0]), nodes => [...code, ...a(nodes)]) :
|
|
18
|
-
// (imm = imm.map(immedname), nodes => [...code, ...imm.flatMap(imm => imm(nodes))])
|
|
19
|
-
})
|
|
7
|
+
INSTR.forEach((op, i) => INSTR[op] = i >= 0x11a ? [0xfd, i - 0x11a] : i >= 0xfc ? [0xfc, i - 0xfc] : [i]);
|
|
20
8
|
|
|
9
|
+
// console.log(INSTR)
|
|
21
10
|
/**
|
|
22
11
|
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
23
12
|
*
|
|
24
13
|
* @param {string|Array} nodes - The WAT tree or string to be compiled to WASM binary.
|
|
14
|
+
* @param {Object} opt - opt.fullSize for fixed-width uleb encoding
|
|
25
15
|
* @returns {Uint8Array} The compiled WASM binary data.
|
|
26
16
|
*/
|
|
27
|
-
export default (nodes)
|
|
17
|
+
export default function watr(nodes) {
|
|
28
18
|
// normalize to (module ...) form
|
|
29
|
-
if (typeof nodes === 'string') nodes = parse(nodes);
|
|
19
|
+
if (typeof nodes === 'string') nodes = parse(nodes);
|
|
20
|
+
else nodes = clone(nodes)
|
|
30
21
|
|
|
31
22
|
// module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
|
|
32
|
-
if (nodes[0] === 'module') nodes.shift(),
|
|
23
|
+
if (nodes[0] === 'module') nodes.shift(), nodes[0]?.[0] === '$' && nodes.shift()
|
|
24
|
+
// single node, not module
|
|
33
25
|
else if (typeof nodes[0] === 'string') nodes = [nodes]
|
|
34
26
|
|
|
27
|
+
// binary abbr "\00" "\0x61" ...
|
|
28
|
+
if (nodes[0] === 'binary') {
|
|
29
|
+
nodes.shift()
|
|
30
|
+
return Uint8Array.from(str(nodes.map(i => i.slice(1, -1)).join('')))
|
|
31
|
+
}
|
|
32
|
+
// quote "a" "b"
|
|
33
|
+
else if (nodes[0] === 'quote') {
|
|
34
|
+
nodes.shift()
|
|
35
|
+
return watr(nodes.map(i => i.slice(1, -1)).join(''))
|
|
36
|
+
}
|
|
37
|
+
|
|
35
38
|
// Scopes are stored directly on section array by key, eg. section.func.$name = idx
|
|
36
|
-
// FIXME: make direct binary instead (faster)
|
|
37
39
|
const sections = []
|
|
38
|
-
for (let kind in SECTION)
|
|
40
|
+
for (let kind in SECTION) (sections[SECTION[kind]] = sections[kind] = []).name = kind
|
|
41
|
+
sections._ = {} // implicit types
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
0x01, 0x00, 0x00, 0x00, // version
|
|
43
|
-
]
|
|
43
|
+
for (let [kind, ...node] of nodes) {
|
|
44
|
+
let imported // if node needs to be imported
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (let kind in SECTION) nodeGroups.push(nodeGroups[kind] = [])
|
|
46
|
+
// import abbr
|
|
47
|
+
// (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()
|
|
49
49
|
|
|
50
|
-
for (let [kind, ...node] of nodes) {
|
|
51
50
|
// index, alias
|
|
52
|
-
let name =
|
|
53
|
-
if (name) sections[kind][name] = idx; // save alias
|
|
51
|
+
let name = node[0]?.[0] === '$' && node.shift(), idx = sections[kind].length;
|
|
52
|
+
if (name) name in sections[kind] ? err(`Duplicate ${kind} ${name}`) : sections[kind][name] = idx; // save alias
|
|
54
53
|
|
|
55
54
|
// export abbr
|
|
56
55
|
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
57
|
-
while (node[0]?.[0] === 'export')
|
|
56
|
+
while (node[0]?.[0] === 'export') sections.export.push([node.shift()[1], [kind, idx]])
|
|
58
57
|
|
|
59
|
-
// import
|
|
60
|
-
|
|
61
|
-
if (node[0]?.[0] === 'import') node = [...node.shift(), [kind, ...(name ? [name] : []), ...node]], kind = node.shift()
|
|
58
|
+
// for import nodes - redirect output to import
|
|
59
|
+
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
62
60
|
|
|
63
61
|
// table abbr
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
if (kind === 'table') {
|
|
63
|
+
// (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
|
|
64
|
+
if (node[1]?.[0] === 'elem') {
|
|
65
|
+
let [reftype, [, ...els]] = node
|
|
66
|
+
node = [els.length, els.length, reftype]
|
|
67
|
+
sections.elem.push([['table', name || sections.table.length], ['i32.const', '0'], reftype, ...els])
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// data abbr
|
|
72
72
|
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
73
|
-
if (node[0]?.[0] === 'data') {
|
|
74
|
-
let [
|
|
75
|
-
|
|
73
|
+
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
74
|
+
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
75
|
+
sections.data.push([['memory', idx], ['i32.const', 0], ...data])
|
|
76
76
|
node = [m, m]
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// keep start name
|
|
80
|
+
else if (kind === 'start') name && node.push(name);
|
|
79
81
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
if (kind === 'import') {
|
|
83
|
-
let [mod, field, [kind, ...dfn]] = node
|
|
84
|
-
let name = id(dfn)
|
|
85
|
-
if (name) sections[kind][name] = nodeGroups[kind].length
|
|
86
|
-
nodeGroups[kind].length++
|
|
87
|
-
node[2] = [kind, ...dfn]
|
|
88
|
-
}
|
|
89
|
-
else if (kind === 'start') {name && node.unshift(name);}
|
|
82
|
+
// [func, [param, result]] -> [param, result], alias
|
|
83
|
+
else if (kind === 'type') node[0].shift(), node = paramres(node[0]), sections.type['$' + node.join('>')] ??= idx
|
|
90
84
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// build final binary
|
|
98
|
-
for (let secCode = 0; secCode < sections.length; secCode++) {
|
|
99
|
-
let items = sections[secCode], bytes = [], count = 0
|
|
100
|
-
for (let item of items) {
|
|
101
|
-
if (!item) { continue } // ignore empty items (like import placeholders)
|
|
102
|
-
count++ // count number of items in section
|
|
103
|
-
bytes.push(...item)
|
|
85
|
+
// dupe to code section, save implicit type
|
|
86
|
+
else if (kind === 'func') {
|
|
87
|
+
let [idx, param, result] = typeuse(node, sections);
|
|
88
|
+
idx ?? (sections._[idx = '$' + param + '>' + result] = [param, result]);
|
|
89
|
+
!imported && nodes.push(['code', [idx, param, result], ...plain(node, sections)]) // pass param since they may have names
|
|
90
|
+
node.unshift(['type', idx])
|
|
104
91
|
}
|
|
105
|
-
// ignore empty sections
|
|
106
|
-
if (!bytes.length) continue
|
|
107
|
-
// skip start section count - write length
|
|
108
|
-
if (secCode !== 8) bytes.unshift(...uleb(count))
|
|
109
|
-
binary.push(secCode, ...vec(bytes))
|
|
110
|
-
}
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
// import writes to import section amd adds placeholder for (kind) section
|
|
94
|
+
if (imported) sections.import.push([...imported, [kind, ...node]]), node = null
|
|
114
95
|
|
|
115
|
-
|
|
116
|
-
|
|
96
|
+
sections[kind].push(node)
|
|
97
|
+
}
|
|
117
98
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
// (type $id? (func params result))
|
|
121
|
-
// we cannot squash types since indices can refer to them
|
|
122
|
-
type(idx, [...node], ctx) {
|
|
123
|
-
let [, ...sig] = node?.[0] || [], [param, result] = paramres(sig)
|
|
124
|
-
|
|
125
|
-
ctx.type[idx] = Object.assign(
|
|
126
|
-
[TYPE.func, ...vec(param.map(t => TYPE[t])), ...vec(result.map(t => TYPE[t]))],
|
|
127
|
-
{ param, result } // save params for the type name
|
|
128
|
-
)
|
|
129
|
-
ctx.type[param + '>' + result] ??= idx // alias for quick search (don't increment if exists)
|
|
130
|
-
},
|
|
99
|
+
// add implicit types - main types receive aliases, implicit types are added if no explicit types exist
|
|
100
|
+
for (let n in sections._) sections.type[n] ??= sections.type.push(sections._[n]) - 1
|
|
131
101
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
let details
|
|
102
|
+
// patch datacount if data === 0
|
|
103
|
+
if (!sections.data.length) sections.datacount.length = 0
|
|
135
104
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
else if (kind === 'memory') {
|
|
142
|
-
details = limits(dfn)
|
|
143
|
-
}
|
|
144
|
-
else if (kind === 'global') {
|
|
145
|
-
let [type] = dfn, mut = type[0] === 'mut' ? 1 : 0
|
|
146
|
-
details = [TYPE[mut ? type[1] : type], mut]
|
|
147
|
-
}
|
|
148
|
-
else if (kind === 'table') {
|
|
149
|
-
details = [TYPE[dfn.pop()], ...limits(dfn)]
|
|
150
|
-
}
|
|
105
|
+
// convert nodes to bytes
|
|
106
|
+
const bin = (kind, count = true) => {
|
|
107
|
+
let items = sections[kind].filter(Boolean).map(item => build[kind](item, sections))
|
|
108
|
+
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
109
|
+
}
|
|
151
110
|
|
|
152
|
-
|
|
153
|
-
|
|
111
|
+
// build final binary
|
|
112
|
+
return Uint8Array.from([
|
|
113
|
+
0x00, 0x61, 0x73, 0x6d, // magic
|
|
114
|
+
0x01, 0x00, 0x00, 0x00, // version
|
|
115
|
+
...bin(SECTION.custom),
|
|
116
|
+
...bin(SECTION.type),
|
|
117
|
+
...bin(SECTION.import),
|
|
118
|
+
...bin(SECTION.func),
|
|
119
|
+
...bin(SECTION.table),
|
|
120
|
+
...bin(SECTION.memory),
|
|
121
|
+
...bin(SECTION.global),
|
|
122
|
+
...bin(SECTION.export),
|
|
123
|
+
...bin(SECTION.start, false),
|
|
124
|
+
...bin(SECTION.elem),
|
|
125
|
+
...bin(SECTION.datacount, false),
|
|
126
|
+
...bin(SECTION.code),
|
|
127
|
+
...bin(SECTION.data)
|
|
128
|
+
])
|
|
129
|
+
}
|
|
154
130
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
132
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
133
|
+
const plain = (nodes, ctx) => {
|
|
134
|
+
let out = []
|
|
158
135
|
|
|
159
|
-
|
|
136
|
+
while (nodes.length) {
|
|
137
|
+
let node = nodes.shift()
|
|
160
138
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let locals = [] // list of local variables
|
|
139
|
+
if (typeof node === 'string') {
|
|
140
|
+
out.push(node)
|
|
164
141
|
|
|
165
|
-
|
|
166
|
-
while (node[0]?.[0] === 'local') {
|
|
167
|
-
let [, ...types] = node.shift(), name
|
|
168
|
-
if (types[0]?.[0] === '$')
|
|
169
|
-
param[name = types.shift()] ? err('Ambiguous name ' + name) : // FIXME: not supposed to happen
|
|
170
|
-
locals[name] = param.length + locals.length
|
|
171
|
-
locals.push(...types.map(t => TYPE[t]))
|
|
142
|
+
if (abbr[node]) out.push(...abbr[node](nodes, ctx))
|
|
172
143
|
}
|
|
173
144
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (!nodes?.length) return out
|
|
145
|
+
// FIXME: try to move this to abbr
|
|
146
|
+
else {
|
|
147
|
+
node = plain(node, ctx)
|
|
178
148
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (group = Array.isArray(op)) {
|
|
183
|
-
args = [...op] // op is immutable
|
|
184
|
-
opCode = INSTR[op = args.shift()]
|
|
185
|
-
}
|
|
186
|
-
else opCode = INSTR[op]
|
|
187
|
-
|
|
188
|
-
// v128s: (v128.load x) etc
|
|
189
|
-
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
|
|
190
|
-
if (opCode >= 0x10f) {
|
|
191
|
-
opCode -= 0x10f
|
|
192
|
-
immed = [0xfd, ...uleb(opCode)]
|
|
193
|
-
// (v128.load)
|
|
194
|
-
if (opCode <= 0x0b) {
|
|
195
|
-
const o = memarg(args)
|
|
196
|
-
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
|
|
197
|
-
}
|
|
198
|
-
// (v128.load_lane offset? align? idx)
|
|
199
|
-
else if (opCode >= 0x54 && opCode <= 0x5d) {
|
|
200
|
-
const o = memarg(args)
|
|
201
|
-
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
|
|
202
|
-
// (v128.load_lane_zero)
|
|
203
|
-
if (opCode <= 0x5b) immed.push(...uleb(args.shift()))
|
|
204
|
-
}
|
|
205
|
-
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
206
|
-
else if (opCode === 0x0d) {
|
|
207
|
-
// i8, i16, i32 - bypass the encoding
|
|
208
|
-
for (let i = 0; i < 16; i++) immed.push(encode.i32.parse(args.shift()))
|
|
209
|
-
}
|
|
210
|
-
// (v128.const i32x4)
|
|
211
|
-
else if (opCode === 0x0c) {
|
|
212
|
-
args.unshift(op)
|
|
213
|
-
immed = expr(args, ctx)
|
|
214
|
-
}
|
|
215
|
-
// (i8x16.extract_lane_s 0 ...)
|
|
216
|
-
else if (opCode >= 0x15 && opCode <= 0x22) {
|
|
217
|
-
immed.push(...uleb(args.shift()))
|
|
218
|
-
}
|
|
219
|
-
opCode = null // ignore opcode
|
|
149
|
+
// (block ...) -> block ... end
|
|
150
|
+
if (node[0] === 'block' || node[0] === 'loop') {
|
|
151
|
+
out.push(...node, 'end')
|
|
220
152
|
}
|
|
221
153
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
// even opCodes (memory.init, memory.copy, table.init, table.copy) have 2nd predefined immediate
|
|
230
|
-
if (!(opCode & 0b1)) immed.push(0)
|
|
231
|
-
opCode = null // ignore opcode
|
|
232
|
-
}
|
|
154
|
+
// (if ...) -> if ... end
|
|
155
|
+
else if (node[0] === 'if') {
|
|
156
|
+
let thenelse = [], blocktype = [node.shift()]
|
|
157
|
+
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
158
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
159
|
+
if (node[node.length - 1]?.[0] === 'else') thenelse.unshift(...node.pop())
|
|
160
|
+
if (node[node.length - 1]?.[0] === 'then') thenelse.unshift(...node.pop())
|
|
233
161
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
immed = uleb(args[0][0] === '$' ? ctx.func[args.shift()] : +args.shift())
|
|
237
|
-
}
|
|
238
|
-
// ref.null
|
|
239
|
-
else if (opCode == 0xd0) {
|
|
240
|
-
immed = [TYPE[args.shift() + 'ref']] // func->funcref, extern->externref
|
|
241
|
-
}
|
|
162
|
+
// label?
|
|
163
|
+
if (node[0]?.[0] === '$') blocktype.push(node.shift())
|
|
242
164
|
|
|
243
|
-
|
|
244
|
-
|
|
165
|
+
// blocktype? - (param) are removed already via plain
|
|
166
|
+
if (node[0]?.[0] === 'type' || node[0]?.[0] === 'result') blocktype.push(node.shift());
|
|
245
167
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
let o = memarg(args)
|
|
250
|
-
immed = [Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0)]
|
|
251
|
-
}
|
|
168
|
+
// ignore empty else
|
|
169
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
170
|
+
if (thenelse[thenelse.length - 1] === 'else') thenelse.pop()
|
|
252
171
|
|
|
253
|
-
|
|
254
|
-
else if (opCode >= 0x41 && opCode <= 0x44) {
|
|
255
|
-
immed = encode[op.split('.')[0]](args.shift())
|
|
172
|
+
out.push(...node, ...blocktype, ...thenelse, 'end')
|
|
256
173
|
}
|
|
174
|
+
else out.push(node)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
257
177
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
immed = uleb(args[0]?.[0] === '$' ? param[id = args.shift()] ?? locals[id] ?? err('Unknown local ' + id) : +args.shift())
|
|
261
|
-
}
|
|
178
|
+
return out
|
|
179
|
+
}
|
|
262
180
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
181
|
+
const abbr = {
|
|
182
|
+
// block typeuse?
|
|
183
|
+
block: (nodes, ctx) => {
|
|
184
|
+
let out = []
|
|
267
185
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
let fnName = args.shift()
|
|
271
|
-
immed = uleb(id = fnName[0] === '$' ? ctx.func[fnName] : +fnName);
|
|
272
|
-
// FIXME: how to get signature of imported function
|
|
273
|
-
}
|
|
186
|
+
// (loop $l?)
|
|
187
|
+
if (nodes[0]?.[0] === '$') out.push(nodes.shift())
|
|
274
188
|
|
|
275
|
-
|
|
276
|
-
else if (opCode == 0x11) {
|
|
277
|
-
let tableidx = args[0]?.[0] === '$' ? ctx.table[args.shift()] : 0
|
|
278
|
-
let [typeidx] = typeuse(args, ctx)
|
|
279
|
-
immed = [...uleb(typeidx), ...uleb(tableidx)]
|
|
280
|
-
}
|
|
189
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
281
190
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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])
|
|
285
199
|
|
|
286
|
-
|
|
287
|
-
|
|
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
|
+
}
|
|
288
238
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
immed = [TYPE[type]]
|
|
294
|
-
}
|
|
295
|
-
// (result i32 i32)
|
|
296
|
-
else if (args[0]?.[0] === 'result' || args[0]?.[0] === 'param') {
|
|
297
|
-
let [typeidx] = typeuse(args, ctx)
|
|
298
|
-
immed = uleb(typeidx)
|
|
299
|
-
}
|
|
300
|
-
// FIXME: that def can be done nicer
|
|
301
|
-
else if (args[0]?.[0] === 'type') {
|
|
302
|
-
let [typeidx, params, result] = typeuse(args, ctx)
|
|
303
|
-
if (!params.length && !result.length) immed = [TYPE.void]
|
|
304
|
-
else if (!param.length && result.length === 1) immed = [TYPE[result[0]]]
|
|
305
|
-
else immed = uleb(typeidx)
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
immed = [TYPE.void]
|
|
309
|
-
}
|
|
239
|
+
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
240
|
+
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
241
|
+
const typeuse = (nodes, ctx, names) => {
|
|
242
|
+
let idx, param, result
|
|
310
243
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (opCode < 4) while (args.length) nodes.unshift(args.pop())
|
|
316
|
-
// (if cond a) -> cond if a end
|
|
317
|
-
else if (args.length < 3) nodes.unshift(args.pop())
|
|
318
|
-
// (if cond (then a) (else b)) -> `cond if a else b end`
|
|
319
|
-
else {
|
|
320
|
-
nodes.unshift(args.pop())
|
|
321
|
-
// (if cond a b) -> (if cond a else b)
|
|
322
|
-
if (nodes[0][0] !== 'else') nodes.unshift('else')
|
|
323
|
-
// (if a b (else)) -> (if a b)
|
|
324
|
-
else if (nodes[0].length < 2) nodes.shift()
|
|
325
|
-
nodes.unshift(args.pop())
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
244
|
+
// explicit type (type 0|$name)
|
|
245
|
+
if (nodes[0]?.[0] === 'type') {
|
|
246
|
+
[, idx] = nodes.shift();
|
|
247
|
+
[param, result] = paramres(nodes, names);
|
|
329
248
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (group) while (args.length) nodes.unshift(args.pop())
|
|
334
|
-
}
|
|
335
|
-
// (then)
|
|
336
|
-
else if (opCode === 6) {
|
|
337
|
-
opCode = null // ignore opcode
|
|
338
|
-
}
|
|
249
|
+
// check type consistency (excludes forward refs)
|
|
250
|
+
if ((param.length || result.length) && idx in ctx.type)
|
|
251
|
+
if (ctx.type[id(idx, ctx.type)].join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
339
252
|
|
|
340
|
-
|
|
341
|
-
|
|
253
|
+
return [idx]
|
|
254
|
+
}
|
|
342
255
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
else if (opCode == 0x0c || opCode == 0x0d) {
|
|
346
|
-
// br index indicates how many block items to pop
|
|
347
|
-
immed = uleb(args[0]?.[0] === '$' ? blocks.length - blocks[args.shift()] : args.shift())
|
|
348
|
-
}
|
|
256
|
+
// implicit type (param i32 i32)(result i32)
|
|
257
|
+
[param, result] = paramres(nodes, names)
|
|
349
258
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
immed = []
|
|
353
|
-
while (args[0] && !Array.isArray(args[0])) {
|
|
354
|
-
id = args.shift()
|
|
355
|
-
immed.push(...uleb(id[0][0] === '$' ? blocks.length - blocks[id] : id))
|
|
356
|
-
}
|
|
357
|
-
immed.unshift(...uleb(immed.length - 1))
|
|
358
|
-
}
|
|
259
|
+
return [, param, result]
|
|
260
|
+
}
|
|
359
261
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
262
|
+
// consume (param t+)* (result t+)* sequence
|
|
263
|
+
const paramres = (nodes, names = true) => {
|
|
264
|
+
let param = [], result = []
|
|
364
265
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
266
|
+
// collect param (param i32 i64) (param $x? i32)
|
|
267
|
+
while (nodes[0]?.[0] === 'param') {
|
|
268
|
+
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
|
+
let name = args[0]?.[0] === '$' && args.shift()
|
|
271
|
+
// expose name refs, if allowed
|
|
272
|
+
if (name) names ? param[name] = param.length : err(`Unexpected param name ${name}`)
|
|
273
|
+
param.push(...args)
|
|
274
|
+
}
|
|
369
275
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
276
|
+
// collect result eg. (result f64 f32)(result i32)
|
|
277
|
+
while (nodes[0]?.[0] === 'result') {
|
|
278
|
+
let [, ...args] = nodes.shift()
|
|
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
|
+
}
|
|
374
282
|
|
|
375
|
-
|
|
283
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
376
284
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (group) while (args.length) consume(args, out)
|
|
285
|
+
return [param, result]
|
|
286
|
+
}
|
|
380
287
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
288
|
+
// build section binary [by section codes] (non consuming)
|
|
289
|
+
const build = [,
|
|
290
|
+
// (type $id? (func params result))
|
|
291
|
+
([param, result], ctx) => ([0x60, ...vec(param.map(t => type(t, ctx))), ...vec(result.map(t => type(t, ctx)))]),
|
|
384
292
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
bytes.push(0x0b)
|
|
293
|
+
// (import "math" "add" (func|table|global|memory typedef?))
|
|
294
|
+
([mod, field, [kind, ...dfn]], ctx) => {
|
|
295
|
+
let details
|
|
389
296
|
|
|
390
|
-
|
|
391
|
-
|
|
297
|
+
if (kind[0] === 'f') {
|
|
298
|
+
// we track imported funcs in func section to share namespace, and skip them on final build
|
|
299
|
+
let [[, typeidx]] = dfn
|
|
300
|
+
details = uleb(id(typeidx, ctx.type))
|
|
301
|
+
}
|
|
302
|
+
else if (kind[0] === 'm') {
|
|
303
|
+
details = limits(dfn)
|
|
304
|
+
}
|
|
305
|
+
else if (kind[0] === 'g') {
|
|
306
|
+
let [t] = dfn, mut = t[0] === 'mut' ? 1 : 0
|
|
307
|
+
details = [...type(mut ? t[1] : t, ctx), mut]
|
|
308
|
+
}
|
|
309
|
+
else if (kind[0] === 't') {
|
|
310
|
+
details = [...type(dfn.pop(), ctx), ...limits(dfn)]
|
|
311
|
+
}
|
|
392
312
|
|
|
393
|
-
|
|
394
|
-
ctx.code[idx] = vec([...uleb(loctypes.length), ...loctypes.flatMap(([n, t]) => [...uleb(n), t]), ...bytes])
|
|
313
|
+
return ([...vec(str(mod.slice(1, -1))), ...vec(str(field.slice(1, -1))), KIND[kind], ...details])
|
|
395
314
|
},
|
|
396
315
|
|
|
397
|
-
// (
|
|
398
|
-
|
|
399
|
-
|
|
316
|
+
// (func $name? ...params result ...body)
|
|
317
|
+
([[, typeidx]], ctx) => (uleb(id(typeidx, ctx.type))),
|
|
318
|
+
|
|
319
|
+
// (table 1 2 funcref)
|
|
320
|
+
(node, ctx) => {
|
|
321
|
+
let lims = limits(node), t = type(node.shift(), ctx), [init] = node
|
|
322
|
+
return init ? [0x40, 0x00, ...t, ...lims, ...expr(init, ctx)] : [...t, ...lims]
|
|
400
323
|
},
|
|
401
324
|
|
|
402
325
|
// (memory id? export* min max shared)
|
|
403
|
-
|
|
404
|
-
ctx.memory[idx] = limits(node)
|
|
405
|
-
},
|
|
326
|
+
(node, ctx) => limits(node),
|
|
406
327
|
|
|
407
328
|
// (global $id? (mut i32) (i32.const 42))
|
|
408
|
-
|
|
409
|
-
let [
|
|
329
|
+
(node, ctx) => {
|
|
330
|
+
let [t] = node, mut = t[0] === 'mut' ? 1 : 0
|
|
410
331
|
|
|
411
|
-
let [,
|
|
412
|
-
|
|
332
|
+
let [, init] = node
|
|
333
|
+
return ([...type(mut ? t[1] : t, ctx), mut, ...expr(init, ctx)])
|
|
413
334
|
},
|
|
414
335
|
|
|
415
336
|
// (export "name" (func|table|mem $name|idx))
|
|
416
|
-
|
|
417
|
-
// put placeholder to future-init
|
|
418
|
-
let idx = id[0] === '$' ? ctx[kind][id] : +id
|
|
419
|
-
ctx.export.push([...str(nm), KIND[kind], ...uleb(idx)])
|
|
420
|
-
},
|
|
337
|
+
([nm, [kind, l]], ctx) => ([...vec(str(nm.slice(1, -1))), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
421
338
|
|
|
422
339
|
// (start $main)
|
|
423
|
-
|
|
424
|
-
id = id[0] === '$' ? ctx.func[id] : +id
|
|
425
|
-
ctx.start[0] = uleb(id)
|
|
426
|
-
},
|
|
340
|
+
([l], ctx) => uleb(id(l, ctx.func)),
|
|
427
341
|
|
|
342
|
+
// (elem elem*) - passive
|
|
343
|
+
// (elem declare elem*) - declarative
|
|
344
|
+
// (elem (table idx)? (offset expr)|(expr) elem*) - active
|
|
345
|
+
// elems := funcref|externref (item expr)|expr (item expr)|expr
|
|
346
|
+
// idxs := func? $id0 $id1
|
|
428
347
|
// ref: https://webassembly.github.io/spec/core/binary/modules.html#element-section
|
|
429
|
-
|
|
430
|
-
// declarative: (elem declare elem*)
|
|
431
|
-
// active: (elem (table idx)? (offset expr)|(expr) elem*)
|
|
432
|
-
// elems: funcref|externref (item expr)|expr (item expr)|expr
|
|
433
|
-
// idxs: func? $id0 $id1
|
|
434
|
-
elem(idx,[...parts], ctx) {
|
|
348
|
+
(parts, ctx) => {
|
|
435
349
|
let tabidx, offset, mode = 0b000, reftype
|
|
436
350
|
|
|
437
351
|
// declare?
|
|
@@ -440,218 +354,425 @@ const build = {
|
|
|
440
354
|
// table?
|
|
441
355
|
if (parts[0][0] === 'table') {
|
|
442
356
|
[, tabidx] = parts.shift()
|
|
443
|
-
tabidx = tabidx
|
|
357
|
+
tabidx = id(tabidx, ctx.table)
|
|
444
358
|
// ignore table=0
|
|
445
359
|
if (tabidx) mode |= 0b010
|
|
446
360
|
}
|
|
447
361
|
|
|
448
362
|
// (offset expr)|expr
|
|
449
363
|
if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
|
|
450
|
-
|
|
451
|
-
if (offset[0] === 'offset') [,
|
|
364
|
+
offset = parts.shift()
|
|
365
|
+
if (offset[0] === 'offset') [, offset] = offset
|
|
452
366
|
}
|
|
453
367
|
else mode |= 0b001 // passive
|
|
454
368
|
|
|
455
|
-
// funcref
|
|
456
|
-
if (parts[0]
|
|
457
|
-
// externref
|
|
458
|
-
if (
|
|
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()
|
|
373
|
+
|
|
374
|
+
// legacy abbr if func is skipped
|
|
375
|
+
if (!reftype) !tabidx ? reftype = 'funcref' : err(`Undefined reftype`)
|
|
459
376
|
|
|
377
|
+
// externref makes explicit table index
|
|
378
|
+
if (reftype === 'externref' || reftype[0] === 'ref') offset ||= ['i32.const', 0], mode = 0b110
|
|
460
379
|
// reset to simplest mode if no actual elements
|
|
461
|
-
if (!parts.length) mode &= 0b011
|
|
380
|
+
else if (!parts.length) mode &= 0b011
|
|
462
381
|
|
|
463
|
-
// simplify els
|
|
382
|
+
// simplify els sequence
|
|
464
383
|
parts = parts.map(el => {
|
|
465
|
-
if (el[0] === 'item') [, el] = el
|
|
384
|
+
if (el[0] === 'item') [, ...el] = el
|
|
466
385
|
if (el[0] === 'ref.func') [, el] = el
|
|
467
|
-
// (ref.null func) and other expressions
|
|
386
|
+
// (ref.null func) and other expressions turn expr init mode
|
|
468
387
|
if (typeof el !== 'string') mode |= 0b100
|
|
469
388
|
return el
|
|
470
389
|
})
|
|
471
390
|
|
|
472
|
-
|
|
391
|
+
return ([
|
|
473
392
|
mode,
|
|
474
393
|
...(
|
|
475
394
|
// 0b000 e:expr y*:vec(funcidx) | type=funcref, init ((ref.func y)end)*, active (table=0,offset=e)
|
|
476
|
-
mode === 0b000 ?
|
|
395
|
+
mode === 0b000 ? expr(offset, ctx) :
|
|
477
396
|
// 0b001 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive
|
|
478
397
|
mode === 0b001 ? [0x00] :
|
|
479
398
|
// 0b010 x:tabidx e:expr et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, active (table=x,offset=e)
|
|
480
|
-
mode === 0b010 ? [...uleb(tabidx || 0), ...expr(offset
|
|
399
|
+
mode === 0b010 ? [...uleb(tabidx || 0), ...expr(offset, ctx), 0x00] :
|
|
481
400
|
// 0b011 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive declare
|
|
482
401
|
mode === 0b011 ? [0x00] :
|
|
483
402
|
// 0b100 e:expr el*:vec(expr) | type=funcref, init el*, active (table=0, offset=e)
|
|
484
|
-
mode === 0b100 ?
|
|
403
|
+
mode === 0b100 ? expr(offset, ctx) :
|
|
485
404
|
// 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
|
|
486
|
-
mode === 0b101 ?
|
|
405
|
+
mode === 0b101 ? type(reftype, ctx) :
|
|
487
406
|
// 0b110 x:tabidx e:expr et:reftype el*:vec(expr) | type=et, init el*, active (table=x, offset=e)
|
|
488
|
-
mode === 0b110 ? [...uleb(tabidx || 0), ...expr(offset),
|
|
407
|
+
mode === 0b110 ? [...uleb(tabidx || 0), ...expr(offset, ctx), ...type(reftype, ctx)] :
|
|
489
408
|
// 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare
|
|
490
|
-
|
|
409
|
+
type(reftype, ctx)
|
|
491
410
|
),
|
|
492
|
-
...
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
411
|
+
...vec(
|
|
412
|
+
parts.map(mode & 0b100 ?
|
|
413
|
+
// ((ref.func y)end)*
|
|
414
|
+
el => expr(typeof el === 'string' ? ['ref.func', el] : el, ctx) :
|
|
415
|
+
// el*
|
|
416
|
+
el => uleb(id(el, ctx.func))
|
|
417
|
+
)
|
|
498
418
|
)
|
|
499
419
|
])
|
|
500
420
|
},
|
|
501
421
|
|
|
422
|
+
// (code)
|
|
423
|
+
(body, ctx) => {
|
|
424
|
+
let [typeidx, param] = body.shift()
|
|
425
|
+
if (!param) [param] = ctx.type[id(typeidx, ctx.type)]
|
|
426
|
+
|
|
427
|
+
// provide param/local in ctx
|
|
428
|
+
ctx.local = Object.create(param) // list of local variables - some of them are params
|
|
429
|
+
ctx.block = [] // control instructions / blocks stack
|
|
430
|
+
|
|
431
|
+
// display names for error messages
|
|
432
|
+
ctx.local.name = 'local'
|
|
433
|
+
ctx.block.name = 'block'
|
|
434
|
+
|
|
435
|
+
// collect locals
|
|
436
|
+
while (body[0]?.[0] === 'local') {
|
|
437
|
+
let [, ...types] = body.shift()
|
|
438
|
+
if (types[0]?.[0] === '$') ctx.local[types.shift()] = ctx.local.length
|
|
439
|
+
ctx.local.push(...types)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const bytes = []
|
|
443
|
+
while (body.length) bytes.push(...instr(body, ctx))
|
|
444
|
+
bytes.push(0x0b)
|
|
445
|
+
|
|
446
|
+
// squash locals into (n:u32 t:valtype)*, n is number and t is type
|
|
447
|
+
// we skip locals provided by params
|
|
448
|
+
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), [])
|
|
449
|
+
|
|
450
|
+
// cleanup tmp state
|
|
451
|
+
ctx.local = ctx.block = null
|
|
452
|
+
|
|
453
|
+
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
454
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...type(t, ctx)])), ...bytes])
|
|
455
|
+
},
|
|
456
|
+
|
|
502
457
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
503
458
|
// (data (memory ref) (offset (i32.const 0)) "\aa" "\bb"?)
|
|
504
459
|
// (data (global.get $x) "\aa" "\bb"?)
|
|
505
|
-
|
|
506
|
-
let offset,
|
|
460
|
+
(inits, ctx) => {
|
|
461
|
+
let offset, memidx = 0
|
|
507
462
|
|
|
508
463
|
// (memory ref)?
|
|
509
464
|
if (inits[0]?.[0] === 'memory') {
|
|
510
|
-
[,
|
|
511
|
-
|
|
512
|
-
mem = !mem ? [0] : [2, ...uleb(mem)]
|
|
465
|
+
[, memidx] = inits.shift()
|
|
466
|
+
memidx = id(memidx, ctx.memory)
|
|
513
467
|
}
|
|
514
468
|
|
|
515
469
|
// (offset (i32.const 0)) or (i32.const 0)
|
|
516
470
|
if (typeof inits[0] !== 'string') {
|
|
517
471
|
offset = inits.shift()
|
|
518
472
|
if (offset[0] === 'offset') [, offset] = offset
|
|
473
|
+
offset ?? err('Bad offset', offset)
|
|
519
474
|
}
|
|
520
|
-
else offset = ['i32.const', 0]
|
|
521
|
-
ctx.data[idx] = [...mem, ...expr([...offset], ctx), 0x0b, ...str(inits.map(i => i.slice(1, -1)).join(''))]
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
475
|
|
|
525
|
-
|
|
526
|
-
|
|
476
|
+
return ([
|
|
477
|
+
...(
|
|
478
|
+
// active: 2, x=memidx, e=expr
|
|
479
|
+
memidx ? [2, ...uleb(memidx), ...expr(offset, ctx)] :
|
|
480
|
+
// active: 0, e=expr
|
|
481
|
+
offset ? [0, ...expr(offset, ctx)] :
|
|
482
|
+
// passive: 1
|
|
483
|
+
[1]
|
|
484
|
+
),
|
|
485
|
+
...vec(str(inits.map(i => i.slice(1, -1)).join('')))
|
|
486
|
+
])
|
|
487
|
+
},
|
|
527
488
|
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
489
|
+
// datacount
|
|
490
|
+
(nodes, ctx) => uleb(ctx.data.length)
|
|
491
|
+
]
|
|
531
492
|
|
|
532
|
-
|
|
533
|
-
|
|
493
|
+
// insert type, either direct or ref type
|
|
494
|
+
const type = (t, ctx) =>
|
|
495
|
+
t[0] === 'ref' ? ([t[1] == 'null' ? TYPE.refnull : TYPE.ref, ...uleb(TYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))])
|
|
496
|
+
: [TYPE[t] ?? err(`Unknown type ${t}`)]
|
|
534
497
|
|
|
535
|
-
|
|
536
|
-
|
|
498
|
+
// consume one instruction from nodes sequence
|
|
499
|
+
const instr = (nodes, ctx) => {
|
|
500
|
+
if (!nodes?.length) return []
|
|
537
501
|
|
|
538
|
-
|
|
539
|
-
if (cmd === 'const') return [0x41 + ['i32', 'i64', 'f32', 'f64'].indexOf(type), ...encode[type](node[0])]
|
|
502
|
+
let out = [], op = nodes.shift(), immed, code
|
|
540
503
|
|
|
541
|
-
//
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
504
|
+
// consume group
|
|
505
|
+
if (Array.isArray(op)) {
|
|
506
|
+
immed = instr(op, ctx)
|
|
507
|
+
while (op.length) out.push(...instr(op, ctx))
|
|
508
|
+
out.push(...immed)
|
|
509
|
+
return out
|
|
547
510
|
}
|
|
548
511
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
512
|
+
[...immed] = isNaN(op[0]) && INSTR[op] || err(`Unknown instruction ${op}`)
|
|
513
|
+
code = immed[0]
|
|
514
|
+
|
|
515
|
+
// v128s: (v128.load x) etc
|
|
516
|
+
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
|
|
517
|
+
if (code === 0xfd) {
|
|
518
|
+
[, code] = immed
|
|
519
|
+
immed = [0xfd, ...uleb(code)]
|
|
520
|
+
// (v128.load)
|
|
521
|
+
if (code <= 0x0b) {
|
|
522
|
+
const [a, o] = memarg(nodes)
|
|
523
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
524
|
+
}
|
|
525
|
+
// (v128.load_lane offset? align? idx)
|
|
526
|
+
else if (code >= 0x54 && code <= 0x5d) {
|
|
527
|
+
const [a, o] = memarg(nodes)
|
|
528
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
529
|
+
// (v128.load_lane_zero)
|
|
530
|
+
if (code <= 0x5b) immed.push(...uleb(nodes.shift()))
|
|
531
|
+
}
|
|
532
|
+
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
533
|
+
else if (code === 0x0d) {
|
|
534
|
+
// i8, i16, i32 - bypass the encoding
|
|
535
|
+
for (let i = 0; i < 16; i++) immed.push(encode.i32.parse(nodes.shift()))
|
|
536
|
+
}
|
|
537
|
+
// (v128.const i32x4 1 2 3 4)
|
|
538
|
+
else if (code === 0x0c) {
|
|
539
|
+
let [t, n] = nodes.shift().split('x'), stride = t.slice(1) >>> 3 // i16 -> 2, f32 -> 4
|
|
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()))
|
|
559
|
+
}
|
|
560
|
+
}
|
|
556
561
|
|
|
557
|
-
// (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
562
|
+
// bulk memory: (memory.init) (memory.copy) (data.drop) (memory.fill)
|
|
563
|
+
// table ops: (table.init|copy|grow|size|fill) (elem.drop)
|
|
564
|
+
// https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#instruction-encoding
|
|
565
|
+
else if (code == 0xfc) {
|
|
566
|
+
[, code] = immed
|
|
561
567
|
|
|
562
|
-
|
|
568
|
+
// memory.init idx, data.drop idx,
|
|
569
|
+
if (code === 0x08 || code === 0x09) {
|
|
570
|
+
immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// memory placeholders
|
|
574
|
+
if (code == 0x08 || code == 0x0b) immed.push(0)
|
|
575
|
+
else if (code === 0x0a) immed.push(0, 0)
|
|
563
576
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
577
|
+
// elem.drop elemidx
|
|
578
|
+
if (code === 0x0d) {
|
|
579
|
+
immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
580
|
+
}
|
|
581
|
+
// table.init tableidx elemidx -> 0xfc 0x0c elemidx tableidx
|
|
582
|
+
else if (code === 0x0c) {
|
|
583
|
+
immed.push(...uleb(id(nodes[1], ctx.elem)), ...uleb(id(nodes.shift(), ctx.table)))
|
|
584
|
+
nodes.shift()
|
|
585
|
+
}
|
|
586
|
+
// table.* tableidx?
|
|
587
|
+
// abbrs https://webassembly.github.io/spec/core/text/instructions.html#id1
|
|
588
|
+
else if (code >= 0x0c && code < 0x13) {
|
|
589
|
+
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
590
|
+
// table.copy tableidx? tableidx?
|
|
591
|
+
if (code === 0x0e) immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
569
592
|
}
|
|
570
|
-
return new Uint8Array(arr.buffer)
|
|
571
593
|
}
|
|
572
594
|
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
595
|
+
// control block abbrs
|
|
596
|
+
// block ..., loop ..., if ...
|
|
597
|
+
else if (code === 2 || code === 3 || code === 4) {
|
|
598
|
+
ctx.block.push(code)
|
|
599
|
+
|
|
600
|
+
// (block $x) (loop $y)
|
|
601
|
+
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
602
|
+
|
|
603
|
+
let typeidx = nodes[0]?.[0] === 'type' && id(nodes.shift()[1], ctx.type)
|
|
604
|
+
|
|
605
|
+
let [param, result] = typeidx !== false ? ctx.type[typeidx] : nodes[0]?.[0] === 'result' ? [, [nodes.shift()[1]]] : []
|
|
606
|
+
|
|
607
|
+
// void
|
|
608
|
+
if (!param?.length && !result?.length) immed.push(TYPE.void)
|
|
609
|
+
// (result i32) - doesn't require registering type
|
|
610
|
+
else if (!param?.length && result.length === 1) immed.push(...type(result[0], ctx))
|
|
611
|
+
// (type idx)
|
|
612
|
+
else immed.push(...uleb(typeidx))
|
|
613
|
+
}
|
|
614
|
+
// else
|
|
615
|
+
else if (code === 5) { }
|
|
616
|
+
// then
|
|
617
|
+
else if (code === 6) immed = [] // ignore
|
|
618
|
+
|
|
619
|
+
// local.get $id, local.tee $id x
|
|
620
|
+
else if (code == 0x20 || code == 0x21 || code == 0x22) {
|
|
621
|
+
immed.push(...uleb(id(nodes.shift(), ctx.local)))
|
|
577
622
|
}
|
|
578
623
|
|
|
579
|
-
|
|
580
|
-
|
|
624
|
+
// global.get $id, global.set $id
|
|
625
|
+
else if (code == 0x23 || code == 0x24) {
|
|
626
|
+
immed.push(...uleb(id(nodes.shift(), ctx.global)))
|
|
627
|
+
}
|
|
581
628
|
|
|
582
|
-
//
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
629
|
+
// call $func ...nodes
|
|
630
|
+
// return_call $func
|
|
631
|
+
else if (code == 0x10 || code == 0x12) {
|
|
632
|
+
immed.push(...uleb(id(nodes.shift(), ctx.func)))
|
|
633
|
+
}
|
|
586
634
|
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
635
|
+
// call_indirect $table (type $typeName) ...nodes
|
|
636
|
+
// return_call_indirect $table (type $typeName) ... nodes
|
|
637
|
+
else if (code == 0x11 || code == 0x13) {
|
|
638
|
+
immed.push(
|
|
639
|
+
...uleb(id(nodes[1][1], ctx.type)),
|
|
640
|
+
...uleb(id(nodes.shift(), ctx.table))
|
|
641
|
+
), nodes.shift()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// call_ref $type
|
|
645
|
+
// return_call_ref $type
|
|
646
|
+
else if (code == 0x14 || code == 0x15) {
|
|
647
|
+
immed.push(...uleb(id(nodes.shift(), ctx.type)))
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// end
|
|
651
|
+
else if (code == 0x0b) ctx.block.pop()
|
|
652
|
+
|
|
653
|
+
// br $label result?
|
|
654
|
+
// br_if $label cond result?
|
|
655
|
+
// br_on_null $l, br_on_non_null $l
|
|
656
|
+
else if (code == 0x0c || code == 0x0d || code == 0xd5 || code == 0xd6) {
|
|
657
|
+
// br index indicates how many block items to pop
|
|
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))
|
|
661
|
+
}
|
|
590
662
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
(
|
|
596
|
-
|
|
663
|
+
// br_table 1 2 3 4 0 selector result?
|
|
664
|
+
else if (code == 0x0e) {
|
|
665
|
+
let args = []
|
|
666
|
+
while (nodes[0] && (!isNaN(nodes[0]) || nodes[0][0] === '$')) {
|
|
667
|
+
let l = nodes.shift(), i = l[0][0] === '$' ? ctx.block.length - ctx.block[l] : +l
|
|
668
|
+
i <= ctx.block.length || err(`Bad label ${l}`)
|
|
669
|
+
args.push(...uleb(i))
|
|
597
670
|
}
|
|
671
|
+
args.unshift(...uleb(args.length - 1))
|
|
672
|
+
immed.push(...args)
|
|
598
673
|
}
|
|
599
674
|
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
675
|
+
// select (result t+)
|
|
676
|
+
else if (code == 0x1b) {
|
|
677
|
+
let result = nodes.shift()
|
|
678
|
+
// 0x1b -> 0x1c
|
|
679
|
+
if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t => type(t, ctx))))
|
|
605
680
|
}
|
|
606
681
|
|
|
607
|
-
|
|
608
|
-
|
|
682
|
+
// ref.func $id
|
|
683
|
+
else if (code == 0xd2) {
|
|
684
|
+
immed.push(...uleb(id(nodes.shift(), ctx.func)))
|
|
685
|
+
}
|
|
609
686
|
|
|
610
|
-
//
|
|
611
|
-
|
|
612
|
-
|
|
687
|
+
// ref.null func
|
|
688
|
+
else if (code == 0xd0) {
|
|
689
|
+
let t = nodes.shift()
|
|
690
|
+
immed.push(HEAPTYPE[t] || id(t, ctx.type)) // func->funcref, extern->externref
|
|
691
|
+
}
|
|
613
692
|
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
693
|
+
// binary/unary (i32.add a b) - no immed
|
|
694
|
+
else if (code >= 0x45) { }
|
|
695
|
+
|
|
696
|
+
// i32.store align=n offset=m
|
|
697
|
+
else if (code >= 0x28 && code <= 0x3e) {
|
|
698
|
+
let [a, o] = memarg(nodes)
|
|
699
|
+
immed.push(...uleb((a ?? align(op))), ...uleb(o ?? 0))
|
|
620
700
|
}
|
|
621
701
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
result.push(...args)
|
|
702
|
+
// i32.const 123, f32.const 123.45
|
|
703
|
+
else if (code >= 0x41 && code <= 0x44) {
|
|
704
|
+
immed.push(...encode[op.split('.')[0]](nodes.shift()))
|
|
626
705
|
}
|
|
627
706
|
|
|
628
|
-
|
|
707
|
+
// memory.grow|size $idx - mandatory 0x00
|
|
708
|
+
// https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
|
|
709
|
+
else if (code == 0x3f || code == 0x40) {
|
|
710
|
+
immed.push(0)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// table.get $id
|
|
714
|
+
else if (code == 0x25 || code == 0x26) {
|
|
715
|
+
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
out.push(...immed)
|
|
719
|
+
|
|
720
|
+
return out
|
|
629
721
|
}
|
|
630
722
|
|
|
631
|
-
//
|
|
723
|
+
// instantiation time value initializer (consuming) - we redirect to instr
|
|
724
|
+
const expr = (node, ctx) => [...instr([node], ctx), 0x0b]
|
|
725
|
+
|
|
726
|
+
// consume align/offset params
|
|
632
727
|
const memarg = (args) => {
|
|
633
|
-
let
|
|
634
|
-
while (args[0]?.includes('='))
|
|
635
|
-
|
|
728
|
+
let align, offset, k, v
|
|
729
|
+
while (args[0]?.includes('=')) [k, v] = args.shift().split('='), k === 'offset' ? offset = +v : k === 'align' ? align = +v : err(`Unknown param ${k}=${v}`)
|
|
730
|
+
|
|
731
|
+
if (offset < 0 || offset > 0xffffffff) err(`Bad offset ${offset}`)
|
|
732
|
+
if (align <= 0 || align > 0xffffffff) err(`Bad align ${align}`)
|
|
733
|
+
if (align) ((align = Math.log2(align)) % 1) && err(`Bad align ${align}`)
|
|
734
|
+
return [align, offset]
|
|
636
735
|
}
|
|
637
736
|
|
|
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
|
+
// const ALIGN = {
|
|
742
|
+
// 'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
743
|
+
// 'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
744
|
+
// 'i64.load8_s': 1, 'i64.load8_u': 1, 'i64.load16_s': 2, 'i64.load16_u': 2, 'i64.load32_s': 4, 'i64.load32_u': 4, 'i32.store': 4,
|
|
745
|
+
// 'i64.store': 8, 'f32.store': 4, 'f64.store': 8, 'i32.store8': 1, 'i32.store16': 2, 'i64.store8': 1, 'i64.store16': 2, 'i64.store32': 4,
|
|
746
|
+
// 'v128.load': 16, 'v128.load8x8_s': 8, 'v128.load8x8_u': 8, 'v128.load16x4_s': 8, 'v128.load16x4_u': 8, 'v128.load32x2_s': 8, 'v128.load32x2_u': 8, 'v128.load8_splat': 1, 'v128.load16_splat': 2, 'v128.load32_splat': 4, 'v128.load64_splat': 8, 'v128.store': 16,
|
|
747
|
+
// '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
|
|
748
|
+
// }
|
|
749
|
+
const align = (op) => {
|
|
750
|
+
let [group, opname] = op.split('.'); // v128.load8x8_u -> group = v128, opname = load8x8_u
|
|
751
|
+
let [lsize] = (opname[0] === 'l' ? opname.slice(4) : opname.slice(5)).split('_') // load8x8_u -> lsize = 8x8
|
|
752
|
+
let [size, x] = lsize ? lsize.split('x') : [group.slice(1)] // 8x8 -> size = 8
|
|
753
|
+
return Math.log2(x ? 8 : +size / 8)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// build limits sequence (consuming)
|
|
757
|
+
const limits = (node) => isNaN(parseInt(node[1])) ? [0, ...uleb(node.shift())] : [node[2] === 'shared' ? 3 : 1, ...uleb(node.shift()), ...uleb(node.shift())]
|
|
758
|
+
|
|
638
759
|
// escape codes
|
|
639
|
-
const escape = { n: 10, r: 13, t: 9, v: 1, '\\': 92 }
|
|
760
|
+
const escape = { n: 10, r: 13, t: 9, v: 1, '"': 34, "'": 39, '\\': 92 }
|
|
640
761
|
|
|
641
762
|
// build string binary
|
|
642
763
|
const str = str => {
|
|
643
|
-
str = str[0] === '"' ? str.slice(1, -1) : str
|
|
644
764
|
let res = [], i = 0, c, BSLASH = 92
|
|
645
765
|
// https://webassembly.github.io/spec/core/text/values.html#strings
|
|
646
766
|
for (; i < str.length;) {
|
|
647
767
|
c = str.charCodeAt(i++)
|
|
648
768
|
res.push(c === BSLASH ? escape[str[i++]] || parseInt(str.slice(i - 1, ++i), 16) : c)
|
|
649
769
|
}
|
|
650
|
-
|
|
651
|
-
return vec(res)
|
|
770
|
+
return res
|
|
652
771
|
}
|
|
653
772
|
|
|
654
|
-
//
|
|
655
|
-
const
|
|
773
|
+
// serialize binary array
|
|
774
|
+
const vec = a => [...uleb(a.length), ...a.flat()]
|
|
656
775
|
|
|
657
776
|
const err = text => { throw Error(text) }
|
|
777
|
+
|
|
778
|
+
const clone = items => items.map(item => Array.isArray(item) ? clone(item) : item)
|