watr 4.2.0 → 4.3.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
@@ -7,7 +7,7 @@ import { err, unescape, str } from './util.js'
7
7
 
8
8
  /**
9
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.
10
+ * Preserves @custom and @metadata.code.* annotations. Preserves .loc for error reporting.
11
11
  *
12
12
  * @param {any} node - AST node
13
13
  * @param {Array} [result] - Internal accumulator
@@ -25,8 +25,8 @@ const cleanup = (node, result) => !Array.isArray(node) ? (
25
25
  ) :
26
26
  // remove annotations like (@name ...) except @custom and @metadata.code.*
27
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)
28
+ // unwrap single-element array containing module (after removing comments), preserve .loc
29
+ (result = node.map(cleanup).filter(n => n != null), result.loc = node.loc, result.length === 1 && result[0]?.[0] === 'module' ? result[0] : result)
30
30
 
31
31
 
32
32
  /**
@@ -39,7 +39,7 @@ export default function compile(nodes) {
39
39
  // normalize to (module ...) form
40
40
  if (typeof nodes === 'string') err.src = nodes, nodes = parse(nodes) || []
41
41
  else err.src = '' // clear source if AST passed directly
42
- err.i = 0
42
+ err.loc = 0
43
43
 
44
44
  nodes = cleanup(nodes) || []
45
45
 
@@ -56,6 +56,20 @@ export default function compile(nodes) {
56
56
  // quote "a" "b"
57
57
  if (nodes[idx] === 'quote') return compile(nodes.slice(++idx).map(v => v.valueOf().slice(1, -1)).flat().join(''))
58
58
 
59
+ // expand grouped imports: (import "mod" (item "name" type)*) -> individual imports
60
+ // compact import section (Phase 3)
61
+ nodes = nodes.flatMap((n, i) => {
62
+ if (i < idx || !Array.isArray(n) || n[0] !== 'import') return [n]
63
+ const [, mod, ...rest] = n
64
+ if (!rest.some(r => Array.isArray(r) && r[0] === 'item')) return [n]
65
+ const lastIsType = Array.isArray(rest.at(-1)) && rest.at(-1)[0] !== 'item'
66
+ if (lastIsType) {
67
+ const type = rest.at(-1)
68
+ return rest.slice(0, -1).filter(r => r[0] === 'item').map(([, nm]) => ['import', mod, nm, type])
69
+ }
70
+ return rest.filter(r => r[0] === 'item').map(([, nm, type]) => ['import', mod, nm, type])
71
+ })
72
+
59
73
  // scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
60
74
  const ctx = []
61
75
  for (let kind in SECTION) (ctx[SECTION[kind]] = ctx[kind] = []).name = kind
@@ -64,12 +78,22 @@ export default function compile(nodes) {
64
78
  // initialize types
65
79
  nodes.slice(idx).filter((n) => {
66
80
  if (!Array.isArray(n)) {
67
- let pos = err.src?.indexOf(n, err.i)
68
- if (pos >= 0) err.i = pos
81
+ // find token as standalone word (not substring of another token)
82
+ let pos = err.loc, src = err.src, c
83
+ while ((pos = src.indexOf(n, pos)) >= 0) {
84
+ c = src.charCodeAt(pos - 1)
85
+ // check not preceded by word char or $
86
+ if (pos > 0 && (c > 47 && c < 58 || c > 64 && c < 91 || c > 96 && c < 123 || c === 95 || c === 36)) { pos++; continue }
87
+ c = src.charCodeAt(pos + n.length)
88
+ // check not followed by word char
89
+ if (c > 47 && c < 58 || c > 64 && c < 91 || c > 96 && c < 123 || c === 95) { pos++; continue }
90
+ break
91
+ }
92
+ if (pos >= 0) err.loc = pos
69
93
  err(`Unexpected token ${n}`)
70
94
  }
71
95
  let [kind, ...node] = n
72
- err.i = n.i // track position for errors
96
+ err.loc = n.loc // track position for errors
73
97
  // (@custom "name" placement? data) - custom section support
74
98
  if (kind === '@custom') {
75
99
  ctx.custom.push(node)
@@ -81,8 +105,12 @@ export default function compile(nodes) {
81
105
  // add rest of subtypes as regular type nodes with subtype flag
82
106
  for (let i = 0; i < node.length; i++) {
83
107
  let [, ...subnode] = node[i]
84
- name(subnode, ctx.type);
85
- (subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
108
+ name(subnode, ctx.type)
109
+ // extract top-level descriptor/describes (custom descriptors, Phase 3)
110
+ const tdesc = []
111
+ while (subnode[0]?.[0] === 'descriptor' || subnode[0]?.[0] === 'describes') tdesc.push(subnode.shift())
112
+ ;(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
113
+ if (tdesc.length) subnode.desc = subnode.desc ? [...tdesc, ...subnode.desc] : tdesc
86
114
  ctx.type.push(subnode)
87
115
  }
88
116
  }
@@ -91,8 +119,13 @@ export default function compile(nodes) {
91
119
  // (type (struct (field a)*)
92
120
  // (type (sub final? $nm* (struct|array|func ...)))
93
121
  else if (kind === 'type') {
94
- name(node, ctx.type);
95
- ctx.type.push(typedef(node, ctx));
122
+ name(node, ctx.type)
123
+ // extract top-level descriptor/describes (custom descriptors, Phase 3)
124
+ const tdesc = []
125
+ while (node[0]?.[0] === 'descriptor' || node[0]?.[0] === 'describes') tdesc.push(node.shift())
126
+ const td = typedef(node, ctx)
127
+ if (tdesc.length) td.desc = td.desc ? [...tdesc, ...td.desc] : tdesc
128
+ ctx.type.push(td)
96
129
  }
97
130
  // other sections may have id
98
131
  else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
@@ -103,7 +136,7 @@ export default function compile(nodes) {
103
136
  // prepare/normalize nodes
104
137
  .forEach((n) => {
105
138
  let [kind, ...node] = n
106
- err.i = n.i // track position for errors
139
+ err.loc = n.loc // track position for errors
107
140
  let imported // if node needs to be imported
108
141
 
109
142
  // import abbr
@@ -136,7 +169,8 @@ export default function compile(nodes) {
136
169
  else if (kind === 'memory') {
137
170
  const is64 = node[0] === 'i64', idx = is64 ? 1 : 0
138
171
  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)
172
+ const ps = (node.find(n => Array.isArray(n) && n[0] === 'pagesize')?.[1]) ?? 65536
173
+ let [, ...data] = node.splice(idx, 1)[0], m = '' + Math.ceil(data.reduce((s, d) => s + d.length, 0) / ps)
140
174
  ctx.data.push([['memory', items.length], [is64 ? 'i64.const' : 'i32.const', is64 ? 0n : 0], ...data])
141
175
  node = is64 ? ['i64', m, m] : [m, m]
142
176
  }
@@ -266,7 +300,7 @@ function normalize(nodes, ctx) {
266
300
  }
267
301
  else if (Array.isArray(node)) {
268
302
  const op = node[0]
269
- node.i != null && (err.i = node.i) // track position for errors
303
+ node.loc != null && (err.loc = node.loc) // track position for errors
270
304
 
271
305
  // code metadata annotations - pass through as marker with metadata type and data
272
306
  // (@metadata.code.<type> data:str)
@@ -308,7 +342,7 @@ function normalize(nodes, ctx) {
308
342
  else {
309
343
  const imm = []
310
344
  // 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())
345
+ while (parts.length && (!Array.isArray(parts[0]) || 'type,param,result,ref,exact,on'.includes(parts[0][0]))) imm.push(parts.shift())
312
346
  out.push(...normalize(parts, ctx), op, ...imm)
313
347
  nodes.unshift(...out.splice(out.length - 1 - imm.length))
314
348
  }
@@ -408,18 +442,20 @@ const name = (node, list) => {
408
442
  }
409
443
 
410
444
  /**
411
- * Parse type definition: func, array, struct, or sub(type).
412
- * Handles recursive types and subtyping.
445
+ * Parse type definition: func, array, struct, cont, or sub(type).
446
+ * Handles recursive types, subtyping, and custom descriptor clauses (Phase 3).
413
447
  *
414
- * @param {Array} node - [definition] where definition is func/array/struct/sub
448
+ * @param {Array} node - [definition] where definition is func/array/struct/cont/sub
415
449
  * @param {Object} ctx - Compilation context
416
- * @returns {[string, any, string, string[]]} [kind, fields, subkind, supertypes]
450
+ * @returns {Array} [kind, fields, subkind, supertypes] with optional .desc property
417
451
  */
