watr 4.6.6 → 4.6.8
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 +340 -405
- 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/encode.js +1 -1
- package/src/optimize.js +179 -236
- 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/encode.js
CHANGED
|
@@ -129,7 +129,7 @@ const _u8 = new Uint8Array(_buf), _i32 = new Int32Array(_buf), _f32 = new Float3
|
|
|
129
129
|
i64.parse = n => {
|
|
130
130
|
n = cleanInt(n)
|
|
131
131
|
const neg = n[0] === '-'
|
|
132
|
-
const body = neg ? n.slice(1) : n
|
|
132
|
+
const body = neg || n[0] === '+' ? n.slice(1) : n
|
|
133
133
|
// Range check on the literal string before BigInt conversion (lexicographic compare on clean digits).
|
|
134
134
|
let max
|
|
135
135
|
if (body[0] === '0' && (body[1] === 'x' || body[1] === 'X')) {
|
package/src/optimize.js
CHANGED
|
@@ -7,40 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import parse from './parse.js'
|
|
9
9
|
import compile from './compile.js'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 { i32, i64 } from './encode.js'
|
|
11
|
+
import { walk, walkPost, clone } from './util.js'
|
|
12
|
+
import { resultType } from './const.js'
|
|
44
13
|
|
|
45
14
|
/**
|
|
46
15
|
* Recursively count AST nodes — fast size heuristic without compiling.
|
|
@@ -77,64 +46,6 @@ const equal = (a, b) => {
|
|
|
77
46
|
return true
|
|
78
47
|
}
|
|
79
48
|
|
|
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
49
|
/**
|
|
139
50
|
* Locate the parts of an `(if ...)` node:
|
|
140
51
|
* condIdx → index of the condition expression
|
|
@@ -344,117 +255,117 @@ const i64c = (fn) => (a, b) => fn(a, b) ? 1 : 0
|
|
|
344
255
|
const u64c = (fn) => (a, b) => fn(BigInt.asUintN(64, a), BigInt.asUintN(64, b)) ? 1 : 0
|
|
345
256
|
|
|
346
257
|
/**
|
|
347
|
-
* Constant folders, keyed by op. Each entry is
|
|
348
|
-
*
|
|
258
|
+
* Constant folders, keyed by op. Each entry is the fold function; the result
|
|
259
|
+
* value-type is derived once via `resultType` (see `fold`).
|
|
349
260
|
*/
|
|
350
261
|
const FOLDABLE = {
|
|
351
262
|
// 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':
|
|
263
|
+
'i32.add': (a, b) => (a + b) | 0,
|
|
264
|
+
'i32.sub': (a, b) => (a - b) | 0,
|
|
265
|
+
'i32.mul': (a, b) => Math.imul(a, b),
|
|
266
|
+
'i32.div_s': (a, b) => b !== 0 ? (a / b) | 0 : null,
|
|
267
|
+
'i32.div_u': (a, b) => b !== 0 ? ((a >>> 0) / (b >>> 0)) | 0 : null,
|
|
268
|
+
'i32.rem_s': (a, b) => b !== 0 ? (a % b) | 0 : null,
|
|
269
|
+
'i32.rem_u': (a, b) => b !== 0 ? ((a >>> 0) % (b >>> 0)) | 0 : null,
|
|
270
|
+
'i32.and': (a, b) => a & b,
|
|
271
|
+
'i32.or': (a, b) => a | b,
|
|
272
|
+
'i32.xor': (a, b) => a ^ b,
|
|
273
|
+
'i32.shl': (a, b) => a << (b & 31),
|
|
274
|
+
'i32.shr_s': (a, b) => a >> (b & 31),
|
|
275
|
+
'i32.shr_u': (a, b) => a >>> (b & 31),
|
|
276
|
+
'i32.rotl': (a, b) => { b &= 31; return ((a << b) | (a >>> (32 - b))) | 0 },
|
|
277
|
+
'i32.rotr': (a, b) => { b &= 31; return ((a >>> b) | (a << (32 - b))) | 0 },
|
|
278
|
+
'i32.eq': i32c((a, b) => a === b),
|
|
279
|
+
'i32.ne': i32c((a, b) => a !== b),
|
|
280
|
+
'i32.lt_s': i32c((a, b) => a < b),
|
|
281
|
+
'i32.lt_u': u32c((a, b) => a < b),
|
|
282
|
+
'i32.gt_s': i32c((a, b) => a > b),
|
|
283
|
+
'i32.gt_u': u32c((a, b) => a > b),
|
|
284
|
+
'i32.le_s': i32c((a, b) => a <= b),
|
|
285
|
+
'i32.le_u': u32c((a, b) => a <= b),
|
|
286
|
+
'i32.ge_s': i32c((a, b) => a >= b),
|
|
287
|
+
'i32.ge_u': u32c((a, b) => a >= b),
|
|
288
|
+
'i32.eqz': (a) => a === 0 ? 1 : 0,
|
|
289
|
+
'i32.clz': (a) => Math.clz32(a),
|
|
290
|
+
'i32.ctz': (a) => a === 0 ? 32 : 31 - Math.clz32(a & -a),
|
|
291
|
+
'i32.popcnt': (a) => { let c = 0; while (a) { c += a & 1; a >>>= 1 } return c },
|
|
292
|
+
'i32.wrap_i64': (a) => Number(BigInt.asIntN(32, a)),
|
|
293
|
+
'i32.extend8_s': (a) => (a << 24) >> 24,
|
|
294
|
+
'i32.extend16_s': (a) => (a << 16) >> 16,
|
|
384
295
|
|
|
385
296
|
// 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':
|
|
297
|
+
'i64.add': (a, b) => BigInt.asIntN(64, a + b),
|
|
298
|
+
'i64.sub': (a, b) => BigInt.asIntN(64, a - b),
|
|
299
|
+
'i64.mul': (a, b) => BigInt.asIntN(64, a * b),
|
|
300
|
+
'i64.div_s': (a, b) => b !== 0n ? BigInt.asIntN(64, a / b) : null,
|
|
301
|
+
'i64.div_u': (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) / BigInt.asUintN(64, b)) : null,
|
|
302
|
+
'i64.rem_s': (a, b) => b !== 0n ? BigInt.asIntN(64, a % b) : null,
|
|
303
|
+
'i64.rem_u': (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) % BigInt.asUintN(64, b)) : null,
|
|
304
|
+
'i64.and': (a, b) => BigInt.asIntN(64, a & b),
|
|
305
|
+
'i64.or': (a, b) => BigInt.asIntN(64, a | b),
|
|
306
|
+
'i64.xor': (a, b) => BigInt.asIntN(64, a ^ b),
|
|
307
|
+
'i64.shl': (a, b) => BigInt.asIntN(64, a << (b & 63n)),
|
|
308
|
+
'i64.shr_s': (a, b) => BigInt.asIntN(64, a >> (b & 63n)),
|
|
309
|
+
'i64.shr_u': (a, b) => BigInt.asUintN(64, BigInt.asUintN(64, a) >> (b & 63n)),
|
|
310
|
+
'i64.eq': i64c((a, b) => a === b),
|
|
311
|
+
'i64.ne': i64c((a, b) => a !== b),
|
|
312
|
+
'i64.lt_s': i64c((a, b) => a < b),
|
|
313
|
+
'i64.lt_u': u64c((a, b) => a < b),
|
|
314
|
+
'i64.gt_s': i64c((a, b) => a > b),
|
|
315
|
+
'i64.gt_u': u64c((a, b) => a > b),
|
|
316
|
+
'i64.le_s': i64c((a, b) => a <= b),
|
|
317
|
+
'i64.le_u': u64c((a, b) => a <= b),
|
|
318
|
+
'i64.ge_s': i64c((a, b) => a >= b),
|
|
319
|
+
'i64.ge_u': u64c((a, b) => a >= b),
|
|
320
|
+
'i64.eqz': (a) => a === 0n ? 1 : 0,
|
|
321
|
+
'i64.extend_i32_s': (a) => BigInt(a),
|
|
322
|
+
'i64.extend_i32_u': (a) => BigInt(a >>> 0),
|
|
323
|
+
'i64.extend8_s': (a) => BigInt.asIntN(64, BigInt.asIntN(8, a)),
|
|
324
|
+
'i64.extend16_s': (a) => BigInt.asIntN(64, BigInt.asIntN(16, a)),
|
|
325
|
+
'i64.extend32_s': (a) => BigInt.asIntN(64, BigInt.asIntN(32, a)),
|
|
415
326
|
|
|
416
327
|
// 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':
|
|
328
|
+
'f32.add': (a, b) => Math.fround(a + b),
|
|
329
|
+
'f32.sub': (a, b) => Math.fround(a - b),
|
|
330
|
+
'f32.mul': (a, b) => Math.fround(a * b),
|
|
331
|
+
'f32.div': (a, b) => Math.fround(a / b),
|
|
332
|
+
'f32.neg': (a) => Math.fround(-a),
|
|
333
|
+
'f32.abs': (a) => Math.fround(Math.abs(a)),
|
|
334
|
+
'f32.sqrt': (a) => Math.fround(Math.sqrt(a)),
|
|
335
|
+
'f32.ceil': (a) => Math.fround(Math.ceil(a)),
|
|
336
|
+
'f32.floor': (a) => Math.fround(Math.floor(a)),
|
|
337
|
+
'f32.trunc': (a) => Math.fround(Math.trunc(a)),
|
|
338
|
+
'f32.nearest': (a) => Math.fround(roundEven(a)),
|
|
339
|
+
|
|
340
|
+
'f64.add': (a, b) => a + b,
|
|
341
|
+
'f64.sub': (a, b) => a - b,
|
|
342
|
+
'f64.mul': (a, b) => a * b,
|
|
343
|
+
'f64.div': (a, b) => a / b,
|
|
344
|
+
'f64.neg': (a) => -a,
|
|
345
|
+
'f64.abs': Math.abs,
|
|
346
|
+
'f64.sqrt': Math.sqrt,
|
|
347
|
+
'f64.ceil': Math.ceil,
|
|
348
|
+
'f64.floor': Math.floor,
|
|
349
|
+
'f64.trunc': Math.trunc,
|
|
350
|
+
'f64.nearest': roundEven,
|
|
440
351
|
|
|
441
352
|
// Bit-exact reinterprets (preserve NaN payloads)
|
|
442
|
-
'i32.reinterpret_f32':
|
|
443
|
-
'f32.reinterpret_i32':
|
|
444
|
-
'i64.reinterpret_f64':
|
|
445
|
-
'f64.reinterpret_i64':
|
|
353
|
+
'i32.reinterpret_f32': i32FromF32,
|
|
354
|
+
'f32.reinterpret_i32': f32FromI32,
|
|
355
|
+
'i64.reinterpret_f64': i64FromF64,
|
|
356
|
+
'f64.reinterpret_i64': f64FromI64,
|
|
446
357
|
|
|
447
358
|
// 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':
|
|
359
|
+
'f32.convert_i32_s': (a) => Math.fround(a | 0),
|
|
360
|
+
'f32.convert_i32_u': (a) => Math.fround(a >>> 0),
|
|
361
|
+
'f32.convert_i64_s': (a) => Math.fround(Number(BigInt.asIntN(64, a))),
|
|
362
|
+
'f32.convert_i64_u': (a) => Math.fround(Number(BigInt.asUintN(64, a))),
|
|
363
|
+
'f64.convert_i32_s': (a) => (a | 0),
|
|
364
|
+
'f64.convert_i32_u': (a) => (a >>> 0),
|
|
365
|
+
'f64.convert_i64_s': (a) => Number(BigInt.asIntN(64, a)),
|
|
366
|
+
'f64.convert_i64_u': (a) => Number(BigInt.asUintN(64, a)),
|
|
367
|
+
'f32.demote_f64': (a) => Math.fround(a),
|
|
368
|
+
'f64.promote_f32': (a) => Math.fround(a),
|
|
458
369
|
}
|
|
459
370
|
|
|
460
371
|
/**
|
|
@@ -487,8 +398,8 @@ const _parseNanF32 = (s, i = s?.indexOf?.('nan')) => {
|
|
|
487
398
|
const getConst = (node) => {
|
|
488
399
|
if (!Array.isArray(node) || node.length !== 2) return null
|
|
489
400
|
const [op, val] = node
|
|
490
|
-
if (op === 'i32.const') return { type: 'i32', value:
|
|
491
|
-
if (op === 'i64.const') return { type: 'i64', value: BigInt(val) }
|
|
401
|
+
if (op === 'i32.const') return { type: 'i32', value: (typeof val === 'string' ? i32.parse(val) : val) | 0 }
|
|
402
|
+
if (op === 'i64.const') return { type: 'i64', value: typeof val === 'string' ? i64.parse(val) : BigInt(val) }
|
|
492
403
|
if (op === 'f32.const') {
|
|
493
404
|
const n = _parseNanF32(val)
|
|
494
405
|
return { type: 'f32', value: n !== null ? n : Math.fround(Number(val)) }
|
|
@@ -522,9 +433,8 @@ const makeConst = (type, value) => {
|
|
|
522
433
|
const fold = (ast) => {
|
|
523
434
|
return walkPost(ast, (node) => {
|
|
524
435
|
if (!Array.isArray(node)) return
|
|
525
|
-
const
|
|
526
|
-
if (!
|
|
527
|
-
const [fn, t] = entry
|
|
436
|
+
const fn = FOLDABLE[node[0]]
|
|
437
|
+
if (!fn) return
|
|
528
438
|
|
|
529
439
|
// Unary
|
|
530
440
|
if (fn.length === 1 && node.length === 2) {
|
|
@@ -532,7 +442,7 @@ const fold = (ast) => {
|
|
|
532
442
|
if (!a) return
|
|
533
443
|
const r = fn(a.value)
|
|
534
444
|
if (r === null) return
|
|
535
|
-
return makeConst(
|
|
445
|
+
return makeConst(resultType(node[0]), r)
|
|
536
446
|
}
|
|
537
447
|
// Binary
|
|
538
448
|
if (fn.length === 2 && node.length === 3) {
|
|
@@ -540,7 +450,7 @@ const fold = (ast) => {
|
|
|
540
450
|
if (!a || !b) return
|
|
541
451
|
const r = fn(a.value, b.value)
|
|
542
452
|
if (r === null) return
|
|
543
|
-
return makeConst(
|
|
453
|
+
return makeConst(resultType(node[0]), r)
|
|
544
454
|
}
|
|
545
455
|
})
|
|
546
456
|
}
|
|
@@ -1344,8 +1254,7 @@ const inline = (ast) => {
|
|
|
1344
1254
|
}
|
|
1345
1255
|
|
|
1346
1256
|
// Substitute params with args
|
|
1347
|
-
const substituted = clone(body)
|
|
1348
|
-
walkPost(substituted, (n) => {
|
|
1257
|
+
const substituted = walkPost(clone(body), (n) => {
|
|
1349
1258
|
if (!Array.isArray(n) || n[0] !== 'local.get') return
|
|
1350
1259
|
const local = n[1]
|
|
1351
1260
|
const paramIdx = params.findIndex(p => p.name === local)
|
|
@@ -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
|
|
@@ -1946,10 +1855,6 @@ const PEEPHOLE = {
|
|
|
1946
1855
|
'i64.sub': (a, b) => equal(a, b) ? ['i64.const', 0n] : null,
|
|
1947
1856
|
'i32.xor': (a, b) => equal(a, b) ? ['i32.const', 0] : null,
|
|
1948
1857
|
'i64.xor': (a, b) => equal(a, b) ? ['i64.const', 0n] : null,
|
|
1949
|
-
'i32.and': (a, b) => equal(a, b) ? a : null,
|
|
1950
|
-
'i64.and': (a, b) => equal(a, b) ? a : null,
|
|
1951
|
-
'i32.or': (a, b) => equal(a, b) ? a : null,
|
|
1952
|
-
'i64.or': (a, b) => equal(a, b) ? a : null,
|
|
1953
1858
|
'i32.eq': (a, b) => equal(a, b) ? ['i32.const', 1] : null,
|
|
1954
1859
|
'i64.eq': (a, b) => equal(a, b) ? ['i32.const', 1] : null,
|
|
1955
1860
|
'i32.ne': (a, b) => equal(a, b) ? ['i32.const', 0] : null,
|
|
@@ -1983,22 +1888,25 @@ const PEEPHOLE = {
|
|
|
1983
1888
|
return null
|
|
1984
1889
|
},
|
|
1985
1890
|
'i32.and': (a, b) => {
|
|
1891
|
+
if (equal(a, b)) return a
|
|
1986
1892
|
const ca = getConst(a), cb = getConst(b)
|
|
1987
1893
|
if (ca?.value === 0 || cb?.value === 0) return ['i32.const', 0]
|
|
1988
|
-
// x & x → x handled above in self-operands, but null here lets that win
|
|
1989
1894
|
return null
|
|
1990
1895
|
},
|
|
1991
1896
|
'i64.and': (a, b) => {
|
|
1897
|
+
if (equal(a, b)) return a
|
|
1992
1898
|
const ca = getConst(a), cb = getConst(b)
|
|
1993
1899
|
if (ca?.value === 0n || cb?.value === 0n) return ['i64.const', 0n]
|
|
1994
1900
|
return null
|
|
1995
1901
|
},
|
|
1996
1902
|
'i32.or': (a, b) => {
|
|
1903
|
+
if (equal(a, b)) return a
|
|
1997
1904
|
const ca = getConst(a), cb = getConst(b)
|
|
1998
1905
|
if (ca?.value === -1 || cb?.value === -1) return ['i32.const', -1]
|
|
1999
1906
|
return null
|
|
2000
1907
|
},
|
|
2001
1908
|
'i64.or': (a, b) => {
|
|
1909
|
+
if (equal(a, b)) return a
|
|
2002
1910
|
const ca = getConst(a), cb = getConst(b)
|
|
2003
1911
|
if (ca?.value === -1n || cb?.value === -1n) return ['i64.const', -1n]
|
|
2004
1912
|
return null
|
|
@@ -2920,6 +2828,66 @@ const reorder = (ast) => {
|
|
|
2920
2828
|
|
|
2921
2829
|
// ==================== MAIN ====================
|
|
2922
2830
|
|
|
2831
|
+
/**
|
|
2832
|
+
* Optimization passes, in the order they run within each round. Each entry is
|
|
2833
|
+
* `[optionKey, fn, defaultOn, doc]` — the single source of truth that the
|
|
2834
|
+
* dispatch loop, the `OPTS` catalogue, and `normalize` all derive from.
|
|
2835
|
+
* Passes that are off by default can bloat output or are expensive — opt-in.
|
|
2836
|
+
*/
|
|
2837
|
+
const PASSES = [
|
|
2838
|
+
['stripmut', stripmut, true, 'strip mut from never-written globals'],
|
|
2839
|
+
['globals', globals, true, 'propagate immutable global constants'],
|
|
2840
|
+
['fold', fold, true, 'constant folding'],
|
|
2841
|
+
['identity', identity, true, 'remove identity ops (x + 0 → x)'],
|
|
2842
|
+
['peephole', peephole, true, 'x-x→0, x&0→0, etc.'],
|
|
2843
|
+
['strength', strength, true, 'strength reduction (x * 2 → x << 1)'],
|
|
2844
|
+
['branch', branch, true, 'simplify constant branches'],
|
|
2845
|
+
['propagate', propagate, true, 'forward-propagate single-use locals & tiny consts (never inflates)'],
|
|
2846
|
+
['inlineOnce', inlineOnce, true, 'inline single-call functions into their lone caller (never duplicates)'],
|
|
2847
|
+
['inline', inline, false, 'inline tiny functions — can duplicate bodies'],
|
|
2848
|
+
['offset', offset, true, 'fold add+const into load/store offset'],
|
|
2849
|
+
['unbranch', unbranch, true, 'remove redundant br at end of own block'],
|
|
2850
|
+
['loopify', loopify, true, 'collapse block+loop+brif while-idiom into loop+if'],
|
|
2851
|
+
['brif', brif, true, 'if-then-br → br_if'],
|
|
2852
|
+
['foldarms', foldarms, false, 'merge identical trailing if arms — can add block wrapper'],
|
|
2853
|
+
['deadcode', deadcode, true, 'eliminate dead code after unreachable/br/return'],
|
|
2854
|
+
['vacuum', vacuum, true, 'remove nops, drop-of-pure, empty branches'],
|
|
2855
|
+
['mergeBlocks', mergeBlocks, true, 'unwrap `(block $L …)` whose label is never targeted'],
|
|
2856
|
+
['coalesce', coalesceLocals, true, 'share local slots between same-type non-overlapping locals'],
|
|
2857
|
+
['locals', localReuse, true, 'remove unused locals'],
|
|
2858
|
+
['dedupe', dedupe, true, 'eliminate duplicate functions'],
|
|
2859
|
+
['dedupTypes', dedupTypes, true, 'merge identical type definitions'],
|
|
2860
|
+
['packData', packData, true, 'trim trailing zeros, merge adjacent data segments'],
|
|
2861
|
+
['reorder', reorder, false, 'put hot functions first — no AST reduction'],
|
|
2862
|
+
['treeshake', treeshake, true, 'remove unused funcs/globals/types/tables'],
|
|
2863
|
+
['minifyImports', minifyImports, false, 'shorten import names — enable only when you control the host'],
|
|
2864
|
+
]
|
|
2865
|
+
|
|
2866
|
+
/** Option name → default-on map — the public catalogue of passes. */
|
|
2867
|
+
const OPTS = Object.fromEntries(PASSES.map(p => [p[0], p[2]]))
|
|
2868
|
+
|
|
2869
|
+
/**
|
|
2870
|
+
* Normalize options to a { passName: bool } map. An explicit object is kept
|
|
2871
|
+
* as-is (preserving `log`/`verbose`), with any unmentioned pass filled to its
|
|
2872
|
+
* default; `true` selects the defaults; a string selects only the named
|
|
2873
|
+
* passes (or all of them via `'all'`).
|
|
2874
|
+
*
|
|
2875
|
+
* @param {boolean|string|Object} opts
|
|
2876
|
+
* @returns {Object}
|
|
2877
|
+
*/
|
|
2878
|
+
const normalize = (opts) => {
|
|
2879
|
+
if (opts === false) return {}
|
|
2880
|
+
if (opts !== true && typeof opts !== 'string') {
|
|
2881
|
+
const m = { ...opts }
|
|
2882
|
+
for (const p of PASSES) if (m[p[0]] === undefined) m[p[0]] = p[2]
|
|
2883
|
+
return m
|
|
2884
|
+
}
|
|
2885
|
+
const set = typeof opts === 'string' ? new Set(opts.split(/\s+/).filter(Boolean)) : null
|
|
2886
|
+
const m = {}
|
|
2887
|
+
for (const p of PASSES) m[p[0]] = set ? (set.has('all') || set.has(p[0])) : p[2]
|
|
2888
|
+
return m
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2923
2891
|
/**
|
|
2924
2892
|
* Optimize AST.
|
|
2925
2893
|
*
|
|
@@ -2953,32 +2921,7 @@ export default function optimize(ast, opts = true) {
|
|
|
2953
2921
|
beforeRound = clone(ast)
|
|
2954
2922
|
const sizeBefore = binarySize(ast)
|
|
2955
2923
|
|
|
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)
|
|
2924
|
+
for (const [key, fn] of PASSES) if (opts[key]) ast = fn(ast)
|
|
2982
2925
|
// Second propagate sweep: `inlineOnce`/`inline` (above) leave fresh
|
|
2983
2926
|
// `(local.set $p arg) … (local.get $p)` wrappers around each inlined call;
|
|
2984
2927
|
// re-running propagation collapses them within this same round, so the size
|