watr 2.4.1 → 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 +6 -3
- package/readme.md +40 -41
- package/src/compile.js +673 -389
- package/src/const.js +30 -33
- package/src/encode.js +48 -12
- package/src/parse.js +15 -7
- package/src/util.js +0 -1
package/src/compile.js
CHANGED
|
@@ -1,494 +1,778 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb } from './encode.js'
|
|
3
|
-
import {
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, REFTYPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
-
import { err } from './util.js'
|
|
6
5
|
|
|
6
|
+
// build instructions index
|
|
7
|
+
INSTR.forEach((op, i) => INSTR[op] = i >= 0x11a ? [0xfd, i - 0x11a] : i >= 0xfc ? [0xfc, i - 0xfc] : [i]);
|
|
7
8
|
|
|
9
|
+
// console.log(INSTR)
|
|
8
10
|
/**
|
|
9
|
-
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (
|
|
11
|
+
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
10
12
|
*
|
|
11
|
-
* @param {string|Array} nodes - The WAT tree or string to be compiled to
|
|
12
|
-
* @
|
|
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
|
|
15
|
+
* @returns {Uint8Array} The compiled WASM binary data.
|
|
13
16
|
*/
|
|
14
|
-
export default (nodes)
|
|
17
|
+
export default function watr(nodes) {
|
|
18
|
+
// normalize to (module ...) form
|
|
15
19
|
if (typeof nodes === 'string') nodes = parse(nodes);
|
|
20
|
+
else nodes = clone(nodes)
|
|
16
21
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
// module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
|
|
23
|
+
if (nodes[0] === 'module') nodes.shift(), nodes[0]?.[0] === '$' && nodes.shift()
|
|
24
|
+
// single node, not module
|
|
25
|
+
else if (typeof nodes[0] === 'string') nodes = [nodes]
|
|
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
|
+
|
|
38
|
+
// Scopes are stored directly on section array by key, eg. section.func.$name = idx
|
|
39
|
+
const sections = []
|
|
40
|
+
for (let kind in SECTION) (sections[SECTION[kind]] = sections[kind] = []).name = kind
|
|
41
|
+
sections._ = {} // implicit types
|
|
42
|
+
|
|
43
|
+
for (let [kind, ...node] of nodes) {
|
|
44
|
+
let imported // if node needs to be imported
|
|
45
|
+
|
|
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
|
+
|
|
50
|
+
// index, 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
|
|
53
|
+
|
|
54
|
+
// export abbr
|
|
55
|
+
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
56
|
+
while (node[0]?.[0] === 'export') sections.export.push([node.shift()[1], [kind, idx]])
|
|
57
|
+
|
|
58
|
+
// for import nodes - redirect output to import
|
|
59
|
+
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
60
|
+
|
|
61
|
+
// table abbr
|
|
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
|
+
}
|
|
35
69
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
|
|
71
|
+
// data abbr
|
|
72
|
+
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
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
|
+
node = [m, m]
|
|
39
77
|
}
|
|
40
|
-
return node
|
|
41
|
-
})
|
|
42
78
|
|
|
43
|
-
|
|
44
|
-
|
|
79
|
+
// keep start name
|
|
80
|
+
else if (kind === 'start') name && node.push(name);
|
|
81
|
+
|
|
82
|
+
// [func, [param, result]] -> [param, result], alias
|
|
83
|
+
else if (kind === 'type') node[0].shift(), node = paramres(node[0]), sections.type['$' + node.join('>')] ??= idx
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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])
|
|
50
91
|
}
|
|
51
|
-
|
|
92
|
+
|
|
93
|
+
// import writes to import section amd adds placeholder for (kind) section
|
|
94
|
+
if (imported) sections.import.push([...imported, [kind, ...node]]), node = null
|
|
95
|
+
|
|
96
|
+
sections[kind].push(node)
|
|
52
97
|
}
|
|
53
98
|
|
|
54
|
-
//
|
|
55
|
-
for (let
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
for (let item of items) bytes.push(...item)
|
|
65
|
-
binary.push(sectionCode, ...uleb(bytes.length), ...bytes)
|
|
99
|
+
// add implicit types - main types receive aliases, implicit types are added if no explicit types exist
|
|
100
|
+
for (let n in sections._) sections.type[n] ??= sections.type.push(sections._[n]) - 1
|
|
101
|
+
|
|
102
|
+
// patch datacount if data === 0
|
|
103
|
+
if (!sections.data.length) sections.datacount.length = 0
|
|
104
|
+
|
|
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)]
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
|
|
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
|
+
])
|
|
69
129
|
}
|
|
70
130
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
type([, typeName, [kind, ...sig]], ctx) {
|
|
76
|
-
if (kind !== 'func') err(`Unknown type kind '${kind}'`)
|
|
77
|
-
const [idx] = consumeType(sig, ctx)
|
|
78
|
-
if (typeName) ctx.type[typeName] = idx
|
|
79
|
-
},
|
|
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 = []
|
|
80
135
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
let locals = [], // list of local variables
|
|
84
|
-
blocks = [] // control instructions / blocks stack
|
|
136
|
+
while (nodes.length) {
|
|
137
|
+
let node = nodes.shift()
|
|
85
138
|
|
|
86
|
-
|
|
87
|
-
|
|
139
|
+
if (typeof node === 'string') {
|
|
140
|
+
out.push(node)
|
|
88
141
|
|
|
89
|
-
|
|
90
|
-
|
|
142
|
+
if (abbr[node]) out.push(...abbr[node](nodes, ctx))
|
|
143
|
+
}
|
|
91
144
|
|
|
92
|
-
//
|
|
93
|
-
|
|
145
|
+
// FIXME: try to move this to abbr
|
|
146
|
+
else {
|
|
147
|
+
node = plain(node, ctx)
|
|
94
148
|
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
// (block ...) -> block ... end
|
|
150
|
+
if (node[0] === 'block' || node[0] === 'loop') {
|
|
151
|
+
out.push(...node, 'end')
|
|
152
|
+
}
|
|
97
153
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
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())
|
|
106
161
|
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
// label?
|
|
163
|
+
if (node[0]?.[0] === '$') blocktype.push(node.shift())
|
|
109
164
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (!nodes?.length) return out
|
|
165
|
+
// blocktype? - (param) are removed already via plain
|
|
166
|
+
if (node[0]?.[0] === 'type' || node[0]?.[0] === 'result') blocktype.push(node.shift());
|
|
113
167
|
|
|
114
|
-
|
|
168
|
+
// ignore empty else
|
|
169
|
+
// https://webassembly.github.io/spec/core/text/instructions.html#abbreviations
|
|
170
|
+
if (thenelse[thenelse.length - 1] === 'else') thenelse.pop()
|
|
115
171
|
|
|
116
|
-
|
|
117
|
-
if (group = Array.isArray(op)) {
|
|
118
|
-
args = [...op] // op is immutable
|
|
119
|
-
opCode = OP.indexOf(op = args.shift())
|
|
120
|
-
}
|
|
121
|
-
else opCode = OP.indexOf(op)
|
|
122
|
-
|
|
123
|
-
// NOTE: numeric comparison is faster than generic hash lookup
|
|
124
|
-
|
|
125
|
-
// v128s: (v128.load x) etc
|
|
126
|
-
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
|
|
127
|
-
if (opCode >= 268) {
|
|
128
|
-
opCode -= 268
|
|
129
|
-
immed = [0xfd, ...uleb(opCode)]
|
|
130
|
-
// (v128.load)
|
|
131
|
-
if (opCode <= 0x0b) {
|
|
132
|
-
const o = consumeParams(args)
|
|
133
|
-
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
|
|
134
|
-
}
|
|
135
|
-
// (v128.load_lane offset? align? idx)
|
|
136
|
-
else if (opCode >= 0x54 && opCode <= 0x5d) {
|
|
137
|
-
const o = consumeParams(args)
|
|
138
|
-
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
|
|
139
|
-
// (v128.load_lane_zero)
|
|
140
|
-
if (opCode <= 0x5b) immed.push(...uleb(args.shift()))
|
|
141
|
-
}
|
|
142
|
-
// (i8x16.shuffle 0 1 ... 15 a b)
|
|
143
|
-
else if (opCode === 0x0d) {
|
|
144
|
-
// i8, i16, i32 - bypass the encoding
|
|
145
|
-
for (let i = 0; i < 16; i++) immed.push(encode.i32.parse(args.shift()))
|
|
146
|
-
}
|
|
147
|
-
// (v128.const i32x4)
|
|
148
|
-
else if (opCode === 0x0c) {
|
|
149
|
-
args.unshift(op)
|
|
150
|
-
immed = consumeConst(args, ctx)
|
|
151
|
-
}
|
|
152
|
-
// (i8x16.extract_lane_s 0 ...)
|
|
153
|
-
else if (opCode >= 0x15 && opCode <= 0x22) {
|
|
154
|
-
immed.push(...uleb(args.shift()))
|
|
155
|
-
}
|
|
156
|
-
opCode = null // ignore opcode
|
|
172
|
+
out.push(...node, ...blocktype, ...thenelse, 'end')
|
|
157
173
|
}
|
|
174
|
+
else out.push(node)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
158
177
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
else if (opCode >= 252) {
|
|
162
|
-
immed = [0xfc, ...uleb(opCode -= 252)]
|
|
163
|
-
// memory.init idx, memory.drop idx, table.init idx, table.drop idx
|
|
164
|
-
if (!(opCode & 0b10)) immed.push(...uleb(args.shift()))
|
|
165
|
-
else immed.push(0)
|
|
166
|
-
// even opCodes (memory.init, memory.copy, table.init, table.copy) have 2nd predefined immediate
|
|
167
|
-
if (!(opCode & 0b1)) immed.push(0)
|
|
168
|
-
opCode = null // ignore opcode
|
|
169
|
-
}
|
|
178
|
+
return out
|
|
179
|
+
}
|
|
170
180
|
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
const abbr = {
|
|
182
|
+
// block typeuse?
|
|
183
|
+
block: (nodes, ctx) => {
|
|
184
|
+
let out = []
|
|
173
185
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// FIXME: figure out point in Math.log2 aligns
|
|
177
|
-
let o = consumeParams(args)
|
|
178
|
-
immed = [Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0)]
|
|
179
|
-
}
|
|
186
|
+
// (loop $l?)
|
|
187
|
+
if (nodes[0]?.[0] === '$') out.push(nodes.shift())
|
|
180
188
|
|
|
181
|
-
|
|
182
|
-
else if (opCode >= 0x41 && opCode <= 0x44) {
|
|
183
|
-
immed = encode[op.split('.')[0]](args.shift())
|
|
184
|
-
}
|
|
189
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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])
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
}
|
|
195
238
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// FIXME: how to get signature of imported function
|
|
201
|
-
}
|
|
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
|
|
202
243
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
immed = uleb(typeId), immed.push(0) // extra immediate indicates table idx (reserved)
|
|
208
|
-
}
|
|
244
|
+
// explicit type (type 0|$name)
|
|
245
|
+
if (nodes[0]?.[0] === 'type') {
|
|
246
|
+
[, idx] = nodes.shift();
|
|
247
|
+
[param, result] = paramres(nodes, names);
|
|
209
248
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
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`)
|
|
214
252
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
blocks.push(opCode)
|
|
253
|
+
return [idx]
|
|
254
|
+
}
|
|
218
255
|
|
|
219
|
-
|
|
220
|
-
|
|
256
|
+
// implicit type (param i32 i32)(result i32)
|
|
257
|
+
[param, result] = paramres(nodes, names)
|
|
221
258
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (args[0]?.[0] === 'result' && args[0].length < 3) {
|
|
225
|
-
let [, type] = args.shift()
|
|
226
|
-
immed = [TYPE[type]]
|
|
227
|
-
}
|
|
228
|
-
// (result i32 i32)
|
|
229
|
-
else if (args[0]?.[0] === 'result' || args[0]?.[0] === 'param') {
|
|
230
|
-
let [typeId] = consumeType(args, ctx)
|
|
231
|
-
immed = [typeId]
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
immed = [TYPE.void]
|
|
235
|
-
}
|
|
259
|
+
return [, param, result]
|
|
260
|
+
}
|
|
236
261
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (opCode < 4) while (args.length) nodes.unshift(args.pop())
|
|
242
|
-
|
|
243
|
-
// (if cond a) -> cond if a end
|
|
244
|
-
else if (args.length < 3) nodes.unshift(args.pop())
|
|
245
|
-
// (if cond (then a) (else b)) -> `cond if a else b end`
|
|
246
|
-
else {
|
|
247
|
-
nodes.unshift(args.pop())
|
|
248
|
-
// (if cond a b) -> (if cond a else b)
|
|
249
|
-
if (nodes[0][0] !== 'else') nodes.unshift('else')
|
|
250
|
-
// (if a b (else)) -> (if a b)
|
|
251
|
-
else if (nodes[0].length < 2) nodes.shift()
|
|
252
|
-
nodes.unshift(args.pop())
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
262
|
+
// consume (param t+)* (result t+)* sequence
|
|
263
|
+
const paramres = (nodes, names = true) => {
|
|
264
|
+
let param = [], result = []
|
|
256
265
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
+
}
|
|
266
275
|
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
}
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
// (br_if $label cond result?)
|
|
272
|
-
else if (opCode == 0x0c || opCode == 0x0d) {
|
|
273
|
-
// br index indicates how many block items to pop
|
|
274
|
-
immed = uleb(args[0]?.[0] === '$' ? blocks.length - blocks[args.shift()] : args.shift())
|
|
275
|
-
}
|
|
283
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
276
284
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
immed = []
|
|
280
|
-
while (!Array.isArray(args[0])) id = args.shift(), immed.push(...uleb(id[0][0] === '$' ? blocks.length - blocks[id] : id))
|
|
281
|
-
immed.unshift(...uleb(immed.length - 1))
|
|
282
|
-
}
|
|
283
|
-
else if (opCode < 0) err(`Unknown instruction \`${op}\``)
|
|
285
|
+
return [param, result]
|
|
286
|
+
}
|
|
284
287
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
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)))]),
|
|
290
292
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
// (import "math" "add" (func|table|global|memory typedef?))
|
|
294
|
+
([mod, field, [kind, ...dfn]], ctx) => {
|
|
295
|
+
let details
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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)]
|
|
301
311
|
}
|
|
302
|
-
},
|
|
303
312
|
|
|
304
|
-
|
|
305
|
-
// (memory $name min max shared)
|
|
306
|
-
// (memory (export "mem") 5)
|
|
307
|
-
memory([, ...parts], ctx) {
|
|
308
|
-
if (parts[0][0] === '$') ctx.memory[parts.shift()] = ctx.memory.length
|
|
309
|
-
if (parts[0][0] === 'export') build.export([...parts.shift(), ['memory', ctx.memory.length]], ctx)
|
|
310
|
-
ctx.memory.push(range(parts))
|
|
313
|
+
return ([...vec(str(mod.slice(1, -1))), ...vec(str(field.slice(1, -1))), KIND[kind], ...details])
|
|
311
314
|
},
|
|
312
315
|
|
|
313
|
-
// (
|
|
314
|
-
|
|
315
|
-
// (global $id (mut i32) (i32.const 42))
|
|
316
|
-
global([, ...args], ctx) {
|
|
317
|
-
let name = args[0][0] === '$' && args.shift()
|
|
318
|
-
if (name) ctx.global[name] = ctx.global.length
|
|
319
|
-
let [type, [...init]] = args, mut = type[0] === 'mut' ? 1 : 0
|
|
320
|
-
ctx.global.push([TYPE[mut ? type[1] : type], mut, ...consumeConst(init, ctx), 0x0b])
|
|
321
|
-
},
|
|
316
|
+
// (func $name? ...params result ...body)
|
|
317
|
+
([[, typeidx]], ctx) => (uleb(id(typeidx, ctx.type))),
|
|
322
318
|
|
|
323
|
-
// (table 1 2
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (name) ctx.table[name] = ctx.table.length
|
|
328
|
-
let lims = range(args)
|
|
329
|
-
ctx.table.push([TYPE[args.pop()], ...lims])
|
|
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]
|
|
330
323
|
},
|
|
331
324
|
|
|
332
|
-
// (
|
|
333
|
-
|
|
334
|
-
const tableIdx = 0 // FIXME: table index can be defined
|
|
335
|
-
ctx.elem.push([tableIdx, ...consumeConst(offset, ctx), 0x0b, ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0] === '$' ? ctx.func[el] : el))])
|
|
336
|
-
},
|
|
325
|
+
// (memory id? export* min max shared)
|
|
326
|
+
(node, ctx) => limits(node),
|
|
337
327
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
328
|
+
// (global $id? (mut i32) (i32.const 42))
|
|
329
|
+
(node, ctx) => {
|
|
330
|
+
let [t] = node, mut = t[0] === 'mut' ? 1 : 0
|
|
331
|
+
|
|
332
|
+
let [, init] = node
|
|
333
|
+
return ([...type(mut ? t[1] : t, ctx), mut, ...expr(init, ctx)])
|
|
342
334
|
},
|
|
343
335
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
// (import "js" "mem" (memory $name 1))
|
|
347
|
-
// (import "js" "v" (global $name (mut f64)))
|
|
348
|
-
import([, mod, field, ref], ctx) {
|
|
349
|
-
let details, [kind, ...parts] = ref,
|
|
350
|
-
name = parts[0]?.[0] === '$' && parts.shift();
|
|
336
|
+
// (export "name" (func|table|mem $name|idx))
|
|
337
|
+
([nm, [kind, l]], ctx) => ([...vec(str(nm.slice(1, -1))), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
351
338
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
339
|
+
// (start $main)
|
|
340
|
+
([l], ctx) => uleb(id(l, ctx.func)),
|
|
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
|
|
347
|
+
// ref: https://webassembly.github.io/spec/core/binary/modules.html#element-section
|
|
348
|
+
(parts, ctx) => {
|
|
349
|
+
let tabidx, offset, mode = 0b000, reftype
|
|
350
|
+
|
|
351
|
+
// declare?
|
|
352
|
+
if (parts[0] === 'declare') parts.shift(), mode |= 0b010
|
|
353
|
+
|
|
354
|
+
// table?
|
|
355
|
+
if (parts[0][0] === 'table') {
|
|
356
|
+
[, tabidx] = parts.shift()
|
|
357
|
+
tabidx = id(tabidx, ctx.table)
|
|
358
|
+
// ignore table=0
|
|
359
|
+
if (tabidx) mode |= 0b010
|
|
358
360
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
|
|
362
|
+
// (offset expr)|expr
|
|
363
|
+
if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
|
|
364
|
+
offset = parts.shift()
|
|
365
|
+
if (offset[0] === 'offset') [, offset] = offset
|
|
362
366
|
}
|
|
363
|
-
else
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
else mode |= 0b001 // passive
|
|
368
|
+
|
|
369
|
+
// func ... === funcref ..., https://webassembly.github.io/function-references/core/text/modules.html#id7
|
|
370
|
+
if (HEAPTYPE[parts[0]]) parts[0] += 'ref'
|
|
371
|
+
// reftype: funcref|externref|(ref ...)
|
|
372
|
+
if (parts[0] === 'funcref' || parts[0] === 'externref' || parts[0]?.[0] === 'ref') reftype = parts.shift()
|
|
373
|
+
|
|
374
|
+
// legacy abbr if func is skipped
|
|
375
|
+
if (!reftype) !tabidx ? reftype = 'funcref' : err(`Undefined reftype`)
|
|
376
|
+
|
|
377
|
+
// externref makes explicit table index
|
|
378
|
+
if (reftype === 'externref' || reftype[0] === 'ref') offset ||= ['i32.const', 0], mode = 0b110
|
|
379
|
+
// reset to simplest mode if no actual elements
|
|
380
|
+
else if (!parts.length) mode &= 0b011
|
|
381
|
+
|
|
382
|
+
// simplify els sequence
|
|
383
|
+
parts = parts.map(el => {
|
|
384
|
+
if (el[0] === 'item') [, ...el] = el
|
|
385
|
+
if (el[0] === 'ref.func') [, el] = el
|
|
386
|
+
// (ref.null func) and other expressions turn expr init mode
|
|
387
|
+
if (typeof el !== 'string') mode |= 0b100
|
|
388
|
+
return el
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
return ([
|
|
392
|
+
mode,
|
|
393
|
+
...(
|
|
394
|
+
// 0b000 e:expr y*:vec(funcidx) | type=funcref, init ((ref.func y)end)*, active (table=0,offset=e)
|
|
395
|
+
mode === 0b000 ? expr(offset, ctx) :
|
|
396
|
+
// 0b001 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive
|
|
397
|
+
mode === 0b001 ? [0x00] :
|
|
398
|
+
// 0b010 x:tabidx e:expr et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, active (table=x,offset=e)
|
|
399
|
+
mode === 0b010 ? [...uleb(tabidx || 0), ...expr(offset, ctx), 0x00] :
|
|
400
|
+
// 0b011 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive declare
|
|
401
|
+
mode === 0b011 ? [0x00] :
|
|
402
|
+
// 0b100 e:expr el*:vec(expr) | type=funcref, init el*, active (table=0, offset=e)
|
|
403
|
+
mode === 0b100 ? expr(offset, ctx) :
|
|
404
|
+
// 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
|
|
405
|
+
mode === 0b101 ? type(reftype, ctx) :
|
|
406
|
+
// 0b110 x:tabidx e:expr et:reftype el*:vec(expr) | type=et, init el*, active (table=x, offset=e)
|
|
407
|
+
mode === 0b110 ? [...uleb(tabidx || 0), ...expr(offset, ctx), ...type(reftype, ctx)] :
|
|
408
|
+
// 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare
|
|
409
|
+
type(reftype, ctx)
|
|
410
|
+
),
|
|
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
|
+
)
|
|
418
|
+
)
|
|
419
|
+
])
|
|
420
|
+
},
|
|
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)
|
|
370
440
|
}
|
|
371
|
-
else throw Error('Unimplemented ' + kind)
|
|
372
441
|
|
|
373
|
-
|
|
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])
|
|
374
455
|
},
|
|
375
456
|
|
|
376
457
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
377
|
-
// (data (offset (i32.const 0))
|
|
458
|
+
// (data (memory ref) (offset (i32.const 0)) "\aa" "\bb"?)
|
|
378
459
|
// (data (global.get $x) "\aa" "\bb"?)
|
|
379
|
-
|
|
380
|
-
let offset,
|
|
460
|
+
(inits, ctx) => {
|
|
461
|
+
let offset, memidx = 0
|
|
462
|
+
|
|
463
|
+
// (memory ref)?
|
|
464
|
+
if (inits[0]?.[0] === 'memory') {
|
|
465
|
+
[, memidx] = inits.shift()
|
|
466
|
+
memidx = id(memidx, ctx.memory)
|
|
467
|
+
}
|
|
381
468
|
|
|
382
|
-
|
|
383
|
-
if (inits[0]
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
469
|
+
// (offset (i32.const 0)) or (i32.const 0)
|
|
470
|
+
if (typeof inits[0] !== 'string') {
|
|
471
|
+
offset = inits.shift()
|
|
472
|
+
if (offset[0] === 'offset') [, offset] = offset
|
|
473
|
+
offset ?? err('Bad offset', offset)
|
|
474
|
+
}
|
|
387
475
|
|
|
388
|
-
|
|
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
|
+
])
|
|
389
487
|
},
|
|
390
488
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
489
|
+
// datacount
|
|
490
|
+
(nodes, ctx) => uleb(ctx.data.length)
|
|
491
|
+
]
|
|
492
|
+
|
|
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}`)]
|
|
497
|
+
|
|
498
|
+
// consume one instruction from nodes sequence
|
|
499
|
+
const instr = (nodes, ctx) => {
|
|
500
|
+
if (!nodes?.length) return []
|
|
501
|
+
|
|
502
|
+
let out = [], op = nodes.shift(), immed, code
|
|
503
|
+
|
|
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
|
|
394
510
|
}
|
|
395
|
-
}
|
|
396
511
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
}
|
|
400
561
|
|
|
401
|
-
// (
|
|
402
|
-
|
|
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
|
|
403
567
|
|
|
404
|
-
|
|
405
|
-
|
|
568
|
+
// memory.init idx, data.drop idx,
|
|
569
|
+
if (code === 0x08 || code === 0x09) {
|
|
570
|
+
immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
571
|
+
}
|
|
406
572
|
|
|
407
|
-
|
|
408
|
-
|
|
573
|
+
// memory placeholders
|
|
574
|
+
if (code == 0x08 || code == 0x0b) immed.push(0)
|
|
575
|
+
else if (code === 0x0a) immed.push(0, 0)
|
|
409
576
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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)))
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
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)))
|
|
622
|
+
}
|
|
417
623
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
624
|
+
// global.get $id, global.set $id
|
|
625
|
+
else if (code == 0x23 || code == 0x24) {
|
|
626
|
+
immed.push(...uleb(id(nodes.shift(), ctx.global)))
|
|
627
|
+
}
|
|
628
|
+
|
|
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
|
+
}
|
|
634
|
+
|
|
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
|
+
}
|
|
422
643
|
|
|
423
|
-
|
|
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
|
+
}
|
|
424
662
|
|
|
425
|
-
//
|
|
426
|
-
if (
|
|
427
|
-
let
|
|
428
|
-
|
|
429
|
-
|
|
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))
|
|
430
670
|
}
|
|
431
|
-
|
|
671
|
+
args.unshift(...uleb(args.length - 1))
|
|
672
|
+
immed.push(...args)
|
|
673
|
+
}
|
|
674
|
+
|
|
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))))
|
|
432
680
|
}
|
|
433
681
|
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
arr.set(encode[t](args.shift()), i * stride)
|
|
682
|
+
// ref.func $id
|
|
683
|
+
else if (code == 0xd2) {
|
|
684
|
+
immed.push(...uleb(id(nodes.shift(), ctx.func)))
|
|
438
685
|
}
|
|
439
686
|
|
|
440
|
-
|
|
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
|
+
}
|
|
692
|
+
|
|
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))
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// i32.const 123, f32.const 123.45
|
|
703
|
+
else if (code >= 0x41 && code <= 0x44) {
|
|
704
|
+
immed.push(...encode[op.split('.')[0]](nodes.shift()))
|
|
705
|
+
}
|
|
706
|
+
|
|
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
|
|
721
|
+
}
|
|
722
|
+
|
|
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
|
|
727
|
+
const memarg = (args) => {
|
|
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]
|
|
735
|
+
}
|
|
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)
|
|
441
754
|
}
|
|
442
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
|
+
|
|
443
759
|
// escape codes
|
|
444
|
-
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 }
|
|
445
761
|
|
|
446
762
|
// build string binary
|
|
447
763
|
const str = str => {
|
|
448
|
-
str = str[0] === '"' ? str.slice(1, -1) : str
|
|
449
764
|
let res = [], i = 0, c, BSLASH = 92
|
|
450
|
-
//
|
|
765
|
+
// https://webassembly.github.io/spec/core/text/values.html#strings
|
|
451
766
|
for (; i < str.length;) {
|
|
452
767
|
c = str.charCodeAt(i++)
|
|
453
768
|
res.push(c === BSLASH ? escape[str[i++]] || parseInt(str.slice(i - 1, ++i), 16) : c)
|
|
454
769
|
}
|
|
455
|
-
|
|
456
|
-
res.unshift(...uleb(res.length))
|
|
457
770
|
return res
|
|
458
771
|
}
|
|
459
772
|
|
|
460
|
-
//
|
|
461
|
-
const
|
|
773
|
+
// serialize binary array
|
|
774
|
+
const vec = a => [...uleb(a.length), ...a.flat()]
|
|
462
775
|
|
|
463
|
-
|
|
464
|
-
// returns registered (reused) type idx, params bytes, result bytes
|
|
465
|
-
// eg. (type $return_i32 (func (result i32)))
|
|
466
|
-
const consumeType = (nodes, ctx) => {
|
|
467
|
-
let params = [], result = [], idx, bytes
|
|
776
|
+
const err = text => { throw Error(text) }
|
|
468
777
|
|
|
469
|
-
|
|
470
|
-
while (nodes[0]?.[0] === 'param') {
|
|
471
|
-
let [, ...types] = nodes.shift()
|
|
472
|
-
if (types[0]?.[0] === '$') params[types.shift()] = params.length
|
|
473
|
-
params.push(...types.map(t => TYPE[t]))
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// collect result type
|
|
477
|
-
if (nodes[0]?.[0] === 'result') result = nodes.shift().slice(1).map(t => TYPE[t])
|
|
478
|
-
|
|
479
|
-
// reuse existing type or register new one
|
|
480
|
-
bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result]
|
|
481
|
-
idx = ctx.type.findIndex((t) => t.every((byte, i) => byte === bytes[i]))
|
|
482
|
-
|
|
483
|
-
// register new type, if not found
|
|
484
|
-
if (idx < 0) idx = ctx.type.push(bytes) - 1
|
|
485
|
-
|
|
486
|
-
return [idx, params, result]
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// consume align/offset/etc params
|
|
490
|
-
const consumeParams = (args) => {
|
|
491
|
-
let params = {}, param
|
|
492
|
-
while (args[0]?.includes('=')) param = args.shift().split('='), params[param[0]] = Number(param[1])
|
|
493
|
-
return params
|
|
494
|
-
}
|
|
778
|
+
const clone = items => items.map(item => Array.isArray(item) ? clone(item) : item)
|