watr 3.3.0 → 4.0.0

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