watr 4.3.3 → 4.3.4
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 +92 -45
- package/dist/watr.min.js +6 -6
- package/dist/watr.wat +128517 -0
- package/package.json +1 -1
- package/src/optimize.js +108 -60
- package/types/src/optimize.d.ts +4 -5
- package/types/src/optimize.d.ts.map +1 -1
- package/watr.js +2 -2
package/package.json
CHANGED
package/src/optimize.js
CHANGED
|
@@ -285,6 +285,9 @@ const treeshake = (ast) => {
|
|
|
285
285
|
|
|
286
286
|
// ==================== CONSTANT FOLDING ====================
|
|
287
287
|
|
|
288
|
+
/** IEEE 754 roundTiesToEven (bankers' rounding) */
|
|
289
|
+
const roundEven = (x) => x - Math.floor(x) !== 0.5 ? Math.round(x) : 2 * Math.round(x / 2)
|
|
290
|
+
|
|
288
291
|
/** Operators that can be constant-folded */
|
|
289
292
|
const FOLDABLE = {
|
|
290
293
|
// i32
|
|
@@ -358,7 +361,7 @@ const FOLDABLE = {
|
|
|
358
361
|
'f32.ceil': (a) => Math.fround(Math.ceil(a)),
|
|
359
362
|
'f32.floor': (a) => Math.fround(Math.floor(a)),
|
|
360
363
|
'f32.trunc': (a) => Math.fround(Math.trunc(a)),
|
|
361
|
-
'f32.nearest': (a) => Math.fround(
|
|
364
|
+
'f32.nearest': (a) => Math.fround(roundEven(a)),
|
|
362
365
|
|
|
363
366
|
'f64.add': (a, b) => a + b,
|
|
364
367
|
'f64.sub': (a, b) => a - b,
|
|
@@ -370,7 +373,7 @@ const FOLDABLE = {
|
|
|
370
373
|
'f64.ceil': (a) => Math.ceil(a),
|
|
371
374
|
'f64.floor': (a) => Math.floor(a),
|
|
372
375
|
'f64.trunc': (a) => Math.trunc(a),
|
|
373
|
-
'f64.nearest':
|
|
376
|
+
'f64.nearest': roundEven,
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
/**
|
|
@@ -842,82 +845,127 @@ const localReuse = (ast) => {
|
|
|
842
845
|
return result
|
|
843
846
|
}
|
|
844
847
|
|
|
845
|
-
// ====================
|
|
848
|
+
// ==================== PROPAGATION & LOCAL ELIMINATION ====================
|
|
849
|
+
|
|
850
|
+
/** Check if expression is pure (no side effects, no memory ops) */
|
|
851
|
+
const isPure = (node) => {
|
|
852
|
+
if (!Array.isArray(node)) return true
|
|
853
|
+
const op = node[0]
|
|
854
|
+
if (typeof op !== 'string') return false
|
|
855
|
+
if (op === 'call' || op === 'call_indirect' || op === 'return_call' || op === 'return_call_indirect') return false
|
|
856
|
+
if (op.includes('.store') || op.includes('.load') || op.includes('memory.')) return false
|
|
857
|
+
if (op === 'global.set') return false
|
|
858
|
+
for (let i = 1; i < node.length; i++) if (Array.isArray(node[i]) && !isPure(node[i])) return false
|
|
859
|
+
return true
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** Count all local.get/set/tee occurrences in one walk */
|
|
863
|
+
const countLocalUses = (node) => {
|
|
864
|
+
const counts = new Map()
|
|
865
|
+
const ensure = name => { if (!counts.has(name)) counts.set(name, { gets: 0, sets: 0, tees: 0 }); return counts.get(name) }
|
|
866
|
+
walk(node, n => {
|
|
867
|
+
if (!Array.isArray(n) || n.length < 2 || typeof n[1] !== 'string') return
|
|
868
|
+
if (n[0] === 'local.get') ensure(n[1]).gets++
|
|
869
|
+
else if (n[0] === 'local.set') ensure(n[1]).sets++
|
|
870
|
+
else if (n[0] === 'local.tee') ensure(n[1]).tees++
|
|
871
|
+
})
|
|
872
|
+
return counts
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** Can this tracked value be substituted for a local.get? */
|
|
876
|
+
const canSubst = (k) => getConst(k.val) || (k.pure && k.singleUse)
|
|
877
|
+
|
|
878
|
+
/** Try substitute local.get nodes with known values */
|
|
879
|
+
const substGets = (node, known) => walkPost(node, n => {
|
|
880
|
+
if (!Array.isArray(n) || n[0] !== 'local.get' || n.length !== 2) return
|
|
881
|
+
const k = typeof n[1] === 'string' && known.get(n[1])
|
|
882
|
+
if (k && canSubst(k)) return clone(k.val)
|
|
883
|
+
})
|
|
846
884
|
|
|
847
885
|
/**
|
|
848
|
-
* Propagate
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* @returns {Array}
|
|
886
|
+
* Propagate values through locals and eliminate single-use/dead locals.
|
|
887
|
+
* Constants propagate to all uses; pure single-use exprs inline into get site.
|
|
888
|
+
* Multi-pass with batch counting for convergence.
|
|
852
889
|
*/
|
|
853
890
|
const propagate = (ast) => {
|
|
854
891
|
const result = clone(ast)
|
|
855
892
|
|
|
856
|
-
walk(result, (
|
|
857
|
-
if (!Array.isArray(
|
|
893
|
+
walk(result, (funcNode) => {
|
|
894
|
+
if (!Array.isArray(funcNode) || funcNode[0] !== 'func') return
|
|
858
895
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
896
|
+
const params = new Set()
|
|
897
|
+
for (const sub of funcNode)
|
|
898
|
+
if (Array.isArray(sub) && sub[0] === 'param' && typeof sub[1] === 'string') params.add(sub[1])
|
|
862
899
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
900
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
901
|
+
let changed = false
|
|
902
|
+
const uses = countLocalUses(funcNode)
|
|
903
|
+
const getUses = name => uses.get(name) || { gets: 0, sets: 0, tees: 0 }
|
|
904
|
+
const known = new Map()
|
|
868
905
|
|
|
906
|
+
for (let i = 1; i < funcNode.length; i++) {
|
|
907
|
+
const instr = funcNode[i]
|
|
908
|
+
if (!Array.isArray(instr)) continue
|
|
869
909
|
const op = instr[0]
|
|
870
910
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// local.tee also sets
|
|
883
|
-
else if (op === 'local.tee' && instr.length === 3) {
|
|
884
|
-
const local = instr[1]
|
|
885
|
-
const val = instr[2]
|
|
886
|
-
const c = getConst(val)
|
|
887
|
-
if (c && typeof local === 'string') {
|
|
888
|
-
constLocals.set(local, val)
|
|
889
|
-
} else if (typeof local === 'string') {
|
|
890
|
-
constLocals.delete(local)
|
|
891
|
-
}
|
|
911
|
+
if (op === 'param' || op === 'result' || op === 'local' || op === 'type' || op === 'export') continue
|
|
912
|
+
|
|
913
|
+
// Track local.set values
|
|
914
|
+
if (op === 'local.set' && instr.length === 3 && typeof instr[1] === 'string') {
|
|
915
|
+
substGets(instr[2], known) // substitute known values in RHS
|
|
916
|
+
const u = getUses(instr[1])
|
|
917
|
+
known.set(instr[1], {
|
|
918
|
+
val: instr[2], pure: isPure(instr[2]),
|
|
919
|
+
singleUse: u.gets <= 1 && u.sets <= 1 && u.tees === 0
|
|
920
|
+
})
|
|
921
|
+
continue
|
|
892
922
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
923
|
+
|
|
924
|
+
// Invalidate at control-flow boundaries
|
|
925
|
+
if (op === 'block' || op === 'loop' || op === 'if') known.clear()
|
|
926
|
+
// Calls only invalidate non-constant tracked values
|
|
927
|
+
if (op === 'call' || op === 'call_indirect' || op === 'return_call' || op === 'return_call_indirect')
|
|
928
|
+
for (const [k, v] of known) if (!getConst(v.val)) known.delete(k)
|
|
929
|
+
|
|
930
|
+
// Substitute: standalone local.get (walkPost can't replace root)
|
|
931
|
+
if (op === 'local.get' && instr.length === 2 && typeof instr[1] === 'string') {
|
|
932
|
+
const k = known.get(instr[1])
|
|
933
|
+
if (k && canSubst(k)) {
|
|
934
|
+
const r = clone(k.val)
|
|
935
|
+
instr.length = 0; instr.push(...(Array.isArray(r) ? r : [r]))
|
|
936
|
+
changed = true; continue
|
|
901
937
|
}
|
|
902
938
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
939
|
+
|
|
940
|
+
// Substitute nested local.gets (skip control-flow nodes — locals may be reassigned inside)
|
|
941
|
+
if (op !== 'block' && op !== 'loop' && op !== 'if') {
|
|
942
|
+
const prev = JSON.stringify(instr)
|
|
943
|
+
substGets(instr, known)
|
|
944
|
+
if (JSON.stringify(instr) !== prev) changed = true
|
|
906
945
|
}
|
|
946
|
+
}
|
|
907
947
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
948
|
+
// Remove dead stores + unused local decls in one reverse pass
|
|
949
|
+
const postUses = countLocalUses(funcNode)
|
|
950
|
+
const pu = name => postUses.get(name) || { gets: 0, sets: 0, tees: 0 }
|
|
951
|
+
for (let i = funcNode.length - 1; i >= 1; i--) {
|
|
952
|
+
const sub = funcNode[i]
|
|
953
|
+
if (!Array.isArray(sub)) continue
|
|
954
|
+
const name = typeof sub[1] === 'string' ? sub[1] : null
|
|
955
|
+
if (!name || params.has(name)) continue
|
|
956
|
+
const u = pu(name)
|
|
957
|
+
// Dead store: set but never read, pure RHS
|
|
958
|
+
if (sub[0] === 'local.set' && u.gets === 0 && u.tees === 0 && isPure(sub[2])) {
|
|
959
|
+
funcNode.splice(i, 1); changed = true
|
|
960
|
+
}
|
|
961
|
+
// Unused local declaration
|
|
962
|
+
else if (sub[0] === 'local' && name[0] === '$' && u.gets === 0 && u.sets === 0 && u.tees === 0) {
|
|
963
|
+
funcNode.splice(i, 1); changed = true
|
|
964
|
+
}
|
|
917
965
|
}
|
|
918
|
-
}
|
|
919
966
|
|
|
920
|
-
|
|
967
|
+
if (!changed) break
|
|
968
|
+
}
|
|
921
969
|
})
|
|
922
970
|
|
|
923
971
|
return result
|
package/types/src/optimize.d.ts
CHANGED
|
@@ -56,12 +56,11 @@ export function strength(ast: any[]): any[];
|
|
|
56
56
|
*/
|
|
57
57
|
export function branch(ast: any[]): any[];
|
|
58
58
|
/**
|
|
59
|
-
* Propagate
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @returns {Array}
|
|
59
|
+
* Propagate values through locals and eliminate single-use/dead locals.
|
|
60
|
+
* Constants propagate to all uses; pure single-use exprs inline into get site.
|
|
61
|
+
* Multi-pass with batch counting for convergence.
|
|
63
62
|
*/
|
|
64
|
-
export function propagate(ast: any
|
|
63
|
+
export function propagate(ast: any): any;
|
|
65
64
|
/**
|
|
66
65
|
* Inline tiny functions (single expression, no locals, no params or simple params).
|
|
67
66
|
* @param {Array} ast
|
|
@@ -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":"AAirCA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAwB/B;AAtnCD;;;;;GAKG;AACH,6CA8LC;AA4HD;;;;GAIG;AACH,wCAmCC;AAwQD;;;;GAIG;AACH,4CAuBC;AA+CD;;;;;GAKG;AACH,8CAqDC;AApTD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CA6DC;AAID;;;;GAIG;AACH,0CA0EC;AAoLD;;;;GAIG;AACH,yCAkFC;AAID;;;;GAIG;AACH,0CAwFC;AAnhCD;;;;GAIG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAe/B"}
|
package/watr.js
CHANGED
|
@@ -152,7 +152,7 @@ function genImports(imports) {
|
|
|
152
152
|
function compile(source, ...values) {
|
|
153
153
|
// Options object as last argument (non-template call)
|
|
154
154
|
let opts = {}
|
|
155
|
-
if (!Array.isArray(source) && values.length && typeof values[values.length - 1] === 'object' && values[values.length - 1] !== null && !
|
|
155
|
+
if (!Array.isArray(source) && values.length && typeof values[values.length - 1] === 'object' && values[values.length - 1] !== null && !values[values.length - 1].byteLength) {
|
|
156
156
|
opts = values.pop()
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -189,7 +189,7 @@ function compile(source, ...values) {
|
|
|
189
189
|
return parsed
|
|
190
190
|
}
|
|
191
191
|
// Uint8Array → convert to plain array for flat() compatibility
|
|
192
|
-
if (value
|
|
192
|
+
if (value.byteLength !== undefined) return [...value]
|
|
193
193
|
return value
|
|
194
194
|
}
|
|
195
195
|
return node
|