watr 4.6.6 → 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 +334 -398
- 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 +171 -227
- 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
|
}
|
|
@@ -1539,12 +1448,12 @@ const inlineOnce = (ast) => {
|
|
|
1539
1448
|
|
|
1540
1449
|
const callee = funcByName.get(calleeName)
|
|
1541
1450
|
const params = [], locals = []
|
|
1542
|
-
let
|
|
1451
|
+
let inlResult = null
|
|
1543
1452
|
for (let i = 2; i < callee.length; i++) {
|
|
1544
1453
|
const c = callee[i]
|
|
1545
1454
|
if (typeof c === 'string' || !Array.isArray(c)) continue
|
|
1546
1455
|
if (c[0] === 'param') params.push({ name: c[1], type: c[2] })
|
|
1547
|
-
else if (c[0] === 'result') { if (c.length > 1)
|
|
1456
|
+
else if (c[0] === 'result') { if (c.length > 1) inlResult = c[1] }
|
|
1548
1457
|
else if (c[0] === 'local') locals.push({ name: c[1], type: c[2] })
|
|
1549
1458
|
else if (c[0] === 'export' || c[0] === 'type') continue
|
|
1550
1459
|
else break
|
|
@@ -1600,8 +1509,8 @@ const inlineOnce = (ast) => {
|
|
|
1600
1509
|
.map(l => ['local.set', rename.get(l.name), zeroFor(l.type)])
|
|
1601
1510
|
const inner = cBody.map(sub)
|
|
1602
1511
|
done = true
|
|
1603
|
-
return
|
|
1604
|
-
? ['block', exit, ['result',
|
|
1512
|
+
return inlResult
|
|
1513
|
+
? ['block', exit, ['result', inlResult], ...setup, ...resets, ...inner]
|
|
1605
1514
|
: ['block', exit, ...setup, ...resets, ...inner]
|
|
1606
1515
|
})
|
|
1607
1516
|
if (replaced !== fn[i]) fn[i] = replaced
|
|
@@ -2920,6 +2829,66 @@ const reorder = (ast) => {
|
|
|
2920
2829
|
|
|
2921
2830
|
// ==================== MAIN ====================
|
|
2922
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
|
+
|
|
2923
2892
|
/**
|
|
2924
2893
|
* Optimize AST.
|
|
2925
2894
|
*
|
|
@@ -2953,32 +2922,7 @@ export default function optimize(ast, opts = true) {
|
|
|
2953
2922
|
beforeRound = clone(ast)
|
|
2954
2923
|
const sizeBefore = binarySize(ast)
|
|
2955
2924
|
|
|
2956
|
-
if (opts
|
|
2957
|
-
if (opts.globals) ast = globals(ast)
|
|
2958
|
-
if (opts.fold) ast = fold(ast)
|
|
2959
|
-
if (opts.identity) ast = identity(ast)
|
|
2960
|
-
if (opts.peephole) ast = peephole(ast)
|
|
2961
|
-
if (opts.strength) ast = strength(ast)
|
|
2962
|
-
if (opts.branch) ast = branch(ast)
|
|
2963
|
-
if (opts.propagate) ast = propagate(ast)
|
|
2964
|
-
if (opts.inlineOnce) ast = inlineOnce(ast)
|
|
2965
|
-
if (opts.inline) ast = inline(ast)
|
|
2966
|
-
if (opts.offset) ast = offset(ast)
|
|
2967
|
-
if (opts.unbranch) ast = unbranch(ast)
|
|
2968
|
-
if (opts.loopify) ast = loopify(ast)
|
|
2969
|
-
if (opts.brif) ast = brif(ast)
|
|
2970
|
-
if (opts.foldarms) ast = foldarms(ast)
|
|
2971
|
-
if (opts.deadcode) ast = deadcode(ast)
|
|
2972
|
-
if (opts.vacuum) ast = vacuum(ast)
|
|
2973
|
-
if (opts.mergeBlocks) ast = mergeBlocks(ast)
|
|
2974
|
-
if (opts.coalesce) ast = coalesceLocals(ast)
|
|
2975
|
-
if (opts.locals) ast = localReuse(ast)
|
|
2976
|
-
if (opts.dedupe) ast = dedupe(ast)
|
|
2977
|
-
if (opts.dedupTypes) ast = dedupTypes(ast)
|
|
2978
|
-
if (opts.packData) ast = packData(ast)
|
|
2979
|
-
if (opts.reorder) ast = reorder(ast)
|
|
2980
|
-
if (opts.treeshake) ast = treeshake(ast)
|
|
2981
|
-
if (opts.minifyImports) ast = minifyImports(ast)
|
|
2925
|
+
for (const [key, fn] of PASSES) if (opts[key]) ast = fn(ast)
|
|
2982
2926
|
// Second propagate sweep: `inlineOnce`/`inline` (above) leave fresh
|
|
2983
2927
|
// `(local.set $p arg) … (local.get $p)` wrappers around each inlined call;
|
|
2984
2928
|
// re-running propagation collapses them within this same round, so the size
|