watr 4.6.5 → 4.6.7
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 +354 -401
- package/dist/watr.min.js +6 -6
- package/dist/watr.wasm +0 -0
- package/package.json +1 -1
- package/src/const.js +23 -0
- package/src/optimize.js +191 -230
- package/src/polyfill.js +91 -135
- package/src/template.js +245 -0
- package/src/util.js +43 -7
- package/types/src/const.d.ts +1 -0
- package/types/src/const.d.ts.map +1 -1
- package/types/src/optimize.d.ts +7 -29
- package/types/src/optimize.d.ts.map +1 -1
- package/types/src/polyfill.d.ts +8 -14
- package/types/src/polyfill.d.ts.map +1 -1
- package/types/src/template.d.ts +22 -0
- package/types/src/template.d.ts.map +1 -0
- package/types/src/util.d.ts +3 -1
- package/types/src/util.d.ts.map +1 -1
- package/types/watr.d.ts +4 -10
- package/types/watr.d.ts.map +1 -1
- package/watr.js +9 -224
package/dist/watr.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/const.js
CHANGED
|
@@ -174,6 +174,29 @@ export const INSTR = [
|
|
|
174
174
|
]
|
|
175
175
|
]
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Result value-type of an instruction, inferred from its name — the single
|
|
179
|
+
* source of truth for both constant folding and import type-inference.
|
|
180
|
+
* Comparisons and `eqz` on scalar int/float collapse to `i32`; otherwise the
|
|
181
|
+
* type is the name prefix (`i32.add`→`i32`, `f64.sqrt`→`f64`). Returns null
|
|
182
|
+
* when the type can't be inferred from the name alone.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} op - Instruction name
|
|
185
|
+
* @returns {string|null}
|
|
186
|
+
*/
|
|
187
|
+
export const resultType = (op) => {
|
|
188
|
+
if (typeof op !== 'string') return null
|
|
189
|
+
const dot = op.indexOf('.')
|
|
190
|
+
if (dot < 0) return null
|
|
191
|
+
const prefix = op.slice(0, dot)
|
|
192
|
+
const scalar = prefix === 'i32' || prefix === 'i64' || prefix === 'f32' || prefix === 'f64'
|
|
193
|
+
// comparisons & eqz on scalar types yield i32 regardless of operand type
|
|
194
|
+
if (scalar && /^(eqz?|ne|[lg][te])(_[su])?$/.test(op.slice(dot + 1))) return 'i32'
|
|
195
|
+
if (scalar || prefix === 'v128') return prefix
|
|
196
|
+
if (op === 'memory.size' || op === 'memory.grow') return 'i32'
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
// Binary section type codes
|
|
178
201
|
export const SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, tag: 13, strings: 14, global: 6, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 }
|
|
179
202
|
|
package/src/optimize.js
CHANGED
|
@@ -7,40 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import parse from './parse.js'
|
|
9
9
|
import compile from './compile.js'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* Passes defaulting to false can bloat output or are expensive — opt-in only. */
|
|
13
|
-
const OPTS = {
|
|
14
|
-
treeshake: true, // remove unused funcs/globals/types/tables
|
|
15
|
-
fold: true, // constant folding
|
|
16
|
-
deadcode: true, // eliminate dead code after unreachable/br/return
|
|
17
|
-
locals: true, // remove unused locals
|
|
18
|
-
identity: true, // remove identity ops (x + 0 → x)
|
|
19
|
-
strength: true, // strength reduction (x * 2 → x << 1)
|
|
20
|
-
branch: true, // simplify constant branches
|
|
21
|
-
propagate: true, // forward-propagate single-use locals & tiny consts (never inflates)
|
|
22
|
-
inline: false, // inline tiny functions — can duplicate bodies
|
|
23
|
-
inlineOnce: true, // inline single-call functions into their lone caller (never duplicates)
|
|
24
|
-
vacuum: true, // remove nops, drop-of-pure, empty branches
|
|
25
|
-
mergeBlocks: true, // unwrap `(block $L …)` whose label is never targeted
|
|
26
|
-
coalesce: true, // share local slots between same-type non-overlapping locals
|
|
27
|
-
peephole: true, // x-x→0, x&0→0, etc.
|
|
28
|
-
globals: true, // propagate immutable global constants
|
|
29
|
-
offset: true, // fold add+const into load/store offset
|
|
30
|
-
unbranch: true, // remove redundant br at end of own block
|
|
31
|
-
loopify: true, // collapse block+loop+brif while-idiom into loop+if
|
|
32
|
-
stripmut: true, // strip mut from never-written globals
|
|
33
|
-
brif: true, // if-then-br → br_if
|
|
34
|
-
foldarms: false, // merge identical trailing if arms — can add block wrapper
|
|
35
|
-
dedupe: true, // eliminate duplicate functions
|
|
36
|
-
reorder: false, // put hot functions first — no AST reduction
|
|
37
|
-
dedupTypes: true, // merge identical type definitions
|
|
38
|
-
packData: true, // trim trailing zeros, merge adjacent data segments
|
|
39
|
-
minifyImports: false, // shorten import names — enable only when you control the host
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** All optimization names */
|
|
43
|
-
const ALL = Object.keys(OPTS)
|
|
10
|
+
import { walk, walkPost, clone } from './util.js'
|
|
11
|
+
import { resultType } from './const.js'
|
|
44
12
|
|
|
45
13
|
/**
|
|
46
14
|
* Recursively count AST nodes — fast size heuristic without compiling.
|
|
@@ -77,64 +45,6 @@ const equal = (a, b) => {
|
|
|
77
45
|
return true
|
|
78
46
|
}
|
|
79
47
|
|
|
80
|
-
/**
|
|
81
|
-
* Normalize options to { opt: bool } map.
|
|
82
|
-
* @param {boolean|string|Object} opts
|
|
83
|
-
* @returns {Object}
|
|
84
|
-
*/
|
|
85
|
-
const normalize = (opts) => {
|
|
86
|
-
if (opts === true) return { ...OPTS }
|
|
87
|
-
if (opts === false) return {}
|
|
88
|
-
if (typeof opts === 'string') {
|
|
89
|
-
const set = new Set(opts.split(/\s+/).filter(Boolean))
|
|
90
|
-
if (set.has('all')) return Object.fromEntries(ALL.map(f => [f, true]))
|
|
91
|
-
// Explicit pass names enable ONLY those passes (not the full default set).
|
|
92
|
-
return Object.fromEntries(ALL.map(f => [f, set.has(f)]))
|
|
93
|
-
}
|
|
94
|
-
return { ...OPTS, ...opts }
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Deep clone AST.
|
|
98
|
-
* @param {any} node
|
|
99
|
-
* @returns {any}
|
|
100
|
-
*/
|
|
101
|
-
const clone = (node) => {
|
|
102
|
-
if (!Array.isArray(node)) return node
|
|
103
|
-
return node.map(clone)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Walk AST depth-first (pre-order).
|
|
108
|
-
* @param {any} node
|
|
109
|
-
* @param {Function} fn - (node, parent, idx) => void
|
|
110
|
-
* @param {any} [parent]
|
|
111
|
-
* @param {number} [idx]
|
|
112
|
-
*/
|
|
113
|
-
const walk = (node, fn, parent, idx) => {
|
|
114
|
-
fn(node, parent, idx)
|
|
115
|
-
if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walk(node[i], fn, node, i)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Walk AST depth-first (post-order), transform children before parent.
|
|
120
|
-
* Returns the (potentially replaced) node.
|
|
121
|
-
* @param {any} node
|
|
122
|
-
* @param {Function} fn - (node, parent, idx) => newNode|undefined
|
|
123
|
-
* @param {any} [parent]
|
|
124
|
-
* @param {number} [idx]
|
|
125
|
-
* @returns {any}
|
|
126
|
-
*/
|
|
127
|
-
const walkPost = (node, fn, parent, idx) => {
|
|
128
|
-
if (Array.isArray(node)) {
|
|
129
|
-
for (let i = 0; i < node.length; i++) {
|
|
130
|
-
const result = walkPost(node[i], fn, node, i)
|
|
131
|
-
if (result !== undefined) node[i] = result
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const result = fn(node, parent, idx)
|
|
135
|
-
return result !== undefined ? result : node
|
|
136
|
-
}
|
|
137
|
-
|
|
138
48
|
/**
|
|
139
49
|
* Locate the parts of an `(if ...)` node:
|
|
140
50
|
* condIdx → index of the condition expression
|
|
@@ -344,117 +254,117 @@ const i64c = (fn) => (a, b) => fn(a, b) ? 1 : 0
|
|
|
344
254
|
const u64c = (fn) => (a, b) => fn(BigInt.asUintN(64, a), BigInt.asUintN(64, b)) ? 1 : 0
|
|
345
255
|
|
|
346
256
|
/**
|
|
347
|
-
* Constant folders, keyed by op. Each entry is
|
|
348
|
-
*
|
|
257
|
+
* Constant folders, keyed by op. Each entry is the fold function; the result
|
|
258
|
+
* value-type is derived once via `resultType` (see `fold`).
|
|
349
259
|
*/
|
|
350
260
|
const FOLDABLE = {
|
|
351
261
|
// i32 arithmetic
|
|
352
|
-
'i32.add':
|
|
353
|
-
'i32.sub':
|
|
354
|
-
'i32.mul':
|
|
355
|
-
'i32.div_s':
|
|
356
|
-
'i32.div_u':
|
|
357
|
-
'i32.rem_s':
|
|
358
|
-
'i32.rem_u':
|
|
359
|
-
'i32.and':
|
|
360
|
-
'i32.or':
|
|
361
|
-
'i32.xor':
|
|
362
|
-
'i32.shl':
|
|
363
|
-
'i32.shr_s':
|
|
364
|
-
'i32.shr_u':
|
|
365
|
-
'i32.rotl':
|
|
366
|
-
'i32.rotr':
|
|
367
|
-
'i32.eq':
|
|
368
|
-
'i32.ne':
|
|
369
|
-
'i32.lt_s':
|
|
370
|
-
'i32.lt_u':
|
|
371
|
-
'i32.gt_s':
|
|
372
|
-
'i32.gt_u':
|
|
373
|
-
'i32.le_s':
|
|
374
|
-
'i32.le_u':
|
|
375
|
-
'i32.ge_s':
|
|
376
|
-
'i32.ge_u':
|
|
377
|
-
'i32.eqz':
|
|
378
|
-
'i32.clz':
|
|
379
|
-
'i32.ctz':
|
|
380
|
-
'i32.popcnt':
|
|
381
|
-
'i32.wrap_i64':
|
|
382
|
-
'i32.extend8_s':
|
|
383
|
-
'i32.extend16_s':
|
|
262
|
+
'i32.add': (a, b) => (a + b) | 0,
|
|
263
|
+
'i32.sub': (a, b) => (a - b) | 0,
|
|
264
|
+
'i32.mul': (a, b) => Math.imul(a, b),
|
|
265
|
+
'i32.div_s': (a, b) => b !== 0 ? (a / b) | 0 : null,
|
|
266
|
+
'i32.div_u': (a, b) => b !== 0 ? ((a >>> 0) / (b >>> 0)) | 0 : null,
|
|
267
|
+
'i32.rem_s': (a, b) => b !== 0 ? (a % b) | 0 : null,
|
|
268
|
+
'i32.rem_u': (a, b) => b !== 0 ? ((a >>> 0) % (b >>> 0)) | 0 : null,
|
|
269
|
+
'i32.and': (a, b) => a & b,
|
|
270
|
+
'i32.or': (a, b) => a | b,
|
|
271
|
+
'i32.xor': (a, b) => a ^ b,
|
|
272
|
+
'i32.shl': (a, b) => a << (b & 31),
|
|
273
|
+
'i32.shr_s': (a, b) => a >> (b & 31),
|
|
274
|
+
'i32.shr_u': (a, b) => a >>> (b & 31),
|
|
275
|
+
'i32.rotl': (a, b) => { b &= 31; return ((a << b) | (a >>> (32 - b))) | 0 },
|
|
276
|
+
'i32.rotr': (a, b) => { b &= 31; return ((a >>> b) | (a << (32 - b))) | 0 },
|
|
277
|
+
'i32.eq': i32c((a, b) => a === b),
|
|
278
|
+
'i32.ne': i32c((a, b) => a !== b),
|
|
279
|
+
'i32.lt_s': i32c((a, b) => a < b),
|
|
280
|
+
'i32.lt_u': u32c((a, b) => a < b),
|
|
281
|
+
'i32.gt_s': i32c((a, b) => a > b),
|
|
282
|
+
'i32.gt_u': u32c((a, b) => a > b),
|
|
283
|
+
'i32.le_s': i32c((a, b) => a <= b),
|
|
284
|
+
'i32.le_u': u32c((a, b) => a <= b),
|
|
285
|
+
'i32.ge_s': i32c((a, b) => a >= b),
|
|
286
|
+
'i32.ge_u': u32c((a, b) => a >= b),
|
|
287
|
+
'i32.eqz': (a) => a === 0 ? 1 : 0,
|
|
288
|
+
'i32.clz': (a) => Math.clz32(a),
|
|
289
|
+
'i32.ctz': (a) => a === 0 ? 32 : 31 - Math.clz32(a & -a),
|
|
290
|
+
'i32.popcnt': (a) => { let c = 0; while (a) { c += a & 1; a >>>= 1 } return c },
|
|
291
|
+
'i32.wrap_i64': (a) => Number(BigInt.asIntN(32, a)),
|
|
292
|
+
'i32.extend8_s': (a) => (a << 24) >> 24,
|
|
293
|
+
'i32.extend16_s': (a) => (a << 16) >> 16,
|
|
384
294
|
|
|
385
295
|
// i64 (using BigInt)
|
|
386
|
-
'i64.add':
|
|
387
|
-
'i64.sub':
|
|
388
|
-
'i64.mul':
|
|
389
|
-
'i64.div_s':
|
|
390
|
-
'i64.div_u':
|
|
391
|
-
'i64.rem_s':
|
|
392
|
-
'i64.rem_u':
|
|
393
|
-
'i64.and':
|
|
394
|
-
'i64.or':
|
|
395
|
-
'i64.xor':
|
|
396
|
-
'i64.shl':
|
|
397
|
-
'i64.shr_s':
|
|
398
|
-
'i64.shr_u':
|
|
399
|
-
'i64.eq':
|
|
400
|
-
'i64.ne':
|
|
401
|
-
'i64.lt_s':
|
|
402
|
-
'i64.lt_u':
|
|
403
|
-
'i64.gt_s':
|
|
404
|
-
'i64.gt_u':
|
|
405
|
-
'i64.le_s':
|
|
406
|
-
'i64.le_u':
|
|
407
|
-
'i64.ge_s':
|
|
408
|
-
'i64.ge_u':
|
|
409
|
-
'i64.eqz':
|
|
410
|
-
'i64.extend_i32_s':
|
|
411
|
-
'i64.extend_i32_u':
|
|
412
|
-
'i64.extend8_s':
|
|
413
|
-
'i64.extend16_s':
|
|
414
|
-
'i64.extend32_s':
|
|
296
|
+
'i64.add': (a, b) => BigInt.asIntN(64, a + b),
|
|
297
|
+
'i64.sub': (a, b) => BigInt.asIntN(64, a - b),
|
|
298
|
+
'i64.mul': (a, b) => BigInt.asIntN(64, a * b),
|
|
299
|
+
'i64.div_s': (a, b) => b !== 0n ? BigInt.asIntN(64, a / b) : null,
|
|
300
|
+
'i64.div_u': (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) / BigInt.asUintN(64, b)) : null,
|
|
301
|
+
'i64.rem_s': (a, b) => b !== 0n ? BigInt.asIntN(64, a % b) : null,
|
|
302
|
+
'i64.rem_u': (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) % BigInt.asUintN(64, b)) : null,
|
|
303
|
+
'i64.and': (a, b) => BigInt.asIntN(64, a & b),
|
|
304
|
+
'i64.or': (a, b) => BigInt.asIntN(64, a | b),
|
|
305
|
+
'i64.xor': (a, b) => BigInt.asIntN(64, a ^ b),
|
|
306
|
+
'i64.shl': (a, b) => BigInt.asIntN(64, a << (b & 63n)),
|
|
307
|
+
'i64.shr_s': (a, b) => BigInt.asIntN(64, a >> (b & 63n)),
|
|
308
|
+
'i64.shr_u': (a, b) => BigInt.asUintN(64, BigInt.asUintN(64, a) >> (b & 63n)),
|
|
309
|
+
'i64.eq': i64c((a, b) => a === b),
|
|
310
|
+
'i64.ne': i64c((a, b) => a !== b),
|
|
311
|
+
'i64.lt_s': i64c((a, b) => a < b),
|
|
312
|
+
'i64.lt_u': u64c((a, b) => a < b),
|
|
313
|
+
'i64.gt_s': i64c((a, b) => a > b),
|
|
314
|
+
'i64.gt_u': u64c((a, b) => a > b),
|
|
315
|
+
'i64.le_s': i64c((a, b) => a <= b),
|
|
316
|
+
'i64.le_u': u64c((a, b) => a <= b),
|
|
317
|
+
'i64.ge_s': i64c((a, b) => a >= b),
|
|
318
|
+
'i64.ge_u': u64c((a, b) => a >= b),
|
|
319
|
+
'i64.eqz': (a) => a === 0n ? 1 : 0,
|
|
320
|
+
'i64.extend_i32_s': (a) => BigInt(a),
|
|
321
|
+
'i64.extend_i32_u': (a) => BigInt(a >>> 0),
|
|
322
|
+
'i64.extend8_s': (a) => BigInt.asIntN(64, BigInt.asIntN(8, a)),
|
|
323
|
+
'i64.extend16_s': (a) => BigInt.asIntN(64, BigInt.asIntN(16, a)),
|
|
324
|
+
'i64.extend32_s': (a) => BigInt.asIntN(64, BigInt.asIntN(32, a)),
|
|
415
325
|
|
|
416
326
|
// f32/f64 (NaN/precision-aware via Math.fround)
|
|
417
|
-
'f32.add':
|
|
418
|
-
'f32.sub':
|
|
419
|
-
'f32.mul':
|
|
420
|
-
'f32.div':
|
|
421
|
-
'f32.neg':
|
|
422
|
-
'f32.abs':
|
|
423
|
-
'f32.sqrt':
|
|
424
|
-
'f32.ceil':
|
|
425
|
-
'f32.floor':
|
|
426
|
-
'f32.trunc':
|
|
427
|
-
'f32.nearest':
|
|
428
|
-
|
|
429
|
-
'f64.add':
|
|
430
|
-
'f64.sub':
|
|
431
|
-
'f64.mul':
|
|
432
|
-
'f64.div':
|
|
433
|
-
'f64.neg':
|
|
434
|
-
'f64.abs':
|
|
435
|
-
'f64.sqrt':
|
|
436
|
-
'f64.ceil':
|
|
437
|
-
'f64.floor':
|
|
438
|
-
'f64.trunc':
|
|
439
|
-
'f64.nearest':
|
|
327
|
+
'f32.add': (a, b) => Math.fround(a + b),
|
|
328
|
+
'f32.sub': (a, b) => Math.fround(a - b),
|
|
329
|
+
'f32.mul': (a, b) => Math.fround(a * b),
|
|
330
|
+
'f32.div': (a, b) => Math.fround(a / b),
|
|
331
|
+
'f32.neg': (a) => Math.fround(-a),
|
|
332
|
+
'f32.abs': (a) => Math.fround(Math.abs(a)),
|
|
333
|
+
'f32.sqrt': (a) => Math.fround(Math.sqrt(a)),
|
|
334
|
+
'f32.ceil': (a) => Math.fround(Math.ceil(a)),
|
|
335
|
+
'f32.floor': (a) => Math.fround(Math.floor(a)),
|
|
336
|
+
'f32.trunc': (a) => Math.fround(Math.trunc(a)),
|
|
337
|
+
'f32.nearest': (a) => Math.fround(roundEven(a)),
|
|
338
|
+
|
|
339
|
+
'f64.add': (a, b) => a + b,
|
|
340
|
+
'f64.sub': (a, b) => a - b,
|
|
341
|
+
'f64.mul': (a, b) => a * b,
|
|
342
|
+
'f64.div': (a, b) => a / b,
|
|
343
|
+
'f64.neg': (a) => -a,
|
|
344
|
+
'f64.abs': Math.abs,
|
|
345
|
+
'f64.sqrt': Math.sqrt,
|
|
346
|
+
'f64.ceil': Math.ceil,
|
|
347
|
+
'f64.floor': Math.floor,
|
|
348
|
+
'f64.trunc': Math.trunc,
|
|
349
|
+
'f64.nearest': roundEven,
|
|
440
350
|
|
|
441
351
|
// Bit-exact reinterprets (preserve NaN payloads)
|
|
442
|
-
'i32.reinterpret_f32':
|
|
443
|
-
'f32.reinterpret_i32':
|
|
444
|
-
'i64.reinterpret_f64':
|
|
445
|
-
'f64.reinterpret_i64':
|
|
352
|
+
'i32.reinterpret_f32': i32FromF32,
|
|
353
|
+
'f32.reinterpret_i32': f32FromI32,
|
|
354
|
+
'i64.reinterpret_f64': i64FromF64,
|
|
355
|
+
'f64.reinterpret_i64': f64FromI64,
|
|
446
356
|
|
|
447
357
|
// Numeric conversions (value-preserving where representable)
|
|
448
|
-
'f32.convert_i32_s':
|
|
449
|
-
'f32.convert_i32_u':
|
|
450
|
-
'f32.convert_i64_s':
|
|
451
|
-
'f32.convert_i64_u':
|
|
452
|
-
'f64.convert_i32_s':
|
|
453
|
-
'f64.convert_i32_u':
|
|
454
|
-
'f64.convert_i64_s':
|
|
455
|
-
'f64.convert_i64_u':
|
|
456
|
-
'f32.demote_f64':
|
|
457
|
-
'f64.promote_f32':
|
|
358
|
+
'f32.convert_i32_s': (a) => Math.fround(a | 0),
|
|
359
|
+
'f32.convert_i32_u': (a) => Math.fround(a >>> 0),
|
|
360
|
+
'f32.convert_i64_s': (a) => Math.fround(Number(BigInt.asIntN(64, a))),
|
|
361
|
+
'f32.convert_i64_u': (a) => Math.fround(Number(BigInt.asUintN(64, a))),
|
|
362
|
+
'f64.convert_i32_s': (a) => (a | 0),
|
|
363
|
+
'f64.convert_i32_u': (a) => (a >>> 0),
|
|
364
|
+
'f64.convert_i64_s': (a) => Number(BigInt.asIntN(64, a)),
|
|
365
|
+
'f64.convert_i64_u': (a) => Number(BigInt.asUintN(64, a)),
|
|
366
|
+
'f32.demote_f64': (a) => Math.fround(a),
|
|
367
|
+
'f64.promote_f32': (a) => Math.fround(a),
|
|
458
368
|
}
|
|
459
369
|
|
|
460
370
|
/**
|
|
@@ -522,9 +432,8 @@ const makeConst = (type, value) => {
|
|
|
522
432
|
const fold = (ast) => {
|
|
523
433
|
return walkPost(ast, (node) => {
|
|
524
434
|
if (!Array.isArray(node)) return
|
|
525
|
-
const
|
|
526
|
-
if (!
|
|
527
|
-
const [fn, t] = entry
|
|
435
|
+
const fn = FOLDABLE[node[0]]
|
|
436
|
+
if (!fn) return
|
|
528
437
|
|
|
529
438
|
// Unary
|
|
530
439
|
if (fn.length === 1 && node.length === 2) {
|
|
@@ -532,7 +441,7 @@ const fold = (ast) => {
|
|
|
532
441
|
if (!a) return
|
|
533
442
|
const r = fn(a.value)
|
|
534
443
|
if (r === null) return
|
|
535
|
-
return makeConst(
|
|
444
|
+
return makeConst(resultType(node[0]), r)
|
|
536
445
|
}
|
|
537
446
|
// Binary
|
|
538
447
|
if (fn.length === 2 && node.length === 3) {
|
|
@@ -540,7 +449,7 @@ const fold = (ast) => {
|
|
|
540
449
|
if (!a || !b) return
|
|
541
450
|
const r = fn(a.value, b.value)
|
|
542
451
|
if (r === null) return
|
|
543
|
-
return makeConst(
|
|
452
|
+
return makeConst(resultType(node[0]), r)
|
|
544
453
|
}
|
|
545
454
|
})
|
|
546
455
|
}
|
|
@@ -1184,9 +1093,19 @@ const eliminateDeadStores = (funcNode, params, useCounts) => {
|
|
|
1184
1093
|
const name = typeof sub[1] === 'string' ? sub[1] : null
|
|
1185
1094
|
if (!name || params.has(name)) continue
|
|
1186
1095
|
const uses = getPostUseCount(name)
|
|
1187
|
-
// Dead store: set but never read
|
|
1188
|
-
if (sub[0] === 'local.set' && uses.gets === 0 && uses.tees === 0
|
|
1189
|
-
|
|
1096
|
+
// Dead store: set but never read.
|
|
1097
|
+
if (sub[0] === 'local.set' && uses.gets === 0 && uses.tees === 0) {
|
|
1098
|
+
// `(local.set $x VALUE)` — drop the store with its value, but only when
|
|
1099
|
+
// VALUE is pure (its side effects would otherwise still need to run).
|
|
1100
|
+
if (sub.length === 3) {
|
|
1101
|
+
if (isPure(sub[2])) { funcNode.splice(i, 1); changed = true }
|
|
1102
|
+
}
|
|
1103
|
+
// Bare `(local.set $x)` — the value is implicit on the stack (e.g. an
|
|
1104
|
+
// exception payload landing from a `try_table` catch). Demote to `drop`
|
|
1105
|
+
// so the dead store goes away without unbalancing the stack.
|
|
1106
|
+
else if (sub.length === 2) {
|
|
1107
|
+
funcNode[i] = ['drop']; changed = true
|
|
1108
|
+
}
|
|
1190
1109
|
}
|
|
1191
1110
|
// Unused local declaration
|
|
1192
1111
|
else if (sub[0] === 'local' && name[0] === '$' && uses.gets === 0 && uses.sets === 0 && uses.tees === 0) {
|
|
@@ -1529,12 +1448,12 @@ const inlineOnce = (ast) => {
|
|
|
1529
1448
|
|
|
1530
1449
|
const callee = funcByName.get(calleeName)
|
|
1531
1450
|
const params = [], locals = []
|
|
1532
|
-
let
|
|
1451
|
+
let inlResult = null
|
|
1533
1452
|
for (let i = 2; i < callee.length; i++) {
|
|
1534
1453
|
const c = callee[i]
|
|
1535
1454
|
if (typeof c === 'string' || !Array.isArray(c)) continue
|
|
1536
1455
|
if (c[0] === 'param') params.push({ name: c[1], type: c[2] })
|
|
1537
|
-
else if (c[0] === 'result') { if (c.length > 1)
|
|
1456
|
+
else if (c[0] === 'result') { if (c.length > 1) inlResult = c[1] }
|
|
1538
1457
|
else if (c[0] === 'local') locals.push({ name: c[1], type: c[2] })
|
|
1539
1458
|
else if (c[0] === 'export' || c[0] === 'type') continue
|
|
1540
1459
|
else break
|
|
@@ -1590,8 +1509,8 @@ const inlineOnce = (ast) => {
|
|
|
1590
1509
|
.map(l => ['local.set', rename.get(l.name), zeroFor(l.type)])
|
|
1591
1510
|
const inner = cBody.map(sub)
|
|
1592
1511
|
done = true
|
|
1593
|
-
return
|
|
1594
|
-
? ['block', exit, ['result',
|
|
1512
|
+
return inlResult
|
|
1513
|
+
? ['block', exit, ['result', inlResult], ...setup, ...resets, ...inner]
|
|
1595
1514
|
: ['block', exit, ...setup, ...resets, ...inner]
|
|
1596
1515
|
})
|
|
1597
1516
|
if (replaced !== fn[i]) fn[i] = replaced
|
|
@@ -1634,6 +1553,13 @@ const targetsLabel = (body, label) => {
|
|
|
1634
1553
|
if (typeof n[j] === 'string') { if (n[j] === label) { found = true; return } }
|
|
1635
1554
|
else break
|
|
1636
1555
|
}
|
|
1556
|
+
} else if (op === 'catch' || op === 'catch_ref') {
|
|
1557
|
+
// `try_table` catch clause `(catch $tag $label)` / `(catch_ref $tag $label)`
|
|
1558
|
+
// branches to an enclosing block label just like `br` does.
|
|
1559
|
+
if (n[2] === label) { found = true; return }
|
|
1560
|
+
} else if (op === 'catch_all' || op === 'catch_all_ref') {
|
|
1561
|
+
// `(catch_all $label)` / `(catch_all_ref $label)`
|
|
1562
|
+
if (n[1] === label) { found = true; return }
|
|
1637
1563
|
}
|
|
1638
1564
|
}
|
|
1639
1565
|
for (let i = 1; i < n.length; i++) search(n[i], inner)
|
|
@@ -2903,6 +2829,66 @@ const reorder = (ast) => {
|
|
|
2903
2829
|
|
|
2904
2830
|
// ==================== MAIN ====================
|
|
2905
2831
|
|
|
2832
|
+
/**
|
|
2833
|
+
* Optimization passes, in the order they run within each round. Each entry is
|
|
2834
|
+
* `[optionKey, fn, defaultOn, doc]` — the single source of truth that the
|
|
2835
|
+
* dispatch loop, the `OPTS` catalogue, and `normalize` all derive from.
|
|
2836
|
+
* Passes that are off by default can bloat output or are expensive — opt-in.
|
|
2837
|
+
*/
|
|
2838
|
+
const PASSES = [
|
|
2839
|
+
['stripmut', stripmut, true, 'strip mut from never-written globals'],
|
|
2840
|
+
['globals', globals, true, 'propagate immutable global constants'],
|
|
2841
|
+
['fold', fold, true, 'constant folding'],
|
|
2842
|
+
['identity', identity, true, 'remove identity ops (x + 0 → x)'],
|
|
2843
|
+
['peephole', peephole, true, 'x-x→0, x&0→0, etc.'],
|
|
2844
|
+
['strength', strength, true, 'strength reduction (x * 2 → x << 1)'],
|
|
2845
|
+
['branch', branch, true, 'simplify constant branches'],
|
|
2846
|
+
['propagate', propagate, true, 'forward-propagate single-use locals & tiny consts (never inflates)'],
|
|
2847
|
+
['inlineOnce', inlineOnce, true, 'inline single-call functions into their lone caller (never duplicates)'],
|
|
2848
|
+
['inline', inline, false, 'inline tiny functions — can duplicate bodies'],
|
|
2849
|
+
['offset', offset, true, 'fold add+const into load/store offset'],
|
|
2850
|
+
['unbranch', unbranch, true, 'remove redundant br at end of own block'],
|
|
2851
|
+
['loopify', loopify, true, 'collapse block+loop+brif while-idiom into loop+if'],
|
|
2852
|
+
['brif', brif, true, 'if-then-br → br_if'],
|
|
2853
|
+
['foldarms', foldarms, false, 'merge identical trailing if arms — can add block wrapper'],
|
|
2854
|
+
['deadcode', deadcode, true, 'eliminate dead code after unreachable/br/return'],
|
|
2855
|
+
['vacuum', vacuum, true, 'remove nops, drop-of-pure, empty branches'],
|
|
2856
|
+
['mergeBlocks', mergeBlocks, true, 'unwrap `(block $L …)` whose label is never targeted'],
|
|
2857
|
+
['coalesce', coalesceLocals, true, 'share local slots between same-type non-overlapping locals'],
|
|
2858
|
+
['locals', localReuse, true, 'remove unused locals'],
|
|
2859
|
+
['dedupe', dedupe, true, 'eliminate duplicate functions'],
|
|
2860
|
+
['dedupTypes', dedupTypes, true, 'merge identical type definitions'],
|
|
2861
|
+
['packData', packData, true, 'trim trailing zeros, merge adjacent data segments'],
|
|
2862
|
+
['reorder', reorder, false, 'put hot functions first — no AST reduction'],
|
|
2863
|
+
['treeshake', treeshake, true, 'remove unused funcs/globals/types/tables'],
|
|
2864
|
+
['minifyImports', minifyImports, false, 'shorten import names — enable only when you control the host'],
|
|
2865
|
+
]
|
|
2866
|
+
|
|
2867
|
+
/** Option name → default-on map — the public catalogue of passes. */
|
|
2868
|
+
const OPTS = Object.fromEntries(PASSES.map(p => [p[0], p[2]]))
|
|
2869
|
+
|
|
2870
|
+
/**
|
|
2871
|
+
* Normalize options to a { passName: bool } map. An explicit object is kept
|
|
2872
|
+
* as-is (preserving `log`/`verbose`), with any unmentioned pass filled to its
|
|
2873
|
+
* default; `true` selects the defaults; a string selects only the named
|
|
2874
|
+
* passes (or all of them via `'all'`).
|
|
2875
|
+
*
|
|
2876
|
+
* @param {boolean|string|Object} opts
|
|
2877
|
+
* @returns {Object}
|
|
2878
|
+
*/
|
|
2879
|
+
const normalize = (opts) => {
|
|
2880
|
+
if (opts === false) return {}
|
|
2881
|
+
if (opts !== true && typeof opts !== 'string') {
|
|
2882
|
+
const m = { ...opts }
|
|
2883
|
+
for (const p of PASSES) if (m[p[0]] === undefined) m[p[0]] = p[2]
|
|
2884
|
+
return m
|
|
2885
|
+
}
|
|
2886
|
+
const set = typeof opts === 'string' ? new Set(opts.split(/\s+/).filter(Boolean)) : null
|
|
2887
|
+
const m = {}
|
|
2888
|
+
for (const p of PASSES) m[p[0]] = set ? (set.has('all') || set.has(p[0])) : p[2]
|
|
2889
|
+
return m
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2906
2892
|
/**
|
|
2907
2893
|
* Optimize AST.
|
|
2908
2894
|
*
|
|
@@ -2936,32 +2922,7 @@ export default function optimize(ast, opts = true) {
|
|
|
2936
2922
|
beforeRound = clone(ast)
|
|
2937
2923
|
const sizeBefore = binarySize(ast)
|
|
2938
2924
|
|
|
2939
|
-
if (opts
|
|
2940
|
-
if (opts.globals) ast = globals(ast)
|
|
2941
|
-
if (opts.fold) ast = fold(ast)
|
|
2942
|
-
if (opts.identity) ast = identity(ast)
|
|
2943
|
-
if (opts.peephole) ast = peephole(ast)
|
|
2944
|
-
if (opts.strength) ast = strength(ast)
|
|
2945
|
-
if (opts.branch) ast = branch(ast)
|
|
2946
|
-
if (opts.propagate) ast = propagate(ast)
|
|
2947
|
-
if (opts.inlineOnce) ast = inlineOnce(ast)
|
|
2948
|
-
if (opts.inline) ast = inline(ast)
|
|
2949
|
-
if (opts.offset) ast = offset(ast)
|
|
2950
|
-
if (opts.unbranch) ast = unbranch(ast)
|
|
2951
|
-
if (opts.loopify) ast = loopify(ast)
|
|
2952
|
-
if (opts.brif) ast = brif(ast)
|
|
2953
|
-
if (opts.foldarms) ast = foldarms(ast)
|
|
2954
|
-
if (opts.deadcode) ast = deadcode(ast)
|
|
2955
|
-
if (opts.vacuum) ast = vacuum(ast)
|
|
2956
|
-
if (opts.mergeBlocks) ast = mergeBlocks(ast)
|
|
2957
|
-
if (opts.coalesce) ast = coalesceLocals(ast)
|
|
2958
|
-
if (opts.locals) ast = localReuse(ast)
|
|
2959
|
-
if (opts.dedupe) ast = dedupe(ast)
|
|
2960
|
-
if (opts.dedupTypes) ast = dedupTypes(ast)
|
|
2961
|
-
if (opts.packData) ast = packData(ast)
|
|
2962
|
-
if (opts.reorder) ast = reorder(ast)
|
|
2963
|
-
if (opts.treeshake) ast = treeshake(ast)
|
|
2964
|
-
if (opts.minifyImports) ast = minifyImports(ast)
|
|
2925
|
+
for (const [key, fn] of PASSES) if (opts[key]) ast = fn(ast)
|
|
2965
2926
|
// Second propagate sweep: `inlineOnce`/`inline` (above) leave fresh
|
|
2966
2927
|
// `(local.set $p arg) … (local.get $p)` wrappers around each inlined call;
|
|
2967
2928
|
// re-running propagation collapses them within this same round, so the size
|