watr 4.7.0 → 4.7.2
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 +108 -17
- package/dist/watr.min.js +6 -6
- package/dist/watr.wasm +0 -0
- package/package.json +1 -1
- package/src/compile.js +92 -13
- package/src/optimize.js +20 -19
- package/types/src/compile.d.ts +10 -9
- package/types/src/compile.d.ts.map +1 -1
- package/types/src/optimize.d.ts +34 -2
- package/types/src/optimize.d.ts.map +1 -1
package/dist/watr.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/compile.js
CHANGED
|
@@ -37,17 +37,10 @@ const cleanup = (node, result) => {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @returns {Uint8Array} The compiled WASM binary. When the WAT carries
|
|
45
|
-
* `@metadata.code.<type>` annotations, the result also exposes a `.metadata`
|
|
46
|
-
* property — `{ [type]: [[absoluteByteOffset, data], ...] }`, each offset the
|
|
47
|
-
* instruction's position in the final binary. That's the hook a source-map
|
|
48
|
-
* emitter needs to correlate generated wasm back to its source.
|
|
49
|
-
*/
|
|
50
|
-
export default function compile(nodes) {
|
|
40
|
+
// Internal: assemble the module. sizeOnly=false → wasm bytes (Uint8Array); sizeOnly=true →
|
|
41
|
+
// just the byte LENGTH, skipping materialization of the multi-MB binary — that's size()'s
|
|
42
|
+
// reason to exist (the optimizer's size-revert guard can't afford to build bytes it discards).
|
|
43
|
+
function assemble(nodes, sizeOnly) {
|
|
51
44
|
// normalize to (module ...) form
|
|
52
45
|
if (typeof nodes === 'string') err.src = nodes, nodes = parse(nodes) || []
|
|
53
46
|
else err.src = '' // clear source if AST passed directly
|
|
@@ -63,10 +56,10 @@ export default function compile(nodes) {
|
|
|
63
56
|
else if (typeof nodes[0] === 'string') nodes = [nodes]
|
|
64
57
|
|
|
65
58
|
// binary abbr "\00" "\0x61" ...
|
|
66
|
-
if (nodes[idx] === 'binary')
|
|
59
|
+
if (nodes[idx] === 'binary') { const b = Uint8Array.from(nodes.slice(++idx).flat()); return sizeOnly ? b.length : b }
|
|
67
60
|
|
|
68
61
|
// quote "a" "b"
|
|
69
|
-
if (nodes[idx] === 'quote') return
|
|
62
|
+
if (nodes[idx] === 'quote') return assemble(nodes.slice(++idx).map(v => v.valueOf().slice(1, -1)).flat().join(''), sizeOnly)
|
|
70
63
|
|
|
71
64
|
// expand grouped imports: (import "mod" (item "name" type)*) -> individual imports
|
|
72
65
|
// compact import section (Phase 3)
|
|
@@ -241,6 +234,26 @@ export default function compile(nodes) {
|
|
|
241
234
|
// pre-compute sections that may collect string constants (for strings section ordering)
|
|
242
235
|
const globalSection = bin(SECTION.global)
|
|
243
236
|
const elemSection = bin(SECTION.elem)
|
|
237
|
+
// measure: byte LENGTH only — skip building + materializing the multi-MB code byte array
|
|
238
|
+
// (the optimizer's size-revert guard only needs the count). Sizes the code section via
|
|
239
|
+
// codeItemSize; other sections are small, built normally + summed. Same string-collection
|
|
240
|
+
// ORDER as the emit path (global, elem, code, meta, data → strings).
|
|
241
|
+
if (sizeOnly) {
|
|
242
|
+
const codeSizes = ctx.code.filter(Boolean).map(item => codeItemSize(item, ctx))
|
|
243
|
+
const innerLen = codeSizes.length ? ulebSize(codeSizes.length) + codeSizes.reduce((a, b) => a + b, 0) : 0
|
|
244
|
+
const codeSecLen = codeSizes.length ? 1 + ulebSize(innerLen) + innerLen : 0 // SECTION.code + vec(vec(items))
|
|
245
|
+
const metaSection = binMeta()
|
|
246
|
+
const dataSection = bin(SECTION.data)
|
|
247
|
+
const stringsSection = ctx.strings.length ? [SECTION.strings, ...vec([0x00, ...vec(ctx.strings.map(s => vec(s)))])] : []
|
|
248
|
+
const others = [
|
|
249
|
+
bin(SECTION.custom), bin(SECTION.type), bin(SECTION.import), bin(SECTION.func),
|
|
250
|
+
bin(SECTION.table), bin(SECTION.memory), bin(SECTION.tag), stringsSection,
|
|
251
|
+
globalSection, bin(SECTION.export), bin(SECTION.start, false), elemSection,
|
|
252
|
+
bin(SECTION.datacount, false), metaSection, dataSection
|
|
253
|
+
]
|
|
254
|
+
return 8 + codeSecLen + others.reduce((s, sec) => s + sec.length, 0) // 8 = magic + version
|
|
255
|
+
}
|
|
256
|
+
|
|
244
257
|
// inline bin(code) so the per-function item bytes survive — with ctx.codeSizePrefix
|
|
245
258
|
// they let us lift code-metadata positions to absolute binary offsets below
|
|
246
259
|
const codeItems = ctx.code.filter(Boolean).map(item => build[SECTION.code](item, ctx)).filter(Boolean)
|
|
@@ -317,6 +330,19 @@ export default function compile(nodes) {
|
|
|
317
330
|
}
|
|
318
331
|
}
|
|
319
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Convert a WAT tree (or source string) to a wasm binary (Uint8Array). When the WAT carries
|
|
335
|
+
* `@metadata.code.<type>` annotations the result also exposes `.metadata` —
|
|
336
|
+
* `{ [type]: [[absByteOffset, data], ...] }`, the hook a source-map emitter needs.
|
|
337
|
+
*/
|
|
338
|
+
export default function compile(nodes) { return assemble(nodes) }
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Byte size of `compile(nodes)` without materializing the binary — a peer transform for
|
|
342
|
+
* tooling (the optimizer's size-revert guard, size budgeting). Exactly `compile(nodes).length`,
|
|
343
|
+
* summed from section sizes instead of building them (invariant-tested).
|
|
344
|
+
*/
|
|
345
|
+
export function size(nodes) { return assemble(nodes, true) }
|
|
320
346
|
|
|
321
347
|
/** Check if node is a valid index reference ($name or number) */
|
|
322
348
|
const isIdx = n => n?.[0] === '$' || !isNaN(n)
|
|
@@ -1062,6 +1088,59 @@ const instr = (nodes, ctx) => {
|
|
|
1062
1088
|
return out.push(0x0b), out
|
|
1063
1089
|
}
|
|
1064
1090
|
|
|
1091
|
+
// LEB128 byte-width of an unsigned integer (exact, reuses uleb so it can never drift).
|
|
1092
|
+
const ulebSize = (n) => uleb(n).length
|
|
1093
|
+
|
|
1094
|
+
// Size-only twin of instr(): the byte LENGTH of the encoded instruction stream,
|
|
1095
|
+
// WITHOUT building the (multi-MB) byte array. Reuses every HANDLER for exact
|
|
1096
|
+
// immediate widths + index resolution — only `out.push(...bytes)` accumulation is
|
|
1097
|
+
// replaced by integer summation (the dominant cost on large modules). The
|
|
1098
|
+
// opcode-value tweaks in instr() (typed-select / nullable-reftype) change a byte
|
|
1099
|
+
// VALUE, not the length, so they're irrelevant here. MUST equal instr(...).length
|
|
1100
|
+
// (asserted by the byteSize↔compile invariant test).
|
|
1101
|
+
const instrSize = (nodes, ctx) => {
|
|
1102
|
+
let size = 0, meta = []
|
|
1103
|
+
while (nodes?.length) {
|
|
1104
|
+
let op = nodes.shift()
|
|
1105
|
+
if (op?.[0] === '@metadata') { meta.push(op.slice(1)); continue }
|
|
1106
|
+
if (Array.isArray(op)) { op.loc != null && (err.loc = op.loc); err(`Unknown instruction ${op[0]}`) }
|
|
1107
|
+
const opc = INSTR[op] || err(`Unknown instruction ${op}`)
|
|
1108
|
+
let n = opc.length
|
|
1109
|
+
if (HANDLER[op]) n += HANDLER[op](nodes, ctx, op).length
|
|
1110
|
+
if (meta.length) { for (const [type, data] of meta) ((ctx.meta[type] ??= []).push([size, data])); meta = [] }
|
|
1111
|
+
size += n
|
|
1112
|
+
}
|
|
1113
|
+
return size + 1 // trailing 0x0b (end)
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Size-only twin of build[SECTION.code]: byte LENGTH of one function's code item.
|
|
1117
|
+
// Mirrors the builder exactly (local setup, squash, metadata bookkeeping, vec
|
|
1118
|
+
// framing) but uses instrSize for the body — so locals/strings/metadata side
|
|
1119
|
+
// effects on ctx stay identical, only the body byte array is never materialized.
|
|
1120
|
+
const codeItemSize = (body, ctx) => {
|
|
1121
|
+
let [typeidx, param] = body.shift()
|
|
1122
|
+
if (!param) [, [param]] = ctx.type[id(typeidx, ctx.type)]
|
|
1123
|
+
ctx.local = Object.create(param); ctx.block = []
|
|
1124
|
+
ctx.local.name = 'local'; ctx.block.name = 'block'
|
|
1125
|
+
if (ctx._codeIdx === undefined) ctx._codeIdx = 0
|
|
1126
|
+
let codeIdx = ctx._codeIdx++
|
|
1127
|
+
while (body[0]?.[0] === 'local') {
|
|
1128
|
+
let [, ...types] = body.shift()
|
|
1129
|
+
if (isId(types[0])) { let nm = types.shift(); if (nm in ctx.local) err(`Duplicate local ${nm}`); else ctx.local[nm] = ctx.local.length }
|
|
1130
|
+
ctx.local.push(...types)
|
|
1131
|
+
}
|
|
1132
|
+
ctx.meta = {}
|
|
1133
|
+
const bytesLen = instrSize(body, ctx)
|
|
1134
|
+
let loctypes = ctx.local.slice(param.length).reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
|
|
1135
|
+
const locals = vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)]))
|
|
1136
|
+
const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
|
|
1137
|
+
for (const type in ctx.meta) { for (const inst of ctx.meta[type]) inst[0] += locals.length; ((ctx.metadata ??= {})[type] ??= []).push([funcIdx, ctx.meta[type]]) }
|
|
1138
|
+
ctx.local = ctx.block = ctx.meta = null
|
|
1139
|
+
const bodyLen = locals.length + bytesLen
|
|
1140
|
+
;(ctx.codeSizePrefix ??= [])[codeIdx] = ulebSize(bodyLen) // = vec prefix width
|
|
1141
|
+
return ulebSize(bodyLen) + bodyLen // vec([...locals, ...bytes]).length
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1065
1144
|
// instantiation time value initializer (consuming) - normalize then encode + add end byte
|
|
1066
1145
|
const expr = (node, ctx) => instr(normalize([node], ctx), ctx)
|
|
1067
1146
|
|
package/src/optimize.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module wat/optimize
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import compile from './compile.js'
|
|
10
|
+
import compile, { size } from './compile.js'
|
|
11
11
|
import parse from './parse.js'
|
|
12
12
|
|
|
13
13
|
// Fixpoint round caps — empirical convergence bounds, not correctness limits.
|
|
@@ -68,7 +68,10 @@ const count = (node) => {
|
|
|
68
68
|
* @returns {number}
|
|
69
69
|
*/
|
|
70
70
|
const binarySize = (ast) => {
|
|
71
|
-
|
|
71
|
+
// `size` = byte length without materializing the binary — the size-revert guard only needs
|
|
72
|
+
// the count, and re-encoding the whole module each round is the optimizer's dominant cost on
|
|
73
|
+
// large modules. Exactly compile(ast).length (invariant-tested).
|
|
74
|
+
try { return size(ast) } catch { return Infinity }
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
/**
|
|
@@ -1873,7 +1876,7 @@ const buildInline = (params, locals, inlResult, cBody, args) => {
|
|
|
1873
1876
|
*
|
|
1874
1877
|
* A callee qualifies when it is small (≤ INLINE_MAX_NODES IR nodes), named with
|
|
1875
1878
|
* named params/locals, single-result-or-void, non-recursive, not pinned
|
|
1876
|
-
* (export/start/elem/ref.func), not
|
|
1879
|
+
* (export/start/elem/ref.func), not in the caller's `pin` set, and free of
|
|
1877
1880
|
* depth-relative branches / tail calls (the inlParse + inlUnsafe liftability
|
|
1878
1881
|
* contract). Runs to a fixpoint so small-helper chains (sdf → sdRep) collapse.
|
|
1879
1882
|
*
|
|
@@ -1893,7 +1896,7 @@ const buildInline = (params, locals, inlResult, cBody, args) => {
|
|
|
1893
1896
|
const INLINE_MAX_NODES = 90
|
|
1894
1897
|
const isV128SimdHelper = (params, inlResult) =>
|
|
1895
1898
|
inlResult === 'v128' && params.length > 0 && params.every(p => p.type === 'v128')
|
|
1896
|
-
const inline = (ast, { simdOnly = false } = {}) => {
|
|
1899
|
+
const inline = (ast, { simdOnly = false, pin = EMPTY_SET } = {}) => {
|
|
1897
1900
|
if (!Array.isArray(ast) || ast[0] !== 'module') return ast
|
|
1898
1901
|
|
|
1899
1902
|
const skip = new Set() // callees with a non-inlinable site (arity mismatch) — don't re-pick
|
|
@@ -1917,7 +1920,7 @@ const inline = (ast, { simdOnly = false } = {}) => {
|
|
|
1917
1920
|
// Pick a small, liftable, non-recursive callee with ≥1 plain-call site.
|
|
1918
1921
|
let calleeName = null, parsed = null
|
|
1919
1922
|
for (const [name, fn] of funcByName) {
|
|
1920
|
-
if (skip.has(name) || pinned.has(name) || otherRef.has(name) ||
|
|
1923
|
+
if (skip.has(name) || pinned.has(name) || otherRef.has(name) || pin.has(name)) continue
|
|
1921
1924
|
if (!(callRefs.get(name) >= 1)) continue
|
|
1922
1925
|
if (inlBodySize(fn) > INLINE_MAX_NODES) continue
|
|
1923
1926
|
if (inlCallsSelf(fn, name)) continue
|
|
@@ -2254,12 +2257,7 @@ const devirt = (ast) => {
|
|
|
2254
2257
|
* @param {Array} ast
|
|
2255
2258
|
* @returns {Array}
|
|
2256
2259
|
*/
|
|
2257
|
-
|
|
2258
|
-
// src/optimize/vectorize.js). inlineOnce must NOT dissolve their call nodes when single-caller —
|
|
2259
|
-
// the post-phase lift needs the call to rewrite it. Keep in sync with PPC_CALL2's keys.
|
|
2260
|
-
const SIMD_PROTECTED = new Set(['$math.sin_core', '$math.cos_core', '$math.sin', '$math.cos', '$math.pow', '$math.atan2', '$math.hypot', '$math.log'])
|
|
2261
|
-
|
|
2262
|
-
const inlineOnce = (ast) => {
|
|
2260
|
+
const inlineOnce = (ast, { pin = EMPTY_SET } = {}) => {
|
|
2263
2261
|
if (!Array.isArray(ast) || ast[0] !== 'module') return ast
|
|
2264
2262
|
|
|
2265
2263
|
// Lift primitives are shared with `inline` (defined once above buildInline). inlineOnce
|
|
@@ -2291,11 +2289,10 @@ const inlineOnce = (ast) => {
|
|
|
2291
2289
|
for (const [name, fn] of funcByName) {
|
|
2292
2290
|
if (pinned.has(name) || otherRef.has(name)) continue
|
|
2293
2291
|
if (callRefs.get(name) !== 1) continue
|
|
2294
|
-
//
|
|
2295
|
-
//
|
|
2296
|
-
//
|
|
2297
|
-
|
|
2298
|
-
if (SIMD_PROTECTED.has(name)) continue
|
|
2292
|
+
// Caller-pinned functions stay intact: inlining a single-caller helper would dissolve the
|
|
2293
|
+
// call node, and a consumer may rely on it surviving (e.g. jz pins the scalar transcendentals
|
|
2294
|
+
// its auto-vectorizer later rewrites to f64x2 mirrors). The policy lives with the caller.
|
|
2295
|
+
if (pin.has(name)) continue
|
|
2299
2296
|
if (callsSelf(fn, name)) continue
|
|
2300
2297
|
// named params/locals only (we'll rename them); reject locals with types
|
|
2301
2298
|
// we can't zero-init on block re-entry.
|
|
@@ -3867,6 +3864,10 @@ export default function optimize(ast, opts = true) {
|
|
|
3867
3864
|
if (typeof ast === 'string') ast = parse(ast) // accept WAT source directly
|
|
3868
3865
|
const strictGuard = opts === true // default: zero tolerance for bloat
|
|
3869
3866
|
opts = normalize(opts)
|
|
3867
|
+
// `pin`: caller-supplied function names that inlineOnce/inline must NOT dissolve. Keeps
|
|
3868
|
+
// optimizer policy with the CALLER — e.g. jz pins the scalar transcendentals its own
|
|
3869
|
+
// auto-vectorizer later rewrites to f64x2 mirrors, so no consumer-specific names live here.
|
|
3870
|
+
opts.pin = opts.pin instanceof Set ? opts.pin : new Set(opts.pin || [])
|
|
3870
3871
|
|
|
3871
3872
|
const log = opts.log ? (msg, delta) => opts.log(msg, delta) : () => {}
|
|
3872
3873
|
const verbose = opts.verbose || opts.log
|
|
@@ -3888,7 +3889,7 @@ export default function optimize(ast, opts = true) {
|
|
|
3888
3889
|
if (!opts.inline) return a
|
|
3889
3890
|
// `inline: 'simd'` → SIMD-helper-only (jz's speed tier, avoids general bloat);
|
|
3890
3891
|
// `inline: true` / `'all'` → general inlining of tiny functions.
|
|
3891
|
-
a = inline(a, { simdOnly: opts.inline === 'simd' })
|
|
3892
|
+
a = inline(a, { simdOnly: opts.inline === 'simd', pin: opts.pin })
|
|
3892
3893
|
if (opts.propagate) a = propagate(a)
|
|
3893
3894
|
if (opts.mergeBlocks) a = mergeBlocks(a)
|
|
3894
3895
|
if (opts.vacuum) a = vacuum(a)
|
|
@@ -3909,7 +3910,7 @@ export default function optimize(ast, opts = true) {
|
|
|
3909
3910
|
// propagate, and it would only confirm what `mayInline` already proved.
|
|
3910
3911
|
for (let round = 0; round < 3; round++) {
|
|
3911
3912
|
const beforeRound = clone(ast)
|
|
3912
|
-
for (const [key, fn] of PASSES) if (opts[key] && key !== 'inlineOnce' && key !== 'inline' && key !== 'devirt') ast = fn(ast)
|
|
3913
|
+
for (const [key, fn] of PASSES) if (opts[key] && key !== 'inlineOnce' && key !== 'inline' && key !== 'devirt') ast = fn(ast, opts)
|
|
3913
3914
|
if (equal(beforeRound, ast)) break // fixpoint
|
|
3914
3915
|
if (verbose) log(` round ${round + 1} applied`)
|
|
3915
3916
|
}
|
|
@@ -3926,7 +3927,7 @@ export default function optimize(ast, opts = true) {
|
|
|
3926
3927
|
for (let round = 0; round < 3; round++) {
|
|
3927
3928
|
beforeRound = clone(ast)
|
|
3928
3929
|
|
|
3929
|
-
for (const [key, fn] of PASSES) if (opts[key] && key !== 'devirt' && key !== 'inline') ast = fn(ast)
|
|
3930
|
+
for (const [key, fn] of PASSES) if (opts[key] && key !== 'devirt' && key !== 'inline') ast = fn(ast, opts)
|
|
3930
3931
|
// Second propagate sweep: `inlineOnce`/`inline` (above) leave fresh
|
|
3931
3932
|
// `(local.set $p arg) … (local.get $p)` wrappers around each inlined call;
|
|
3932
3933
|
// re-running propagation collapses them within this same round, so the size
|
package/types/src/compile.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @returns {Uint8Array} The compiled WASM binary. When the WAT carries
|
|
6
|
-
* `@metadata.code.<type>` annotations, the result also exposes a `.metadata`
|
|
7
|
-
* property — `{ [type]: [[absoluteByteOffset, data], ...] }`, each offset the
|
|
8
|
-
* instruction's position in the final binary. That's the hook a source-map
|
|
9
|
-
* emitter needs to correlate generated wasm back to its source.
|
|
2
|
+
* Convert a WAT tree (or source string) to a wasm binary (Uint8Array). When the WAT carries
|
|
3
|
+
* `@metadata.code.<type>` annotations the result also exposes `.metadata` —
|
|
4
|
+
* `{ [type]: [[absByteOffset, data], ...] }`, the hook a source-map emitter needs.
|
|
10
5
|
*/
|
|
11
|
-
export default function compile(nodes:
|
|
6
|
+
export default function compile(nodes: any): any;
|
|
7
|
+
/**
|
|
8
|
+
* Byte size of `compile(nodes)` without materializing the binary — a peer transform for
|
|
9
|
+
* tooling (the optimizer's size-revert guard, size budgeting). Exactly `compile(nodes).length`,
|
|
10
|
+
* summed from section sizes instead of building them (invariant-tested).
|
|
11
|
+
*/
|
|
12
|
+
export function size(nodes: any): any;
|
|
12
13
|
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"AA4UA;;;;GAIG;AACH,iDAAiE;AAEjE;;;;GAIG;AACH,sCAA4D"}
|
package/types/src/optimize.d.ts
CHANGED
|
@@ -56,10 +56,42 @@ export function strength(ast: any[]): any[];
|
|
|
56
56
|
*/
|
|
57
57
|
export function branch(ast: any[]): any[];
|
|
58
58
|
export function propagate(ast: any): any;
|
|
59
|
-
export function inline(ast: any, { simdOnly }?: {
|
|
59
|
+
export function inline(ast: any, { simdOnly, pin }?: {
|
|
60
60
|
simdOnly?: boolean;
|
|
61
|
+
pin?: any;
|
|
61
62
|
}): any;
|
|
62
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Inline functions that are called from exactly one place into their lone caller,
|
|
65
|
+
* then delete them. Unlike {@link inline} (which duplicates tiny stateless bodies),
|
|
66
|
+
* this never duplicates code and never inflates: each inlined function drops a
|
|
67
|
+
* function-section entry, a type-section entry (if now unused), and a `call`
|
|
68
|
+
* instruction, paying back only a `block`/`local.set` wrapper. This is what
|
|
69
|
+
* `wasm-opt -Oz` does — collapsing helper chains down to a couple of functions —
|
|
70
|
+
* and it's the bulk of the gap between hand-tuned WASM and naive codegen.
|
|
71
|
+
*
|
|
72
|
+
* A function `$f` qualifies when it is, all of:
|
|
73
|
+
* • named, with named params and locals (numeric indices can't be safely renamed);
|
|
74
|
+
* • referenced exactly once across the whole module, by a plain `call` (no
|
|
75
|
+
* `return_call`, `ref.func`, `elem`, `export`, or `start` reference, and not
|
|
76
|
+
* recursive);
|
|
77
|
+
* • single-result or void (a multi-value result can't be modeled as `(block (result …))`);
|
|
78
|
+
* • free of numeric (depth-relative) branch labels — those would shift under the
|
|
79
|
+
* extra block nesting — and of `return_call*` in its body.
|
|
80
|
+
*
|
|
81
|
+
* `(call $f a0 a1 …)` becomes
|
|
82
|
+
* (block $__inlN (result T)?
|
|
83
|
+
* (local.set $__inlN_p0 a0) (local.set $__inlN_p1 a1) … ;; args evaluated once, in order
|
|
84
|
+
* …body, params/locals renamed to $__inlN_*, `return X` → `br $__inlN X`…)
|
|
85
|
+
* and the renamed params+locals are appended to the caller's `local` decls; the
|
|
86
|
+
* body's own block/loop/if labels are renamed too so they can't shadow the caller's.
|
|
87
|
+
* Runs to a fixpoint so helper chains fully collapse.
|
|
88
|
+
*
|
|
89
|
+
* @param {Array} ast
|
|
90
|
+
* @returns {Array}
|
|
91
|
+
*/
|
|
92
|
+
export function inlineOnce(ast: any[], { pin }?: {
|
|
93
|
+
pin?: any;
|
|
94
|
+
}): any[];
|
|
63
95
|
/**
|
|
64
96
|
* Devirtualize `call_indirect` through NaN-boxed closure values with a statically
|
|
65
97
|
* known candidate set. `let f = c ? a : b; … f(x)` emits a select of two i64
|
|
@@ -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":"AAsxHA,gEA6FC;AA/zHD;;;;GAIG;AACH,4BAHW,GAAG,GACD,MAAM,CAOlB;AAED;;;;GAIG;AACH,wCAFa,MAAM,CAOlB;AA8CD;;;;;GAKG;AACH,6CAgJC;AAwUD;;;;GAIG;AACH,wCAkDC;AA0YD;;;;GAIG;AACH,4CAqBC;AA+CD;;;;;GAKG;AACH,8CAmDC;AAndD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CAwDC;AAID;;;;GAIG;AACH,0CA6CC;AAuyBD,yCAqCC;AAmND;;;QAqEC;AAuQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;;UA+IC;AA/aD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,sCAwOC;AA0gDD;;;;;;;;GAQG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAc/B;AAvBD,qEAAqE;AACrE,uBAA8D;AArlC9D;;;;;GAKG;AACH,0CA8DC;AA+ED;;;;GAIG;AACH,4CAQC;AAiCD;;;;;;;;;;;GAWG;AACH,2CAyDC;AAID,2EAA2E;AAC3E,sCAgEC;AAID;;;;GAIG;AACH,4CAyCC;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,2CAiDC;AAID;;;;;GAKG;AACH,4CAqBC;AAID;;;;;;GAMG;AACH,wCAmBC;AAID;;;;;GAKG;AACH,4CAiDC;AAoCD;;;;;GAKG;AACH,0CA8DC;AAwWD,uCAyBC;AA7XD;;;;;GAKG;AACH,8CAyEC;AA2ID;;;;GAIG;AACH,4CAyDC;AAqBD;;;;;GAKG;AACH,iDAgBC;AA/sCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,+CAiFC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,kDAyFC"}
|