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 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, count2 = true) => {
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(count2 ? vec(items) : items.flat())];
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 = [], count2 = 0;
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
- count2++;
1878
+ count++;
1879
1879
  }
1880
1880
  c.block.push(1);
1881
- return [...result, ...uleb(count2), ...catches];
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 = [], count2 = 0;
1890
- while (n[0] && (!isNaN(n[0]) || isId(n[0]))) labels.push(...uleb(blockid(n.shift(), c.block))), count2++;
1891
- return [...uleb(count2 - 1), ...labels];
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: false,
3068
- // constant propagation can duplicate expressions
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 count = (node) => {
3100
- if (!Array.isArray(node)) return 1;
3101
- let n = 1;
3102
- for (let i = 0; i < node.length; i++) n += count(node[i]);
3103
- return n;
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 canSubst = (k) => getConst(k.val) || k.pure && k.singleUse;
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
- for (let pass = 0; pass < 4; pass++) {
3850
- let changed = false;
3851
- if (forwardPropagate(funcNode, params, countLocalUses(funcNode))) changed = true;
3852
- if (eliminateSetGetPairs(funcNode, params, countLocalUses(funcNode))) changed = true;
3853
- if (createLocalTees(funcNode, params, countLocalUses(funcNode))) changed = true;
3854
- if (eliminateDeadStores(funcNode, params, countLocalUses(funcNode))) changed = true;
3855
- if (!changed) break;
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 mutableGlobals = /* @__PURE__ */ new Set();
4413
+ const exported = /* @__PURE__ */ new Set();
4042
4414
  for (const node of ast.slice(1)) {
4043
- if (!Array.isArray(node) || node[0] !== "global") continue;
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
- const hasName = typeof node[1] === "string" && node[1][0] === "$";
4047
- const initIdx = hasName ? 3 : 2;
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
- const init = node[initIdx];
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) || n[0] !== "global.set") return;
4433
+ if (!Array.isArray(n)) return;
4055
4434
  const ref = n[1];
4056
- if (typeof ref === "string" && ref[0] === "$") mutableGlobals.add(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
- for (const name2 of mutableGlobals) constGlobals.delete(name2);
4059
- if (constGlobals.size === 0) return ast;
4060
- return walkPost2(ast, (node) => {
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
- const ref = node[1];
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 = count(ast);
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
- const sizeAfter = count(ast);
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} nodes`, delta);
5026
+ log(` round ${round + 1}: ${delta > 0 ? "+" : ""}${delta} bytes`, delta);
4629
5027
  }
4630
- const tolerance = strictGuard ? 0 : 5;
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
  }