watr 4.5.3 → 4.6.0
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 +441 -43
- package/dist/watr.min.js +6 -6
- package/dist/watr.wasm +0 -0
- package/package.json +8 -3
- package/readme.md +1 -1
- package/src/optimize.js +527 -49
- package/types/src/optimize.d.ts +68 -6
- package/types/src/optimize.d.ts.map +1 -1
package/dist/watr.js
CHANGED
|
@@ -1459,10 +1459,10 @@ function compile(nodes) {
|
|
|
1459
1459
|
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null;
|
|
1460
1460
|
items.push(node);
|
|
1461
1461
|
});
|
|
1462
|
-
const bin = (kind,
|
|
1462
|
+
const bin = (kind, count = true) => {
|
|
1463
1463
|
const items = ctx[kind].filter(Boolean).map((item) => build[kind](item, ctx)).filter(Boolean);
|
|
1464
1464
|
if (kind === SECTION.custom) return items.flatMap((content) => [kind, ...vec(content)]);
|
|
1465
|
-
return !items.length ? [] : [kind, ...vec(
|
|
1465
|
+
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items.flat())];
|
|
1466
1466
|
};
|
|
1467
1467
|
const binMeta = () => {
|
|
1468
1468
|
const sections = [];
|
|
@@ -1869,16 +1869,16 @@ var IMM = {
|
|
|
1869
1869
|
isId(n[0]) && (c.block[n.shift()] = c.block.length + 1);
|
|
1870
1870
|
let blocktype2 = n.shift();
|
|
1871
1871
|
let result = !blocktype2 ? [TYPE.void] : blocktype2[0] === "result" ? reftype(blocktype2[1], c) : uleb(id(blocktype2[1], c.type));
|
|
1872
|
-
let catches = [],
|
|
1872
|
+
let catches = [], count = 0;
|
|
1873
1873
|
while (n[0]?.[0] === "catch" || n[0]?.[0] === "catch_ref" || n[0]?.[0] === "catch_all" || n[0]?.[0] === "catch_all_ref") {
|
|
1874
1874
|
let clause = n.shift();
|
|
1875
1875
|
let kind = clause[0] === "catch" ? 0 : clause[0] === "catch_ref" ? 1 : clause[0] === "catch_all" ? 2 : 3;
|
|
1876
1876
|
if (kind <= 1) catches.push(kind, ...uleb(id(clause[1], c.tag)), ...uleb(blockid(clause[2], c.block)));
|
|
1877
1877
|
else catches.push(kind, ...uleb(blockid(clause[1], c.block)));
|
|
1878
|
-
|
|
1878
|
+
count++;
|
|
1879
1879
|
}
|
|
1880
1880
|
c.block.push(1);
|
|
1881
|
-
return [...result, ...uleb(
|
|
1881
|
+
return [...result, ...uleb(count), ...catches];
|
|
1882
1882
|
},
|
|
1883
1883
|
end: (_n, c) => (c.block.pop(), []),
|
|
1884
1884
|
call_indirect: (n, c) => {
|
|
@@ -1886,9 +1886,9 @@ var IMM = {
|
|
|
1886
1886
|
return [...uleb(id(idx, c.type)), ...uleb(id(t, c.table))];
|
|
1887
1887
|
},
|
|
1888
1888
|
br_table: (n, c) => {
|
|
1889
|
-
let labels = [],
|
|
1890
|
-
while (n[0] && (!isNaN(n[0]) || isId(n[0]))) labels.push(...uleb(blockid(n.shift(), c.block))),
|
|
1891
|
-
return [...uleb(
|
|
1889
|
+
let labels = [], count = 0;
|
|
1890
|
+
while (n[0] && (!isNaN(n[0]) || isId(n[0]))) labels.push(...uleb(blockid(n.shift(), c.block))), count++;
|
|
1891
|
+
return [...uleb(count - 1), ...labels];
|
|
1892
1892
|
},
|
|
1893
1893
|
select: (n, c) => {
|
|
1894
1894
|
let r = n.shift() || [];
|
|
@@ -3064,12 +3064,18 @@ var OPTS = {
|
|
|
3064
3064
|
// strength reduction (x * 2 → x << 1)
|
|
3065
3065
|
branch: true,
|
|
3066
3066
|
// simplify constant branches
|
|
3067
|
-
propagate:
|
|
3068
|
-
//
|
|
3067
|
+
propagate: true,
|
|
3068
|
+
// forward-propagate single-use locals & tiny consts (never inflates)
|
|
3069
3069
|
inline: false,
|
|
3070
3070
|
// inline tiny functions — can duplicate bodies
|
|
3071
|
+
inlineOnce: true,
|
|
3072
|
+
// inline single-call functions into their lone caller (never duplicates)
|
|
3071
3073
|
vacuum: true,
|
|
3072
3074
|
// remove nops, drop-of-pure, empty branches
|
|
3075
|
+
mergeBlocks: true,
|
|
3076
|
+
// unwrap `(block $L …)` whose label is never targeted
|
|
3077
|
+
coalesce: true,
|
|
3078
|
+
// share local slots between same-type non-overlapping locals
|
|
3073
3079
|
peephole: true,
|
|
3074
3080
|
// x-x→0, x&0→0, etc.
|
|
3075
3081
|
globals: true,
|
|
@@ -3096,11 +3102,12 @@ var OPTS = {
|
|
|
3096
3102
|
// shorten import names — enable only when you control the host
|
|
3097
3103
|
};
|
|
3098
3104
|
var ALL2 = Object.keys(OPTS);
|
|
3099
|
-
var
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3105
|
+
var binarySize = (ast) => {
|
|
3106
|
+
try {
|
|
3107
|
+
return compile(ast).length;
|
|
3108
|
+
} catch {
|
|
3109
|
+
return Infinity;
|
|
3110
|
+
}
|
|
3104
3111
|
};
|
|
3105
3112
|
var equal = (a, b) => {
|
|
3106
3113
|
if (a === b) return true;
|
|
@@ -3736,7 +3743,29 @@ var countLocalUses = (node) => {
|
|
|
3736
3743
|
});
|
|
3737
3744
|
return counts;
|
|
3738
3745
|
};
|
|
3739
|
-
var
|
|
3746
|
+
var isTinyConst = (node) => {
|
|
3747
|
+
const c = getConst(node);
|
|
3748
|
+
if (!c) return false;
|
|
3749
|
+
if (c.type === "i32") {
|
|
3750
|
+
const v = c.value | 0;
|
|
3751
|
+
return v >= -64 && v <= 63;
|
|
3752
|
+
}
|
|
3753
|
+
if (c.type === "i64") {
|
|
3754
|
+
const v = typeof c.value === "bigint" ? c.value : BigInt(c.value);
|
|
3755
|
+
return v >= -64n && v <= 63n;
|
|
3756
|
+
}
|
|
3757
|
+
return false;
|
|
3758
|
+
};
|
|
3759
|
+
var canSubst = (k) => k.pure && k.singleUse || isTinyConst(k.val);
|
|
3760
|
+
var purgeRefs = (known, name2) => {
|
|
3761
|
+
for (const [key, tracked] of known) {
|
|
3762
|
+
let refs = false;
|
|
3763
|
+
walk2(tracked.val, (n) => {
|
|
3764
|
+
if (Array.isArray(n) && (n[0] === "local.get" || n[0] === "local.tee") && n[1] === name2) refs = true;
|
|
3765
|
+
});
|
|
3766
|
+
if (refs) known.delete(key);
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3740
3769
|
var substGets = (node, known) => walkPost2(node, (n) => {
|
|
3741
3770
|
if (!Array.isArray(n) || n[0] !== "local.get" || n.length !== 2) return;
|
|
3742
3771
|
const k = typeof n[1] === "string" && known.get(n[1]);
|
|
@@ -3754,6 +3783,7 @@ var forwardPropagate = (funcNode, params, useCounts) => {
|
|
|
3754
3783
|
if ((op === "local.set" || op === "local.tee") && instr2.length === 3 && typeof instr2[1] === "string") {
|
|
3755
3784
|
substGets(instr2[2], known);
|
|
3756
3785
|
const uses = getUseCount(instr2[1]);
|
|
3786
|
+
purgeRefs(known, instr2[1]);
|
|
3757
3787
|
known.set(instr2[1], {
|
|
3758
3788
|
val: instr2[2],
|
|
3759
3789
|
pure: isPure(instr2[2]),
|
|
@@ -3780,8 +3810,10 @@ var forwardPropagate = (funcNode, params, useCounts) => {
|
|
|
3780
3810
|
substGets(instr2, known);
|
|
3781
3811
|
if (!equal(prev, instr2)) changed = true;
|
|
3782
3812
|
walk2(instr2, (n) => {
|
|
3783
|
-
if (Array.isArray(n) && (n[0] === "local.set" || n[0] === "local.tee") && typeof n[1] === "string")
|
|
3813
|
+
if (Array.isArray(n) && (n[0] === "local.set" || n[0] === "local.tee") && typeof n[1] === "string") {
|
|
3784
3814
|
known.delete(n[1]);
|
|
3815
|
+
purgeRefs(known, n[1]);
|
|
3816
|
+
}
|
|
3785
3817
|
});
|
|
3786
3818
|
}
|
|
3787
3819
|
}
|
|
@@ -3840,19 +3872,27 @@ var eliminateDeadStores = (funcNode, params, useCounts) => {
|
|
|
3840
3872
|
}
|
|
3841
3873
|
return changed;
|
|
3842
3874
|
};
|
|
3875
|
+
var isScopeNode = (n) => Array.isArray(n) && (n[0] === "func" || n[0] === "block" || n[0] === "loop" || n[0] === "then" || n[0] === "else");
|
|
3843
3876
|
var propagate = (ast) => {
|
|
3844
3877
|
walk2(ast, (funcNode) => {
|
|
3845
3878
|
if (!Array.isArray(funcNode) || funcNode[0] !== "func") return;
|
|
3846
3879
|
const params = /* @__PURE__ */ new Set();
|
|
3847
3880
|
for (const sub of funcNode)
|
|
3848
3881
|
if (Array.isArray(sub) && sub[0] === "param" && typeof sub[1] === "string") params.add(sub[1]);
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
if (
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3882
|
+
const scopes = [];
|
|
3883
|
+
walkPost2(funcNode, (n) => {
|
|
3884
|
+
if (isScopeNode(n)) scopes.push(n);
|
|
3885
|
+
});
|
|
3886
|
+
for (let round = 0; round < 6; round++) {
|
|
3887
|
+
const useCounts = countLocalUses(funcNode);
|
|
3888
|
+
let progressed = false;
|
|
3889
|
+
for (const scope of scopes) {
|
|
3890
|
+
if (forwardPropagate(scope, params, useCounts)) progressed = true;
|
|
3891
|
+
if (eliminateSetGetPairs(scope, params, useCounts)) progressed = true;
|
|
3892
|
+
if (createLocalTees(scope, params, useCounts)) progressed = true;
|
|
3893
|
+
if (eliminateDeadStores(scope, params, useCounts)) progressed = true;
|
|
3894
|
+
}
|
|
3895
|
+
if (!progressed) break;
|
|
3856
3896
|
}
|
|
3857
3897
|
});
|
|
3858
3898
|
return ast;
|
|
@@ -3927,6 +3967,311 @@ var inline = (ast) => {
|
|
|
3927
3967
|
});
|
|
3928
3968
|
return ast;
|
|
3929
3969
|
};
|
|
3970
|
+
var inlineUid = 0;
|
|
3971
|
+
var inlineOnce = (ast) => {
|
|
3972
|
+
if (!Array.isArray(ast) || ast[0] !== "module") return ast;
|
|
3973
|
+
const HEAD = /* @__PURE__ */ new Set(["export", "type", "param", "result", "local"]);
|
|
3974
|
+
const bodyStart = (fn) => {
|
|
3975
|
+
let i = 2;
|
|
3976
|
+
while (i < fn.length && (typeof fn[i] === "string" || Array.isArray(fn[i]) && HEAD.has(fn[i][0]))) i++;
|
|
3977
|
+
return i;
|
|
3978
|
+
};
|
|
3979
|
+
const isBranch = (op) => op === "br" || op === "br_if" || op === "br_table";
|
|
3980
|
+
const unsafe = (n) => {
|
|
3981
|
+
if (!Array.isArray(n)) return false;
|
|
3982
|
+
const op = n[0];
|
|
3983
|
+
if (op === "return_call" || op === "return_call_indirect" || op === "return_call_ref") return true;
|
|
3984
|
+
if (op === "try" || op === "try_table" || op === "delegate" || op === "rethrow") return true;
|
|
3985
|
+
if (isBranch(op)) {
|
|
3986
|
+
for (let i = 1; i < n.length; i++) if (typeof n[i] === "number" || typeof n[i] === "string" && /^\d+$/.test(n[i])) return true;
|
|
3987
|
+
}
|
|
3988
|
+
for (let i = 1; i < n.length; i++) if (unsafe(n[i])) return true;
|
|
3989
|
+
return false;
|
|
3990
|
+
};
|
|
3991
|
+
const callsSelf = (n, name2) => {
|
|
3992
|
+
if (!Array.isArray(n)) return false;
|
|
3993
|
+
if ((n[0] === "call" || n[0] === "return_call") && n[1] === name2) return true;
|
|
3994
|
+
for (let i = 1; i < n.length; i++) if (callsSelf(n[i], name2)) return true;
|
|
3995
|
+
return false;
|
|
3996
|
+
};
|
|
3997
|
+
const collectPinned = (n, pinned) => {
|
|
3998
|
+
if (!Array.isArray(n)) return;
|
|
3999
|
+
const op = n[0];
|
|
4000
|
+
if (op === "export" && Array.isArray(n[2]) && n[2][0] === "func" && typeof n[2][1] === "string") pinned.add(n[2][1]);
|
|
4001
|
+
else if (op === "start" && typeof n[1] === "string") pinned.add(n[1]);
|
|
4002
|
+
else if (op === "ref.func" && typeof n[1] === "string") pinned.add(n[1]);
|
|
4003
|
+
else if (op === "elem") {
|
|
4004
|
+
for (const c of n) if (typeof c === "string" && c[0] === "$") pinned.add(c);
|
|
4005
|
+
}
|
|
4006
|
+
for (const c of n) collectPinned(c, pinned);
|
|
4007
|
+
};
|
|
4008
|
+
for (let round = 0; round < 16; round++) {
|
|
4009
|
+
const funcs = ast.filter((n) => Array.isArray(n) && n[0] === "func");
|
|
4010
|
+
const funcByName = /* @__PURE__ */ new Map();
|
|
4011
|
+
for (const n of funcs) if (typeof n[1] === "string") funcByName.set(n[1], n);
|
|
4012
|
+
const callRefs = /* @__PURE__ */ new Map(), otherRef = /* @__PURE__ */ new Set();
|
|
4013
|
+
const countRefs = (n) => {
|
|
4014
|
+
if (!Array.isArray(n)) return;
|
|
4015
|
+
const op = n[0];
|
|
4016
|
+
if (op === "call" && typeof n[1] === "string") callRefs.set(n[1], (callRefs.get(n[1]) || 0) + 1);
|
|
4017
|
+
else if (op === "return_call" && typeof n[1] === "string") otherRef.add(n[1]);
|
|
4018
|
+
for (let i = 1; i < n.length; i++) countRefs(n[i]);
|
|
4019
|
+
};
|
|
4020
|
+
countRefs(ast);
|
|
4021
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
4022
|
+
for (const n of ast) if (!Array.isArray(n) || n[0] !== "func") collectPinned(n, pinned);
|
|
4023
|
+
let calleeName = null;
|
|
4024
|
+
for (const [name2, fn] of funcByName) {
|
|
4025
|
+
if (pinned.has(name2) || otherRef.has(name2)) continue;
|
|
4026
|
+
if (callRefs.get(name2) !== 1) continue;
|
|
4027
|
+
if (callsSelf(fn, name2)) continue;
|
|
4028
|
+
let ok = true, nResult = 0;
|
|
4029
|
+
for (let i = 2; i < fn.length; i++) {
|
|
4030
|
+
const c = fn[i];
|
|
4031
|
+
if (typeof c === "string") continue;
|
|
4032
|
+
if (!Array.isArray(c)) {
|
|
4033
|
+
ok = false;
|
|
4034
|
+
break;
|
|
4035
|
+
}
|
|
4036
|
+
if (c[0] === "param" || c[0] === "local") {
|
|
4037
|
+
if (typeof c[1] !== "string" || c[1][0] !== "$") {
|
|
4038
|
+
ok = false;
|
|
4039
|
+
break;
|
|
4040
|
+
}
|
|
4041
|
+
} else if (c[0] === "result") nResult += c.length - 1;
|
|
4042
|
+
else if (c[0] === "export") {
|
|
4043
|
+
ok = false;
|
|
4044
|
+
break;
|
|
4045
|
+
} else if (c[0] === "type") continue;
|
|
4046
|
+
else break;
|
|
4047
|
+
}
|
|
4048
|
+
if (!ok || nResult > 1) continue;
|
|
4049
|
+
let bad = false;
|
|
4050
|
+
for (let i = bodyStart(fn); i < fn.length; i++) if (unsafe(fn[i])) {
|
|
4051
|
+
bad = true;
|
|
4052
|
+
break;
|
|
4053
|
+
}
|
|
4054
|
+
if (bad) continue;
|
|
4055
|
+
calleeName = name2;
|
|
4056
|
+
break;
|
|
4057
|
+
}
|
|
4058
|
+
if (!calleeName) break;
|
|
4059
|
+
const callee = funcByName.get(calleeName);
|
|
4060
|
+
const params = [], locals = [];
|
|
4061
|
+
let resultType = null;
|
|
4062
|
+
for (let i = 2; i < callee.length; i++) {
|
|
4063
|
+
const c = callee[i];
|
|
4064
|
+
if (typeof c === "string" || !Array.isArray(c)) continue;
|
|
4065
|
+
if (c[0] === "param") params.push({ name: c[1], type: c[2] });
|
|
4066
|
+
else if (c[0] === "result") {
|
|
4067
|
+
if (c.length > 1) resultType = c[1];
|
|
4068
|
+
} else if (c[0] === "local") locals.push({ name: c[1], type: c[2] });
|
|
4069
|
+
else if (c[0] === "export" || c[0] === "type") continue;
|
|
4070
|
+
else break;
|
|
4071
|
+
}
|
|
4072
|
+
const cBody = callee.slice(bodyStart(callee));
|
|
4073
|
+
const uid2 = ++inlineUid;
|
|
4074
|
+
const exit = `$__inl${uid2}`;
|
|
4075
|
+
const rename = /* @__PURE__ */ new Map();
|
|
4076
|
+
for (const p of params) rename.set(p.name, `$__inl${uid2}_${p.name.slice(1)}`);
|
|
4077
|
+
for (const l of locals) rename.set(l.name, `$__inl${uid2}_${l.name.slice(1)}`);
|
|
4078
|
+
const isBlockLabel = (op) => op === "block" || op === "loop" || op === "if";
|
|
4079
|
+
const labelRename = /* @__PURE__ */ new Map();
|
|
4080
|
+
const collectLabels = (n) => {
|
|
4081
|
+
if (!Array.isArray(n)) return;
|
|
4082
|
+
if (isBlockLabel(n[0]) && typeof n[1] === "string" && n[1][0] === "$" && !labelRename.has(n[1]))
|
|
4083
|
+
labelRename.set(n[1], `$__inl${uid2}L_${n[1].slice(1)}`);
|
|
4084
|
+
for (let i = 1; i < n.length; i++) collectLabels(n[i]);
|
|
4085
|
+
};
|
|
4086
|
+
for (const n of cBody) collectLabels(n);
|
|
4087
|
+
const sub = (n) => {
|
|
4088
|
+
if (!Array.isArray(n)) return n;
|
|
4089
|
+
const op = n[0];
|
|
4090
|
+
if ((op === "local.get" || op === "local.set" || op === "local.tee") && typeof n[1] === "string" && rename.has(n[1]))
|
|
4091
|
+
return [op, rename.get(n[1]), ...n.slice(2).map(sub)];
|
|
4092
|
+
if (op === "return") return ["br", exit, ...n.slice(1).map(sub)];
|
|
4093
|
+
if (isBlockLabel(op) && typeof n[1] === "string" && labelRename.has(n[1]))
|
|
4094
|
+
return [op, labelRename.get(n[1]), ...n.slice(2).map(sub)];
|
|
4095
|
+
if (isBranch(op)) return [op, ...n.slice(1).map((c) => typeof c === "string" && labelRename.has(c) ? labelRename.get(c) : sub(c))];
|
|
4096
|
+
return n.map((c, i) => i === 0 ? c : sub(c));
|
|
4097
|
+
};
|
|
4098
|
+
let done = false;
|
|
4099
|
+
for (const fn of funcs) {
|
|
4100
|
+
if (fn === callee || done) continue;
|
|
4101
|
+
const start = bodyStart(fn);
|
|
4102
|
+
for (let i = start; i < fn.length; i++) {
|
|
4103
|
+
const replaced = walkPost2(fn[i], (n) => {
|
|
4104
|
+
if (done || !Array.isArray(n) || n[0] !== "call" || n[1] !== calleeName) return;
|
|
4105
|
+
const args = n.slice(2);
|
|
4106
|
+
if (args.length !== params.length) return;
|
|
4107
|
+
const setup = params.map((p, k) => ["local.set", rename.get(p.name), args[k]]);
|
|
4108
|
+
const inner = cBody.map(sub);
|
|
4109
|
+
done = true;
|
|
4110
|
+
return resultType ? ["block", exit, ["result", resultType], ...setup, ...inner] : ["block", exit, ...setup, ...inner];
|
|
4111
|
+
});
|
|
4112
|
+
if (replaced !== fn[i]) fn[i] = replaced;
|
|
4113
|
+
if (done) {
|
|
4114
|
+
const decls = [...params, ...locals].map((p) => ["local", rename.get(p.name), p.type]);
|
|
4115
|
+
if (decls.length) fn.splice(bodyStart(fn), 0, ...decls);
|
|
4116
|
+
break;
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
if (done) break;
|
|
4120
|
+
}
|
|
4121
|
+
if (!done) break;
|
|
4122
|
+
const idx = ast.indexOf(callee);
|
|
4123
|
+
if (idx >= 0) ast.splice(idx, 1);
|
|
4124
|
+
}
|
|
4125
|
+
return ast;
|
|
4126
|
+
};
|
|
4127
|
+
var targetsLabel = (body, label) => {
|
|
4128
|
+
let found = false;
|
|
4129
|
+
const search = (n, shadowed) => {
|
|
4130
|
+
if (found || !Array.isArray(n)) return;
|
|
4131
|
+
const op = n[0];
|
|
4132
|
+
let inner = shadowed;
|
|
4133
|
+
if ((op === "block" || op === "loop") && typeof n[1] === "string" && n[1] === label) inner = true;
|
|
4134
|
+
if (!shadowed) {
|
|
4135
|
+
if (op === "br" || op === "br_if" || op === "br_on_null" || op === "br_on_non_null" || op === "br_on_cast" || op === "br_on_cast_fail") {
|
|
4136
|
+
if (n[1] === label) {
|
|
4137
|
+
found = true;
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
} else if (op === "br_table") {
|
|
4141
|
+
for (let j = 1; j < n.length; j++) {
|
|
4142
|
+
if (typeof n[j] === "string") {
|
|
4143
|
+
if (n[j] === label) {
|
|
4144
|
+
found = true;
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
} else break;
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
for (let i = 1; i < n.length; i++) search(n[i], inner);
|
|
4152
|
+
};
|
|
4153
|
+
for (const node of body) search(node, false);
|
|
4154
|
+
return found;
|
|
4155
|
+
};
|
|
4156
|
+
var mergeBlocks = (ast) => {
|
|
4157
|
+
walk2(ast, (node) => {
|
|
4158
|
+
if (!isScopeNode(node)) return;
|
|
4159
|
+
let i = 1;
|
|
4160
|
+
while (i < node.length) {
|
|
4161
|
+
const child = node[i];
|
|
4162
|
+
if (!Array.isArray(child) || child[0] !== "block") {
|
|
4163
|
+
i++;
|
|
4164
|
+
continue;
|
|
4165
|
+
}
|
|
4166
|
+
let bi = 1, label = null;
|
|
4167
|
+
if (typeof child[1] === "string" && child[1][0] === "$") {
|
|
4168
|
+
label = child[1];
|
|
4169
|
+
bi = 2;
|
|
4170
|
+
}
|
|
4171
|
+
let typed = false;
|
|
4172
|
+
for (let j = bi; j < child.length; j++) {
|
|
4173
|
+
const c = child[j];
|
|
4174
|
+
if (Array.isArray(c) && (c[0] === "param" || c[0] === "result" || c[0] === "type")) {
|
|
4175
|
+
typed = true;
|
|
4176
|
+
break;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
if (typed) {
|
|
4180
|
+
i++;
|
|
4181
|
+
continue;
|
|
4182
|
+
}
|
|
4183
|
+
const body = child.slice(bi);
|
|
4184
|
+
if (label && targetsLabel(body, label)) {
|
|
4185
|
+
i++;
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
node.splice(i, 1, ...body);
|
|
4189
|
+
i += body.length;
|
|
4190
|
+
}
|
|
4191
|
+
});
|
|
4192
|
+
return ast;
|
|
4193
|
+
};
|
|
4194
|
+
var coalesceLocals = (ast) => {
|
|
4195
|
+
walk2(ast, (funcNode) => {
|
|
4196
|
+
if (!Array.isArray(funcNode) || funcNode[0] !== "func") return;
|
|
4197
|
+
const decls = /* @__PURE__ */ new Map();
|
|
4198
|
+
for (const sub of funcNode) {
|
|
4199
|
+
if (Array.isArray(sub) && sub[0] === "local" && typeof sub[1] === "string" && sub[1][0] === "$" && typeof sub[2] === "string") {
|
|
4200
|
+
decls.set(sub[1], sub[2]);
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
if (decls.size < 2) return;
|
|
4204
|
+
const uses = /* @__PURE__ */ new Map();
|
|
4205
|
+
const loopStack = [];
|
|
4206
|
+
let pos = 0, abort = false, condDepth = 0;
|
|
4207
|
+
const visit = (n) => {
|
|
4208
|
+
if (abort || !Array.isArray(n)) return;
|
|
4209
|
+
const op = n[0];
|
|
4210
|
+
const isLoop = op === "loop";
|
|
4211
|
+
if (isLoop) loopStack.push({ start: pos, end: pos });
|
|
4212
|
+
const isSet = op === "local.set" || op === "local.tee";
|
|
4213
|
+
if (isSet || op === "local.get") {
|
|
4214
|
+
const name2 = n[1];
|
|
4215
|
+
if (typeof name2 !== "string" || name2[0] !== "$") {
|
|
4216
|
+
abort = true;
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
if (isSet) for (let i = 2; i < n.length; i++) visit(n[i]);
|
|
4220
|
+
const here = pos++;
|
|
4221
|
+
if (decls.has(name2)) {
|
|
4222
|
+
let u = uses.get(name2);
|
|
4223
|
+
if (!u) {
|
|
4224
|
+
u = { start: here, end: here, firstOp: op, firstCond: condDepth > 0, loops: /* @__PURE__ */ new Set() };
|
|
4225
|
+
uses.set(name2, u);
|
|
4226
|
+
}
|
|
4227
|
+
if (here > u.end) u.end = here;
|
|
4228
|
+
for (const ls of loopStack) u.loops.add(ls);
|
|
4229
|
+
}
|
|
4230
|
+
} else {
|
|
4231
|
+
pos++;
|
|
4232
|
+
const isIf = op === "if";
|
|
4233
|
+
for (let i = 1; i < n.length; i++) {
|
|
4234
|
+
const c = n[i];
|
|
4235
|
+
const cond = isIf && Array.isArray(c) && (c[0] === "then" || c[0] === "else");
|
|
4236
|
+
if (cond) condDepth++;
|
|
4237
|
+
visit(c);
|
|
4238
|
+
if (cond) condDepth--;
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
if (isLoop) {
|
|
4242
|
+
const ls = loopStack.pop();
|
|
4243
|
+
ls.end = pos;
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
visit(funcNode);
|
|
4247
|
+
if (abort) return;
|
|
4248
|
+
for (const u of uses.values()) {
|
|
4249
|
+
for (const ls of u.loops) {
|
|
4250
|
+
if (ls.start < u.start) u.start = ls.start;
|
|
4251
|
+
if (ls.end > u.end) u.end = ls.end;
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
const ordered = [...uses.entries()].sort((a, b) => a[1].start - b[1].start);
|
|
4255
|
+
const rename = /* @__PURE__ */ new Map();
|
|
4256
|
+
const slots = [];
|
|
4257
|
+
for (const [name2, range] of ordered) {
|
|
4258
|
+
const readsZero = range.firstOp === "local.get" || range.firstCond;
|
|
4259
|
+
const type = decls.get(name2);
|
|
4260
|
+
const slot = readsZero ? null : slots.find((s) => s.type === type && s.end < range.start);
|
|
4261
|
+
if (slot) {
|
|
4262
|
+
rename.set(name2, slot.primary);
|
|
4263
|
+
if (range.end > slot.end) slot.end = range.end;
|
|
4264
|
+
} else slots.push({ primary: name2, type, end: range.end });
|
|
4265
|
+
}
|
|
4266
|
+
if (rename.size === 0) return;
|
|
4267
|
+
walk2(funcNode, (n) => {
|
|
4268
|
+
if (Array.isArray(n) && (n[0] === "local.get" || n[0] === "local.set" || n[0] === "local.tee") && rename.has(n[1])) {
|
|
4269
|
+
n[1] = rename.get(n[1]);
|
|
4270
|
+
}
|
|
4271
|
+
});
|
|
4272
|
+
});
|
|
4273
|
+
return ast;
|
|
4274
|
+
};
|
|
3930
4275
|
var vacuum = (ast) => {
|
|
3931
4276
|
return walkPost2(ast, (node) => {
|
|
3932
4277
|
if (!Array.isArray(node)) return;
|
|
@@ -4035,33 +4380,82 @@ var peephole = (ast) => {
|
|
|
4035
4380
|
if (result !== null) return result;
|
|
4036
4381
|
});
|
|
4037
4382
|
};
|
|
4383
|
+
var slebSize = (v) => {
|
|
4384
|
+
let x = typeof v === "bigint" ? v : BigInt(Math.trunc(Number(v) || 0));
|
|
4385
|
+
let n = 1;
|
|
4386
|
+
while (true) {
|
|
4387
|
+
const b = x & 0x7fn;
|
|
4388
|
+
x >>= 7n;
|
|
4389
|
+
if (x === 0n && (b & 0x40n) === 0n || x === -1n && (b & 0x40n) !== 0n) return n;
|
|
4390
|
+
n++;
|
|
4391
|
+
}
|
|
4392
|
+
};
|
|
4393
|
+
var constInstrSize = (node) => {
|
|
4394
|
+
if (!Array.isArray(node)) return 4;
|
|
4395
|
+
switch (node[0]) {
|
|
4396
|
+
case "i32.const":
|
|
4397
|
+
case "i64.const":
|
|
4398
|
+
return 1 + slebSize(node[1]);
|
|
4399
|
+
case "f32.const":
|
|
4400
|
+
return 5;
|
|
4401
|
+
case "f64.const":
|
|
4402
|
+
return 9;
|
|
4403
|
+
case "v128.const":
|
|
4404
|
+
return 18;
|
|
4405
|
+
default:
|
|
4406
|
+
return 4;
|
|
4407
|
+
}
|
|
4408
|
+
};
|
|
4409
|
+
var GLOBAL_GET_SIZE = 2;
|
|
4038
4410
|
var globals = (ast) => {
|
|
4039
4411
|
if (!Array.isArray(ast) || ast[0] !== "module") return ast;
|
|
4040
4412
|
const constGlobals = /* @__PURE__ */ new Map();
|
|
4041
|
-
const
|
|
4413
|
+
const exported = /* @__PURE__ */ new Set();
|
|
4042
4414
|
for (const node of ast.slice(1)) {
|
|
4043
|
-
if (!Array.isArray(node)
|
|
4415
|
+
if (!Array.isArray(node)) continue;
|
|
4416
|
+
if (node[0] === "export" && Array.isArray(node[2]) && node[2][0] === "global" && typeof node[2][1] === "string") {
|
|
4417
|
+
exported.add(node[2][1]);
|
|
4418
|
+
continue;
|
|
4419
|
+
}
|
|
4420
|
+
if (node[0] !== "global") continue;
|
|
4044
4421
|
const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : null;
|
|
4045
4422
|
if (!name2) continue;
|
|
4046
|
-
|
|
4047
|
-
const
|
|
4048
|
-
const typeSlot = hasName ? node[2] : node[1];
|
|
4423
|
+
if (node.some((c) => Array.isArray(c) && c[0] === "export")) exported.add(name2);
|
|
4424
|
+
const typeSlot = node[2];
|
|
4049
4425
|
if (Array.isArray(typeSlot) && typeSlot[0] === "mut") continue;
|
|
4050
|
-
|
|
4426
|
+
if (Array.isArray(typeSlot) && typeSlot[0] === "import") continue;
|
|
4427
|
+
const init = node[3];
|
|
4051
4428
|
if (getConst(init)) constGlobals.set(name2, init);
|
|
4052
4429
|
}
|
|
4430
|
+
if (constGlobals.size === 0) return ast;
|
|
4431
|
+
const reads = /* @__PURE__ */ new Map();
|
|
4053
4432
|
walk2(ast, (n) => {
|
|
4054
|
-
if (!Array.isArray(n)
|
|
4433
|
+
if (!Array.isArray(n)) return;
|
|
4055
4434
|
const ref = n[1];
|
|
4056
|
-
if (typeof ref
|
|
4435
|
+
if (typeof ref !== "string" || ref[0] !== "$") return;
|
|
4436
|
+
if (n[0] === "global.set") constGlobals.delete(ref);
|
|
4437
|
+
else if (n[0] === "global.get") reads.set(ref, (reads.get(ref) || 0) + 1);
|
|
4057
4438
|
});
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4439
|
+
const propagate2 = /* @__PURE__ */ new Set();
|
|
4440
|
+
for (const [name2, init] of constGlobals) {
|
|
4441
|
+
const r = reads.get(name2) || 0;
|
|
4442
|
+
if (r === 0) continue;
|
|
4443
|
+
const cs = constInstrSize(init);
|
|
4444
|
+
const declSize = cs + 2;
|
|
4445
|
+
const before = r * GLOBAL_GET_SIZE + declSize;
|
|
4446
|
+
const after = r * cs + (exported.has(name2) ? declSize : 0);
|
|
4447
|
+
if (after <= before) propagate2.add(name2);
|
|
4448
|
+
}
|
|
4449
|
+
if (propagate2.size === 0) return ast;
|
|
4450
|
+
walkPost2(ast, (node) => {
|
|
4061
4451
|
if (!Array.isArray(node) || node[0] !== "global.get" || node.length !== 2) return;
|
|
4062
|
-
|
|
4063
|
-
if (constGlobals.has(ref)) return clone2(constGlobals.get(ref));
|
|
4452
|
+
if (propagate2.has(node[1])) return clone2(constGlobals.get(node[1]));
|
|
4064
4453
|
});
|
|
4454
|
+
for (let i = ast.length - 1; i >= 1; i--) {
|
|
4455
|
+
const n = ast[i];
|
|
4456
|
+
if (Array.isArray(n) && n[0] === "global" && typeof n[1] === "string" && propagate2.has(n[1]) && !exported.has(n[1])) ast.splice(i, 1);
|
|
4457
|
+
}
|
|
4458
|
+
return ast;
|
|
4065
4459
|
};
|
|
4066
4460
|
var offset = (ast) => {
|
|
4067
4461
|
return walkPost2(ast, (node) => {
|
|
@@ -4141,7 +4535,7 @@ var unbranch = (ast) => {
|
|
|
4141
4535
|
if (lastIdx < 0) return;
|
|
4142
4536
|
const last = node[lastIdx];
|
|
4143
4537
|
if (Array.isArray(last) && last[0] === "br" && last[1] === label) {
|
|
4144
|
-
node.splice(lastIdx, 1);
|
|
4538
|
+
node.splice(lastIdx, 1, ...last.slice(2));
|
|
4145
4539
|
}
|
|
4146
4540
|
});
|
|
4147
4541
|
return ast;
|
|
@@ -4599,7 +4993,7 @@ function optimize(ast, opts = true) {
|
|
|
4599
4993
|
let beforeRound = null;
|
|
4600
4994
|
for (let round = 0; round < 3; round++) {
|
|
4601
4995
|
beforeRound = clone2(ast);
|
|
4602
|
-
const sizeBefore =
|
|
4996
|
+
const sizeBefore = binarySize(ast);
|
|
4603
4997
|
if (opts.stripmut) ast = stripmut(ast);
|
|
4604
4998
|
if (opts.globals) ast = globals(ast);
|
|
4605
4999
|
if (opts.fold) ast = fold(ast);
|
|
@@ -4608,6 +5002,7 @@ function optimize(ast, opts = true) {
|
|
|
4608
5002
|
if (opts.strength) ast = strength(ast);
|
|
4609
5003
|
if (opts.branch) ast = branch(ast);
|
|
4610
5004
|
if (opts.propagate) ast = propagate(ast);
|
|
5005
|
+
if (opts.inlineOnce) ast = inlineOnce(ast);
|
|
4611
5006
|
if (opts.inline) ast = inline(ast);
|
|
4612
5007
|
if (opts.offset) ast = offset(ast);
|
|
4613
5008
|
if (opts.unbranch) ast = unbranch(ast);
|
|
@@ -4615,6 +5010,8 @@ function optimize(ast, opts = true) {
|
|
|
4615
5010
|
if (opts.foldarms) ast = foldarms(ast);
|
|
4616
5011
|
if (opts.deadcode) ast = deadcode(ast);
|
|
4617
5012
|
if (opts.vacuum) ast = vacuum(ast);
|
|
5013
|
+
if (opts.mergeBlocks) ast = mergeBlocks(ast);
|
|
5014
|
+
if (opts.coalesce) ast = coalesceLocals(ast);
|
|
4618
5015
|
if (opts.locals) ast = localReuse(ast);
|
|
4619
5016
|
if (opts.dedupe) ast = dedupe(ast);
|
|
4620
5017
|
if (opts.dedupTypes) ast = dedupTypes(ast);
|
|
@@ -4622,14 +5019,15 @@ function optimize(ast, opts = true) {
|
|
|
4622
5019
|
if (opts.reorder) ast = reorder(ast);
|
|
4623
5020
|
if (opts.treeshake) ast = treeshake(ast);
|
|
4624
5021
|
if (opts.minifyImports) ast = minifyImports(ast);
|
|
4625
|
-
|
|
5022
|
+
if (opts.propagate && (opts.inlineOnce || opts.inline)) ast = propagate(ast);
|
|
5023
|
+
const sizeAfter = binarySize(ast);
|
|
4626
5024
|
const delta = sizeAfter - sizeBefore;
|
|
4627
5025
|
if (verbose || delta !== 0) {
|
|
4628
|
-
log(` round ${round + 1}: ${delta > 0 ? "+" : ""}${delta}
|
|
5026
|
+
log(` round ${round + 1}: ${delta > 0 ? "+" : ""}${delta} bytes`, delta);
|
|
4629
5027
|
}
|
|
4630
|
-
const tolerance = strictGuard ? 0 :
|
|
5028
|
+
const tolerance = strictGuard ? 0 : 16;
|
|
4631
5029
|
if (delta > tolerance) {
|
|
4632
|
-
if (verbose) log(` \u26A0 round ${round + 1} inflated by ${delta}, reverting`, delta);
|
|
5030
|
+
if (verbose) log(` \u26A0 round ${round + 1} inflated by ${delta} bytes, reverting`, delta);
|
|
4633
5031
|
ast = beforeRound;
|
|
4634
5032
|
break;
|
|
4635
5033
|
}
|