418
452
  const typedef = ([dfn], ctx) => {
419
- let subkind = 'subfinal', supertypes = [], compkind
453
+ let subkind = 'subfinal', supertypes = [], compkind, desc = []
420
454
  if (dfn[0] === 'sub') {
421
455
  subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
422
456
  dfn = (supertypes = dfn).pop() // last item is definition
457
+ // extract descriptor/describes from supertypes (custom descriptors, Phase 3)
458
+ supertypes = supertypes.filter(n => Array.isArray(n) && (n[0] === 'descriptor' || n[0] === 'describes') ? (desc.push(n), false) : true)
423
459
  }
424
460
 
425
461
  [compkind, ...dfn] = dfn // composite type kind
@@ -427,8 +463,11 @@ const typedef = ([dfn], ctx) => {
427
463
  if (compkind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
428
464
  else if (compkind === 'struct') dfn = fieldseq(dfn, 'field')
429
465
  else if (compkind === 'array') [dfn] = dfn
466
+ // cont type: (cont $ft) - continuation wrapping function type (stack switching, Phase 3)
430
467
 
431
- return [compkind, dfn, subkind, supertypes]
468
+ const result = [compkind, dfn, subkind, supertypes]
469
+ if (desc.length) result.desc = desc
470
+ return result
432
471
  }
433
472
 
434
473
 
@@ -450,40 +489,50 @@ const build = [
450
489
  // (func params result)
451
490
  // (array i8)
452
491
  // (struct ...fields)
453
- ([kind, fields, subkind, supertypes, rec], ctx) => {
492
+ // (cont $ft) - stack switching (Phase 3)
493
+ (node, ctx) => {
494
+ const [kind, fields, subkind, supertypes, rec] = node
454
495
  if (rec === true) return // ignore rec subtypes cept for 1st one
455
496
 
456
- let details
497
+ // descriptor/describes prefix bytes (custom descriptors, Phase 3)
498
+ const descPfx = (node.desc ?? []).flatMap(([clause, ref]) =>
499
+ [clause === 'descriptor' ? 0x4D : 0x4C, ...uleb(id(ref, ctx.type))])
500
+
501
+ // build comptype bytes without sub wrapper or descriptor prefix
502
+ const comptype = (k, f) => {
503
+ if (k === 'func') return [DEFTYPE.func, ...vec(f[0].map(t => reftype(t, ctx))), ...vec(f[1].map(t => reftype(t, ctx)))]
504
+ if (k === 'array') return [DEFTYPE.array, ...fieldtype(f, ctx)]
505
+ if (k === 'struct') return [DEFTYPE.struct, ...vec(f.map(t => fieldtype(t, ctx)))]
506
+ if (k === 'cont') return [DEFTYPE.cont, ...uleb(id(f[0] ?? f, ctx.type))]
507
+ return [DEFTYPE[k]]
508
+ }
509
+
457
510
  // (rec (sub ...)*)
458
511
  if (rec) {
459
- kind = 'rec'
460
- let [from, length] = rec, subtypes = Array.from({ length }, (_, i) => build[SECTION.type](ctx.type[from + i].slice(0, 4), ctx))
461
- details = vec(subtypes)
512
+ let [from, length] = rec
513
+ const subtypes = Array.from({ length }, (_, i) => {
514
+ const t = ctx.type[from + i], sub = t.slice(0, 4)
515
+ if (t.desc) sub.desc = t.desc
516
+ return build[SECTION.type](sub, ctx)
517
+ })
518
+ return [DEFTYPE.rec, ...vec(subtypes)]
462
519
  }
463
520
  // (sub final? sups* (type...))
464
521
  else if (subkind === 'sub' || supertypes?.length) {
465
- details = [...vec(supertypes.map(n => id(n, ctx.type))), ...build[SECTION.type]([kind, fields], ctx)]
466
- kind = subkind
467
- }
468
-
469
- else if (kind === 'func') {
470
- details = [...vec(fields[0].map(t => reftype(t, ctx))), ...vec(fields[1].map(t => reftype(t, ctx)))]
471
- }
472
- else if (kind === 'array') {
473
- details = fieldtype(fields, ctx)
474
- }
475
- else if (kind === 'struct') {
476
- details = vec(fields.map(t => fieldtype(t, ctx)))
522
+ return [DEFTYPE[subkind], ...vec(supertypes.map(n => id(n, ctx.type))), ...descPfx, ...comptype(kind, fields)]
477
523
  }
478
524
 
479
- return [DEFTYPE[kind], ...details]
525
+ return [...descPfx, ...comptype(kind, fields)]
480
526
  },
481
527
 
482
528
  // (import "math" "add" (func|table|global|memory|tag dfn?))
483
529
  ([mod, field, [kind, ...dfn]], ctx) => {
484
- let details
530
+ let details, kindByte = KIND[kind]
485
531
 
486
532
  if (kind === 'func') {
533
+ // exact func import: (func exact (type $t)) - custom descriptors (Phase 3)
534
+ const isExact = dfn[0] === 'exact' && dfn.shift()
535
+ if (isExact) kindByte = 0x20
487
536
  // we track imported funcs in func section to share namespace, and skip them on final build
488
537
  let [[, typeidx]] = dfn
489
538
  details = uleb(id(typeidx, ctx.type))
@@ -503,7 +552,7 @@ const build = [
503
552
  }
504
553
  else err(`Unknown kind ${kind}`)
505
554
 
506
- return ([...vec(mod), ...vec(field), KIND[kind], ...details])
555
+ return ([...vec(mod), ...vec(field), kindByte, ...details])
507
556
  },
508
557
 
509
558
  // (func $name? ...params result ...body)
@@ -667,6 +716,7 @@ const build = [
667
716
  // (data (i32.const 0) "\aa" "\bb"?)
668
717
  // (data (memory ref) (offset (i32.const 0)) "\aa" "\bb"?)
669
718
  // (data (global.get $x) "\aa" "\bb"?)
719
+ // (data (i8 1 2 3) ...) numeric values (WAT numeric values, Phase 2)
670
720
  (inits, ctx) => {
671
721
  let offset, memidx = 0
672
722
 
@@ -697,7 +747,7 @@ const build = [
697
747
  // passive: 1
698
748
  [1]
699
749
  ),
700
- ...vec(inits.flat())
750
+ ...vec(inits.flatMap(item => numdata(item) ?? [...item]))
701
751
  ])
702
752
  },
703
753
 
@@ -710,10 +760,15 @@ const build = [
710
760
 
711
761
  // Build reference type encoding (ref/refnull forms, not related to regtype which handles func types)
712
762
  // https://webassembly.github.io/gc/core/binary/types.html#reference-types
763
+ // (exact $T) support added for custom descriptors (Phase 3): encoded as 0x62 typeidx
713
764
  const reftype = (t, ctx) => (
714
765
  t[0] === 'ref' ?
715
766
  t[1] == 'null' ?
767
+ // (ref null (exact $T)) - exact nullable ref
768
+ Array.isArray(t[2]) && t[2][0] === 'exact' ? [TYPE.refnull, 0x62, ...uleb(id(t[2][1], ctx.type))] :
716
769
  TYPE[t[2]] ? [TYPE[t[2]]] : [TYPE.refnull, ...uleb(id(t[t.length - 1], ctx.type))] :
770
+ // (ref (exact $T)) - exact non-null ref
771
+ Array.isArray(t[1]) && t[1][0] === 'exact' ? [TYPE.ref, 0x62, ...uleb(id(t[1][1], ctx.type))] :
717
772
  [TYPE.ref, ...uleb(TYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))] :
718
773
  // abbrs
719
774
  [TYPE[t] ?? err(`Unknown type ${t}`)]
@@ -760,11 +815,11 @@ const IMM = {
760
815
  return [...uleb(count - 1), ...labels]
761
816
  },
762
817
  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)) },
818
+ ref_null: (n, c) => { let t = n.shift(); return Array.isArray(t) && t[0] === 'exact' ? [0x62, ...uleb(id(t[1], c.type))] : TYPE[t] ? [TYPE[t]] : uleb(id(t, c.type)) },
764
819
  memarg: (n, c, op) => memargEnc(n, op, isIdx(n[0]) && !isMemParam(n[0]) ? id(n.shift(), c.memory) : 0),
765
820
  opt_memory: (n, c) => uleb(id(isIdx(n[0]) ? n.shift() : 0, c.memory)),
766
821
  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()] },
