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/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, ALIGN, TYPE, KIND, INSTR } from './const.js'
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((instr, i) => {
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); else nodes = [...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(), id(nodes)
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) sections.push(sections[kind] = [])
40
+ for (let kind in SECTION) (sections[SECTION[kind]] = sections[kind] = []).name = kind
41
+ sections._ = {} // implicit types
39
42
 
40
- const binary = [
41
- 0x00, 0x61, 0x73, 0x6d, // magic
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
- // sort nodes by sections
46
- // TODO: make this more elegant
47
- let nodeGroups = []
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 = id(node), idx = nodeGroups[kind].length;
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') nodeGroups.export.push([node.shift()[1], [kind, idx]])
56
+ while (node[0]?.[0] === 'export') sections.export.push([node.shift()[1], [kind, idx]])
58
57
 
59
- // import abbr
60
- // (table|memory|global|func id? (import m n) type) -> (import m n (table|memory|global|func id? type))
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
- // (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
65
- if (node[1]?.[0] === 'elem') {
66
- let [reftype, [, ...els]] = node
67
- node = [els.length, els.length, reftype]
68
- nodeGroups.elem.push([['table', name || nodeGroups.table.length], ['i32.const', '0'], typeof els[0] === 'string' ? 'func' : reftype, ...els])
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 [,...data] = node.shift(), m = ''+Math.ceil(data.map(s => s.slice(1,-1)).join('').length / 65536) // FIXME: figure out actual data size
75
- nodeGroups.data.push([['memory', idx], ['i32.const',0], ...data])
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
- // import increments corresponding section index
81
- // FIXME: can be turned into shallow node
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
- nodeGroups[kind].push(node)
92
- }
93
-
94
- // build sections binaries
95
- for (let kind in SECTION) nodeGroups[kind].map((node,i) => !node ? [] : build[kind](i, node, sections))
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
- return new Uint8Array(binary)
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
- // consume $id
116
- const id = nodes => nodes[0]?.[0] === '$' && nodes.shift()
96
+ sections[kind].push(node)
97
+ }
117
98
 
118
- // build section binary (non consuming)
119
- const build = {
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
- // (import "math" "add" (func|table|global|memory typedef?))
133
- import(_, [mod, field, [kind, ...dfn]], ctx) {
134
- let details
102
+ // patch datacount if data === 0
103
+ if (!sections.data.length) sections.datacount.length = 0
135
104
 
136
- if (kind === 'func') {
137
- // we track imported funcs in func section to share namespace, and skip them on final build
138
- let [typeIdx] = typeuse(dfn, ctx)
139
- details = uleb(typeIdx)
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
- ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details])
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
- // (func $name? ...params result ...body)
156
- func(idx, [...node], ctx) {
157
- const [typeidx, param, result] = typeuse(node, ctx)
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
- ctx.func[idx] = uleb(typeidx)
136
+ while (nodes.length) {
137
+ let node = nodes.shift()
160
138
 
161
- // build code section
162
- let blocks = [] // control instructions / blocks stack
163
- let locals = [] // list of local variables
139
+ if (typeof node === 'string') {
140
+ out.push(node)
164
141
 
165
- // collect locals
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
- // convert sequence of instructions from input nodes to out bytes
175
- // FIXME: make external func
176
- const consume = (nodes, out = []) => {
177
- if (!nodes?.length) return out
145
+ // FIXME: try to move this to abbr
146
+ else {
147
+ node = plain(node, ctx)
178
148
 
179
- let op = nodes.shift(), opCode, args = nodes, immed, id, group
180
-
181
- // flatten groups, eg. (cmd z w) -> z w cmd
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
- // bulk memory: (memory.init) (memory.copy) etc
223
- // https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#instruction-encoding
224
- else if (opCode >= 0xfc) {
225
- immed = [0xfc, ...uleb(opCode -= 0xfc)]
226
- // memory.init idx, memory.drop idx, table.init idx, table.drop idx
227
- if (!(opCode & 0b10)) immed.push(...uleb(args.shift()))
228
- else immed.push(0)
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
- // ref.func $id
235
- else if (opCode == 0xd2) {
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
- // binary/unary (i32.add a b) - no immed
244
- else if (opCode >= 0x45) { }
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
- // (i32.store align=n offset=m at value) etc
247
- else if (opCode >= 0x28 && opCode <= 0x3e) {
248
- // FIXME: figure out point in Math.log2 aligns
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
- // (i32.const 123), (f32.const 123.45) etc
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
- // (local.get $id), (local.tee $id x)
259
- else if (opCode >= 0x20 && opCode <= 0x22) {
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
- // (global.get $id), (global.set $id)
264
- else if (opCode == 0x23 || opCode == 0x24) {
265
- immed = uleb(args[0]?.[0] === '$' ? ctx.global[args.shift()] : +args.shift())
266
- }
181
+ const abbr = {
182
+ // block typeuse?
183
+ block: (nodes, ctx) => {
184
+ let out = []
267
185
 
268
- // (call id ...nodes)
269
- else if (opCode == 0x10) {
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
- // (call_indirect tableIdx? (type $typeName) (idx) ...nodes)
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
- // (block ...), (loop ...), (if ...)
283
- else if (opCode === 2 || opCode === 3 || opCode === 4) {
284
- blocks.push(opCode)
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
- // (block $x) (loop $y)
287
- if (args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
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
- // get type - can be either typeidx or valtype (numtype | reftype)
290
- // (result i32) - doesn't require registering type
291
- if (args[0]?.[0] === 'result' && args[0].length < 3) {
292
- let [, type] = args.shift()
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
- if (group) {
312
- // (block xxx) -> block xxx end
313
- nodes.unshift('end')
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
- // (else)
331
- else if (opCode === 5) {
332
- // (else xxx) -> else xxx
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
- // (end)
341
- else if (opCode == 0x0b) blocks.pop()
253
+ return [idx]
254
+ }
342
255
 
343
- // (br $label result?)
344
- // (br_if $label cond result?)
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
- // (br_table 1 2 3 4 0 selector result?)
351
- else if (opCode == 0x0e) {
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
- // FIXME multiple memory (memory.grow $idx?)
361
- else if (opCode == 0x3f || opCode == 0x40) {
362
- immed = [0]
363
- }
262
+ // consume (param t+)* (result t+)* sequence
263
+ const paramres = (nodes, names = true) => {
264
+ let param = [], result = []
364
265
 
365
- // (table.get $id)
366
- else if (opCode == 0x25 || opCode == 0x26) {
367
- immed = uleb(args[0]?.[0] === '$' ? ctx.table[args.shift()] : +args.shift())
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
- // table.grow id, table.size id, table.fill id
371
- else if (opCode >= 0x0f && opCode <= 0x11) {
372
- immed = []
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
- else if (opCode == null) err(`Unknown instruction \`${op}\``)
283
+ if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
376
284
 
377
- // if group (cmd im1 im2 arg1 arg2) - insert any remaining args first: arg1 arg2
378
- // because inline case has them in stack already
379
- if (group) while (args.length) consume(args, out)
285
+ return [param, result]
286
+ }
380
287
 
381
- if (opCode != null) out.push(opCode)
382
- if (immed) out.push(...immed)
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
- const bytes = []
386
- // FIXME: avoid passing bytes from outside, push result instead
387
- while (node.length) consume(node, bytes)
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
- // squash locals into (n:u32 t:valtype)*, n is number and t is type
391
- let loctypes = locals.reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
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
- // https://webassembly.github.io/spec/core/binary/modules.html#code-section
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
- // (table id? 1 2? funcref)
398
- table(idx, [...node], ctx) {
399
- ctx.table[idx] = [TYPE[node.pop()], ...limits(node)]
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
- memory(idx, [...node], ctx) {
404
- ctx.memory[idx] = limits(node)
405
- },
326
+ (node, ctx) => limits(node),
406
327
 
407
328
  // (global $id? (mut i32) (i32.const 42))
408
- global(idx, [...node], ctx) {
409
- let [type] = node, mut = type[0] === 'mut' ? 1 : 0
329
+ (node, ctx) => {
330
+ let [t] = node, mut = t[0] === 'mut' ? 1 : 0
410
331
 
411
- let [, [...init]] = node
412
- ctx.global[idx] = [TYPE[mut ? type[1] : type], mut, ...expr(init, ctx), 0x0b]
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
- export(_, [nm, [kind, id]], ctx) {
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
- start(_,[id], ctx) {
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
- // passive: (elem elem*)
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[0] === '$' ? ctx.table[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
- [...offset] = parts.shift()
451
- if (offset[0] === 'offset') [, [...offset]] = offset
364
+ offset = parts.shift()
365
+ if (offset[0] === 'offset') [, offset] = offset
452
366
  }
453
367
  else mode |= 0b001 // passive
454
368
 
455
- // funcref|externref|func
456
- if (parts[0]?.[0]!=='$') reftype = parts.shift()
457
- // externref makes explicit table index
458
- if (reftype === 'externref') offset ||= ['i32.const', 0], mode = 0b110
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
- ctx.elem[idx] = ([
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 ? [...expr(offset, ctx), 0x0b] :
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), 0x0b, 0x00] :
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 ? [...expr(offset, ctx), 0x0b] :
403
+ mode === 0b100 ? expr(offset, ctx) :
485
404
  // 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
486
- mode === 0b101 ? [TYPE[reftype]] :
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), 0x0b, TYPE[reftype]] :
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
- [TYPE[reftype]]
409
+ type(reftype, ctx)
491
410
  ),
492
- ...uleb(parts.length),
493
- ...parts.flatMap(mode & 0b100 ?
494
- // ((ref.func y)end)*
495
- el => [...expr(typeof el === 'string' ? ['ref.func', el] : [...el], ctx), 0x0b] :
496
- // el*
497
- el => uleb(el[0] === '$' ? ctx.func[el] : +el)
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
- data(idx, [...inits], ctx) {
506
- let offset, mem = [0]
460
+ (inits, ctx) => {
461
+ let offset, memidx = 0
507
462
 
508
463
  // (memory ref)?
509
464
  if (inits[0]?.[0] === 'memory') {
510
- [, mem] = inits.shift()
511
- mem = mem[0] === '$' ? ctx.memory[mem] : +mem
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
- // serialize binary array
526
- const vec = a => [...uleb(a.length), ...a]
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
- // instantiation time const initializer (consuming)
529
- const expr = (node, ctx) => {
530
- let op = node.shift(), [type, cmd] = op.split('.')
489
+ // datacount
490
+ (nodes, ctx) => uleb(ctx.data.length)
491
+ ]
531
492
 
532
- // (global.get idx)
533
- if (type === 'global') return [0x23, ...uleb(node[0][0] === '$' ? ctx.global[node[0]] : +node)]
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
- // (v128.const i32x4 1 2 3 4)
536
- if (type === 'v128') return [0xfd, 0x0c, ...v128(node)]
498
+ // consume one instruction from nodes sequence
499
+ const instr = (nodes, ctx) => {
500
+ if (!nodes?.length) return []
537
501
 
538
- // (i32.const 1)
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
- // (ref.func $x) or (ref.null func|extern)
542
- if (type === 'ref') {
543
- return cmd === 'func' ?
544
- [0xd2, ...uleb(node[0][0] === '$' ? ctx.func[node[0]] : +node)] :
545
- // heaptype
546
- [0xd0, TYPE[node[0] + 'ref']] // func->funcref, extern->externref
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
- // (i32.add a b), (i32.mult a b) etc
550
- return [
551
- ...expr(node.shift(), ctx),
552
- ...expr(node.shift(), ctx),
553
- INSTR[op]
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
- // (v128.const i32x4 1 2 3 4)
558
- const v128 = (args) => {
559
- let [t, n] = args.shift().split('x'),
560
- stride = t.slice(1) >>> 3 // i16 -> 2, f32 -> 4
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
- n = +n
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
- // i8, i16, i32 - bypass the encoding
565
- if (t[0] === 'i') {
566
- let arr = n === 16 ? new Uint8Array(16) : n === 8 ? new Uint16Array(8) : n === 4 ? new Uint32Array(4) : new BigInt64Array(2)
567
- for (let i = 0; i < n; i++) {
568
- arr[i] = encode[t].parse(args.shift())
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
- // f32, f64 - encode
574
- let arr = new Uint8Array(16)
575
- for (let i = 0; i < n; i++) {
576
- arr.set(encode[t](args.shift()), i * stride)
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
- return arr
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
- // https://webassembly.github.io/spec/core/text/modules.html#type-uses
583
- // consume (type $id|id) (param t+)* (result t+)*
584
- const typeuse = (nodes, ctx) => {
585
- let idx, param, result, alias
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
- // existing/new type (type 0|$name)
588
- if (nodes[0]?.[0] === 'type') {
589
- [, idx] = nodes.shift();
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
- // (type 0), (type $n) - existing type
592
- if (ctx.type[idx] != null) {
593
- paramres(nodes);
594
- if (idx[0] === '$') idx = ctx.type[idx];
595
- ({ param, result } = ctx.type[idx] ?? err('Bad type ' + idx));
596
- return [+idx, param, result]
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
- // if new type - find existing match
601
- ;[param, result] = paramres(nodes), alias = param + '>' + result
602
- // or register new type
603
- if (ctx.type[alias] == null) {
604
- build.type(ctx.type.length, [[, ['param', ...param], ['result', ...result]]], ctx)
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
- return [ctx.type[alias], param, result]
608
- }
682
+ // ref.func $id
683
+ else if (code == 0xd2) {
684
+ immed.push(...uleb(id(nodes.shift(), ctx.func)))
685
+ }
609
686
 
610
- // consume (param t+)* (result t+)* sequence
611
- const paramres = (nodes) => {
612
- let param = [], result = []
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
- // collect param (param i32 i64) (param $x? i32)
615
- while (nodes[0]?.[0] === 'param') {
616
- let [, ...args] = nodes.shift()
617
- let name = args[0]?.[0] === '$' && args.shift()
618
- if (name) param[name] = param.length // expose name refs
619
- param.push(...args)
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
- // collect result eg. (result f64 f32)(result i32)
623
- while (nodes[0]?.[0] === 'result') {
624
- let [, ...args] = nodes.shift()
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
- return [param, result]
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
- // consume align/offset/etc params
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 ao = {}, kv
634
- while (args[0]?.includes('=')) kv = args.shift().split('='), ao[kv[0]] = Number(kv[1])
635
- return ao
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
- // build limits sequence (non-consuming)
655
- const limits = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared === 'shared' ? 3 : 1, ...uleb(min), ...uleb(max)]
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)