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/dist/watr.js +186 -51
- package/dist/watr.min.js +5 -5
- package/package.json +1 -1
- package/readme.md +7 -12
- package/src/compile.js +165 -49
- package/src/const.js +12 -4
- package/src/optimize.js +15 -2
- package/src/parse.js +2 -2
- package/src/util.js +4 -4
- package/types/src/compile.d.ts.map +1 -1
- package/types/src/const.d.ts +6 -0
- package/types/src/const.d.ts.map +1 -1
- package/types/src/optimize.d.ts.map +1 -1
- package/types/src/util.d.ts +3 -3
- package/types/src/util.d.ts.map +1 -1
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 .
|
|
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 .
|
|
29
|
-
(result = node.map(cleanup).filter(n => n != null), 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.
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
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 [
|
|
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),
|
|
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.
|
|
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
|
|
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.
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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 `.
|
|
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.
|
|
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.
|
|
4
|
-
* If pos provided or err.
|
|
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.
|
|
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.
|
|
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,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"AA+BA;;;;;GAKG;AACH,uCAHW,MAAM,QAAM,GACV,UAAU,CAqNtB"}
|
package/types/src/const.d.ts
CHANGED
|
@@ -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;
|
package/types/src/const.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,
|
|
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":"
|
|
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"}
|
package/types/src/util.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Throws an error with optional source position.
|
|
3
|
-
* Uses err.src for source and err.
|
|
4
|
-
* If pos provided or err.
|
|
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.
|
|
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;
|
package/types/src/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,mBAAoB,MAJT,MAIa,EAAE,MAHf,
|
|
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"}
|