822
+ reftype2: (n, c) => { let b = blockid(n.shift(), c.block), h1 = reftype(n.shift(), c), h2 = reftype(n.shift(), c), ht = h => h.length > 1 ? h.slice(1) : h; return [((h2[0] !== TYPE.ref) << 1) | (h1[0] !== TYPE.ref), ...uleb(b), ...ht(h1), ...ht(h2)] },
768
823
  v128const: (n) => {
769
824
  let [t, num] = n.shift().split('x'), bits = +t.slice(1), stride = bits >>> 3; num = +num
770
825
  if (t[0] === 'i') {
@@ -818,7 +873,45 @@ const IMM = {
818
873
  typeidx_typeidx: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.type))],
819
874
  dataidx_memoryidx: (n, c) => [...uleb(id(n.shift(), c.data)), ...uleb(id(n.shift(), c.memory))],
820
875
  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))]
876
+ tableidx_tableidx: (n, c) => [...uleb(id(n.shift(), c.table)), ...uleb(id(n.shift(), c.table))],
877
+
878
+ // stack switching handlers (Phase 3)
879
+ cont_bind: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.type))],
880
+ switch_cont: (n, c) => [...uleb(id(n.shift(), c.type)), ...uleb(id(n.shift(), c.tag))],
881
+ resume: (n, c) => {
882
+ const typeidx = uleb(id(n.shift(), c.type))
883
+ const handlers = []; let cnt = 0
884
+ while (n[0]?.[0] === 'on') {
885
+ const [, tag, label] = n.shift()
886
+ if (label === 'switch') handlers.push(0x01, ...uleb(id(tag, c.tag)))
887
+ else handlers.push(0x00, ...uleb(id(tag, c.tag)), ...uleb(blockid(label, c.block)))
888
+ cnt++
889
+ }
890
+ return [...typeidx, ...uleb(cnt), ...handlers]
891
+ },
892
+ resume_throw: (n, c) => {
893
+ const typeidx = uleb(id(n.shift(), c.type))
894
+ const exnidx = uleb(id(n.shift(), c.tag))
895
+ const handlers = []; let cnt = 0
896
+ while (n[0]?.[0] === 'on') {
897
+ const [, tag, label] = n.shift()
898
+ if (label === 'switch') handlers.push(0x01, ...uleb(id(tag, c.tag)))
899
+ else handlers.push(0x00, ...uleb(id(tag, c.tag)), ...uleb(blockid(label, c.block)))
900
+ cnt++
901
+ }
902
+ return [...typeidx, ...exnidx, ...uleb(cnt), ...handlers]
903
+ },
904
+ resume_throw_ref: (n, c) => {
905
+ const typeidx = uleb(id(n.shift(), c.type))
906
+ const handlers = []; let cnt = 0
907
+ while (n[0]?.[0] === 'on') {
908
+ const [, tag, label] = n.shift()
909
+ if (label === 'switch') handlers.push(0x01, ...uleb(id(tag, c.tag)))
910
+ else handlers.push(0x00, ...uleb(id(tag, c.tag)), ...uleb(blockid(label, c.block)))
911
+ cnt++
912
+ }
913
+ return [...typeidx, ...uleb(cnt), ...handlers]
914
+ }
822
915
  };
