watr 4.5.3 → 4.6.1

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,
@@ -3078,6 +3084,8 @@ var OPTS = {
3078
3084
  // fold add+const into load/store offset
3079
3085
  unbranch: true,
3080
3086
  // remove redundant br at end of own block
3087
+ loopify: true,
3088
+ // collapse block+loop+brif while-idiom into loop+if
3081
3089
  stripmut: true,
3082
3090
  // strip mut from never-written globals
3083
3091
  brif: true,
@@ -3096,11 +3104,12 @@ var OPTS = {
3096
3104
  // shorten import names — enable only when you control the host
3097
3105
  };
3098
3106
  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;
3107
+ var binarySize = (ast) => {
3108
+ try {
3109
+ return compile(ast).length;
3110
+ } catch {
3111
+ return Infinity;
3112
+ }
3104
3113
  };
3105
3114
  var equal = (a, b) => {
3106
3115
  if (a === b) return true;
@@ -3305,6 +3314,28 @@ var treeshake = (ast) => {
3305
3314
  return result;
3306
3315
  };
3307
3316
  var roundEven = (x) => x - Math.floor(x) !== 0.5 ? Math.round(x) : 2 * Math.round(x / 2);
3317
+ var _rb8 = new ArrayBuffer(8);
3318
+ var _rf64 = new Float64Array(_rb8);
3319
+ var _ri64 = new BigInt64Array(_rb8);
3320
+ var _rb4 = new ArrayBuffer(4);
3321
+ var _rf32 = new Float32Array(_rb4);
3322
+ var _ri32 = new Int32Array(_rb4);
3323
+ var i64FromF64 = (x) => {
3324
+ _rf64[0] = x;
3325
+ return _ri64[0];
3326
+ };
3327
+ var f64FromI64 = (x) => {
3328
+ _ri64[0] = BigInt.asIntN(64, x);
3329
+ return _rf64[0];
3330
+ };
3331
+ var i32FromF32 = (x) => {
3332
+ _rf32[0] = x;
3333
+ return _ri32[0];
3334
+ };
3335
+ var f32FromI32 = (x) => {
3336
+ _ri32[0] = x | 0;
3337
+ return _rf32[0];
3338
+ };
3308
3339
  var i32c = (fn) => (a, b) => fn(a, b) ? 1 : 0;
3309
3340
  var u32c = (fn) => (a, b) => fn(a >>> 0, b >>> 0) ? 1 : 0;
3310
3341
  var i64c = (fn) => (a, b) => fn(a, b) ? 1 : 0;
@@ -3408,7 +3439,23 @@ var FOLDABLE = {
3408
3439
  "f64.ceil": [Math.ceil, "f64"],
3409
3440
  "f64.floor": [Math.floor, "f64"],
3410
3441
  "f64.trunc": [Math.trunc, "f64"],
3411
- "f64.nearest": [roundEven, "f64"]
3442
+ "f64.nearest": [roundEven, "f64"],
3443
+ // Bit-exact reinterprets (preserve NaN payloads)
3444
+ "i32.reinterpret_f32": [i32FromF32, "i32"],
3445
+ "f32.reinterpret_i32": [f32FromI32, "f32"],
3446
+ "i64.reinterpret_f64": [i64FromF64, "i64"],
3447
+ "f64.reinterpret_i64": [f64FromI64, "f64"],
3448
+ // Numeric conversions (value-preserving where representable)
3449
+ "f32.convert_i32_s": [(a) => Math.fround(a | 0), "f32"],
3450
+ "f32.convert_i32_u": [(a) => Math.fround(a >>> 0), "f32"],
3451
+ "f32.convert_i64_s": [(a) => Math.fround(Number(BigInt.asIntN(64, a))), "f32"],
3452
+ "f32.convert_i64_u": [(a) => Math.fround(Number(BigInt.asUintN(64, a))), "f32"],
3453
+ "f64.convert_i32_s": [(a) => a | 0, "f64"],
3454
+ "f64.convert_i32_u": [(a) => a >>> 0, "f64"],
3455
+ "f64.convert_i64_s": [(a) => Number(BigInt.asIntN(64, a)), "f64"],
3456
+ "f64.convert_i64_u": [(a) => Number(BigInt.asUintN(64, a)), "f64"],
3457
+ "f32.demote_f64": [(a) => Math.fround(a), "f32"],
3458
+ "f64.promote_f32": [(a) => Math.fround(a), "f64"]
3412
3459
  };
3413
3460
  var getConst = (node) => {
3414
3461
  if (!Array.isArray(node) || node.length !== 2) return null;
@@ -3736,7 +3783,29 @@ var countLocalUses = (node) => {
3736
3783
  });
3737
3784
  return counts;
3738
3785
  };
3739
- var canSubst = (k) => getConst(k.val) || k.pure && k.singleUse;
3786
+ var isTinyConst = (node) => {
3787
+ const c = getConst(node);
3788
+ if (!c) return false;
3789
+ if (c.type === "i32") {
3790
+ const v = c.value | 0;
3791
+ return v >= -64 && v <= 63;
3792
+ }
3793
+ if (c.type === "i64") {
3794
+ const v = typeof c.value === "bigint" ? c.value : BigInt(c.value);
3795
+ return v >= -64n && v <= 63n;
3796
+ }
3797
+ return false;
3798
+ };
3799
+ var canSubst = (k) => k.pure && k.singleUse || isTinyConst(k.val);
3800
+ var purgeRefs = (known, name2) => {
3801
+ for (const [key, tracked] of known) {
3802
+ let refs = false;
3803
+ walk2(tracked.val, (n) => {
3804
+ if (Array.isArray(n) && (n[0] === "local.get" || n[0] === "local.tee") && n[1] === name2) refs = true;
3805
+ });
3806
+ if (refs) known.delete(key);
3807
+ }
3808
+ };
3740
3809
  var substGets = (node, known) => walkPost2(node, (n) => {
3741
3810
  if (!Array.isArray(n) || n[0] !== "local.get" || n.length !== 2) return;
3742
3811
  const k = typeof n[1] === "string" && known.get(n[1]);
@@ -3754,6 +3823,7 @@ var forwardPropagate = (funcNode, params, useCounts) => {
3754
3823
  if ((op === "local.set" || op === "local.tee") && instr2.length === 3 && typeof instr2[1] === "string") {
3755
3824
  substGets(instr2[2], known);
3756
3825
  const uses = getUseCount(instr2[1]);
3826
+ purgeRefs(known, instr2[1]);
3757
3827
  known.set(instr2[1], {
3758
3828
  val: instr2[2],
3759
3829
  pure: isPure(instr2[2]),
@@ -3780,8 +3850,10 @@ var forwardPropagate = (funcNode, params, useCounts) => {
3780
3850
  substGets(instr2, known);
3781
3851
  if (!equal(prev, instr2)) changed = true;
3782
3852
  walk2(instr2, (n) => {
3783
- if (Array.isArray(n) && (n[0] === "local.set" || n[0] === "local.tee") && typeof n[1] === "string")
3853
+ if (Array.isArray(n) && (n[0] === "local.set" || n[0] === "local.tee") && typeof n[1] === "string") {
3784
3854
  known.delete(n[1]);
3855
+ purgeRefs(known, n[1]);
3856
+ }
3785
3857
  });
3786
3858
  }
3787
3859
  }
@@ -3840,19 +3912,27 @@ var eliminateDeadStores = (funcNode, params, useCounts) => {
3840
3912
  }
3841
3913
  return changed;
3842
3914
  };
3915
+ var isScopeNode = (n) => Array.isArray(n) && (n[0] === "func" || n[0] === "block" || n[0] === "loop" || n[0] === "then" || n[0] === "else");
3843
3916
  var propagate = (ast) => {
3844
3917
  walk2(ast, (funcNode) => {
3845
3918
  if (!Array.isArray(funcNode) || funcNode[0] !== "func") return;
3846
3919
  const params = /* @__PURE__ */ new Set();
3847
3920
  for (const sub of funcNode)
3848
3921
  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;
3922
+ const scopes = [];
3923
+ walkPost2(funcNode, (n) => {
3924
+ if (isScopeNode(n)) scopes.push(n);
3925
+ });
3926
+ for (let round = 0; round < 6; round++) {
3927
+ const useCounts = countLocalUses(funcNode);
3928
+ let progressed = false;
3929
+ for (const scope of scopes) {
3930
+ if (forwardPropagate(scope, params, useCounts)) progressed = true;
3931
+ if (eliminateSetGetPairs(scope, params, useCounts)) progressed = true;
3932
+ if (createLocalTees(scope, params, useCounts)) progressed = true;
3933
+ if (eliminateDeadStores(scope, params, useCounts)) progressed = true;
3934
+ }
3935
+ if (!progressed) break;
3856
3936
  }
3857
3937
  });
3858
3938
  return ast;
@@ -3927,6 +4007,336 @@ var inline = (ast) => {
3927
4007
  });
3928
4008
  return ast;
3929
4009
  };
4010
+ var inlineUid = 0;
4011
+ var inlineOnce = (ast) => {
4012
+ if (!Array.isArray(ast) || ast[0] !== "module") return ast;
4013
+ const HEAD = /* @__PURE__ */ new Set(["export", "type", "param", "result", "local"]);
4014
+ const bodyStart = (fn) => {
4015
+ let i = 2;
4016
+ while (i < fn.length && (typeof fn[i] === "string" || Array.isArray(fn[i]) && HEAD.has(fn[i][0]))) i++;
4017
+ return i;
4018
+ };
4019
+ const isBranch = (op) => op === "br" || op === "br_if" || op === "br_table";
4020
+ const unsafe = (n) => {
4021
+ if (!Array.isArray(n)) return false;
4022
+ const op = n[0];
4023
+ if (op === "return_call" || op === "return_call_indirect" || op === "return_call_ref") return true;
4024
+ if (op === "try" || op === "try_table" || op === "delegate" || op === "rethrow") return true;
4025
+ if (isBranch(op)) {
4026
+ for (let i = 1; i < n.length; i++) if (typeof n[i] === "number" || typeof n[i] === "string" && /^\d+$/.test(n[i])) return true;
4027
+ }
4028
+ for (let i = 1; i < n.length; i++) if (unsafe(n[i])) return true;
4029
+ return false;
4030
+ };
4031
+ const callsSelf = (n, name2) => {
4032
+ if (!Array.isArray(n)) return false;
4033
+ if ((n[0] === "call" || n[0] === "return_call") && n[1] === name2) return true;
4034
+ for (let i = 1; i < n.length; i++) if (callsSelf(n[i], name2)) return true;
4035
+ return false;
4036
+ };
4037
+ const collectPinned = (n, pinned) => {
4038
+ if (!Array.isArray(n)) return;
4039
+ const op = n[0];
4040
+ if (op === "export" && Array.isArray(n[2]) && n[2][0] === "func" && typeof n[2][1] === "string") pinned.add(n[2][1]);
4041
+ else if (op === "start" && typeof n[1] === "string") pinned.add(n[1]);
4042
+ else if (op === "ref.func" && typeof n[1] === "string") pinned.add(n[1]);
4043
+ else if (op === "elem") {
4044
+ for (const c of n) if (typeof c === "string" && c[0] === "$") pinned.add(c);
4045
+ }
4046
+ for (const c of n) collectPinned(c, pinned);
4047
+ };
4048
+ for (let round = 0; round < 16; round++) {
4049
+ const funcs = ast.filter((n) => Array.isArray(n) && n[0] === "func");
4050
+ const funcByName = /* @__PURE__ */ new Map();
4051
+ for (const n of funcs) if (typeof n[1] === "string") funcByName.set(n[1], n);
4052
+ const callRefs = /* @__PURE__ */ new Map(), otherRef = /* @__PURE__ */ new Set();
4053
+ const countRefs = (n) => {
4054
+ if (!Array.isArray(n)) return;
4055
+ const op = n[0];
4056
+ if (op === "call" && typeof n[1] === "string") callRefs.set(n[1], (callRefs.get(n[1]) || 0) + 1);
4057
+ else if (op === "return_call" && typeof n[1] === "string") otherRef.add(n[1]);
4058
+ for (let i = 1; i < n.length; i++) countRefs(n[i]);
4059
+ };
4060
+ countRefs(ast);
4061
+ const pinned = /* @__PURE__ */ new Set();
4062
+ for (const n of ast) if (!Array.isArray(n) || n[0] !== "func") collectPinned(n, pinned);
4063
+ let calleeName = null;
4064
+ for (const [name2, fn] of funcByName) {
4065
+ if (pinned.has(name2) || otherRef.has(name2)) continue;
4066
+ if (callRefs.get(name2) !== 1) continue;
4067
+ if (callsSelf(fn, name2)) continue;
4068
+ let ok = true, nResult = 0;
4069
+ for (let i = 2; i < fn.length; i++) {
4070
+ const c = fn[i];
4071
+ if (typeof c === "string") continue;
4072
+ if (!Array.isArray(c)) {
4073
+ ok = false;
4074
+ break;
4075
+ }
4076
+ if (c[0] === "param" || c[0] === "local") {
4077
+ if (typeof c[1] !== "string" || c[1][0] !== "$") {
4078
+ ok = false;
4079
+ break;
4080
+ }
4081
+ } else if (c[0] === "result") nResult += c.length - 1;
4082
+ else if (c[0] === "export") {
4083
+ ok = false;
4084
+ break;
4085
+ } else if (c[0] === "type") continue;
4086
+ else break;
4087
+ }
4088
+ if (!ok || nResult > 1) continue;
4089
+ let bad = false;
4090
+ for (let i = bodyStart(fn); i < fn.length; i++) if (unsafe(fn[i])) {
4091
+ bad = true;
4092
+ break;
4093
+ }
4094
+ if (bad) continue;
4095
+ calleeName = name2;
4096
+ break;
4097
+ }
4098
+ if (!calleeName) break;
4099
+ const callee = funcByName.get(calleeName);
4100
+ const params = [], locals = [];
4101
+ let resultType = null;
4102
+ for (let i = 2; i < callee.length; i++) {
4103
+ const c = callee[i];
4104
+ if (typeof c === "string" || !Array.isArray(c)) continue;
4105
+ if (c[0] === "param") params.push({ name: c[1], type: c[2] });
4106
+ else if (c[0] === "result") {
4107
+ if (c.length > 1) resultType = c[1];
4108
+ } else if (c[0] === "local") locals.push({ name: c[1], type: c[2] });
4109
+ else if (c[0] === "export" || c[0] === "type") continue;
4110
+ else break;
4111
+ }
4112
+ const cBody = callee.slice(bodyStart(callee));
4113
+ const uid2 = ++inlineUid;
4114
+ const exit = `$__inl${uid2}`;
4115
+ const rename = /* @__PURE__ */ new Map();
4116
+ for (const p of params) rename.set(p.name, `$__inl${uid2}_${p.name.slice(1)}`);
4117
+ for (const l of locals) rename.set(l.name, `$__inl${uid2}_${l.name.slice(1)}`);
4118
+ const isBlockLabel = (op) => op === "block" || op === "loop" || op === "if";
4119
+ const labelRename = /* @__PURE__ */ new Map();
4120
+ const collectLabels = (n) => {
4121
+ if (!Array.isArray(n)) return;
4122
+ if (isBlockLabel(n[0]) && typeof n[1] === "string" && n[1][0] === "$" && !labelRename.has(n[1]))
4123
+ labelRename.set(n[1], `$__inl${uid2}L_${n[1].slice(1)}`);
4124
+ for (let i = 1; i < n.length; i++) collectLabels(n[i]);
4125
+ };
4126
+ for (const n of cBody) collectLabels(n);
4127
+ const sub = (n) => {
4128
+ if (!Array.isArray(n)) return n;
4129
+ const op = n[0];
4130
+ if ((op === "local.get" || op === "local.set" || op === "local.tee") && typeof n[1] === "string" && rename.has(n[1]))
4131
+ return [op, rename.get(n[1]), ...n.slice(2).map(sub)];
4132
+ if (op === "return") return ["br", exit, ...n.slice(1).map(sub)];
4133
+ if (isBlockLabel(op) && typeof n[1] === "string" && labelRename.has(n[1]))
4134
+ return [op, labelRename.get(n[1]), ...n.slice(2).map(sub)];
4135
+ if (isBranch(op)) return [op, ...n.slice(1).map((c) => typeof c === "string" && labelRename.has(c) ? labelRename.get(c) : sub(c))];
4136
+ return n.map((c, i) => i === 0 ? c : sub(c));
4137
+ };
4138
+ let done = false;
4139
+ for (const fn of funcs) {
4140
+ if (fn === callee || done) continue;
4141
+ const start = bodyStart(fn);
4142
+ for (let i = start; i < fn.length; i++) {
4143
+ const replaced = walkPost2(fn[i], (n) => {
4144
+ if (done || !Array.isArray(n) || n[0] !== "call" || n[1] !== calleeName) return;
4145
+ const args = n.slice(2);
4146
+ if (args.length !== params.length) return;
4147
+ const setup = params.map((p, k) => ["local.set", rename.get(p.name), args[k]]);
4148
+ const inner = cBody.map(sub);
4149
+ done = true;
4150
+ return resultType ? ["block", exit, ["result", resultType], ...setup, ...inner] : ["block", exit, ...setup, ...inner];
4151
+ });
4152
+ if (replaced !== fn[i]) fn[i] = replaced;
4153
+ if (done) {
4154
+ const decls = [...params, ...locals].map((p) => ["local", rename.get(p.name), p.type]);
4155
+ if (decls.length) fn.splice(bodyStart(fn), 0, ...decls);
4156
+ break;
4157
+ }
4158
+ }
4159
+ if (done) break;
4160
+ }
4161
+ if (!done) break;
4162
+ const idx = ast.indexOf(callee);
4163
+ if (idx >= 0) ast.splice(idx, 1);
4164
+ }
4165
+ return ast;
4166
+ };
4167
+ var targetsLabel = (body, label) => {
4168
+ let found = false;
4169
+ const search = (n, shadowed) => {
4170
+ if (found || !Array.isArray(n)) return;
4171
+ const op = n[0];
4172
+ let inner = shadowed;
4173
+ if ((op === "block" || op === "loop") && typeof n[1] === "string" && n[1] === label) inner = true;
4174
+ if (!shadowed) {
4175
+ if (op === "br" || op === "br_if" || op === "br_on_null" || op === "br_on_non_null" || op === "br_on_cast" || op === "br_on_cast_fail") {
4176
+ if (n[1] === label) {
4177
+ found = true;
4178
+ return;
4179
+ }
4180
+ } else if (op === "br_table") {
4181
+ for (let j = 1; j < n.length; j++) {
4182
+ if (typeof n[j] === "string") {
4183
+ if (n[j] === label) {
4184
+ found = true;
4185
+ return;
4186
+ }
4187
+ } else break;
4188
+ }
4189
+ }
4190
+ }
4191
+ for (let i = 1; i < n.length; i++) search(n[i], inner);
4192
+ };
4193
+ for (const node of body) search(node, false);
4194
+ return found;
4195
+ };
4196
+ var mergeBlocks = (ast) => {
4197
+ walkPost2(ast, (node) => {
4198
+ if (!Array.isArray(node) || node[0] !== "block") return;
4199
+ let bi = 1, label = null;
4200
+ if (typeof node[1] === "string" && node[1][0] === "$") {
4201
+ label = node[1];
4202
+ bi = 2;
4203
+ }
4204
+ let hasResult = false;
4205
+ while (bi < node.length) {
4206
+ const c = node[bi];
4207
+ if (Array.isArray(c) && (c[0] === "param" || c[0] === "type")) {
4208
+ bi++;
4209
+ continue;
4210
+ }
4211
+ if (Array.isArray(c) && c[0] === "result") {
4212
+ hasResult = true;
4213
+ bi++;
4214
+ continue;
4215
+ }
4216
+ break;
4217
+ }
4218
+ const body = node.slice(bi);
4219
+ if (!hasResult || body.length !== 1) return;
4220
+ const only = body[0];
4221
+ if (!Array.isArray(only)) return;
4222
+ if (label && targetsLabel(body, label)) return;
4223
+ node.length = 0;
4224
+ for (const tok of only) node.push(tok);
4225
+ });
4226
+ walk2(ast, (node) => {
4227
+ if (!isScopeNode(node)) return;
4228
+ let i = 1;
4229
+ while (i < node.length) {
4230
+ const child = node[i];
4231
+ if (!Array.isArray(child) || child[0] !== "block") {
4232
+ i++;
4233
+ continue;
4234
+ }
4235
+ let bi = 1, label = null;
4236
+ if (typeof child[1] === "string" && child[1][0] === "$") {
4237
+ label = child[1];
4238
+ bi = 2;
4239
+ }
4240
+ while (bi < child.length) {
4241
+ const c = child[bi];
4242
+ if (Array.isArray(c) && (c[0] === "param" || c[0] === "result" || c[0] === "type")) {
4243
+ bi++;
4244
+ continue;
4245
+ }
4246
+ break;
4247
+ }
4248
+ const body = child.slice(bi);
4249
+ if (label && targetsLabel(body, label)) {
4250
+ i++;
4251
+ continue;
4252
+ }
4253
+ node.splice(i, 1, ...body);
4254
+ i += body.length;
4255
+ }
4256
+ });
4257
+ return ast;
4258
+ };
4259
+ var coalesceLocals = (ast) => {
4260
+ walk2(ast, (funcNode) => {
4261
+ if (!Array.isArray(funcNode) || funcNode[0] !== "func") return;
4262
+ const decls = /* @__PURE__ */ new Map();
4263
+ for (const sub of funcNode) {
4264
+ if (Array.isArray(sub) && sub[0] === "local" && typeof sub[1] === "string" && sub[1][0] === "$" && typeof sub[2] === "string") {
4265
+ decls.set(sub[1], sub[2]);
4266
+ }
4267
+ }
4268
+ if (decls.size < 2) return;
4269
+ const uses = /* @__PURE__ */ new Map();
4270
+ const loopStack = [];
4271
+ let pos = 0, abort = false, condDepth = 0;
4272
+ const visit = (n) => {
4273
+ if (abort || !Array.isArray(n)) return;
4274
+ const op = n[0];
4275
+ const isLoop = op === "loop";
4276
+ if (isLoop) loopStack.push({ start: pos, end: pos });
4277
+ const isSet = op === "local.set" || op === "local.tee";
4278
+ if (isSet || op === "local.get") {
4279
+ const name2 = n[1];
4280
+ if (typeof name2 !== "string" || name2[0] !== "$") {
4281
+ abort = true;
4282
+ return;
4283
+ }
4284
+ if (isSet) for (let i = 2; i < n.length; i++) visit(n[i]);
4285
+ const here = pos++;
4286
+ if (decls.has(name2)) {
4287
+ let u = uses.get(name2);
4288
+ if (!u) {
4289
+ u = { start: here, end: here, firstOp: op, firstCond: condDepth > 0, loops: /* @__PURE__ */ new Set() };
4290
+ uses.set(name2, u);
4291
+ }
4292
+ if (here > u.end) u.end = here;
4293
+ for (const ls of loopStack) u.loops.add(ls);
4294
+ }
4295
+ } else {
4296
+ pos++;
4297
+ const isIf = op === "if";
4298
+ for (let i = 1; i < n.length; i++) {
4299
+ const c = n[i];
4300
+ const cond = isIf && Array.isArray(c) && (c[0] === "then" || c[0] === "else");
4301
+ if (cond) condDepth++;
4302
+ visit(c);
4303
+ if (cond) condDepth--;
4304
+ }
4305
+ }
4306
+ if (isLoop) {
4307
+ const ls = loopStack.pop();
4308
+ ls.end = pos;
4309
+ }
4310
+ };
4311
+ visit(funcNode);
4312
+ if (abort) return;
4313
+ for (const u of uses.values()) {
4314
+ for (const ls of u.loops) {
4315
+ if (ls.start < u.start) u.start = ls.start;
4316
+ if (ls.end > u.end) u.end = ls.end;
4317
+ }
4318
+ }
4319
+ const ordered = [...uses.entries()].sort((a, b) => a[1].start - b[1].start);
4320
+ const rename = /* @__PURE__ */ new Map();
4321
+ const slots = [];
4322
+ for (const [name2, range] of ordered) {
4323
+ const readsZero = range.firstOp === "local.get" || range.firstCond;
4324
+ const type = decls.get(name2);
4325
+ const slot = readsZero ? null : slots.find((s) => s.type === type && s.end < range.start);
4326
+ if (slot) {
4327
+ rename.set(name2, slot.primary);
4328
+ if (range.end > slot.end) slot.end = range.end;
4329
+ } else slots.push({ primary: name2, type, end: range.end });
4330
+ }
4331
+ if (rename.size === 0) return;
4332
+ walk2(funcNode, (n) => {
4333
+ if (Array.isArray(n) && (n[0] === "local.get" || n[0] === "local.set" || n[0] === "local.tee") && rename.has(n[1])) {
4334
+ n[1] = rename.get(n[1]);
4335
+ }
4336
+ });
4337
+ });
4338
+ return ast;
4339
+ };
3930
4340
  var vacuum = (ast) => {
3931
4341
  return walkPost2(ast, (node) => {
3932
4342
  if (!Array.isArray(node)) return;
@@ -4035,33 +4445,82 @@ var peephole = (ast) => {
4035
4445
  if (result !== null) return result;
4036
4446
  });
4037
4447
  };
4448
+ var slebSize = (v) => {
4449
+ let x = typeof v === "bigint" ? v : BigInt(Math.trunc(Number(v) || 0));
4450
+ let n = 1;
4451
+ while (true) {
4452
+ const b = x & 0x7fn;
4453
+ x >>= 7n;
4454
+ if (x === 0n && (b & 0x40n) === 0n || x === -1n && (b & 0x40n) !== 0n) return n;
4455
+ n++;
4456
+ }
4457
+ };
4458
+ var constInstrSize = (node) => {
4459
+ if (!Array.isArray(node)) return 4;
4460
+ switch (node[0]) {
4461
+ case "i32.const":
4462
+ case "i64.const":
4463
+ return 1 + slebSize(node[1]);
4464
+ case "f32.const":
4465
+ return 5;
4466
+ case "f64.const":
4467
+ return 9;
4468
+ case "v128.const":
4469
+ return 18;
4470
+ default:
4471
+ return 4;
4472
+ }
4473
+ };
4474
+ var GLOBAL_GET_SIZE = 2;
4038
4475
  var globals = (ast) => {
4039
4476
  if (!Array.isArray(ast) || ast[0] !== "module") return ast;
4040
4477
  const constGlobals = /* @__PURE__ */ new Map();
4041
- const mutableGlobals = /* @__PURE__ */ new Set();
4478
+ const exported = /* @__PURE__ */ new Set();
4042
4479
  for (const node of ast.slice(1)) {
4043
- if (!Array.isArray(node) || node[0] !== "global") continue;
4480
+ if (!Array.isArray(node)) continue;
4481
+ if (node[0] === "export" && Array.isArray(node[2]) && node[2][0] === "global" && typeof node[2][1] === "string") {
4482
+ exported.add(node[2][1]);
4483
+ continue;
4484
+ }
4485
+ if (node[0] !== "global") continue;
4044
4486
  const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : null;
4045
4487
  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];
4488
+ if (node.some((c) => Array.isArray(c) && c[0] === "export")) exported.add(name2);
4489
+ const typeSlot = node[2];
4049
4490
  if (Array.isArray(typeSlot) && typeSlot[0] === "mut") continue;
4050
- const init = node[initIdx];
4491
+ if (Array.isArray(typeSlot) && typeSlot[0] === "import") continue;
4492
+ const init = node[3];
4051
4493
  if (getConst(init)) constGlobals.set(name2, init);
4052
4494
  }
4495
+ if (constGlobals.size === 0) return ast;
4496
+ const reads = /* @__PURE__ */ new Map();
4053
4497
  walk2(ast, (n) => {
4054
- if (!Array.isArray(n) || n[0] !== "global.set") return;
4498
+ if (!Array.isArray(n)) return;
4055
4499
  const ref = n[1];
4056
- if (typeof ref === "string" && ref[0] === "$") mutableGlobals.add(ref);
4500
+ if (typeof ref !== "string" || ref[0] !== "$") return;
4501
+ if (n[0] === "global.set") constGlobals.delete(ref);
4502
+ else if (n[0] === "global.get") reads.set(ref, (reads.get(ref) || 0) + 1);
4057
4503
  });
4058
- for (const name2 of mutableGlobals) constGlobals.delete(name2);
4059
- if (constGlobals.size === 0) return ast;
4060
- return walkPost2(ast, (node) => {
4504
+ const propagate2 = /* @__PURE__ */ new Set();
4505
+ for (const [name2, init] of constGlobals) {
4506
+ const r = reads.get(name2) || 0;
4507
+ if (r === 0) continue;
4508
+ const cs = constInstrSize(init);
4509
+ const declSize = cs + 2;
4510
+ const before = r * GLOBAL_GET_SIZE + declSize;
4511
+ const after = r * cs + (exported.has(name2) ? declSize : 0);
4512
+ if (after <= before) propagate2.add(name2);
4513
+ }
4514
+ if (propagate2.size === 0) return ast;
4515
+ walkPost2(ast, (node) => {
4061
4516
  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));
4517
+ if (propagate2.has(node[1])) return clone2(constGlobals.get(node[1]));
4064
4518
  });
4519
+ for (let i = ast.length - 1; i >= 1; i--) {
4520
+ const n = ast[i];
4521
+ if (Array.isArray(n) && n[0] === "global" && typeof n[1] === "string" && propagate2.has(n[1]) && !exported.has(n[1])) ast.splice(i, 1);
4522
+ }
4523
+ return ast;
4065
4524
  };
4066
4525
  var offset = (ast) => {
4067
4526
  return walkPost2(ast, (node) => {
@@ -4141,8 +4600,65 @@ var unbranch = (ast) => {
4141
4600
  if (lastIdx < 0) return;
4142
4601
  const last = node[lastIdx];
4143
4602
  if (Array.isArray(last) && last[0] === "br" && last[1] === label) {
4144
- node.splice(lastIdx, 1);
4603
+ node.splice(lastIdx, 1, ...last.slice(2));
4604
+ }
4605
+ });
4606
+ return ast;
4607
+ };
4608
+ var loopify = (ast) => {
4609
+ walk2(ast, (node) => {
4610
+ if (!Array.isArray(node) || node[0] !== "block") return;
4611
+ let bi = 1, label = null;
4612
+ if (typeof node[1] === "string" && node[1][0] === "$") {
4613
+ label = node[1];
4614
+ bi = 2;
4615
+ }
4616
+ if (!label) return;
4617
+ while (bi < node.length) {
4618
+ const c = node[bi];
4619
+ if (Array.isArray(c) && c[0] === "type") {
4620
+ bi++;
4621
+ continue;
4622
+ }
4623
+ if (Array.isArray(c) && (c[0] === "param" || c[0] === "result")) return;
4624
+ break;
4625
+ }
4626
+ if (node.length - bi !== 1) return;
4627
+ const loop = node[bi];
4628
+ if (!Array.isArray(loop) || loop[0] !== "loop") return;
4629
+ let li = 1, loopLabel = null;
4630
+ if (typeof loop[1] === "string" && loop[1][0] === "$") {
4631
+ loopLabel = loop[1];
4632
+ li = 2;
4633
+ }
4634
+ const loopHeader = [];
4635
+ while (li < loop.length) {
4636
+ const c = loop[li];
4637
+ if (Array.isArray(c) && c[0] === "type") {
4638
+ loopHeader.push(c);
4639
+ li++;
4640
+ continue;
4641
+ }
4642
+ if (Array.isArray(c) && (c[0] === "param" || c[0] === "result")) return;
4643
+ break;
4145
4644
  }
4645
+ const body = loop.slice(li);
4646
+ if (body.length < 2) return;
4647
+ const head = body[0];
4648
+ const tail = body[body.length - 1];
4649
+ if (!Array.isArray(head) || head[0] !== "br_if" || head[1] !== label || head.length !== 3) return;
4650
+ if (!Array.isArray(tail) || tail[0] !== "br" || tail[1] !== loopLabel || tail.length !== 2) return;
4651
+ const inner = body.slice(1, -1);
4652
+ if (targetsLabel(inner, label)) return;
4653
+ let cond = head[2];
4654
+ if (Array.isArray(cond) && cond[0] === "i32.eqz" && cond.length === 2) cond = cond[1];
4655
+ else cond = ["i32.eqz", cond];
4656
+ const newLoop = ["loop"];
4657
+ if (loopLabel) newLoop.push(loopLabel);
4658
+ for (const h of loopHeader) newLoop.push(h);
4659
+ newLoop.push(["if", cond, ["then", ...inner, tail]]);
4660
+ node.length = 0;
4661
+ for (const tok of newLoop) node.push(tok);
4146
4662
  });
4147
4663
  return ast;
4148
4664
  };
@@ -4599,7 +5115,7 @@ function optimize(ast, opts = true) {
4599
5115
  let beforeRound = null;
4600
5116
  for (let round = 0; round < 3; round++) {
4601
5117
  beforeRound = clone2(ast);
4602
- const sizeBefore = count(ast);
5118
+ const sizeBefore = binarySize(ast);
4603
5119
  if (opts.stripmut) ast = stripmut(ast);
4604
5120
  if (opts.globals) ast = globals(ast);
4605
5121
  if (opts.fold) ast = fold(ast);
@@ -4608,13 +5124,17 @@ function optimize(ast, opts = true) {
4608
5124
  if (opts.strength) ast = strength(ast);
4609
5125
  if (opts.branch) ast = branch(ast);
4610
5126
  if (opts.propagate) ast = propagate(ast);
5127
+ if (opts.inlineOnce) ast = inlineOnce(ast);
4611
5128
  if (opts.inline) ast = inline(ast);
4612
5129
  if (opts.offset) ast = offset(ast);
4613
5130
  if (opts.unbranch) ast = unbranch(ast);
5131
+ if (opts.loopify) ast = loopify(ast);
4614
5132
  if (opts.brif) ast = brif(ast);
4615
5133
  if (opts.foldarms) ast = foldarms(ast);
4616
5134
  if (opts.deadcode) ast = deadcode(ast);
4617
5135
  if (opts.vacuum) ast = vacuum(ast);
5136
+ if (opts.mergeBlocks) ast = mergeBlocks(ast);
5137
+ if (opts.coalesce) ast = coalesceLocals(ast);
4618
5138
  if (opts.locals) ast = localReuse(ast);
4619
5139
  if (opts.dedupe) ast = dedupe(ast);
4620
5140
  if (opts.dedupTypes) ast = dedupTypes(ast);
@@ -4622,14 +5142,15 @@ function optimize(ast, opts = true) {
4622
5142
  if (opts.reorder) ast = reorder(ast);
4623
5143
  if (opts.treeshake) ast = treeshake(ast);
4624
5144
  if (opts.minifyImports) ast = minifyImports(ast);
4625
- const sizeAfter = count(ast);
5145
+ if (opts.propagate && (opts.inlineOnce || opts.inline)) ast = propagate(ast);
5146
+ const sizeAfter = binarySize(ast);
4626
5147
  const delta = sizeAfter - sizeBefore;
4627
5148
  if (verbose || delta !== 0) {
4628
- log(` round ${round + 1}: ${delta > 0 ? "+" : ""}${delta} nodes`, delta);
5149
+ log(` round ${round + 1}: ${delta > 0 ? "+" : ""}${delta} bytes`, delta);
4629
5150
  }
4630
- const tolerance = strictGuard ? 0 : 5;
5151
+ const tolerance = strictGuard ? 0 : 16;
4631
5152
  if (delta > tolerance) {
4632
- if (verbose) log(` \u26A0 round ${round + 1} inflated by ${delta}, reverting`, delta);
5153
+ if (verbose) log(` \u26A0 round ${round + 1} inflated by ${delta} bytes, reverting`, delta);
4633
5154
  ast = beforeRound;
4634
5155
  break;
4635
5156
  }