watr 5.0.0 → 5.1.1
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 +825 -907
- package/dist/watr.min.js +5 -6
- package/dist/watr.wasm +0 -0
- package/package.json +3 -3
- package/readme.md +19 -5
- package/src/compile.js +236 -110
- package/src/const.js +125 -131
- package/src/encode.js +28 -3
- package/src/optimize.js +2909 -421
- package/src/print.js +8 -3
- package/src/util.js +33 -9
- package/types/src/compile.d.ts +1 -0
- package/types/src/compile.d.ts.map +1 -1
- package/types/src/const.d.ts +2 -1
- package/types/src/const.d.ts.map +1 -1
- package/types/src/encode.d.ts +5 -26
- package/types/src/encode.d.ts.map +1 -1
- package/types/src/optimize.d.ts +41 -33
- package/types/src/optimize.d.ts.map +1 -1
- package/types/src/print.d.ts.map +1 -1
- package/types/src/util.d.ts +5 -10
- package/types/src/util.d.ts.map +1 -1
package/src/compile.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb, i32, i64 } from './encode.js'
|
|
3
|
-
import { SECTION, TYPE, KIND,
|
|
3
|
+
import { SECTION, TYPE, KIND, OPCODE, IMM, DEFTYPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
-
import { err, unescape, str } from './util.js'
|
|
5
|
+
import { err, unescape, str, setErrLoc, setErrSrc, getErrLoc, getErrSrc } from './util.js'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -17,8 +17,16 @@ import { err, unescape, str } from './util.js'
|
|
|
17
17
|
// load past parse — strip them. Predicate stays separate from `cleanup` so
|
|
18
18
|
// neither has to invent a "drop me" sentinel and risk colliding with a
|
|
19
19
|
// legitimate `null`/`undefined` immediate in the AST.
|
|
20
|
+
// A block-comment token is `(;…` (parse.js: `buf = '(' + ';'`) — checking n[1]===';'
|
|
21
|
+
// ALONE (without also requiring n[0]==='(') also matches an ordinary quoted STRING
|
|
22
|
+
// whose CONTENT happens to start with ';' (a string token always starts with n[0]==='"',
|
|
23
|
+
// so n[1] is its first content byte). A data-segment string is exactly this shape once
|
|
24
|
+
// packData splits a segment at a zero run and the surviving byte run starts with ';'
|
|
25
|
+
// (e.g. a WAT-text stdlib template's own comment, interned as static data) — cleanup()
|
|
26
|
+
// then silently drops that segment's whole content as if it were a comment. Mirrors the
|
|
27
|
+
// already-correct guard in optimize.js's own comment-strip (`c[0]==='(' && c[1]===';'`).
|
|
20
28
|
const isDroppable = (n) =>
|
|
21
|
-
(typeof n === 'string' && (n[0] === ';' || n[1] === ';')) ||
|
|
29
|
+
(typeof n === 'string' && (n[0] === ';' || (n[0] === '(' && n[1] === ';'))) ||
|
|
22
30
|
(Array.isArray(n) && n[0]?.[0] === '@' && n[0] !== '@custom' && !n[0]?.startsWith?.('@metadata.code.'))
|
|
23
31
|
|
|
24
32
|
const cleanup = (node, result) => {
|
|
@@ -42,9 +50,9 @@ const cleanup = (node, result) => {
|
|
|
42
50
|
// reason to exist (the optimizer's size-revert guard can't afford to build bytes it discards).
|
|
43
51
|
function assemble(nodes, sizeOnly) {
|
|
44
52
|
// normalize to (module ...) form
|
|
45
|
-
if (typeof nodes === 'string')
|
|
46
|
-
else
|
|
47
|
-
|
|
53
|
+
if (typeof nodes === 'string') setErrSrc(nodes), nodes = parse(nodes) || []
|
|
54
|
+
else setErrSrc('') // clear source if AST passed directly
|
|
55
|
+
setErrLoc(0)
|
|
48
56
|
|
|
49
57
|
nodes = isDroppable(nodes) ? [] : (cleanup(nodes) ?? [])
|
|
50
58
|
|
|
@@ -84,7 +92,7 @@ function assemble(nodes, sizeOnly) {
|
|
|
84
92
|
nodes.slice(idx).filter((n) => {
|
|
85
93
|
if (!Array.isArray(n)) {
|
|
86
94
|
// find token as standalone word (not substring of another token)
|
|
87
|
-
let pos =
|
|
95
|
+
let pos = getErrLoc(), src = getErrSrc(), c
|
|
88
96
|
while ((pos = src.indexOf(n, pos)) >= 0) {
|
|
89
97
|
c = src.charCodeAt(pos - 1)
|
|
90
98
|
// check not preceded by word char or $
|
|
@@ -94,11 +102,11 @@ function assemble(nodes, sizeOnly) {
|
|
|
94
102
|
if (c > 47 && c < 58 || c > 64 && c < 91 || c > 96 && c < 123 || c === 95) { pos++; continue }
|
|
95
103
|
break
|
|
96
104
|
}
|
|
97
|
-
if (pos >= 0)
|
|
105
|
+
if (pos >= 0) setErrLoc(pos)
|
|
98
106
|
err(`Unexpected token ${n}`)
|
|
99
107
|
}
|
|
100
108
|
let [kind, ...node] = n
|
|
101
|
-
|
|
109
|
+
setErrLoc(n.loc) // track position for errors
|
|
102
110
|
// (@custom "name" placement? data) - custom section support
|
|
103
111
|
if (kind === '@custom') {
|
|
104
112
|
ctx.custom.push(node)
|
|
@@ -141,7 +149,7 @@ function assemble(nodes, sizeOnly) {
|
|
|
141
149
|
// prepare/normalize nodes
|
|
142
150
|
.forEach((n) => {
|
|
143
151
|
let [kind, ...node] = n
|
|
144
|
-
|
|
152
|
+
setErrLoc(n.loc) // track position for errors
|
|
145
153
|
let imported // if node needs to be imported
|
|
146
154
|
|
|
147
155
|
// import abbr
|
|
@@ -257,7 +265,15 @@ function assemble(nodes, sizeOnly) {
|
|
|
257
265
|
// inline bin(code) so the per-function item bytes survive — with ctx.codeSizePrefix
|
|
258
266
|
// they let us lift code-metadata positions to absolute binary offsets below
|
|
259
267
|
const codeItems = ctx.code.filter(Boolean).map(item => build[SECTION.code](item, ctx)).filter(Boolean)
|
|
260
|
-
|
|
268
|
+
// fused vec(vec(items)): count + items appended once, then the section frame —
|
|
269
|
+
// the generic path copies the multi-MB stream twice more
|
|
270
|
+
let codeSection = []
|
|
271
|
+
if (codeItems.length) {
|
|
272
|
+
const inner = uleb(codeItems.length)
|
|
273
|
+
for (const it of codeItems) for (let i = 0; i < it.length; i++) inner.push(it[i])
|
|
274
|
+
codeSection = [SECTION.code, ...uleb(inner.length)]
|
|
275
|
+
for (let i = 0; i < inner.length; i++) codeSection.push(inner[i])
|
|
276
|
+
}
|
|
261
277
|
const metaSection = binMeta()
|
|
262
278
|
const dataSection = bin(SECTION.data)
|
|
263
279
|
const stringsSection = ctx.strings.length ? [SECTION.strings, ...vec([0x00, ...vec(ctx.strings.map(s => vec(s)))])] : []
|
|
@@ -282,12 +298,14 @@ function assemble(nodes, sizeOnly) {
|
|
|
282
298
|
dataSection
|
|
283
299
|
]
|
|
284
300
|
|
|
285
|
-
// build final binary
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
])
|
|
301
|
+
// build final binary — sections are flat byte arrays; copy them into place
|
|
302
|
+
// instead of flattening a multi-MB nested array through Uint8Array.from
|
|
303
|
+
let total = 8
|
|
304
|
+
for (const sec of sections) total += sec.length
|
|
305
|
+
const wasm = new Uint8Array(total)
|
|
306
|
+
wasm.set([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])
|
|
307
|
+
let off = 8
|
|
308
|
+
for (const sec of sections) { if (sec.length) { wasm.set(sec, off); off += sec.length } }
|
|
291
309
|
|
|
292
310
|
// Lift any (@metadata.code.*) annotations to absolute binary offsets and hang
|
|
293
311
|
// them off the result, so a source-map emitter can correlate wasm back to
|
|
@@ -360,42 +378,65 @@ const isMemParam = n => n?.[0] === 'a' || n?.[0] === 'o'
|
|
|
360
378
|
* @param {Object} ctx - Compilation context with type info
|
|
361
379
|
* @returns {Array} Flattened instruction sequence
|
|
362
380
|
*/
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
381
|
+
// bare-token classification — one lookup replaces a six-way string-scan chain
|
|
382
|
+
// (built lazily over OPCODE's key set; unknown tokens fall through untouched)
|
|
383
|
+
let NCLS = null
|
|
384
|
+
const nclsOf = (op) => {
|
|
385
|
+
if (!NCLS) {
|
|
386
|
+
NCLS = Object.create(null)
|
|
387
|
+
for (const k in OPCODE) {
|
|
388
|
+
if (k === 'block' || k === 'if' || k === 'loop') NCLS[k] = 1
|
|
389
|
+
else if (k === 'else' || k === 'end') NCLS[k] = 2
|
|
390
|
+
else if (k === 'select') NCLS[k] = 3
|
|
391
|
+
else if (k.endsWith('call_indirect')) NCLS[k] = 4
|
|
392
|
+
else if (k === 'table.init') NCLS[k] = 5
|
|
393
|
+
else if (k === 'table.copy' || k === 'memory.copy') NCLS[k] = 6
|
|
394
|
+
else if (k.startsWith('table.')) NCLS[k] = 7
|
|
395
|
+
else if (k === 'memory.init') NCLS[k] = 8
|
|
396
|
+
else if (k === 'data.drop' || k === 'array.new_data' || k === 'array.init_data') NCLS[k] = 9
|
|
397
|
+
else if (k.startsWith('memory.') || k.endsWith('load') || k.endsWith('store')) NCLS[k] = 10
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return NCLS[op]
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function normalize(nodes, ctx, out = [], owned = false) {
|
|
404
|
+
if (!owned) nodes = [...nodes]
|
|
366
405
|
while (nodes.length) {
|
|
367
406
|
let node = nodes.shift()
|
|
368
407
|
if (typeof node === 'string') {
|
|
369
408
|
out.push(node)
|
|
370
|
-
|
|
409
|
+
const cls = nclsOf(node)
|
|
410
|
+
if (cls === undefined) continue
|
|
411
|
+
if (cls === 1) {
|
|
371
412
|
if (isId(nodes[0])) out.push(nodes.shift())
|
|
372
413
|
out.push(blocktype(nodes, ctx))
|
|
373
414
|
}
|
|
374
|
-
else if (
|
|
415
|
+
else if (cls === 2) {
|
|
375
416
|
if (isId(nodes[0])) nodes.shift()
|
|
376
417
|
}
|
|
377
|
-
else if (
|
|
378
|
-
else if (
|
|
418
|
+
else if (cls === 3) out.push(paramres(nodes)[1])
|
|
419
|
+
else if (cls === 4) {
|
|
379
420
|
let tableidx = isIdx(nodes[0]) ? nodes.shift() : 0, [idx, param, result] = typeuse(nodes, ctx)
|
|
380
421
|
out.push(tableidx, ['type', idx ?? regtype(param, result, ctx)])
|
|
381
422
|
}
|
|
382
|
-
else if (
|
|
383
|
-
else if (
|
|
384
|
-
else if (
|
|
385
|
-
else if (
|
|
423
|
+
else if (cls === 5) out.push(isIdx(nodes[1]) ? nodes.shift() : 0, nodes.shift())
|
|
424
|
+
else if (cls === 6) out.push(isIdx(nodes[0]) ? nodes.shift() : 0, isIdx(nodes[0]) ? nodes.shift() : 0)
|
|
425
|
+
else if (cls === 7) out.push(isIdx(nodes[0]) ? nodes.shift() : 0)
|
|
426
|
+
else if (cls === 8) {
|
|
386
427
|
out.push(...(isIdx(nodes[1]) ? [nodes.shift(), nodes.shift()].reverse() : [nodes.shift(), 0]))
|
|
387
428
|
ctx.datacount && (ctx.datacount[0] = true)
|
|
388
429
|
}
|
|
389
|
-
else if (
|
|
430
|
+
else if (cls === 9) {
|
|
390
431
|
node === 'data.drop' && out.push(nodes.shift())
|
|
391
432
|
ctx.datacount && (ctx.datacount[0] = true)
|
|
392
433
|
}
|
|
393
434
|
// memory.* instructions and load/store with optional memory index
|
|
394
|
-
else if (
|
|
435
|
+
else if (isIdx(nodes[0])) out.push(nodes.shift())
|
|
395
436
|
}
|
|
396
437
|
else if (Array.isArray(node)) {
|
|
397
438
|
let op = node[0]
|
|
398
|
-
node.loc != null && (
|
|
439
|
+
node.loc != null && setErrLoc(node.loc) // track position for errors
|
|
399
440
|
|
|
400
441
|
// code metadata annotations - pass through as marker with metadata type and data
|
|
401
442
|
// (@metadata.code.<type> data:str)
|
|
@@ -405,23 +446,29 @@ function normalize(nodes, ctx) {
|
|
|
405
446
|
continue
|
|
406
447
|
}
|
|
407
448
|
|
|
408
|
-
// Check if node is a valid instruction (string with
|
|
409
|
-
if (typeof op !== 'string' ||
|
|
449
|
+
// Check if node is a valid instruction (string with a known opcode)
|
|
450
|
+
if (typeof op !== 'string' || typeof OPCODE[op] !== 'number') { out.push(node); continue }
|
|
410
451
|
const parts = node.slice(1)
|
|
411
452
|
if (op === 'block' || op === 'loop') {
|
|
412
453
|
out.push(op)
|
|
413
454
|
if (isId(parts[0])) out.push(parts.shift())
|
|
414
|
-
out.push(blocktype(parts, ctx)
|
|
455
|
+
out.push(blocktype(parts, ctx))
|
|
456
|
+
normalize(parts, ctx, out, true)
|
|
457
|
+
out.push('end')
|
|
415
458
|
}
|
|
416
459
|
else if (op === 'if') {
|
|
460
|
+
// then/else normalize BEFORE the condition but EMIT after it — the temp
|
|
461
|
+
// arrays preserve the original type-registration order exactly
|
|
417
462
|
let then = [], els = []
|
|
418
|
-
if (parts.at(-1)?.[0] === 'else') els = normalize(parts.pop().slice(1), ctx)
|
|
419
|
-
if (parts.at(-1)?.[0] === 'then') then = normalize(parts.pop().slice(1), ctx)
|
|
463
|
+
if (parts.at(-1)?.[0] === 'else') els = normalize(parts.pop().slice(1), ctx, [], true)
|
|
464
|
+
if (parts.at(-1)?.[0] === 'then') then = normalize(parts.pop().slice(1), ctx, [], true)
|
|
420
465
|
let immed = [op]
|
|
421
466
|
if (isId(parts[0])) immed.push(parts.shift())
|
|
422
467
|
immed.push(blocktype(parts, ctx))
|
|
423
|
-
|
|
424
|
-
|
|
468
|
+
normalize(parts, ctx, out, true)
|
|
469
|
+
for (let i = 0; i < immed.length; i++) out.push(immed[i])
|
|
470
|
+
for (let i = 0; i < then.length; i++) out.push(then[i])
|
|
471
|
+
if (els.length) { out.push('else'); for (let i = 0; i < els.length; i++) out.push(els[i]) }
|
|
425
472
|
out.push('end')
|
|
426
473
|
}
|
|
427
474
|
else if (op === 'try_table') {
|
|
@@ -432,20 +479,23 @@ function normalize(nodes, ctx) {
|
|
|
432
479
|
while (parts[0]?.[0] === 'catch' || parts[0]?.[0] === 'catch_ref' || parts[0]?.[0] === 'catch_all' || parts[0]?.[0] === 'catch_all_ref') {
|
|
433
480
|
out.push(parts.shift())
|
|
434
481
|
}
|
|
435
|
-
|
|
482
|
+
normalize(parts, ctx, out, true)
|
|
483
|
+
out.push('end')
|
|
436
484
|
}
|
|
437
485
|
else if (op === 'ref.test' || op === 'ref.cast') {
|
|
438
486
|
const type = parts[0]
|
|
439
487
|
const isNullable = !Array.isArray(type) || type[1] === 'null' || type[0] !== 'ref'
|
|
440
488
|
if (isNullable) op += '_null'
|
|
441
|
-
|
|
489
|
+
normalize(parts.slice(1), ctx, out, true)
|
|
490
|
+
out.push(op, type)
|
|
442
491
|
nodes.unshift(...out.splice(out.length - 2))
|
|
443
492
|
}
|
|
444
493
|
else {
|
|
445
494
|
const imm = []
|
|
446
495
|
// Collect immediate operands (non-arrays or special forms like type/param/result/ref)
|
|
447
496
|
while (parts.length && (!Array.isArray(parts[0]) || parts[0].valueOf !== Array.prototype.valueOf || 'type,param,result,ref,exact,on'.includes(parts[0][0]))) imm.push(parts.shift())
|
|
448
|
-
|
|
497
|
+
normalize(parts, ctx, out, true)
|
|
498
|
+
out.push(op, ...imm)
|
|
449
499
|
nodes.unshift(...out.splice(out.length - 1 - imm.length))
|
|
450
500
|
}
|
|
451
501
|
} else out.push(node)
|
|
@@ -819,10 +869,10 @@ const build = [
|
|
|
819
869
|
ctx.local = ctx.block = ctx.meta = null
|
|
820
870
|
|
|
821
871
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
822
|
-
const item =
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
872
|
+
const item = uleb(locals.length + bytes.length)
|
|
873
|
+
;(ctx.codeSizePrefix ??= [])[codeIdx] = item.length // = vec prefix width
|
|
874
|
+
for (let i = 0; i < locals.length; i++) item.push(locals[i])
|
|
875
|
+
for (let i = 0; i < bytes.length; i++) item.push(bytes[i])
|
|
826
876
|
return item
|
|
827
877
|
},
|
|
828
878
|
|
|
@@ -894,15 +944,19 @@ const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[
|
|
|
894
944
|
|
|
895
945
|
|
|
896
946
|
|
|
897
|
-
//
|
|
898
|
-
|
|
899
|
-
|
|
947
|
+
// Immediate encoders, keyed by immediate type (IMM in const.js maps op name → immediate type)
|
|
948
|
+
// leb into `out` (returning undefined — the write-mode sentinel) or a fresh array
|
|
949
|
+
const wleb = (v, out) => { if (out) { uleb(v, out); return } return uleb(v) }
|
|
950
|
+
|
|
951
|
+
const HANDLER = {
|
|
900
952
|
reversed: (n, c) => { let t = n.shift(), e = n.shift(); return [...uleb(id(e, c.elem)), ...uleb(id(t, c.table))] },
|
|
901
|
-
block: (n, c) => {
|
|
953
|
+
block: (n, c, op, out) => {
|
|
902
954
|
c.block.push(1)
|
|
903
955
|
isId(n[0]) && (c.block[n.shift()] = c.block.length)
|
|
904
956
|
let t = n.shift()
|
|
905
|
-
|
|
957
|
+
const b = !t ? [TYPE.void] : t[0] === 'result' ? reftype(t[1], c) : uleb(id(t[1], c.type))
|
|
958
|
+
if (out) { for (let i = 0; i < b.length; i++) out.push(b[i]); return }
|
|
959
|
+
return b
|
|
906
960
|
},
|
|
907
961
|
try_table: (n, c) => {
|
|
908
962
|
isId(n[0]) && (c.block[n.shift()] = c.block.length + 1)
|
|
@@ -921,7 +975,11 @@ const IMM = {
|
|
|
921
975
|
return [...result, ...uleb(count), ...catches]
|
|
922
976
|
},
|
|
923
977
|
end: (_n, c) => (c.block.pop(), []),
|
|
924
|
-
call_indirect: (n, c
|
|
978
|
+
call_indirect: (n, c, op, out) => {
|
|
979
|
+
let t = n.shift(), [, idx] = n.shift()
|
|
980
|
+
if (out) { uleb(id(idx, c.type), out); uleb(id(t, c.table), out); return }
|
|
981
|
+
return [...uleb(id(idx, c.type)), ...uleb(id(t, c.table))]
|
|
982
|
+
},
|
|
925
983
|
br_table: (n, c) => {
|
|
926
984
|
let labels = [], count = 0
|
|
927
985
|
while (n[0] && (!isNaN(n[0]) || isId(n[0]))) (labels.push(...uleb(blockid(n.shift(), c.block))), count++)
|
|
@@ -929,8 +987,8 @@ const IMM = {
|
|
|
929
987
|
},
|
|
930
988
|
select: (n, c) => { let r = n.shift() || []; return r.length ? vec(r.map(t => reftype(t, c))) : [] },
|
|
931
989
|
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)) },
|
|
932
|
-
memarg: (n, c, op) => memargEnc(n, op, isIdx(n[0]) && !isMemParam(n[0]) ? id(n.shift(), c.memory) : 0),
|
|
933
|
-
opt_memory: (n, c) =>
|
|
990
|
+
memarg: (n, c, op, out) => memargEnc(n, op, isIdx(n[0]) && !isMemParam(n[0]) ? id(n.shift(), c.memory) : 0, out),
|
|
991
|
+
opt_memory: (n, c, op, out) => wleb(id(isIdx(n[0]) ? n.shift() : 0, c.memory), out),
|
|
934
992
|
reftype: (n, c) => { let ht = reftype(n.shift(), c); return ht.length > 1 ? ht.slice(1) : ht },
|
|
935
993
|
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)] },
|
|
936
994
|
v128const: (n) => {
|
|
@@ -955,28 +1013,28 @@ const IMM = {
|
|
|
955
1013
|
const memIdx = isId(n[0]) || (isIdx(n[0]) && (isMemParam(n[1]) || isIdx(n[1]))) ? id(n.shift(), c.memory) : 0
|
|
956
1014
|
return [...memargEnc(n, op, memIdx), ...uleb(parseUint(n.shift()))]
|
|
957
1015
|
},
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
//
|
|
961
|
-
labelidx: (n, c) =>
|
|
962
|
-
laneidx: (n) =>
|
|
963
|
-
funcidx: (n, c) =>
|
|
964
|
-
typeidx: (n, c) =>
|
|
965
|
-
tableidx: (n, c) =>
|
|
966
|
-
memoryidx: (n, c) =>
|
|
967
|
-
globalidx: (n, c) =>
|
|
968
|
-
localidx: (n, c) =>
|
|
969
|
-
dataidx: (n, c) =>
|
|
970
|
-
elemidx: (n, c) =>
|
|
971
|
-
tagidx: (n, c) =>
|
|
972
|
-
'memoryidx?': (n, c) =>
|
|
973
|
-
stringidx: (n, c) => { let s = n.shift(), key = s.valueOf(), idx = c.strings.findIndex(x => x.valueOf() === key); if (idx < 0) idx = c.strings.push(s) - 1; return
|
|
1016
|
+
// *idx types — write-mode (out present) pushes in place and returns undefined;
|
|
1017
|
+
// return-mode hands back a fresh array. The sentinel is EXPLICIT: a boxed-
|
|
1018
|
+
// pointer identity compare (`returned !== out`) misfires under the jz kernel.
|
|
1019
|
+
labelidx: (n, c, op, out) => wleb(blockid(n.shift(), c.block), out),
|
|
1020
|
+
laneidx: (n, c, op, out) => { const v = parseUint(n.shift(), 0xff); if (out) { out.push(v); return } return [v] },
|
|
1021
|
+
funcidx: (n, c, op, out) => wleb(id(n.shift(), c.func), out),
|
|
1022
|
+
typeidx: (n, c, op, out) => wleb(id(n.shift(), c.type), out),
|
|
1023
|
+
tableidx: (n, c, op, out) => wleb(id(n.shift(), c.table), out),
|
|
1024
|
+
memoryidx: (n, c, op, out) => wleb(id(n.shift(), c.memory), out),
|
|
1025
|
+
globalidx: (n, c, op, out) => wleb(id(n.shift(), c.global), out),
|
|
1026
|
+
localidx: (n, c, op, out) => wleb(id(n.shift(), c.local), out),
|
|
1027
|
+
dataidx: (n, c, op, out) => wleb(id(n.shift(), c.data), out),
|
|
1028
|
+
elemidx: (n, c, op, out) => wleb(id(n.shift(), c.elem), out),
|
|
1029
|
+
tagidx: (n, c, op, out) => wleb(id(n.shift(), c.tag), out),
|
|
1030
|
+
'memoryidx?': (n, c, op, out) => wleb(id(isIdx(n[0]) ? n.shift() : 0, c.memory), out),
|
|
1031
|
+
stringidx: (n, c, op, out) => { let s = n.shift(), key = s.valueOf(), idx = c.strings.findIndex(x => x.valueOf() === key); if (idx < 0) idx = c.strings.push(s) - 1; return wleb(idx, out) },
|
|
974
1032
|
|
|
975
1033
|
// Value type
|
|
976
|
-
i32: (n) => encode.i32(n.shift()),
|
|
977
|
-
i64: (n) => encode.i64(n.shift()),
|
|
978
|
-
f32: (n) => encode.f32(n.shift()),
|
|
979
|
-
f64: (n) => encode.f64(n.shift()),
|
|
1034
|
+
i32: (n, c, op, out) => { if (out) { encode.i32(n.shift(), out); return } return encode.i32(n.shift()) },
|
|
1035
|
+
i64: (n, c, op, out) => { if (out) { encode.i64(n.shift(), out); return } return encode.i64(n.shift()) },
|
|
1036
|
+
f32: (n, c, op, out) => encode.f32(n.shift(), out),
|
|
1037
|
+
f64: (n, c, op, out) => encode.f64(n.shift(), out),
|
|
980
1038
|
v128: (n) => encode.v128(n.shift()),
|
|
981
1039
|
|
|
982
1040
|
// Combinations
|
|
@@ -1028,21 +1086,9 @@ const IMM = {
|
|
|
1028
1086
|
}
|
|
1029
1087
|
};
|
|
1030
1088
|
|
|
1031
|
-
// per-op imm handlers
|
|
1032
|
-
const HANDLER = {};
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
// Populate INSTR and IMM
|
|
1036
|
-
(function populate(items, pre) {
|
|
1037
|
-
for (let op = 0, item, nm, imm; op < items.length; op++) if (item = items[op]) {
|
|
1038
|
-
// Nested array (0xfb, 0xfc, 0xfd opcodes)
|
|
1039
|
-
if (Array.isArray(item)) populate(item, op)
|
|
1040
|
-
else [nm, imm] = item.split(' '), INSTR[nm] = pre ? [pre, ...uleb(op)] : [op], imm && (HANDLER[nm] = IMM[imm])
|
|
1041
|
-
}
|
|
1042
|
-
})(INSTR);
|
|
1043
|
-
|
|
1044
1089
|
|
|
1045
|
-
// instruction encoder
|
|
1090
|
+
// instruction encoder — bytes land DIRECTLY in the output stream (write-mode
|
|
1091
|
+
// handlers push immediates themselves; cold handlers still return small arrays)
|
|
1046
1092
|
const instr = (nodes, ctx) => {
|
|
1047
1093
|
let out = [], meta = []
|
|
1048
1094
|
|
|
@@ -1058,22 +1104,12 @@ const instr = (nodes, ctx) => {
|
|
|
1058
1104
|
|
|
1059
1105
|
// Array = unknown instruction passed through from normalize
|
|
1060
1106
|
if (Array.isArray(op)) {
|
|
1061
|
-
op.loc != null && (
|
|
1107
|
+
op.loc != null && setErrLoc(op.loc)
|
|
1062
1108
|
err(`Unknown instruction ${op[0]}`)
|
|
1063
1109
|
}
|
|
1064
1110
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
// special op handlers
|
|
1068
|
-
if (HANDLER[op]) {
|
|
1069
|
-
// select: becomes typed select (opcode+1) if next node is an array with result types
|
|
1070
|
-
if (op === 'select' && nodes[0]?.length) bytes[0]++
|
|
1071
|
-
// ref.type|cast: opcode+1 if type is nullable: (ref null $t) or (funcref, anyref, etc.)
|
|
1072
|
-
else if (HANDLER[op] === IMM.reftype && !op.endsWith('_null') && (nodes[0][1] === 'null' || nodes[0][0] !== 'ref')) {
|
|
1073
|
-
bytes[bytes.length - 1]++
|
|
1074
|
-
}
|
|
1075
|
-
bytes.push(...HANDLER[op](nodes, ctx, op))
|
|
1076
|
-
}
|
|
1111
|
+
const code = OPCODE[op]
|
|
1112
|
+
if (typeof code !== 'number') err(`Unknown instruction ${op}`)
|
|
1077
1113
|
|
|
1078
1114
|
// Attach any pending annotations to this instruction's byte position, then
|
|
1079
1115
|
// clear — a (@metadata.code.*) annotation applies to the next instruction only
|
|
@@ -1082,14 +1118,95 @@ const instr = (nodes, ctx) => {
|
|
|
1082
1118
|
meta = []
|
|
1083
1119
|
}
|
|
1084
1120
|
|
|
1085
|
-
out.
|
|
1121
|
+
const at = out.length
|
|
1122
|
+
// unpack: top byte is the page prefix (0xfb-0xfe), low bits the uleb-encoded subopcode
|
|
1123
|
+
if (code > 0xffff) { out.push(code >>> 16); uleb(code & 0xffff, out) }
|
|
1124
|
+
else out.push(code)
|
|
1125
|
+
|
|
1126
|
+
// immediate encoding
|
|
1127
|
+
const imm = IMM[op]
|
|
1128
|
+
if (imm) {
|
|
1129
|
+
// select: becomes typed select (opcode+1) if next node is an array with result types
|
|
1130
|
+
if (op === 'select' && nodes[0]?.length) out[at]++
|
|
1131
|
+
// ref.type|cast: opcode+1 if type is nullable: (ref null $t) or (funcref, anyref, etc.)
|
|
1132
|
+
else if (imm === 'reftype' && !op.endsWith('_null') && (nodes[0][1] === 'null' || nodes[0][0] !== 'ref')) {
|
|
1133
|
+
out[out.length - 1]++
|
|
1134
|
+
}
|
|
1135
|
+
const b = HANDLER[imm](nodes, ctx, op, out)
|
|
1136
|
+
if (b) for (let i = 0; i < b.length; i++) out.push(b[i])
|
|
1137
|
+
}
|
|
1086
1138
|
}
|
|
1087
1139
|
|
|
1088
1140
|
return out.push(0x0b), out
|
|
1089
1141
|
}
|
|
1090
1142
|
|
|
1091
|
-
// LEB128 byte-width of
|
|
1092
|
-
|
|
1143
|
+
// LEB128 byte-width of a non-negative number (7 bits per byte). Non-number
|
|
1144
|
+
// input (BigInt, numeric string) falls back to the real encoder's length.
|
|
1145
|
+
const ulebSize = (n) => {
|
|
1146
|
+
if (typeof n !== 'number' || n < 0) return uleb(n).length
|
|
1147
|
+
let k = 1; n >>>= 7
|
|
1148
|
+
while (n) k++, n >>>= 7
|
|
1149
|
+
return k
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Signed-LEB byte-width of an i32 immediate (mirrors encode.i32's loop).
|
|
1153
|
+
const slebSize32 = (n) => {
|
|
1154
|
+
if (typeof n === 'string') n = i32.parse(n)
|
|
1155
|
+
let k = 1
|
|
1156
|
+
while (true) {
|
|
1157
|
+
const byte = n & 0x7F
|
|
1158
|
+
n >>= 7
|
|
1159
|
+
if ((n === 0 && (byte & 0x40) === 0) || (n === -1 && (byte & 0x40) !== 0)) return k
|
|
1160
|
+
k++
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Width of a memarg (align + optional memidx + offset) without building it.
|
|
1165
|
+
const memargSize = (nodes, op, memIdx = 0) => {
|
|
1166
|
+
const [a, o] = memarg(nodes), alignVal = (a ?? align(op)) | (memIdx && 0x40)
|
|
1167
|
+
return memIdx ? ulebSize(alignVal) + ulebSize(memIdx) + ulebSize(o ?? 0) : ulebSize(alignVal) + ulebSize(o ?? 0)
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Width-only twins of the HOT immediate handlers: identical token consumption
|
|
1171
|
+
// and ctx side effects (block stack, string interning), integer widths instead
|
|
1172
|
+
// of materialized byte arrays — immediate allocation dominates instrSize on
|
|
1173
|
+
// multi-MB modules. Cold immtypes fall back to the real handler's .length, so
|
|
1174
|
+
// the tables cannot drift; equality with compile().length is invariant-tested.
|
|
1175
|
+
const SIZE_HANDLER = {}
|
|
1176
|
+
for (const k in HANDLER) SIZE_HANDLER[k] = (n, c, op) => HANDLER[k](n, c, op).length
|
|
1177
|
+
Object.assign(SIZE_HANDLER, {
|
|
1178
|
+
i32: (n) => slebSize32(n.shift()),
|
|
1179
|
+
f32: (n) => (n.shift(), 4),
|
|
1180
|
+
f64: (n) => (n.shift(), 8),
|
|
1181
|
+
localidx: (n, c) => ulebSize(id(n.shift(), c.local)),
|
|
1182
|
+
funcidx: (n, c) => ulebSize(id(n.shift(), c.func)),
|
|
1183
|
+
typeidx: (n, c) => ulebSize(id(n.shift(), c.type)),
|
|
1184
|
+
tableidx: (n, c) => ulebSize(id(n.shift(), c.table)),
|
|
1185
|
+
memoryidx: (n, c) => ulebSize(id(n.shift(), c.memory)),
|
|
1186
|
+
globalidx: (n, c) => ulebSize(id(n.shift(), c.global)),
|
|
1187
|
+
dataidx: (n, c) => ulebSize(id(n.shift(), c.data)),
|
|
1188
|
+
elemidx: (n, c) => ulebSize(id(n.shift(), c.elem)),
|
|
1189
|
+
tagidx: (n, c) => ulebSize(id(n.shift(), c.tag)),
|
|
1190
|
+
labelidx: (n, c) => ulebSize(blockid(n.shift(), c.block)),
|
|
1191
|
+
laneidx: (n) => (parseUint(n.shift(), 0xff), 1),
|
|
1192
|
+
memarg: (n, c, op) => memargSize(n, op, isIdx(n[0]) && !isMemParam(n[0]) ? id(n.shift(), c.memory) : 0),
|
|
1193
|
+
opt_memory: (n, c) => ulebSize(id(isIdx(n[0]) ? n.shift() : 0, c.memory)),
|
|
1194
|
+
'memoryidx?': (n, c) => ulebSize(id(isIdx(n[0]) ? n.shift() : 0, c.memory)),
|
|
1195
|
+
call_indirect: (n, c) => { const t = n.shift(), [, ti] = n.shift(); return ulebSize(id(ti, c.type)) + ulebSize(id(t, c.table)) },
|
|
1196
|
+
block: (n, c) => {
|
|
1197
|
+
c.block.push(1)
|
|
1198
|
+
isId(n[0]) && (c.block[n.shift()] = c.block.length)
|
|
1199
|
+
const t = n.shift()
|
|
1200
|
+
return !t ? 1 : t[0] === 'result' ? reftype(t[1], c).length : ulebSize(id(t[1], c.type))
|
|
1201
|
+
},
|
|
1202
|
+
end: (_n, c) => (c.block.pop(), 0),
|
|
1203
|
+
stringidx: (n, c) => {
|
|
1204
|
+
const str = n.shift(), key = str.valueOf()
|
|
1205
|
+
let idx = c.strings.findIndex(x => x.valueOf() === key)
|
|
1206
|
+
if (idx < 0) idx = c.strings.push(str) - 1
|
|
1207
|
+
return ulebSize(idx)
|
|
1208
|
+
},
|
|
1209
|
+
})
|
|
1093
1210
|
|
|
1094
1211
|
// Size-only twin of instr(): the byte LENGTH of the encoded instruction stream,
|
|
1095
1212
|
// WITHOUT building the (multi-MB) byte array. Reuses every HANDLER for exact
|
|
@@ -1103,10 +1220,11 @@ const instrSize = (nodes, ctx) => {
|
|
|
1103
1220
|
while (nodes?.length) {
|
|
1104
1221
|
let op = nodes.shift()
|
|
1105
1222
|
if (op?.[0] === '@metadata') { meta.push(op.slice(1)); continue }
|
|
1106
|
-
if (Array.isArray(op)) { op.loc != null && (
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1223
|
+
if (Array.isArray(op)) { op.loc != null && setErrLoc(op.loc); err(`Unknown instruction ${op[0]}`) }
|
|
1224
|
+
const code = OPCODE[op]
|
|
1225
|
+
if (typeof code !== 'number') err(`Unknown instruction ${op}`)
|
|
1226
|
+
let n = code > 0xffff ? 1 + ulebSize(code & 0xffff) : 1
|
|
1227
|
+
if (IMM[op]) n += SIZE_HANDLER[IMM[op]](nodes, ctx, op)
|
|
1110
1228
|
if (meta.length) { for (const [type, data] of meta) ((ctx.meta[type] ??= []).push([size, data])); meta = [] }
|
|
1111
1229
|
size += n
|
|
1112
1230
|
}
|
|
@@ -1145,7 +1263,9 @@ const codeItemSize = (body, ctx) => {
|
|
|
1145
1263
|
const expr = (node, ctx) => instr(normalize([node], ctx), ctx)
|
|
1146
1264
|
|
|
1147
1265
|
// deref id node to numeric idx
|
|
1148
|
-
|
|
1266
|
+
// dense index spaces: a resolved name or an in-range number needs no second
|
|
1267
|
+
// dictionary hit — `n in list` only arbitrates the miss path
|
|
1268
|
+
const id = (nm, list, n) => (n = isId(nm) ? list[nm] : +nm, n >= 0 && n < list.length ? n : n in list ? n : err(`Unknown ${list.name} ${nm}`))
|
|
1149
1269
|
|
|
1150
1270
|
// block id - same as id but for block
|
|
1151
1271
|
// index indicates how many block items to pop
|
|
@@ -1167,8 +1287,14 @@ const memarg = (args) => {
|
|
|
1167
1287
|
|
|
1168
1288
|
// Encode memarg (align + offset) with default values based on instruction
|
|
1169
1289
|
// If memIdx is non-zero, set bit 6 in alignment flags and insert memIdx after align
|
|
1170
|
-
const memargEnc = (nodes, op, memIdx = 0) => {
|
|
1290
|
+
const memargEnc = (nodes, op, memIdx = 0, out) => {
|
|
1171
1291
|
const [a, o] = memarg(nodes), alignVal = (a ?? align(op)) | (memIdx && 0x40)
|
|
1292
|
+
if (out) {
|
|
1293
|
+
uleb(alignVal, out)
|
|
1294
|
+
if (memIdx) uleb(memIdx, out)
|
|
1295
|
+
uleb(o ?? 0, out)
|
|
1296
|
+
return
|
|
1297
|
+
}
|
|
1172
1298
|
return memIdx ? [...uleb(alignVal), ...uleb(memIdx), ...uleb(o ?? 0)] : [...uleb(alignVal), ...uleb(o ?? 0)]
|
|
1173
1299
|
}
|
|
1174
1300
|
|
|
@@ -1194,7 +1320,7 @@ const align = (op) => {
|
|
|
1194
1320
|
}
|
|
1195
1321
|
|
|
1196
1322
|
// Convert WAT numeric data (i8/i16/i32/i64/f32/f64 lists) to bytes (Phase 2: WAT numeric values)
|
|
1197
|
-
const numdata = (item) => {
|
|
1323
|
+
export const numdata = (item) => {
|
|
1198
1324
|
if (!Array.isArray(item)) return null
|
|
1199
1325
|
const [t, ...vs] = item
|
|
1200
1326
|
if (t !== 'i8' && t !== 'i16' && t !== 'i32' && t !== 'i64' && t !== 'f32' && t !== 'f64') return null
|