watr 4.3.1 → 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 +118 -72
- package/dist/watr.min.js +6 -6
- package/dist/watr.wat +128517 -0
- package/package.json +2 -2
- package/src/encode.js +20 -25
- package/src/optimize.js +109 -61
- package/types/src/encode.d.ts +1 -0
- package/types/src/encode.d.ts.map +1 -1
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watr",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"description": "Light & fast WAT compiler – WebAssembly Text to binary, parse, print, transform",
|
|
5
5
|
"main": "watr.js",
|
|
6
6
|
"bin": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "node test",
|
|
47
47
|
"test:repl": "npx playwright test",
|
|
48
48
|
"types": "npx tsc src/*.js watr.js --allowJs --declaration --emitDeclarationOnly --declarationMap --outDir types",
|
|
49
|
-
"prepublishOnly": "npm run build && npm run types",
|
|
49
|
+
"prepublishOnly": "npm test && npm run build && npm run types",
|
|
50
50
|
"prepare": "git submodule update --init --recursive 2>/dev/null || true"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
package/src/encode.js
CHANGED
|
@@ -106,8 +106,9 @@ i32.parse = n => {
|
|
|
106
106
|
*/
|
|
107
107
|
export function i64(n, buffer = []) {
|
|
108
108
|
if (typeof n === 'string') n = i64.parse(n)
|
|
109
|
+
else if (typeof n === 'number') n = BigInt(n)
|
|
109
110
|
// Normalize unsigned to signed: values > MAX_INT64 become negative
|
|
110
|
-
|
|
111
|
+
if (typeof n === 'bigint' && n > 0x7fffffffffffffffn) {
|
|
111
112
|
n = n - 0x10000000000000000n
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -122,35 +123,31 @@ export function i64(n, buffer = []) {
|
|
|
122
123
|
}
|
|
123
124
|
return buffer
|
|
124
125
|
}
|
|
126
|
+
const _buf = new ArrayBuffer(8)
|
|
127
|
+
const _u8 = new Uint8Array(_buf), _i32 = new Int32Array(_buf), _f32 = new Float32Array(_buf), _f64 = new Float64Array(_buf), _i64 = new BigInt64Array(_buf)
|
|
128
|
+
|
|
125
129
|
i64.parse = n => {
|
|
126
130
|
n = cleanInt(n)
|
|
127
131
|
n = n[0] === '-' ? -BigInt(n.slice(1)) : BigInt(n) // can be -0x123
|
|
128
132
|
if (n < -0x8000000000000000n || n > 0xffffffffffffffffn) err(`i64 constant out of range`)
|
|
129
|
-
|
|
130
|
-
return
|
|
133
|
+
_i64[0] = n
|
|
134
|
+
return _i64[0]
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
const byteView = new DataView(new Float64Array(1).buffer)
|
|
134
|
-
|
|
135
137
|
const F32_SIGN = 0x80000000, F32_NAN = 0x7f800000
|
|
136
138
|
export function f32(input, value, idx) {
|
|
137
139
|
if (typeof input === 'string' && ~(idx = input.indexOf('nan:'))) {
|
|
138
140
|
value = i32.parse(input.slice(idx + 4))
|
|
139
141
|
value |= F32_NAN
|
|
140
142
|
if (input[0] === '-') value |= F32_SIGN
|
|
141
|
-
|
|
143
|
+
_i32[0] = value
|
|
142
144
|
}
|
|
143
145
|
else {
|
|
144
146
|
value = typeof input === 'string' ? f32.parse(input) : input
|
|
145
|
-
|
|
147
|
+
_f32[0] = value
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
return [
|
|
149
|
-
byteView.getUint8(3),
|
|
150
|
-
byteView.getUint8(2),
|
|
151
|
-
byteView.getUint8(1),
|
|
152
|
-
byteView.getUint8(0)
|
|
153
|
-
];
|
|
150
|
+
return [_u8[0], _u8[1], _u8[2], _u8[3]]
|
|
154
151
|
}
|
|
155
152
|
|
|
156
153
|
const F64_SIGN = 0x8000000000000000n, F64_NAN = 0x7ff0000000000000n
|
|
@@ -159,23 +156,14 @@ export function f64(input, value, idx) {
|
|
|
159
156
|
value = i64.parse(input.slice(idx + 4))
|
|
160
157
|
value |= F64_NAN
|
|
161
158
|
if (input[0] === '-') value |= F64_SIGN
|
|
162
|
-
|
|
159
|
+
_i64[0] = value
|
|
163
160
|
}
|
|
164
161
|
else {
|
|
165
162
|
value = typeof input === 'string' ? f64.parse(input) : input
|
|
166
|
-
|
|
163
|
+
_f64[0] = value
|
|
167
164
|
}
|
|
168
165
|
|
|
169
|
-
return
|
|
170
|
-
byteView.getUint8(7),
|
|
171
|
-
byteView.getUint8(6),
|
|
172
|
-
byteView.getUint8(5),
|
|
173
|
-
byteView.getUint8(4),
|
|
174
|
-
byteView.getUint8(3),
|
|
175
|
-
byteView.getUint8(2),
|
|
176
|
-
byteView.getUint8(1),
|
|
177
|
-
byteView.getUint8(0)
|
|
178
|
-
]);
|
|
166
|
+
return [_u8[0], _u8[1], _u8[2], _u8[3], _u8[4], _u8[5], _u8[6], _u8[7]]
|
|
179
167
|
}
|
|
180
168
|
|
|
181
169
|
f64.parse = (input, max=Number.MAX_VALUE) => {
|
|
@@ -219,3 +207,10 @@ f64.parse = (input, max=Number.MAX_VALUE) => {
|
|
|
219
207
|
}
|
|
220
208
|
|
|
221
209
|
f32.parse = input => f64.parse(input, 3.4028234663852886e+38)
|
|
210
|
+
|
|
211
|
+
export const v128 = (input) => {
|
|
212
|
+
let n = typeof input === 'string' ? BigInt(input.replaceAll('_', '')) : BigInt(input)
|
|
213
|
+
let arr = new Uint8Array(16)
|
|
214
|
+
for (let i = 0; i < 16; i++) arr[i] = Number(n & 0xffn), n >>= 8n
|
|
215
|
+
return [...arr]
|
|
216
|
+
}
|
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
|
/**
|
|
@@ -804,7 +807,7 @@ const localReuse = (ast) => {
|
|
|
804
807
|
if (!Array.isArray(sub)) continue
|
|
805
808
|
|
|
806
809
|
if (sub[0] === 'local') {
|
|
807
|
-
localDecls.push({
|
|
810
|
+
localDecls.push({ node: sub, idx: i })
|
|
808
811
|
// (local $name type) or (local type)
|
|
809
812
|
if (typeof sub[1] === 'string' && sub[1][0] === '$') {
|
|
810
813
|
localTypes.set(sub[1], sub[2])
|
|
@@ -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/encode.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/encode.js"],"names":[],"mappings":"AA6CA;;;;;;GAMG;AACH,6BAHW,MAAM,GACJ,MAAM,EAAE,CAapB;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;IAQD,4BAIC;;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/encode.js"],"names":[],"mappings":"AA6CA;;;;;;GAMG;AACH,6BAHW,MAAM,GACJ,MAAM,EAAE,CAapB;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;IAQD,4BAIC;;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAoBpB;;IAID,4BAMC;;AAGD,gEAaC;;IA0DD,mCAA6D;;AAvD7D,gEAaC;;IAED,iDAsCC;;AA/LM,wBAJI,MAAM,GAAC,MAAM,GAAC,MAAM,GAAC,IAAI,WACzB,MAAM,EAAE,GACN,MAAM,EAAE,CA8BpB;AAsBD;;;;;;GAMG;AACH,sBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;AApBD;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;AA6HM,wCAKN"}
|
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
|