watr 4.1.0 → 4.2.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
@@ -18,7 +18,7 @@ __export(encode_exports, {
18
18
  });
19
19
 
20
20
  // src/util.js
21
- var err = (text, pos = err.i) => {
21
+ var err = (text, pos = err.loc) => {
22
22
  if (pos != null && err.src) {
23
23
  let line = 1, col = 1;
24
24
  for (let i = 0; i < pos && i < err.src.length; i++) {
@@ -951,7 +951,7 @@ var parse_default = (str2) => {
951
951
  let i = 0, level = [], buf = "", q = 0, depth = 0;
952
952
  const commit = () => buf && (level.push(buf), buf = "");
953
953
  const parseLevel = (pos) => {
954
- level.i = pos;
954
+ level.loc = pos;
955
955
  for (let c, root, p; i < str2.length; ) {
956
956
  c = str2.charCodeAt(i);
957
957
  if (q === 34) buf += str2[i++], c === 92 ? buf += str2[i++] : c === 34 && (commit(), q = 0);
@@ -996,14 +996,14 @@ var cleanup = (node, result) => !Array.isArray(node) ? typeof node !== "string"
996
996
  ) : (
997
997
  // remove annotations like (@name ...) except @custom and @metadata.code.*
998
998
  node[0]?.[0] === "@" && node[0] !== "@custom" && !node[0]?.startsWith?.("@metadata.code.") ? null : (
999
- // unwrap single-element array containing module (after removing comments), preserve .i
1000
- (result = node.map(cleanup).filter((n) => n != null), result.i = node.i, result.length === 1 && result[0]?.[0] === "module" ? result[0] : result)
999
+ // unwrap single-element array containing module (after removing comments), preserve .loc
1000
+ (result = node.map(cleanup).filter((n) => n != null), result.loc = node.loc, result.length === 1 && result[0]?.[0] === "module" ? result[0] : result)
1001
1001
  )
1002
1002
  );
1003
1003
  function compile(nodes) {
1004
1004
  if (typeof nodes === "string") err.src = nodes, nodes = parse_default(nodes) || [];
1005
1005
  else err.src = "";
1006
- err.i = 0;
1006
+ err.loc = 0;
1007
1007
  nodes = cleanup(nodes) || [];
1008
1008
  let idx = 0;
1009
1009
  if (nodes[0] === "module") idx++, isId(nodes[idx]) && idx++;
@@ -1015,12 +1015,25 @@ function compile(nodes) {
1015
1015
  ctx.metadata = {};
1016
1016
  nodes.slice(idx).filter((n) => {
1017
1017
  if (!Array.isArray(n)) {
1018
- let pos = err.src?.indexOf(n, err.i);
1019
- if (pos >= 0) err.i = pos;
1018
+ let pos = err.loc, src = err.src, c;
1019
+ while ((pos = src.indexOf(n, pos)) >= 0) {
1020
+ c = src.charCodeAt(pos - 1);
1021
+ if (pos > 0 && (c > 47 && c < 58 || c > 64 && c < 91 || c > 96 && c < 123 || c === 95 || c === 36)) {
1022
+ pos++;
1023
+ continue;
1024
+ }
1025
+ c = src.charCodeAt(pos + n.length);
1026
+ if (c > 47 && c < 58 || c > 64 && c < 91 || c > 96 && c < 123 || c === 95) {
1027
+ pos++;
1028
+ continue;
1029
+ }
1030
+ break;
1031
+ }
1032
+ if (pos >= 0) err.loc = pos;
1020
1033
  err(`Unexpected token ${n}`);
1021
1034
  }
1022
1035
  let [kind, ...node] = n;
1023
- err.i = n.i;
1036
+ err.loc = n.loc;
1024
1037
  if (kind === "@custom") {
1025
1038
  ctx.custom.push(node);
1026
1039
  } else if (kind === "rec") {
@@ -1037,7 +1050,7 @@ function compile(nodes) {
1037
1050
  else return true;
1038
1051
  }).forEach((n) => {
1039
1052
  let [kind, ...node] = n;
1040
- err.i = n.i;
1053
+ err.loc = n.loc;
1041
1054
  let imported;
1042
1055
  if (kind === "import") [kind, ...node] = (imported = node).pop();
1043
1056
  let items = ctx[kind];
@@ -1147,7 +1160,7 @@ function normalize(nodes, ctx) {
1147
1160
  } else if ((node.startsWith("memory.") || node.endsWith("load") || node.endsWith("store")) && isIdx(nodes[0])) out.push(nodes.shift());
1148
1161
  } else if (Array.isArray(node)) {
1149
1162
  const op = node[0];
1150
- node.i != null && (err.i = node.i);
1163
+ node.loc != null && (err.loc = node.loc);
1151
1164
  if (op?.startsWith?.("@metadata.code.")) {
1152
1165
  let type = op.slice(15);
1153
1166
  out.push(["@metadata", type, node[1]]);
@@ -1561,7 +1574,7 @@ var instr = (nodes, ctx) => {
1561
1574
  continue;
1562
1575
  }
1563
1576
  if (Array.isArray(op)) {
1564
- op.i != null && (err.i = op.i);
1577
+ op.loc != null && (err.loc = op.loc);
1565
1578
  err(`Unknown instruction ${op[0]}`);
1566
1579
  }
1567
1580
  let [...bytes] = INSTR[op] || err(`Unknown instruction ${op}`);
@@ -2566,6 +2579,801 @@ function polyfill(ast, opts = true) {
2566
2579
  return ast;
2567
2580
  }
2568
2581
 
2582
+ // src/optimize.js
2583
+ var OPTS = {
2584
+ treeshake: true,
2585
+ // remove unused funcs/globals/types/tables
2586
+ fold: true,
2587
+ // constant folding
2588
+ deadcode: true,
2589
+ // eliminate dead code after unreachable/br/return
2590
+ locals: true,
2591
+ // remove unused locals
2592
+ identity: true,
2593
+ // remove identity ops (x + 0 → x)
2594
+ strength: true,
2595
+ // strength reduction (x * 2 → x << 1)
2596
+ branch: true,
2597
+ // simplify constant branches
2598
+ propagate: true,
2599
+ // constant propagation through locals
2600
+ inline: true
2601
+ // inline tiny functions
2602
+ };
2603
+ var ALL2 = Object.keys(OPTS);
2604
+ var normalize3 = (opts) => {
2605
+ if (opts === true) return { ...OPTS };
2606
+ if (opts === false) return {};
2607
+ if (typeof opts === "string") {
2608
+ const set = new Set(opts.split(/\s+/).filter(Boolean));
2609
+ if (set.size === 1 && ALL2.includes([...set][0])) {
2610
+ return Object.fromEntries(ALL2.map((f) => [f, set.has(f)]));
2611
+ }
2612
+ return Object.fromEntries(ALL2.map((f) => [f, set.has(f) || set.has("all")]));
2613
+ }
2614
+ return { ...OPTS, ...opts };
2615
+ };
2616
+ var clone2 = (node) => {
2617
+ if (!Array.isArray(node)) return node;
2618
+ return node.map(clone2);
2619
+ };
2620
+ var walk2 = (node, fn, parent, idx) => {
2621
+ fn(node, parent, idx);
2622
+ if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walk2(node[i], fn, node, i);
2623
+ };
2624
+ var walkPost2 = (node, fn, parent, idx) => {
2625
+ if (Array.isArray(node)) {
2626
+ for (let i = 0; i < node.length; i++) {
2627
+ const result2 = walkPost2(node[i], fn, node, i);
2628
+ if (result2 !== void 0) node[i] = result2;
2629
+ }
2630
+ }
2631
+ const result = fn(node, parent, idx);
2632
+ return result !== void 0 ? result : node;
2633
+ };
2634
+ var treeshake = (ast) => {
2635
+ if (!Array.isArray(ast) || ast[0] !== "module") return ast;
2636
+ const funcs = /* @__PURE__ */ new Map();
2637
+ const globals = /* @__PURE__ */ new Map();
2638
+ const types = /* @__PURE__ */ new Map();
2639
+ const tables = /* @__PURE__ */ new Map();
2640
+ const memories = /* @__PURE__ */ new Map();
2641
+ const exports = [];
2642
+ const starts = [];
2643
+ let funcIdx = 0, globalIdx = 0, typeIdx = 0, tableIdx = 0, memIdx = 0, importFuncIdx = 0;
2644
+ for (const node of ast.slice(1)) {
2645
+ if (!Array.isArray(node)) continue;
2646
+ const kind = node[0];
2647
+ if (kind === "type") {
2648
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : typeIdx;
2649
+ types.set(name2, { node, idx: typeIdx, used: false });
2650
+ if (typeof name2 === "string") types.set(typeIdx, types.get(name2));
2651
+ typeIdx++;
2652
+ } else if (kind === "func") {
2653
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : funcIdx;
2654
+ const hasInlineExport = node.some((sub) => Array.isArray(sub) && sub[0] === "export");
2655
+ funcs.set(name2, { node, idx: funcIdx, used: hasInlineExport });
2656
+ if (typeof name2 === "string") funcs.set(funcIdx, funcs.get(name2));
2657
+ funcIdx++;
2658
+ } else if (kind === "global") {
2659
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : globalIdx;
2660
+ const hasInlineExport = node.some((sub) => Array.isArray(sub) && sub[0] === "export");
2661
+ globals.set(name2, { node, idx: globalIdx, used: hasInlineExport });
2662
+ if (typeof name2 === "string") globals.set(globalIdx, globals.get(name2));
2663
+ globalIdx++;
2664
+ } else if (kind === "table") {
2665
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : tableIdx;
2666
+ const hasInlineExport = node.some((sub) => Array.isArray(sub) && sub[0] === "export");
2667
+ tables.set(name2, { node, idx: tableIdx, used: hasInlineExport });
2668
+ if (typeof name2 === "string") tables.set(tableIdx, tables.get(name2));
2669
+ tableIdx++;
2670
+ } else if (kind === "memory") {
2671
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : memIdx;
2672
+ const hasInlineExport = node.some((sub) => Array.isArray(sub) && sub[0] === "export");
2673
+ memories.set(name2, { node, idx: memIdx, used: hasInlineExport });
2674
+ if (typeof name2 === "string") memories.set(memIdx, memories.get(name2));
2675
+ memIdx++;
2676
+ } else if (kind === "import") {
2677
+ for (const sub of node) {
2678
+ if (Array.isArray(sub) && sub[0] === "func") {
2679
+ const name2 = typeof sub[1] === "string" && sub[1][0] === "$" ? sub[1] : importFuncIdx;
2680
+ funcs.set(name2, { node, idx: importFuncIdx, used: true, isImport: true });
2681
+ if (typeof name2 === "string") funcs.set(importFuncIdx, funcs.get(name2));
2682
+ importFuncIdx++;
2683
+ funcIdx++;
2684
+ }
2685
+ }
2686
+ } else if (kind === "export") {
2687
+ exports.push(node);
2688
+ } else if (kind === "start") {
2689
+ starts.push(node);
2690
+ }
2691
+ }
2692
+ for (const exp of exports) {
2693
+ for (const sub of exp) {
2694
+ if (!Array.isArray(sub)) continue;
2695
+ const [kind, ref] = sub;
2696
+ if (kind === "func" && funcs.has(ref)) funcs.get(ref).used = true;
2697
+ else if (kind === "global" && globals.has(ref)) globals.get(ref).used = true;
2698
+ else if (kind === "table" && tables.has(ref)) tables.get(ref).used = true;
2699
+ else if (kind === "memory" && memories.has(ref)) memories.get(ref).used = true;
2700
+ }
2701
+ }
2702
+ for (const start of starts) {
2703
+ let ref = start[1];
2704
+ if (typeof ref === "string" && ref[0] !== "$") ref = +ref;
2705
+ if (funcs.has(ref)) funcs.get(ref).used = true;
2706
+ }
2707
+ let hasExports = exports.length > 0 || starts.length > 0;
2708
+ if (!hasExports) {
2709
+ for (const [, entry] of funcs) if (entry.used) {
2710
+ hasExports = true;
2711
+ break;
2712
+ }
2713
+ if (!hasExports) {
2714
+ for (const [, entry] of globals) if (entry.used) {
2715
+ hasExports = true;
2716
+ break;
2717
+ }
2718
+ }
2719
+ if (!hasExports) {
2720
+ for (const [, entry] of tables) if (entry.used) {
2721
+ hasExports = true;
2722
+ break;
2723
+ }
2724
+ }
2725
+ if (!hasExports) {
2726
+ for (const [, entry] of memories) if (entry.used) {
2727
+ hasExports = true;
2728
+ break;
2729
+ }
2730
+ }
2731
+ }
2732
+ if (!hasExports) {
2733
+ for (const [, entry] of funcs) entry.used = true;
2734
+ for (const [, entry] of globals) entry.used = true;
2735
+ for (const [, entry] of tables) entry.used = true;
2736
+ for (const [, entry] of memories) entry.used = true;
2737
+ }
2738
+ for (const node of ast.slice(1)) {
2739
+ if (!Array.isArray(node) || node[0] !== "elem") continue;
2740
+ walk2(node, (n) => {
2741
+ if (Array.isArray(n) && n[0] === "ref.func") {
2742
+ const ref = n[1];
2743
+ if (funcs.has(ref)) funcs.get(ref).used = true;
2744
+ }
2745
+ if (typeof n === "string" && n[0] === "$" && funcs.has(n)) funcs.get(n).used = true;
2746
+ });
2747
+ }
2748
+ let changed = true;
2749
+ while (changed) {
2750
+ changed = false;
2751
+ for (const [, entry] of funcs) {
2752
+ if (!entry.used || entry.isImport) continue;
2753
+ walk2(entry.node, (n) => {
2754
+ if (!Array.isArray(n)) {
2755
+ if (typeof n === "string" && n[0] === "$" && funcs.has(n) && !funcs.get(n).used) {
2756
+ funcs.get(n).used = true;
2757
+ changed = true;
2758
+ }
2759
+ return;
2760
+ }
2761
+ const [op, ref] = n;
2762
+ if ((op === "call" || op === "return_call" || op === "ref.func") && funcs.has(ref) && !funcs.get(ref).used) {
2763
+ funcs.get(ref).used = true;
2764
+ changed = true;
2765
+ }
2766
+ if ((op === "global.get" || op === "global.set") && globals.has(ref) && !globals.get(ref).used) {
2767
+ globals.get(ref).used = true;
2768
+ changed = true;
2769
+ }
2770
+ if (op === "call_indirect" || op === "return_call_indirect") {
2771
+ for (const sub of n) {
2772
+ if (typeof sub === "string" && sub[0] === "$" && tables.has(sub) && !tables.get(sub).used) {
2773
+ tables.get(sub).used = true;
2774
+ changed = true;
2775
+ }
2776
+ }
2777
+ }
2778
+ if (op === "type" && types.has(ref) && !types.get(ref).used) {
2779
+ types.get(ref).used = true;
2780
+ changed = true;
2781
+ }
2782
+ });
2783
+ }
2784
+ }
2785
+ const result = ["module"];
2786
+ for (const node of ast.slice(1)) {
2787
+ if (!Array.isArray(node)) {
2788
+ result.push(node);
2789
+ continue;
2790
+ }
2791
+ const kind = node[0];
2792
+ if (kind === "func") {
2793
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : null;
2794
+ const entry = name2 ? funcs.get(name2) : [...funcs.values()].find((e) => e.node === node);
2795
+ if (entry?.used) result.push(node);
2796
+ } else if (kind === "global") {
2797
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : null;
2798
+ const entry = name2 ? globals.get(name2) : [...globals.values()].find((e) => e.node === node);
2799
+ if (entry?.used) result.push(node);
2800
+ } else if (kind === "type") {
2801
+ result.push(node);
2802
+ } else {
2803
+ result.push(node);
2804
+ }
2805
+ }
2806
+ return result;
2807
+ };
2808
+ var FOLDABLE = {
2809
+ // i32
2810
+ "i32.add": (a, b) => a + b | 0,
2811
+ "i32.sub": (a, b) => a - b | 0,
2812
+ "i32.mul": (a, b) => Math.imul(a, b),
2813
+ "i32.div_s": (a, b) => b !== 0 ? a / b | 0 : null,
2814
+ "i32.div_u": (a, b) => b !== 0 ? (a >>> 0) / (b >>> 0) | 0 : null,
2815
+ "i32.rem_s": (a, b) => b !== 0 ? a % b | 0 : null,
2816
+ "i32.rem_u": (a, b) => b !== 0 ? (a >>> 0) % (b >>> 0) | 0 : null,
2817
+ "i32.and": (a, b) => a & b,
2818
+ "i32.or": (a, b) => a | b,
2819
+ "i32.xor": (a, b) => a ^ b,
2820
+ "i32.shl": (a, b) => a << (b & 31),
2821
+ "i32.shr_s": (a, b) => a >> (b & 31),
2822
+ "i32.shr_u": (a, b) => a >>> (b & 31),
2823
+ "i32.rotl": (a, b) => {
2824
+ b &= 31;
2825
+ return a << b | a >>> 32 - b | 0;
2826
+ },
2827
+ "i32.rotr": (a, b) => {
2828
+ b &= 31;
2829
+ return a >>> b | a << 32 - b | 0;
2830
+ },
2831
+ "i32.eq": (a, b) => a === b ? 1 : 0,
2832
+ "i32.ne": (a, b) => a !== b ? 1 : 0,
2833
+ "i32.lt_s": (a, b) => a < b ? 1 : 0,
2834
+ "i32.lt_u": (a, b) => a >>> 0 < b >>> 0 ? 1 : 0,
2835
+ "i32.gt_s": (a, b) => a > b ? 1 : 0,
2836
+ "i32.gt_u": (a, b) => a >>> 0 > b >>> 0 ? 1 : 0,
2837
+ "i32.le_s": (a, b) => a <= b ? 1 : 0,
2838
+ "i32.le_u": (a, b) => a >>> 0 <= b >>> 0 ? 1 : 0,
2839
+ "i32.ge_s": (a, b) => a >= b ? 1 : 0,
2840
+ "i32.ge_u": (a, b) => a >>> 0 >= b >>> 0 ? 1 : 0,
2841
+ "i32.eqz": (a) => a === 0 ? 1 : 0,
2842
+ "i32.clz": (a) => Math.clz32(a),
2843
+ "i32.ctz": (a) => a === 0 ? 32 : 31 - Math.clz32(a & -a),
2844
+ "i32.popcnt": (a) => {
2845
+ let c = 0;
2846
+ while (a) {
2847
+ c += a & 1;
2848
+ a >>>= 1;
2849
+ }
2850
+ return c;
2851
+ },
2852
+ "i32.wrap_i64": (a) => Number(BigInt.asIntN(32, a)),
2853
+ // i64 (using BigInt)
2854
+ "i64.add": (a, b) => BigInt.asIntN(64, a + b),
2855
+ "i64.sub": (a, b) => BigInt.asIntN(64, a - b),
2856
+ "i64.mul": (a, b) => BigInt.asIntN(64, a * b),
2857
+ "i64.div_s": (a, b) => b !== 0n ? BigInt.asIntN(64, a / b) : null,
2858
+ "i64.div_u": (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) / BigInt.asUintN(64, b)) : null,
2859
+ "i64.rem_s": (a, b) => b !== 0n ? BigInt.asIntN(64, a % b) : null,
2860
+ "i64.rem_u": (a, b) => b !== 0n ? BigInt.asUintN(64, BigInt.asUintN(64, a) % BigInt.asUintN(64, b)) : null,
2861
+ "i64.and": (a, b) => BigInt.asIntN(64, a & b),
2862
+ "i64.or": (a, b) => BigInt.asIntN(64, a | b),
2863
+ "i64.xor": (a, b) => BigInt.asIntN(64, a ^ b),
2864
+ "i64.shl": (a, b) => BigInt.asIntN(64, a << (b & 63n)),
2865
+ "i64.shr_s": (a, b) => BigInt.asIntN(64, a >> (b & 63n)),
2866
+ "i64.shr_u": (a, b) => BigInt.asUintN(64, BigInt.asUintN(64, a) >> (b & 63n)),
2867
+ "i64.eq": (a, b) => a === b ? 1 : 0,
2868
+ "i64.ne": (a, b) => a !== b ? 1 : 0,
2869
+ "i64.lt_s": (a, b) => a < b ? 1 : 0,
2870
+ "i64.lt_u": (a, b) => BigInt.asUintN(64, a) < BigInt.asUintN(64, b) ? 1 : 0,
2871
+ "i64.gt_s": (a, b) => a > b ? 1 : 0,
2872
+ "i64.gt_u": (a, b) => BigInt.asUintN(64, a) > BigInt.asUintN(64, b) ? 1 : 0,
2873
+ "i64.le_s": (a, b) => a <= b ? 1 : 0,
2874
+ "i64.le_u": (a, b) => BigInt.asUintN(64, a) <= BigInt.asUintN(64, b) ? 1 : 0,
2875
+ "i64.ge_s": (a, b) => a >= b ? 1 : 0,
2876
+ "i64.ge_u": (a, b) => BigInt.asUintN(64, a) >= BigInt.asUintN(64, b) ? 1 : 0,
2877
+ "i64.eqz": (a) => a === 0n ? 1 : 0,
2878
+ "i64.extend_i32_s": (a) => BigInt(a),
2879
+ "i64.extend_i32_u": (a) => BigInt(a >>> 0),
2880
+ // f32/f64 - be careful with NaN/precision
2881
+ "f32.add": (a, b) => Math.fround(a + b),
2882
+ "f32.sub": (a, b) => Math.fround(a - b),
2883
+ "f32.mul": (a, b) => Math.fround(a * b),
2884
+ "f32.div": (a, b) => Math.fround(a / b),
2885
+ "f32.neg": (a) => Math.fround(-a),
2886
+ "f32.abs": (a) => Math.fround(Math.abs(a)),
2887
+ "f32.sqrt": (a) => Math.fround(Math.sqrt(a)),
2888
+ "f32.ceil": (a) => Math.fround(Math.ceil(a)),
2889
+ "f32.floor": (a) => Math.fround(Math.floor(a)),
2890
+ "f32.trunc": (a) => Math.fround(Math.trunc(a)),
2891
+ "f32.nearest": (a) => Math.fround(Math.round(a)),
2892
+ "f64.add": (a, b) => a + b,
2893
+ "f64.sub": (a, b) => a - b,
2894
+ "f64.mul": (a, b) => a * b,
2895
+ "f64.div": (a, b) => a / b,
2896
+ "f64.neg": (a) => -a,
2897
+ "f64.abs": (a) => Math.abs(a),
2898
+ "f64.sqrt": (a) => Math.sqrt(a),
2899
+ "f64.ceil": (a) => Math.ceil(a),
2900
+ "f64.floor": (a) => Math.floor(a),
2901
+ "f64.trunc": (a) => Math.trunc(a),
2902
+ "f64.nearest": (a) => Math.round(a)
2903
+ };
2904
+ var getConst = (node) => {
2905
+ if (!Array.isArray(node) || node.length !== 2) return null;
2906
+ const [op, val] = node;
2907
+ if (op === "i32.const") return { type: "i32", value: Number(val) | 0 };
2908
+ if (op === "i64.const") return { type: "i64", value: BigInt(val) };
2909
+ if (op === "f32.const") return { type: "f32", value: Math.fround(Number(val)) };
2910
+ if (op === "f64.const") return { type: "f64", value: Number(val) };
2911
+ return null;
2912
+ };
2913
+ var makeConst = (type, value) => {
2914
+ if (type === "i32") return ["i32.const", value | 0];
2915
+ if (type === "i64") return ["i64.const", value];
2916
+ if (type === "f32") return ["f32.const", Math.fround(value)];
2917
+ if (type === "f64") return ["f64.const", value];
2918
+ return null;
2919
+ };
2920
+ var fold = (ast) => {
2921
+ return walkPost2(clone2(ast), (node) => {
2922
+ if (!Array.isArray(node)) return;
2923
+ const op = node[0];
2924
+ const fn = FOLDABLE[op];
2925
+ if (!fn) return;
2926
+ if (fn.length === 1 && node.length === 2) {
2927
+ const a = getConst(node[1]);
2928
+ if (!a) return;
2929
+ const result = fn(a.value);
2930
+ if (result === null) return;
2931
+ const resultType = op.startsWith("i64.") && !op.includes("eqz") ? "i64" : op.startsWith("f32.") ? "f32" : op.startsWith("f64.") ? "f64" : "i32";
2932
+ return makeConst(resultType, result);
2933
+ }
2934
+ if (fn.length === 2 && node.length === 3) {
2935
+ const a = getConst(node[1]);
2936
+ const b = getConst(node[2]);
2937
+ if (!a || !b) return;
2938
+ const result = fn(a.value, b.value);
2939
+ if (result === null) return;
2940
+ const isCompare = /\.(eq|ne|[lg][te])/.test(op);
2941
+ const resultType = isCompare ? "i32" : op.startsWith("i64.") ? "i64" : op.startsWith("f32.") ? "f32" : op.startsWith("f64.") ? "f64" : "i32";
2942
+ return makeConst(resultType, result);
2943
+ }
2944
+ });
2945
+ };
2946
+ var IDENTITIES = {
2947
+ // x + 0 → x, 0 + x → x
2948
+ "i32.add": (a, b) => {
2949
+ const ca = getConst(a), cb = getConst(b);
2950
+ if (ca?.value === 0) return b;
2951
+ if (cb?.value === 0) return a;
2952
+ return null;
2953
+ },
2954
+ "i64.add": (a, b) => {
2955
+ const ca = getConst(a), cb = getConst(b);
2956
+ if (ca?.value === 0n) return b;
2957
+ if (cb?.value === 0n) return a;
2958
+ return null;
2959
+ },
2960
+ // x - 0 → x
2961
+ "i32.sub": (a, b) => getConst(b)?.value === 0 ? a : null,
2962
+ "i64.sub": (a, b) => getConst(b)?.value === 0n ? a : null,
2963
+ // x * 1 → x, 1 * x → x
2964
+ "i32.mul": (a, b) => {
2965
+ const ca = getConst(a), cb = getConst(b);
2966
+ if (ca?.value === 1) return b;
2967
+ if (cb?.value === 1) return a;
2968
+ return null;
2969
+ },
2970
+ "i64.mul": (a, b) => {
2971
+ const ca = getConst(a), cb = getConst(b);
2972
+ if (ca?.value === 1n) return b;
2973
+ if (cb?.value === 1n) return a;
2974
+ return null;
2975
+ },
2976
+ // x / 1 → x
2977
+ "i32.div_s": (a, b) => getConst(b)?.value === 1 ? a : null,
2978
+ "i32.div_u": (a, b) => getConst(b)?.value === 1 ? a : null,
2979
+ "i64.div_s": (a, b) => getConst(b)?.value === 1n ? a : null,
2980
+ "i64.div_u": (a, b) => getConst(b)?.value === 1n ? a : null,
2981
+ // x & -1 → x, -1 & x → x (all bits set)
2982
+ "i32.and": (a, b) => {
2983
+ const ca = getConst(a), cb = getConst(b);
2984
+ if (ca?.value === -1) return b;
2985
+ if (cb?.value === -1) return a;
2986
+ return null;
2987
+ },
2988
+ "i64.and": (a, b) => {
2989
+ const ca = getConst(a), cb = getConst(b);
2990
+ if (ca?.value === -1n) return b;
2991
+ if (cb?.value === -1n) return a;
2992
+ return null;
2993
+ },
2994
+ // x | 0 → x, 0 | x → x
2995
+ "i32.or": (a, b) => {
2996
+ const ca = getConst(a), cb = getConst(b);
2997
+ if (ca?.value === 0) return b;
2998
+ if (cb?.value === 0) return a;
2999
+ return null;
3000
+ },
3001
+ "i64.or": (a, b) => {
3002
+ const ca = getConst(a), cb = getConst(b);
3003
+ if (ca?.value === 0n) return b;
3004
+ if (cb?.value === 0n) return a;
3005
+ return null;
3006
+ },
3007
+ // x ^ 0 → x, 0 ^ x → x
3008
+ "i32.xor": (a, b) => {
3009
+ const ca = getConst(a), cb = getConst(b);
3010
+ if (ca?.value === 0) return b;
3011
+ if (cb?.value === 0) return a;
3012
+ return null;
3013
+ },
3014
+ "i64.xor": (a, b) => {
3015
+ const ca = getConst(a), cb = getConst(b);
3016
+ if (ca?.value === 0n) return b;
3017
+ if (cb?.value === 0n) return a;
3018
+ return null;
3019
+ },
3020
+ // x << 0 → x, x >> 0 → x
3021
+ "i32.shl": (a, b) => getConst(b)?.value === 0 ? a : null,
3022
+ "i32.shr_s": (a, b) => getConst(b)?.value === 0 ? a : null,
3023
+ "i32.shr_u": (a, b) => getConst(b)?.value === 0 ? a : null,
3024
+ "i64.shl": (a, b) => getConst(b)?.value === 0n ? a : null,
3025
+ "i64.shr_s": (a, b) => getConst(b)?.value === 0n ? a : null,
3026
+ "i64.shr_u": (a, b) => getConst(b)?.value === 0n ? a : null
3027
+ // f + 0 → x (careful with -0.0, skip for floats)
3028
+ // f * 1 → x (careful with NaN, skip for floats)
3029
+ };
3030
+ var identity = (ast) => {
3031
+ return walkPost2(clone2(ast), (node) => {
3032
+ if (!Array.isArray(node) || node.length !== 3) return;
3033
+ const fn = IDENTITIES[node[0]];
3034
+ if (!fn) return;
3035
+ const result = fn(node[1], node[2]);
3036
+ if (result === null) return;
3037
+ return result;
3038
+ });
3039
+ };
3040
+ var strength = (ast) => {
3041
+ return walkPost2(clone2(ast), (node) => {
3042
+ if (!Array.isArray(node) || node.length !== 3) return;
3043
+ const [op, a, b] = node;
3044
+ if (op === "i32.mul") {
3045
+ const cb = getConst(b);
3046
+ if (cb && cb.value > 0 && (cb.value & cb.value - 1) === 0) {
3047
+ const shift = Math.log2(cb.value);
3048
+ if (Number.isInteger(shift)) return ["i32.shl", a, ["i32.const", shift]];
3049
+ }
3050
+ const ca = getConst(a);
3051
+ if (ca && ca.value > 0 && (ca.value & ca.value - 1) === 0) {
3052
+ const shift = Math.log2(ca.value);
3053
+ if (Number.isInteger(shift)) return ["i32.shl", b, ["i32.const", shift]];
3054
+ }
3055
+ }
3056
+ if (op === "i64.mul") {
3057
+ const cb = getConst(b);
3058
+ if (cb && cb.value > 0n && (cb.value & cb.value - 1n) === 0n) {
3059
+ const shift = BigInt(cb.value.toString(2).length - 1);
3060
+ return ["i64.shl", a, ["i64.const", shift]];
3061
+ }
3062
+ const ca = getConst(a);
3063
+ if (ca && ca.value > 0n && (ca.value & ca.value - 1n) === 0n) {
3064
+ const shift = BigInt(ca.value.toString(2).length - 1);
3065
+ return ["i64.shl", b, ["i64.const", shift]];
3066
+ }
3067
+ }
3068
+ if (op === "i32.div_u") {
3069
+ const cb = getConst(b);
3070
+ if (cb && cb.value > 0 && (cb.value & cb.value - 1) === 0) {
3071
+ const shift = Math.log2(cb.value);
3072
+ if (Number.isInteger(shift)) return ["i32.shr_u", a, ["i32.const", shift]];
3073
+ }
3074
+ }
3075
+ if (op === "i64.div_u") {
3076
+ const cb = getConst(b);
3077
+ if (cb && cb.value > 0n && (cb.value & cb.value - 1n) === 0n) {
3078
+ const shift = BigInt(cb.value.toString(2).length - 1);
3079
+ return ["i64.shr_u", a, ["i64.const", shift]];
3080
+ }
3081
+ }
3082
+ if (op === "i32.rem_u") {
3083
+ const cb = getConst(b);
3084
+ if (cb && cb.value > 0 && (cb.value & cb.value - 1) === 0) {
3085
+ return ["i32.and", a, ["i32.const", cb.value - 1]];
3086
+ }
3087
+ }
3088
+ if (op === "i64.rem_u") {
3089
+ const cb = getConst(b);
3090
+ if (cb && cb.value > 0n && (cb.value & cb.value - 1n) === 0n) {
3091
+ return ["i64.and", a, ["i64.const", cb.value - 1n]];
3092
+ }
3093
+ }
3094
+ });
3095
+ };
3096
+ var branch = (ast) => {
3097
+ return walkPost2(clone2(ast), (node) => {
3098
+ if (!Array.isArray(node)) return;
3099
+ const op = node[0];
3100
+ if (op === "if") {
3101
+ let condIdx = 1;
3102
+ while (condIdx < node.length) {
3103
+ const child = node[condIdx];
3104
+ if (Array.isArray(child) && (child[0] === "then" || child[0] === "else" || child[0] === "result" || child[0] === "param")) {
3105
+ condIdx++;
3106
+ continue;
3107
+ }
3108
+ break;
3109
+ }
3110
+ const cond = node[condIdx];
3111
+ const c = getConst(cond);
3112
+ if (!c) return;
3113
+ let thenBranch = null, elseBranch = null;
3114
+ for (let i = condIdx + 1; i < node.length; i++) {
3115
+ const child = node[i];
3116
+ if (Array.isArray(child)) {
3117
+ if (child[0] === "then") thenBranch = child;
3118
+ else if (child[0] === "else") elseBranch = child;
3119
+ }
3120
+ }
3121
+ if (c.value !== 0 && c.value !== 0n) {
3122
+ if (thenBranch && thenBranch.length > 1) {
3123
+ const contents = thenBranch.slice(1);
3124
+ if (contents.length === 1) return contents[0];
3125
+ return ["block", ...contents];
3126
+ }
3127
+ return ["nop"];
3128
+ } else {
3129
+ if (elseBranch && elseBranch.length > 1) {
3130
+ const contents = elseBranch.slice(1);
3131
+ if (contents.length === 1) return contents[0];
3132
+ return ["block", ...contents];
3133
+ }
3134
+ return ["nop"];
3135
+ }
3136
+ }
3137
+ if (op === "br_if" && node.length >= 3) {
3138
+ const cond = node[node.length - 1];
3139
+ const c = getConst(cond);
3140
+ if (!c) return;
3141
+ if (c.value === 0 || c.value === 0n) return ["nop"];
3142
+ return ["br", node[1]];
3143
+ }
3144
+ if (op === "select" && node.length >= 4) {
3145
+ const cond = node[node.length - 1];
3146
+ const c = getConst(cond);
3147
+ if (!c) return;
3148
+ if (c.value === 0 || c.value === 0n) return node[2];
3149
+ return node[1];
3150
+ }
3151
+ });
3152
+ };
3153
+ var TERMINATORS = /* @__PURE__ */ new Set(["unreachable", "return", "br", "br_table"]);
3154
+ var deadcode = (ast) => {
3155
+ const result = clone2(ast);
3156
+ walk2(result, (node) => {
3157
+ if (!Array.isArray(node)) return;
3158
+ const kind = node[0];
3159
+ if (kind === "func" || kind === "block" || kind === "loop") {
3160
+ eliminateDeadInBlock(node);
3161
+ }
3162
+ if (kind === "if") {
3163
+ for (let i = 1; i < node.length; i++) {
3164
+ if (Array.isArray(node[i]) && (node[i][0] === "then" || node[i][0] === "else")) {
3165
+ eliminateDeadInBlock(node[i]);
3166
+ }
3167
+ }
3168
+ }
3169
+ });
3170
+ return result;
3171
+ };
3172
+ var eliminateDeadInBlock = (block) => {
3173
+ let terminated = false;
3174
+ let firstTerminator = -1;
3175
+ for (let i = 1; i < block.length; i++) {
3176
+ const node = block[i];
3177
+ if (Array.isArray(node)) {
3178
+ const op = node[0];
3179
+ if (op === "param" || op === "result" || op === "local" || op === "type" || op === "export") continue;
3180
+ if (terminated) {
3181
+ if (firstTerminator === -1) firstTerminator = i;
3182
+ }
3183
+ if (TERMINATORS.has(op)) {
3184
+ terminated = true;
3185
+ firstTerminator = i + 1;
3186
+ }
3187
+ } else if (typeof node === "string") {
3188
+ if (terminated) {
3189
+ if (firstTerminator === -1) firstTerminator = i;
3190
+ }
3191
+ if (TERMINATORS.has(node)) {
3192
+ terminated = true;
3193
+ firstTerminator = i + 1;
3194
+ }
3195
+ }
3196
+ }
3197
+ if (firstTerminator > 0 && firstTerminator < block.length) {
3198
+ block.splice(firstTerminator);
3199
+ }
3200
+ };
3201
+ var localReuse = (ast) => {
3202
+ const result = clone2(ast);
3203
+ walk2(result, (node) => {
3204
+ if (!Array.isArray(node) || node[0] !== "func") return;
3205
+ const localDecls = [];
3206
+ const localTypes = /* @__PURE__ */ new Map();
3207
+ const usedLocals = /* @__PURE__ */ new Set();
3208
+ for (let i = 1; i < node.length; i++) {
3209
+ const sub = node[i];
3210
+ if (!Array.isArray(sub)) continue;
3211
+ if (sub[0] === "local") {
3212
+ localDecls.push({ idx: i, node: sub });
3213
+ if (typeof sub[1] === "string" && sub[1][0] === "$") {
3214
+ localTypes.set(sub[1], sub[2]);
3215
+ }
3216
+ }
3217
+ if (sub[0] === "param") {
3218
+ if (typeof sub[1] === "string" && sub[1][0] === "$") {
3219
+ localTypes.set(sub[1], sub[2]);
3220
+ usedLocals.add(sub[1]);
3221
+ }
3222
+ }
3223
+ }
3224
+ walk2(node, (n) => {
3225
+ if (!Array.isArray(n)) return;
3226
+ const op = n[0];
3227
+ if (op === "local.get" || op === "local.set" || op === "local.tee") {
3228
+ const ref = n[1];
3229
+ if (typeof ref === "string") usedLocals.add(ref);
3230
+ }
3231
+ });
3232
+ for (let i = localDecls.length - 1; i >= 0; i--) {
3233
+ const { idx, node: decl } = localDecls[i];
3234
+ const name2 = typeof decl[1] === "string" && decl[1][0] === "$" ? decl[1] : null;
3235
+ if (name2 && !usedLocals.has(name2)) {
3236
+ node.splice(idx, 1);
3237
+ }
3238
+ }
3239
+ });
3240
+ return result;
3241
+ };
3242
+ var propagate = (ast) => {
3243
+ const result = clone2(ast);
3244
+ walk2(result, (node) => {
3245
+ if (!Array.isArray(node) || node[0] !== "func") return;
3246
+ const constLocals = /* @__PURE__ */ new Map();
3247
+ const processBlock = (block, startIdx = 1) => {
3248
+ for (let i = startIdx; i < block.length; i++) {
3249
+ const instr2 = block[i];
3250
+ if (!Array.isArray(instr2)) continue;
3251
+ const op = instr2[0];
3252
+ if (op === "local.set" && instr2.length === 3) {
3253
+ const local = instr2[1];
3254
+ const val = instr2[2];
3255
+ const c = getConst(val);
3256
+ if (c && typeof local === "string") {
3257
+ constLocals.set(local, val);
3258
+ } else if (typeof local === "string") {
3259
+ constLocals.delete(local);
3260
+ }
3261
+ } else if (op === "local.tee" && instr2.length === 3) {
3262
+ const local = instr2[1];
3263
+ const val = instr2[2];
3264
+ const c = getConst(val);
3265
+ if (c && typeof local === "string") {
3266
+ constLocals.set(local, val);
3267
+ } else if (typeof local === "string") {
3268
+ constLocals.delete(local);
3269
+ }
3270
+ } else if (op === "local.get" && instr2.length === 2) {
3271
+ const local = instr2[1];
3272
+ if (typeof local === "string" && constLocals.has(local)) {
3273
+ const constVal = constLocals.get(local);
3274
+ instr2.length = 0;
3275
+ instr2.push(...clone2(constVal));
3276
+ }
3277
+ } else if (op === "block" || op === "loop" || op === "if" || op === "call" || op === "call_indirect") {
3278
+ constLocals.clear();
3279
+ }
3280
+ walkPost2(instr2, (n) => {
3281
+ if (!Array.isArray(n) || n[0] !== "local.get" || n.length !== 2) return;
3282
+ const local = n[1];
3283
+ if (typeof local === "string" && constLocals.has(local)) {
3284
+ const constVal = constLocals.get(local);
3285
+ return clone2(constVal);
3286
+ }
3287
+ });
3288
+ }
3289
+ };
3290
+ processBlock(node);
3291
+ });
3292
+ return result;
3293
+ };
3294
+ var inline = (ast) => {
3295
+ if (!Array.isArray(ast) || ast[0] !== "module") return ast;
3296
+ const result = clone2(ast);
3297
+ const inlinable = /* @__PURE__ */ new Map();
3298
+ for (const node of result.slice(1)) {
3299
+ if (!Array.isArray(node) || node[0] !== "func") continue;
3300
+ const name2 = typeof node[1] === "string" && node[1][0] === "$" ? node[1] : null;
3301
+ if (!name2) continue;
3302
+ let params = [];
3303
+ let body = [];
3304
+ let hasLocals = false;
3305
+ let hasExport = false;
3306
+ for (let i = 1; i < node.length; i++) {
3307
+ const sub = node[i];
3308
+ if (!Array.isArray(sub)) continue;
3309
+ if (sub[0] === "param") {
3310
+ if (typeof sub[1] === "string" && sub[1][0] === "$") {
3311
+ params.push({ name: sub[1], type: sub[2] });
3312
+ } else {
3313
+ params = null;
3314
+ break;
3315
+ }
3316
+ } else if (sub[0] === "local") {
3317
+ hasLocals = true;
3318
+ } else if (sub[0] === "export") {
3319
+ hasExport = true;
3320
+ } else if (sub[0] !== "result" && sub[0] !== "type") {
3321
+ body.push(sub);
3322
+ }
3323
+ }
3324
+ if (params && !hasLocals && !hasExport && params.length <= 2 && body.length === 1) {
3325
+ const paramNames = new Set(params.map((p) => p.name));
3326
+ let mutatesParam = false;
3327
+ walk2(body[0], (n) => {
3328
+ if (!Array.isArray(n)) return;
3329
+ if ((n[0] === "local.set" || n[0] === "local.tee") && paramNames.has(n[1])) {
3330
+ mutatesParam = true;
3331
+ }
3332
+ });
3333
+ if (!mutatesParam) {
3334
+ inlinable.set(name2, { body: body[0], params });
3335
+ }
3336
+ }
3337
+ }
3338
+ if (inlinable.size === 0) return result;
3339
+ walkPost2(result, (node) => {
3340
+ if (!Array.isArray(node) || node[0] !== "call") return;
3341
+ const fname = node[1];
3342
+ if (!inlinable.has(fname)) return;
3343
+ const { body, params } = inlinable.get(fname);
3344
+ const args = node.slice(2);
3345
+ if (params.length === 0) {
3346
+ return clone2(body);
3347
+ }
3348
+ const substituted = clone2(body);
3349
+ walkPost2(substituted, (n) => {
3350
+ if (!Array.isArray(n) || n[0] !== "local.get") return;
3351
+ const local = n[1];
3352
+ const paramIdx = params.findIndex((p) => p.name === local);
3353
+ if (paramIdx !== -1 && args[paramIdx]) {
3354
+ return clone2(args[paramIdx]);
3355
+ }
3356
+ });
3357
+ return substituted;
3358
+ });
3359
+ return result;
3360
+ };
3361
+ function optimize(ast, opts = true) {
3362
+ if (typeof ast === "string") ast = parse_default(ast);
3363
+ ast = clone2(ast);
3364
+ opts = normalize3(opts);
3365
+ if (opts.fold) ast = fold(ast);
3366
+ if (opts.identity) ast = identity(ast);
3367
+ if (opts.strength) ast = strength(ast);
3368
+ if (opts.branch) ast = branch(ast);
3369
+ if (opts.propagate) ast = propagate(ast);
3370
+ if (opts.inline) ast = inline(ast);
3371
+ if (opts.deadcode) ast = deadcode(ast);
3372
+ if (opts.locals) ast = localReuse(ast);
3373
+ if (opts.treeshake) ast = treeshake(ast);
3374
+ return ast;
3375
+ }
3376
+
2569
3377
  // watr.js
2570
3378
  var PUA = "\uE000";
2571
3379
  var instrType = (op) => {
@@ -2587,11 +3395,11 @@ var exprType = (node, ctx = {}) => {
2587
3395
  if (op === "call" && ctx.funcs?.[args[0]]) return ctx.funcs[args[0]].result?.[0];
2588
3396
  return null;
2589
3397
  };
2590
- function walk2(node, fn) {
3398
+ function walk3(node, fn) {
2591
3399
  node = fn(node);
2592
3400
  if (Array.isArray(node)) {
2593
3401
  for (let i = 0; i < node.length; i++) {
2594
- let child = walk2(node[i], fn);
3402
+ let child = walk3(node[i], fn);
2595
3403
  if (child?._splice) node.splice(i, 1, ...child), i += child.length - 1;
2596
3404
  else node[i] = child;
2597
3405
  }
@@ -2601,7 +3409,7 @@ function walk2(node, fn) {
2601
3409
  function inferImports(ast, funcs) {
2602
3410
  const imports = [];
2603
3411
  const importMap = /* @__PURE__ */ new Map();
2604
- walk2(ast, (node) => {
3412
+ walk3(ast, (node) => {
2605
3413
  if (!Array.isArray(node)) return node;
2606
3414
  if (node[0] === "call" && typeof node[1] === "function") {
2607
3415
  const fn = node[1];
@@ -2641,7 +3449,7 @@ function compile2(source, ...values) {
2641
3449
  let ast = parse_default(src);
2642
3450
  const funcsToImport = [];
2643
3451
  let idx = 0;
2644
- ast = walk2(ast, (node) => {
3452
+ ast = walk3(ast, (node) => {
2645
3453
  if (node === PUA) {
2646
3454
  const value = values[idx++];
2647
3455
  if (typeof value === "function") {
@@ -2679,12 +3487,15 @@ function compile2(source, ...values) {
2679
3487
  }
2680
3488
  }
2681
3489
  if (opts.polyfill) ast = polyfill(ast, opts.polyfill);
3490
+ if (opts.optimize) ast = optimize(ast, opts.optimize);
2682
3491
  const binary = compile(ast);
2683
3492
  if (importObjs) binary._imports = importObjs;
2684
3493
  return binary;
2685
3494
  }
2686
- if (opts.polyfill) {
2687
- const ast = polyfill(source, opts.polyfill);
3495
+ if (opts.polyfill || opts.optimize) {
3496
+ let ast = typeof source === "string" ? parse_default(source) : source;
3497
+ if (opts.polyfill) ast = polyfill(ast, opts.polyfill);
3498
+ if (opts.optimize) ast = optimize(ast, opts.optimize);
2688
3499
  return compile(ast);
2689
3500
  }
2690
3501
  return compile(source);
@@ -2699,6 +3510,7 @@ var watr_default = watr;
2699
3510
  export {
2700
3511
  compile2 as compile,
2701
3512
  watr_default as default,
3513
+ optimize,
2702
3514
  parse_default as parse,
2703
3515
  polyfill,
2704
3516
  print,