823
916
 
824
917
  // per-op imm handlers
@@ -851,7 +944,7 @@ const instr = (nodes, ctx) => {
851
944
 
852
945
  // Array = unknown instruction passed through from normalize
853
946
  if (Array.isArray(op)) {
854
- op.i != null && (err.i = op.i)
947
+ op.loc != null && (err.loc = op.loc)
855
948
  err(`Unknown instruction ${op[0]}`)
856
949
  }
857
950
 
@@ -929,23 +1022,46 @@ const align = (op) => {
929
1022
  return Math.log2(m ? (m[2] === 'x' ? 8 : m[1] / 8) : +group / 8)
930
1023
  }
931
1024
 
1025
+ // Convert WAT numeric data (i8/i16/i32/i64/f32/f64 lists) to bytes (Phase 2: WAT numeric values)
1026
+ const numdata = (item) => {
1027
+ if (!Array.isArray(item)) return null
1028
+ const [t, ...vs] = item
1029
+ if (t !== 'i8' && t !== 'i16' && t !== 'i32' && t !== 'i64' && t !== 'f32' && t !== 'f64') return null
1030
+ const out = [], dv = new DataView(new ArrayBuffer(8))
1031
+ for (const v of vs) {
1032
+ if (t === 'i8') out.push((i32.parse(v) & 0xFF + 0x100) & 0xFF)
1033
+ else if (t === 'i16') (dv.setInt16(0, i32.parse(v), true), out.push(...new Uint8Array(dv.buffer, 0, 2)))
1034
+ else if (t === 'i32') (dv.setInt32(0, i32.parse(v), true), out.push(...new Uint8Array(dv.buffer, 0, 4)))
1035
+ else if (t === 'i64') (dv.setBigInt64(0, BigInt(v), true), out.push(...new Uint8Array(dv.buffer, 0, 8)))
1036
+ else if (t === 'f32') out.push(...encode.f32(v))
1037
+ else if (t === 'f64') out.push(...encode.f64(v))
1038
+ }
1039
+ return out
1040
+ }
1041
+
932
1042
  // build limits sequence (consuming)
933
1043
  // Memory64: i64 index type uses flags 0x04-0x07 (bit 2 = is_64)
1044
+ // Custom page sizes (Phase 3): (pagesize N) attr adds bit 3, appends log2(pagesize) as u32
934
1045
  const limits = (node) => {
935
1046
  const is64 = node[0] === 'i64' && node.shift()
936
1047
  const shared = node[node.length - 1] === 'shared' && node.pop()
1048
+ // custom page size: (pagesize N) sub-node
1049
+ const psIdx = node.findIndex(n => Array.isArray(n) && n[0] === 'pagesize')
1050
+ let psLog2 = -1
1051
+ if (psIdx >= 0) psLog2 = Math.log2(+node.splice(psIdx, 1)[0][1])
937
1052
  const hasMax = !isNaN(parseInt(node[1]))
938
- const flag = (is64 ? 4 : 0) | (shared ? 2 : 0) | (hasMax ? 1 : 0)
1053
+ const flag = (psLog2 >= 0 ? 8 : 0) | (is64 ? 4 : 0) | (shared ? 2 : 0) | (hasMax ? 1 : 0)
939
1054
  // For i64, parse as unsigned BigInt (limits are always unsigned)
940
1055
  const parse = is64 ? v => {
941
1056
  if (typeof v === 'bigint') return v
942
1057
  const str = typeof v === 'string' ? v.replaceAll('_', '') : String(v)
943
1058
  return BigInt(str)
944
1059
  } : parseUint
1060
+ const ps = psLog2 >= 0 ? uleb(psLog2) : []
945
1061
 
946
1062
  return hasMax
947
- ? [flag, ...uleb(parse(node.shift())), ...uleb(parse(node.shift()))]
948
- : [flag, ...uleb(parse(node.shift()))]
1063
+ ? [flag, ...uleb(parse(node.shift())), ...uleb(parse(node.shift())), ...ps]
1064
+ : [flag, ...uleb(parse(node.shift())), ...ps]
949
1065
  }
950
1066
 
951
1067
  // check if node is valid int in a range
package/src/const.js CHANGED
@@ -48,15 +48,21 @@ export const INSTR = [
48
48
  'i32.extend8_s', 'i32.extend16_s', 'i64.extend8_s', 'i64.extend16_s', 'i64.extend32_s', , , , , , , , , , , ,
49
49
  // 0xd0-0xd6: reference
50
50
  'ref.null ref_null', 'ref.is_null', 'ref.func funcidx', 'ref.eq', 'ref.as_non_null', 'br_on_null labelidx', 'br_on_non_null labelidx',
51
- // 0xd7-0xfa: padding to 0xfb (36 empty slots)
52
- , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
51
+ // 0xd7-0xdf: padding (9 empty slots)
52
+ , , , , , , , , ,
53
+ // 0xe0-0xe6: stack switching (Phase 3)
54
+ 'cont.new typeidx', 'cont.bind cont_bind', 'suspend tagidx', 'resume resume', 'resume_throw resume_throw', 'resume_throw_ref resume_throw_ref', 'switch switch_cont',
55
+ // 0xe7-0xfa: padding (20 empty slots)
56
+ , , , , , , , , , , , , , , , , , , , ,
53
57
  // 0xfb: GC instructions (nested array for multi-byte opcodes)
54
58
  [
55
59
  'struct.new typeidx', 'struct.new_default typeidx', 'struct.get typeidx_field', 'struct.get_s typeidx_field', 'struct.get_u typeidx_field', 'struct.set typeidx_field',
56
60
  'array.new typeidx', 'array.new_default typeidx', 'array.new_fixed typeidx_multi', 'array.new_data typeidx_dataidx', 'array.new_elem typeidx_elemidx',
57
61
  'array.get typeidx', 'array.get_s typeidx', 'array.get_u typeidx', 'array.set typeidx', 'array.len', 'array.fill typeidx', 'array.copy typeidx_typeidx',
58
62
  'array.init_data typeidx_dataidx', 'array.init_elem typeidx_elemidx', 'ref.test reftype', '', 'ref.cast reftype', '', 'br_on_cast reftype2', 'br_on_cast_fail reftype2',
59
- 'any.convert_extern', 'extern.convert_any', 'ref.i31', 'i31.get_s', 'i31.get_u'
63
+ 'any.convert_extern', 'extern.convert_any', 'ref.i31', 'i31.get_s', 'i31.get_u',
64
+ // custom descriptors (Phase 3): 0xFB 0x20-0x26
65
+ , 'struct.new_desc typeidx', 'struct.new_default_desc typeidx', 'ref.get_desc typeidx', 'ref.cast_desc_eq reftype', , 'br_on_cast_desc_eq reftype2', 'br_on_cast_desc_eq_fail reftype2'
60
66
  ],
61
67
 
62
68
  // 0xfc: Bulk memory/table operations (nested array)
@@ -146,9 +152,11 @@ export const TYPE = {
146
152
  i8: 0x78, i16: 0x77, i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, v128: 0x7B,
147
153
  // Heap types
148
154
  exn: 0x69, noexn: 0x74, nofunc: 0x73, noextern: 0x72, none: 0x71, func: 0x70, extern: 0x6F, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A,
155
+ cont: 0x68, nocont: 0x75, // stack switching (Phase 3)
149
156
  // Reference type abbreviations (absheaptype abbrs)
150
157
  nullfuncref: 0x73, nullexternref: 0x72, nullexnref: 0x74, nullref: 0x71,
151
158
  funcref: 0x70, externref: 0x6F, exnref: 0x69, anyref: 0x6E, eqref: 0x6D, i31ref: 0x6C, structref: 0x6B, arrayref: 0x6A,
159
+ contref: 0x68, nocontref: 0x75, // stack switching abbreviations
152
160
  // ref, refnull
153
161
  ref: 0x64, // -0x1c
154
162
  refnull: 0x63, // -0x1d
@@ -157,7 +165,7 @@ export const TYPE = {
157
165
  }
158
166
 
159
167
  // Type definition codes (different from heap types - func is 0x60 not 0x70)
160
- export const DEFTYPE = { func: 0x60, struct: 0x5F, array: 0x5E, sub: 0x50, subfinal: 0x4F, rec: 0x4E }
168
+ export const DEFTYPE = { func: 0x60, struct: 0x5F, array: 0x5E, cont: 0x5D, sub: 0x50, subfinal: 0x4F, rec: 0x4E }
161
169
 
162
170
  // Import/export kind codes
163
171
  export const KIND = { func: 0, table: 1, memory: 2, global: 3, tag: 4 }
package/src/optimize.js CHANGED
@@ -178,7 +178,9 @@ const treeshake = (ast) => {
178
178
 
179
179
  // Mark start function as used
180
180
  for (const start of starts) {
181
- const ref = start[1]
181
+ let ref = start[1]
182
+ // Convert numeric string refs to numbers
183
+ if (typeof ref === 'string' && ref[0] !== '$') ref = +ref
182
184
  if (funcs.has(ref)) funcs.get(ref).used = true
183
185
  }
184
186
 
@@ -970,7 +972,18 @@ const inline = (ast) => {
970
972
 
971
973
  // Only inline: no locals, <= 2 params, single expression body, not exported
972
974
  if (params && !hasLocals && !hasExport && params.length <= 2 && body.length === 1) {
973
- inlinable.set(name, { body: body[0], params })
975
+ // Check if function mutates any of its params (local.set/tee on param)
976
+ const paramNames = new Set(params.map(p => p.name))
977
+ let mutatesParam = false
978
+ walk(body[0], (n) => {
979
+ if (!Array.isArray(n)) return
980
+ if ((n[0] === 'local.set' || n[0] === 'local.tee') && paramNames.has(n[1])) {
981
+ mutatesParam = true
982
+ }
983
+ })
984
+ if (!mutatesParam) {
985
+ inlinable.set(name, { body: body[0], params })
986
+ }
974
987
  }
975
988
  }
976
989
 
package/src/parse.js CHANGED
@@ -2,7 +2,7 @@ import { err } from "./util.js"
2
2
 
3
3
  /**
4
4
  * Parses a wasm text string and constructs a nested array structure (AST).
5
- * Each array node has `.i` property with source offset for error reporting.
5
+ * Each array node has `.loc` property with source offset for error reporting.
6
6
  *
7
7
  * @param {string} str - The input string with WAT code to parse.
8
8
  * @returns {Array} An array representing the nested syntax tree (AST).
@@ -13,7 +13,7 @@ export default (str) => {
13
13
  const commit = () => buf && (level.push(buf), buf = '')
14
14
 
15
15
  const parseLevel = (pos) => {
16
- level.i = pos // store start position for error reporting
16
+ level.loc = pos // store start position for error reporting
17
17
  for (let c, root, p; i < str.length;) {
18
18
  c = str.charCodeAt(i)
19
19
 
package/src/util.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Throws an error with optional source position.
3
- * Uses err.src for source and err.i for default position.
4
- * If pos provided or err.i set, appends "at line:col".
3
+ * Uses err.src for source and err.loc for default position.
4
+ * If pos provided or err.loc set, appends "at line:col".
5
5
  *
6
6
  * @param {string} text - Error message
7
- * @param {number} [pos] - Byte offset in source (defaults to err.i)
7
+ * @param {number} [pos] - Byte offset in source (defaults to err.loc)
8
8
  * @throws {Error}
9
9
  */
10
- export const err = (text, pos=err.i) => {
10
+ export const err = (text, pos=err.loc) => {
11
11
  if (pos != null && err.src) {
12
12
  let line = 1, col = 1
13
13
  for (let i = 0; i < pos && i < err.src.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"AA+BA;;;;;GAKG;AACH,uCAHW,MAAM,QAAM,GACV,UAAU,CAmLtB"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"AA+BA;;;;;GAKG;AACH,uCAHW,MAAM,QAAM,GACV,UAAU,CAqNtB"}
@@ -40,6 +40,8 @@ export namespace TYPE {
40
40
  export let i31: number;
41
41
  export let struct: number;
42
42
  export let array: number;
43
+ export let cont: number;
44
+ export let nocont: number;
43
45
  export let nullfuncref: number;
44
46
  export let nullexternref: number;
45
47
  export let nullexnref: number;
@@ -52,6 +54,8 @@ export namespace TYPE {
52
54
  export let i31ref: number;
53
55
  export let structref: number;
54
56
  export let arrayref: number;
57
+ export let contref: number;
58
+ export let nocontref: number;
55
59
  export let ref: number;
56
60
  export let refnull: number;
57
61
  export let sub: number;
@@ -65,6 +69,8 @@ export namespace DEFTYPE {
65
69
  export { struct_1 as struct };
66
70
  let array_1: number;
67
71
  export { array_1 as array };
72
+ let cont_1: number;
73
+ export { cont_1 as cont };
68
74
  let sub_1: number;
69
75
  export { sub_1 as sub };
70
76
  let subfinal_1: number;
@@ -1 +1 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,0CAqIC"}
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,0CA2IC"}
@@ -1 +1 @@
1
- {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.js"],"names":[],"mappings":"AAonCA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAwB/B;AAzjCD;;;;;GAKG;AACH,6CA4LC;AAyHD;;;;GAIG;AACH,wCAmCC;AAwQD;;;;GAIG;AACH,4CAuBC;AA+CD;;;;;GAKG;AACH,8CAqDC;AApTD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CA6DC;AAID;;;;GAIG;AACH,0CA0EC;AAiJD;;;;;GAKG;AACH,6CAuEC;AAID;;;;GAIG;AACH,0CA6EC;AAt9BD;;;;GAIG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAe/B"}
1
+ {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.js"],"names":[],"mappings":"AAioCA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAwB/B;AAtkCD;;;;;GAKG;AACH,6CA8LC;AAyHD;;;;GAIG;AACH,wCAmCC;AAwQD;;;;GAIG;AACH,4CAuBC;AA+CD;;;;;GAKG;AACH,8CAqDC;AApTD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CA6DC;AAID;;;;GAIG;AACH,0CA0EC;AAiJD;;;;;GAKG;AACH,6CAuEC;AAID;;;;GAIG;AACH,0CAwFC;AAn+BD;;;;GAIG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAe/B"}
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Throws an error with optional source position.
3
- * Uses err.src for source and err.i for default position.
4
- * If pos provided or err.i set, appends "at line:col".
3
+ * Uses err.src for source and err.loc for default position.
4
+ * If pos provided or err.loc set, appends "at line:col".
5
5
  *
6
6
  * @param {string} text - Error message
7
- * @param {number} [pos] - Byte offset in source (defaults to err.i)
7
+ * @param {number} [pos] - Byte offset in source (defaults to err.loc)
8
8
  * @throws {Error}
9
9
  */
10
10
  export const err: (text: string, pos?: number) => never;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,mBAAoB,MAJT,MAIa,EAAE,MAHf,MAGwB,WAUlC;AAOM,2CAAkF;AAEzF,8DAA8D;AAC9D,2BAAiD;AAEjD,6DAA6D;AAC7D,2BAAiD;AAe1C,uBAHI,MAAM,GACJ,MAAM,EAAE,CA8BpB;AAUM,4BAHI,MAAM,GACJ,MAAM,CAE6C"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,mBAAoB,MAJT,MAIa,EAAE,MAHf,MAG0B,WAUpC;AAOM,2CAAkF;AAEzF,8DAA8D;AAC9D,2BAAiD;AAEjD,6DAA6D;AAC7D,2BAAiD;AAe1C,uBAHI,MAAM,GACJ,MAAM,EAAE,CA8BpB;AAUM,4BAHI,MAAM,GACJ,MAAM,CAE6C"}