@asmlift/core 0.10.0 → 0.11.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.
Files changed (68) hide show
  1. package/package.json +1 -1
  2. package/src/backend/c.ts +1 -0
  3. package/src/backend/cfamily.ts +33 -12
  4. package/src/backend/cpp.ts +1 -0
  5. package/src/backend/lines.ts +10 -0
  6. package/src/backend/pascal.ts +17 -5
  7. package/src/cdecl/parse.ts +21 -0
  8. package/src/contracts.ts +73 -20
  9. package/src/frontend/arm-body.ts +155 -0
  10. package/src/frontend/asmdata.ts +2 -1
  11. package/src/frontend/call-plan.ts +197 -45
  12. package/src/frontend/callee-body.ts +370 -0
  13. package/src/frontend/device-pins.ts +18 -8
  14. package/src/frontend/frame-objects.ts +504 -61
  15. package/src/frontend/frontend.ts +20 -0
  16. package/src/frontend/jump-table.ts +35 -20
  17. package/src/frontend/mips.ts +226 -8
  18. package/src/frontend/ppc.ts +26 -2
  19. package/src/frontend/register-struct-call.ts +1 -86
  20. package/src/frontend/ssa.ts +263 -1
  21. package/src/frontend/stackargs.ts +95 -17
  22. package/src/frontend/thumb-entry.ts +340 -0
  23. package/src/frontend/thumb.ts +410 -61
  24. package/src/ir/alias.ts +29 -8
  25. package/src/ir/bits.ts +1081 -66
  26. package/src/ir/core.ts +154 -4
  27. package/src/ir/entry.ts +3 -5
  28. package/src/ir/opcodes.ts +28 -0
  29. package/src/ir/parse.ts +1 -0
  30. package/src/ir/simplify.ts +61 -10
  31. package/src/ir/verify.ts +16 -0
  32. package/src/l3/argbase.ts +2 -0
  33. package/src/l3/ast.ts +294 -2
  34. package/src/l3/basecse.ts +35 -10
  35. package/src/l3/caseendgoto.ts +223 -0
  36. package/src/l3/coalesce.ts +42 -14
  37. package/src/l3/constinit.ts +34 -6
  38. package/src/l3/dce.ts +52 -8
  39. package/src/l3/ptrcell.ts +2 -0
  40. package/src/l3/regspell.ts +2 -0
  41. package/src/l3/reindex.ts +13 -4
  42. package/src/l3/scopebase.ts +2 -0
  43. package/src/l3/tailmerge.ts +2 -0
  44. package/src/pipeline.ts +507 -23
  45. package/src/proto-context.ts +135 -1
  46. package/src/raise/const.ts +25 -0
  47. package/src/raise/declared-volatile.ts +1 -1
  48. package/src/raise/divpow2.ts +192 -0
  49. package/src/raise/offsetnames.ts +412 -10
  50. package/src/raise/pre-recovery.ts +8 -5
  51. package/src/raise/pre-structure.ts +24 -5
  52. package/src/raise/reroll.ts +4 -24
  53. package/src/raise/shortcircuit.ts +192 -23
  54. package/src/raise/unpre.ts +594 -0
  55. package/src/rank-variations.ts +97 -4
  56. package/src/rank.ts +258 -43
  57. package/src/structure/analysis.ts +56 -10
  58. package/src/structure/cross-jumped-tail.ts +216 -0
  59. package/src/structure/globalaccess.ts +46 -1
  60. package/src/structure/structure.ts +4221 -368
  61. package/src/structure/switch-recover.ts +345 -26
  62. package/src/structure/unfolded-branch-constant.ts +90 -0
  63. package/src/symbols.ts +52 -15
  64. package/src/target.ts +269 -8
  65. package/src/trace.ts +28 -14
  66. package/src/variation-definitions.ts +207 -6
  67. package/src/variation-gates.ts +2 -0
  68. package/src/variation-tokens.ts +63 -2
package/src/ir/bits.ts CHANGED
@@ -10,13 +10,13 @@
10
10
  // It is a function over `Fn` rather than a closure inside a rendering pass so that a test can ask
11
11
  // it directly: reachable only through emitted C, a wrong answer for one opcode reads as a silent
12
12
  // wrong address in a row nobody is looking at.
13
- import type { Block, Op, Value } from './core';
14
- import { OPCODES, type OpSig } from './opcodes';
13
+ import { type Block, type Fn, type Op, type Value, defOpMap, immediateDominators } from './core';
14
+ import { NEGATED_ICMP, OPCODES, type OpSig, SWAPPED_ICMP } from './opcodes';
15
15
 
16
16
  /** A materialized def emits its own named temp, so its value is a VARIABLE at every use — never a
17
17
  * literal this analysis may fold through. Callers that have no materialization model pass none. */
18
18
  export interface BitsCtx {
19
- defs: Map<Value, Op>;
19
+ defs: ReadonlyMap<Value, Op>;
20
20
  materialize?: ReadonlySet<Op>;
21
21
  /** A bound a CALLER already knows for a def — the escape hatch for facts this layer cannot see,
22
22
  * such as a bitfield read a rendering pass has recognized and can price from the declaration.
@@ -64,6 +64,9 @@ export interface WordCtx {
64
64
  * evaluator cannot compute: the memory the function wrote, through an address it cannot match
65
65
  * to the read's, may be every word that read sees. Passed only beside `forwarded`. */
66
66
  overwritten?: ReadonlySet<Op>;
67
+ /** The function's own parameters. Given beside `incoming`, `mayAlwaysHold` also bounds a block
68
+ * param by the ranges its in-edges pass (`stridedRange`); without it no param is bounded. */
69
+ entry?: ReadonlySet<Value>;
67
70
  }
68
71
 
69
72
  /** A value's word, over a lattice: not yet seen (`top`, a loop-carried param before its back
@@ -255,12 +258,41 @@ export function constWord(ctx: WordCtx, v: Value): number | null {
255
258
  * evaluator cannot compute, or a value the solver never settled (a param no executed edge
256
259
  * feeds). The fail-closed reading of `constWord`, for a caller to whom a constant it cannot name
257
260
  * is as bad as the one it is looking for. A value that varies, or that is `w` on some paths only,
258
- * answers false. */
261
+ * answers false — and so does one that cannot hold `w` at all by what its ops leave
262
+ * (`stridedRange`'s bounds, which read widths and arithmetic, never memory): a halfword reload of
263
+ * memory this function overwrote is a constant this cannot compute, but never INT_MIN, and nor is
264
+ * a param both of whose arms pass such a reload. */
259
265
  export function mayAlwaysHold(ctx: WordCtx, v: Value, w: number): boolean {
266
+ const rc = rangeCtxOf(ctx);
267
+ for (const wordFirst of [false, true]) {
268
+ const r = integerRange(rc, v, wordFirst);
269
+ if (r !== undefined && !rangeMayBe(r, w)) {
270
+ return false;
271
+ }
272
+ }
260
273
  const x = wordOf(ctx, v);
261
274
  return x.k === 'konst' || x.k === 'top' || (x.k === 'word' && x.w === (w | 0));
262
275
  }
263
276
 
277
+ /** The range question's reading of a word context. A block param is read through its in-edges
278
+ * only when the caller names the function's own params (`entry`), which arrive from a caller and
279
+ * hold anything even where a back edge passes them a bounded value. */
280
+ function rangeCtxOf(ctx: WordCtx): RangeCtx {
281
+ const entry = ctx.entry;
282
+ return {
283
+ defs: ctx.defs,
284
+ constOf: (x) => {
285
+ const d = ctx.defs.get(x);
286
+ return d?.opcode === 'const' && typeof d.attrs.value === 'number' ? d.attrs.value : undefined;
287
+ },
288
+ incoming:
289
+ entry === undefined || ctx.incoming === undefined
290
+ ? new Map()
291
+ : new Map([...ctx.incoming].map(([p, args]) => [p, args.map((arg) => ({ arg }))])),
292
+ entry: entry ?? new Set(),
293
+ };
294
+ }
295
+
264
296
  /** Where an access lands, normalised: a ROOT (one SSA value, one symbol, or the absolute address
265
297
  * space) and a byte offset from it. `add`/`sub` of a constant move along the root, so a base held
266
298
  * in another register (`add r2, r3, #4`), an addend in the pool word (`.word gX+4`) and the same
@@ -537,7 +569,8 @@ export function overwrittenReads(blocks: readonly Block[]): Set<Op> {
537
569
  return out;
538
570
  }
539
571
 
540
- /** Block param -> the values its in-edges pass, for `WordCtx.incoming`. */
572
+ /** Block param -> the values its in-edges pass, for `WordCtx.incoming`: the edges alone, without
573
+ * what their branches prove (`edgeFacts`), which a word context does not read. */
541
574
  export function incomingArgs(blocks: readonly Block[]): Map<Value, (Value | undefined)[]> {
542
575
  const out = new Map<Value, (Value | undefined)[]>();
543
576
  for (const b of blocks) {
@@ -555,9 +588,115 @@ export function incomingArgs(blocks: readonly Block[]): Map<Value, (Value | unde
555
588
  return out;
556
589
  }
557
590
 
591
+ /** A comparison an edge proves: `lhs <opcode> rhs` holds whenever control takes it. */
592
+ export interface EdgeTest {
593
+ readonly opcode: string;
594
+ readonly lhs: Value;
595
+ readonly rhs: Value;
596
+ }
597
+
598
+ /** One in-edge of a block param: the value it passes, and what the branches on the way prove. */
599
+ export interface InEdge {
600
+ readonly arg: Value | undefined;
601
+ readonly holds?: readonly EdgeTest[];
602
+ }
603
+
604
+ /** An edge: its branch `op` in block `from`, to successor `si`. */
605
+ interface EdgeOut {
606
+ readonly from: Block;
607
+ readonly op: Op;
608
+ readonly si: number;
609
+ }
610
+
611
+ /** The most compares one edge or one block carries, the nearest first; an older one is dropped. */
612
+ const EDGE_TESTS = 8;
613
+
614
+ /** What the branches above a point prove there: for each block, the compares that hold whenever
615
+ * control is in it, and for each edge, the compares that hold whenever it is taken. A `cond_br` on
616
+ * an `icmp_*` proves the compare on its first successor and the negation (`NEGATED_ICMP`) on its
617
+ * second; an edge out of a `cond_br` whose two successors are one block proves nothing — the same
618
+ * edge is taken either way — and nor does any other branch. A block holds what the one edge into
619
+ * each block that DOMINATES it proves (itself included; a block entered by more than one edge, or
620
+ * the entry, proves nothing of its way in), so a test at the top of a loop body still holds past
621
+ * an if/else that joins below it, and agbcc's far latch `bhi .LCB; b .Lhead @long jump` carries
622
+ * the test it made in one block to the jump back from the next. An edge holds its own compare and
623
+ * what its source block holds. Sound because a compare's operands are values: each was defined in
624
+ * a block that dominates the test, and control that passes a dominating edge and reaches the point
625
+ * without passing it again cannot have redefined one (that block would be reachable around the
626
+ * edge), so the instance each test read is the one the point reads. */
627
+ export interface EdgeFacts {
628
+ /** block param -> its in-edges, each with what holds whenever it is taken */
629
+ readonly incoming: Map<Value, InEdge[]>;
630
+ /** the compares that hold in a block, the nearest first */
631
+ holdsAt(b: Block): readonly EdgeTest[];
632
+ }
633
+
634
+ /** `EdgeFacts` over a function's blocks, `blocks[0]` the entry. */
635
+ export function edgeFacts(blocks: readonly Block[]): EdgeFacts {
636
+ const defs = new Map<Value, Op>();
637
+ /** a block -> the one edge into it, or null where there are more, or the caller enters it */
638
+ const only = new Map<Block, EdgeOut | null>();
639
+ if (blocks[0] !== undefined) {
640
+ only.set(blocks[0], null);
641
+ }
642
+ for (const b of blocks) {
643
+ for (const op of b.ops) {
644
+ op.results.forEach((r) => defs.set(r, op));
645
+ op.successors.forEach((s, si) => only.set(s.block, only.has(s.block) ? null : { from: b, op, si }));
646
+ }
647
+ }
648
+ const idom = immediateDominators({ blocks });
649
+ const tested = (op: Op, si: number): EdgeTest | undefined => {
650
+ const c = op.opcode === 'cond_br' ? defs.get(op.operands[0]) : undefined;
651
+ return c !== undefined &&
652
+ c.operands.length === 2 &&
653
+ NEGATED_ICMP[c.opcode] !== undefined &&
654
+ op.successors.length === 2 &&
655
+ op.successors[0].block !== op.successors[1].block
656
+ ? { opcode: si === 0 ? c.opcode : NEGATED_ICMP[c.opcode], lhs: c.operands[0], rhs: c.operands[1] }
657
+ : undefined;
658
+ };
659
+ const at = new Map<Block, readonly EdgeTest[]>();
660
+ const holdsAt = (b: Block): readonly EdgeTest[] => {
661
+ const had = at.get(b);
662
+ if (had !== undefined) {
663
+ return had;
664
+ }
665
+ const out: EdgeTest[] = [];
666
+ for (let x: Block | undefined = b; x !== undefined && out.length < EDGE_TESTS; x = idom.get(x)) {
667
+ const e = only.get(x);
668
+ const t = e === null || e === undefined ? undefined : tested(e.op, e.si);
669
+ if (t !== undefined) {
670
+ out.push(t);
671
+ }
672
+ }
673
+ at.set(b, out);
674
+ return out;
675
+ };
676
+ const incoming = new Map<Value, InEdge[]>();
677
+ for (const b of blocks) {
678
+ for (const op of b.ops) {
679
+ op.successors.forEach((s, si) => {
680
+ if (s.block.params.length === 0) {
681
+ return;
682
+ }
683
+ const own = tested(op, si);
684
+ const holds = [...(own === undefined ? [] : [own]), ...holdsAt(b)].slice(0, EDGE_TESTS);
685
+ s.block.params.forEach((p, i) => {
686
+ if (!incoming.has(p)) {
687
+ incoming.set(p, []);
688
+ }
689
+ incoming.get(p)!.push(holds.length === 0 ? { arg: s.args[i] } : { arg: s.args[i], holds });
690
+ });
691
+ });
692
+ }
693
+ }
694
+ return { incoming, holdsAt };
695
+ }
696
+
558
697
  /** An UPPER BOUND on the significant bits of a value, or 32 when nothing bounds it. Every answer
559
698
  * below 32 comes from an instruction or from a caller's declaration-backed `bound`:
560
- * · a zero-fill shift right by n leaves 32 - n;
699
+ * · a zero-fill shift right by n leaves n fewer bits than its operand has (32 - n of a word);
561
700
  * · a load leaves its width in bits — UNLESS it sign-extends, which leaves all 32;
562
701
  * · an `and` with a non-negative constant leaves that constant's top set bit.
563
702
  * A SIGNED anything is 32: sign extension sets the high bits, and a bound that ignores it turns a
@@ -572,7 +711,7 @@ export function provableBits(ctx: BitsCtx, v: Value): number {
572
711
  return given;
573
712
  }
574
713
  if (d.opcode === 'shr_u' && d.operands.length === 1 && typeof d.attrs.imm === 'number') {
575
- return 32 - (d.attrs.imm as number);
714
+ return Math.max(0, provableBits(ctx, d.operands[0]) - (d.attrs.imm as number));
576
715
  }
577
716
  if (d.opcode === 'load') {
578
717
  return d.attrs.signed === true ? 32 : (d.attrs.width as number) * 8;
@@ -601,19 +740,28 @@ export interface LinearCtx {
601
740
  /** `v` as a sum of runtime values times constants, plus a constant (the `1` key): agbcc's `ch *
602
741
  * 12` is `((ch << 1) + ch) << 2`, which is 12 times `ch` only once the two `ch` terms are added.
603
742
  * Every rule is a ring operation, so the form equals the machine's word modulo 2^32 whatever
604
- * wraps on the way. A value this cannot read further is its own term. */
605
- export function linearTerms(ctx: LinearCtx, v: Value, depth = 0): Map<Value | 1, number> | undefined {
743
+ * wraps on the way. A value this cannot read further is its own term. A coefficient is a JS
744
+ * number, exact only up to 2^53: one past that rounds, and a later `sub` could cancel the rounded
745
+ * coefficient to 0 and drop a term the word still holds, so a form whose coefficient leaves the
746
+ * safe integers is no form (undefined). A value in `opaque` is read no further: it is a term. */
747
+ export function linearTerms(
748
+ ctx: LinearCtx,
749
+ v: Value,
750
+ depth = 0,
751
+ opaque?: { has(v: Value): boolean },
752
+ ): Map<Value | 1, number> | undefined {
606
753
  const c = ctx.constOf(v);
607
754
  if (c !== undefined) {
608
755
  return new Map([[1, c]]);
609
756
  }
610
- const d = ctx.defs.get(v);
757
+ const d = opaque?.has(v) === true ? undefined : ctx.defs.get(v);
611
758
  if (d === undefined || depth > 8) {
612
759
  return new Map([[v, 1]]);
613
760
  }
614
761
  const scaled = (x: Value, by: number) => {
615
- const l = linearTerms(ctx, x, depth + 1);
616
- return l && new Map([...l].map(([t, k]) => [t, k * by] as const));
762
+ const l = linearTerms(ctx, x, depth + 1, opaque);
763
+ const out = l && new Map([...l].map(([t, k]) => [t, k * by] as const));
764
+ return out && [...out.values()].every(Number.isSafeInteger) ? out : undefined;
617
765
  };
618
766
  const k = d.operands.length === 1 ? (d.attrs.imm as number | undefined) : ctx.constOf(d.operands[1]);
619
767
  if (d.opcode === 'shl' && k !== undefined && k >= 0 && k < 31) {
@@ -622,14 +770,18 @@ export function linearTerms(ctx: LinearCtx, v: Value, depth = 0): Map<Value | 1,
622
770
  if (d.opcode === 'mul' && d.operands.length === 2 && k !== undefined) {
623
771
  return scaled(d.operands[0], k);
624
772
  }
773
+ // the wrapping `0 - x`, a ring operation too: agbcc's `mov rN, #2; neg rN, rN` is the constant -2
774
+ if (d.opcode === 'neg' && d.operands.length === 1) {
775
+ return scaled(d.operands[0], -1);
776
+ }
625
777
  if ((d.opcode === 'add' || d.opcode === 'sub') && d.operands.length === 2) {
626
- const x = linearTerms(ctx, d.operands[0], depth + 1);
778
+ const x = linearTerms(ctx, d.operands[0], depth + 1, opaque);
627
779
  const y = scaled(d.operands[1], d.opcode === 'add' ? 1 : -1);
628
780
  if (x === undefined || y === undefined) {
629
781
  return undefined;
630
782
  }
631
783
  y.forEach((n, t) => x.set(t, (x.get(t) ?? 0) + n));
632
- return x;
784
+ return [...x.values()].every(Number.isSafeInteger) ? x : undefined;
633
785
  }
634
786
  return new Map([[v, 1]]);
635
787
  }
@@ -641,7 +793,8 @@ export function linearFactor(ctx: LinearCtx, v: Value): number {
641
793
  }
642
794
 
643
795
  /** Every word a value may hold is `lo + stride * n` for some `n >= 0`, and lies in `[lo, hi]`, an
644
- * unsigned closed interval. `stride` is 0 exactly when `lo === hi`. */
796
+ * unsigned closed interval. `stride` is 0 exactly when `lo === hi`. (Inside this module the same
797
+ * shape also carries an integer range before its move into one word, which may start below 0.) */
645
798
  export interface StridedRange {
646
799
  readonly lo: number;
647
800
  readonly hi: number;
@@ -650,102 +803,828 @@ export interface StridedRange {
650
803
 
651
804
  /** What a strided range may read beyond a linear form. */
652
805
  export interface RangeCtx extends LinearCtx {
653
- /** block param -> the value each in-edge passes (`incomingArgs`) */
654
- readonly incoming: ReadonlyMap<Value, readonly (Value | undefined)[]>;
806
+ /** block param -> its in-edges, each with what its branch proves where that is known
807
+ * (`edgeFacts`) */
808
+ readonly incoming: ReadonlyMap<Value, readonly InEdge[]>;
655
809
  /** the function's own parameters, which arrive from a caller and hold anything */
656
810
  readonly entry: ReadonlySet<Value>;
657
811
  /** the one word a term the form cannot read is known to be — a name a symbol map places */
658
812
  pointOf?(v: Value): number | undefined;
813
+ /** the compares that hold in a block (`edgeFacts`), for a question asked at one */
814
+ holdsAt?(b: Block): readonly EdgeTest[];
659
815
  }
660
816
 
661
817
  const WORD = 2 ** 32;
662
- /** The most terms one `stridedRange` question bounds before it answers nothing. */
818
+ /** The most terms one range question bounds, across every own question it opens (`question`), before
819
+ * what is left answers nothing. */
663
820
  const RANGE_BUDGET = 256;
664
821
 
665
822
  /** Every word `v` may hold, or undefined when this cannot bound it. The form is `linearTerms`; a
666
823
  * term is bounded by the word a caller places it at (`pointOf`), by `provableBits` (a zero-fill
667
- * shift right, an unsigned narrow load, a non-negative mask) or a `zext`'s width, or, a block
668
- * param, by the union of what its in-edges pass. Anything else — a function parameter, a whole
669
- * word loaded from memory, a name nothing places, a param whose range depends on itself through a
670
- * loop — bounds nothing, and nothing is the answer. The form equals the word modulo 2^32, so the
671
- * words are the sum's integer range moved into one word, when it fits one; a sum whose range
672
- * crosses a multiple of 2^32 holds words on both sides of zero, which no interval names. Exact is
673
- * not the point: an answer must hold every word the machine can compute there, and a wider one
674
- * only proves less.
675
- *
676
- * KNOWN GAP: a signed narrow index bounds nothing. Every leaf is unsigned (`provableBits` reads a
677
- * signed value as 32 bits), and each in-edge of a param is moved into one word before the union,
678
- * so `8 * (s8)i` — [-1024, 1016], across zero — names no interval even with a signed leaf. Both
679
- * would have to change; no real GBA DMA function measured yet needs either. */
680
- export function stridedRange(ctx: RangeCtx, v: Value): StridedRange | undefined {
824
+ * shift right, an unsigned narrow load, a non-negative mask) or a `zext`'s width, by the range a
825
+ * narrow read or `sext` leaves (`narrowLeaf`: signed ones included), by what a shift or a mask
826
+ * leaves of its operand's range (`shiftMaskRange`), a remainder by its divisor (`remainderRange`:
827
+ * `smod`, `umod`), or, a block param, by the union of what its in-edges pass, each read under what
828
+ * its branch proves, or by a range every in-edge provably keeps it in (`integerRange`'s induction,
829
+ * for a loop counter whose latch bounds one side). A signed remainder by a power of two spelled
830
+ * with shifts, `x - (t >> k << k)` with `t` a biased `x` and `x` any linear form (`(i + 1) % 8`),
831
+ * is one bounded term (`remainderPair`).
832
+ * Anything else — a function parameter, a whole word loaded from memory, a name nothing places, a
833
+ * param whose range depends on itself through a loop that no branch bounds — bounds nothing, and
834
+ * nothing is the answer. The form equals the word modulo 2^32, so the words are the sum's integer
835
+ * range moved into one word, when it fits one; a sum whose range crosses a multiple of 2^32 holds
836
+ * words on both sides of zero, which no interval names (a lone signed narrow value among them:
837
+ * `(s8)i` is [-128, 127]). Exact is not the point: an answer must hold every word the machine can
838
+ * compute there, and a wider one only proves less. Asked `at` a block (where `v` is read, or any
839
+ * block its definition dominates), what the branches above it prove there (`holdsAt`) bounds the
840
+ * terms they test too: `if (i < 8) a[i]` reads `i` in [0, 7] though `i` is a parameter. The
841
+ * `narrowest` window answers the signed integers those words are where that interval is narrower,
842
+ * `lo` then below 0 — an index straddling zero, IDO's `i % 32` in [-32, 31], that no unsigned
843
+ * interval names — for a reader that adds it to a base and moves the sum into words itself. */
844
+ export function stridedRange(
845
+ ctx: RangeCtx,
846
+ v: Value,
847
+ at?: Block,
848
+ window: 'word' | 'narrowest' = 'word',
849
+ ): StridedRange | undefined {
850
+ const holds = at === undefined ? undefined : ctx.holdsAt?.(at);
851
+ const r = integerRange(ctx, v, false, holds);
852
+ let words = r === undefined ? undefined : intoWord(r);
853
+ let signed = r === undefined || window === 'word' ? undefined : intoSigned(r);
854
+ // the reading that moves each join into one word first, only for a window the first one missed
855
+ if (r !== undefined && (words === undefined || (window === 'narrowest' && signed === undefined))) {
856
+ const moved = integerRange(ctx, v, true, holds);
857
+ words ??= moved === undefined ? undefined : intoWord(moved);
858
+ signed ??= moved === undefined || window === 'word' ? undefined : intoSigned(moved);
859
+ }
860
+ return signed !== undefined && (words === undefined || signed.hi - signed.lo < words.hi - words.lo) ? signed : words;
861
+ }
862
+
863
+ /** An integer range moved into one unsigned word, or undefined when it crosses a multiple of
864
+ * 2^32. Every integer in the answer is congruent modulo 2^32 to the one it moved. */
865
+ function intoWord(r: StridedRange): StridedRange | undefined {
866
+ const base = Math.floor(r.lo / WORD) * WORD;
867
+ return r.hi - base >= WORD ? undefined : { lo: r.lo - base, hi: r.hi - base, stride: r.stride };
868
+ }
869
+
870
+ /** Whether an integer range holds an integer congruent to `w` modulo 2^32 — a word the machine
871
+ * may compute there. Fails open (true) past a range too wide to walk. */
872
+ function rangeMayBe(r: StridedRange, w: number): boolean {
873
+ let x = r.lo + mod(mod(w) - r.lo);
874
+ for (let n = 0; x <= r.hi; x += WORD, n++) {
875
+ if (n > 64 || (r.stride === 0 ? x === r.lo : (x - r.lo) % r.stride === 0)) {
876
+ return true;
877
+ }
878
+ }
879
+ return false;
880
+ }
881
+
882
+ /** The range a narrow read or a `sext` leaves whatever it reads, where `provableBits` names none:
883
+ * a sign-extending `load`, a `sext` (its `width` in bits), and a flat array's `aload` (whose
884
+ * `elemSize` is the element it reads; a struct-array `aload`'s, with a `fieldOff`, is the stride,
885
+ * not the field, and bounds nothing). An integer range: a signed one straddles zero. */
886
+ function narrowLeaf(d: Op): StridedRange | undefined {
887
+ const bits =
888
+ d.opcode === 'sext'
889
+ ? d.attrs.width
890
+ : d.opcode === 'load' && d.attrs.signed === true && typeof d.attrs.width === 'number'
891
+ ? d.attrs.width * 8
892
+ : d.opcode === 'aload' && d.attrs.fieldOff === undefined && typeof d.attrs.elemSize === 'number'
893
+ ? d.attrs.elemSize * 8
894
+ : undefined;
895
+ if (typeof bits !== 'number' || !(bits > 0 && bits < 32)) {
896
+ return undefined;
897
+ }
898
+ return d.opcode === 'sext' || d.attrs.signed === true
899
+ ? { lo: -(2 ** (bits - 1)), hi: 2 ** (bits - 1) - 1, stride: 1 }
900
+ : { lo: 0, hi: 2 ** bits - 1, stride: 1 };
901
+ }
902
+
903
+ /** The words a `zext`, an `and` with a constant or a zero-fill shift right by a constant leaves,
904
+ * from the words its operand may hold (`wordsOf`, undefined where nothing bounds them): a `zext`
905
+ * or a mask of `2^n - 1` keeps every word that already fits it, `x & m` is no greater than `x` or
906
+ * `m`, and `x >>> s` is each word divided by `2^s`, rounded down. A shift left is a product,
907
+ * which `linearTerms` reads, so agbcc's `(u8)k * 2`, `lsl #24; lsr #23`, is `k`'s range doubled
908
+ * where that fits a word. Undefined where it names nothing, and the width bound
909
+ * (`provableBits`) is all there is. */
910
+ function shiftMaskRange(
911
+ ctx: LinearCtx,
912
+ d: Op,
913
+ wordsOf: (x: Value) => StridedRange | undefined,
914
+ ): StridedRange | undefined {
915
+ const amount =
916
+ d.operands.length === 1
917
+ ? typeof d.attrs.imm === 'number'
918
+ ? d.attrs.imm
919
+ : undefined
920
+ : d.operands.length === 2
921
+ ? ctx.constOf(d.operands[1])
922
+ : undefined;
923
+ if (d.opcode === 'zext' && typeof d.attrs.width === 'number' && d.attrs.width > 0 && d.attrs.width < 32) {
924
+ const w = wordsOf(d.operands[0]);
925
+ return w !== undefined && w.hi < 2 ** d.attrs.width ? w : undefined;
926
+ }
927
+ if (d.opcode === 'and') {
928
+ const [x, c] =
929
+ amount !== undefined
930
+ ? [d.operands[0], amount]
931
+ : d.operands.length === 2
932
+ ? [d.operands[1], ctx.constOf(d.operands[0])]
933
+ : [undefined, undefined];
934
+ if (x === undefined || c === undefined) {
935
+ return undefined;
936
+ }
937
+ const m = mod(c);
938
+ const w = wordsOf(x);
939
+ if (w !== undefined && w.hi <= m && Number.isInteger(Math.log2(m + 1))) {
940
+ return w;
941
+ }
942
+ const hi = Math.min(m, w?.hi ?? m);
943
+ return { lo: 0, hi, stride: hi === 0 ? 0 : 1 };
944
+ }
945
+ if (d.opcode === 'shr_u' && amount !== undefined && amount > 0 && amount < 32) {
946
+ const w = wordsOf(d.operands[0]);
947
+ if (w === undefined) {
948
+ return undefined;
949
+ }
950
+ const by = 2 ** amount;
951
+ const [lo, hi] = [Math.floor(w.lo / by), Math.floor(w.hi / by)];
952
+ return { lo, hi, stride: lo === hi ? 0 : w.stride % by === 0 ? w.stride / by : 1 };
953
+ }
954
+ return undefined;
955
+ }
956
+
957
+ /** The integers a remainder `x % y` leaves (`smod`, C's truncating one, or `umod`), from the
958
+ * integers its operands may hold (`rangeOf`, undefined where nothing bounds them): the divisor
959
+ * bounds it whatever the dividend holds, `umod` in [0, y - 1] and `smod` in [-(|y| - 1), |y| - 1],
960
+ * and the dividend narrows that, its sign (`smod` of an `x` proven non-negative is in [0, |y| -
961
+ * 1]) or itself where it is already smaller than every `y` (`x % y` is `x`). IDO's `div; mfhi`,
962
+ * which the MIPS frontend reads as `smod`/`umod`, is `i % 30` in [-29, 29] by this alone. A
963
+ * divisor that may be zero bounds nothing: the remainder is then whatever the machine leaves.
964
+ * The shift spelling of `x % 2^k` (agbcc, gcc, kmc: no `smod` at all) is the same bound read off
965
+ * its own ops (`remainderPair`). */
966
+ function remainderRange(d: Op, rangeOf: (x: Value) => StridedRange | undefined): StridedRange | undefined {
967
+ if ((d.opcode !== 'smod' && d.opcode !== 'umod') || d.operands.length !== 2) {
968
+ return undefined;
969
+ }
970
+ const point = (lo: number, hi: number): StridedRange => ({ lo, hi, stride: lo === hi ? 0 : 1 });
971
+ const [rx, ry] = [rangeOf(d.operands[0]), rangeOf(d.operands[1])];
972
+ if (d.opcode === 'umod') {
973
+ const y = ry === undefined ? undefined : intoWord(ry);
974
+ if (y === undefined || y.lo < 1) {
975
+ return undefined;
976
+ }
977
+ const x = rx === undefined ? undefined : intoWord(rx);
978
+ return x !== undefined && x.hi < y.lo ? x : point(0, Math.min(y.hi - 1, x?.hi ?? y.hi - 1));
979
+ }
980
+ const y = ry === undefined ? undefined : intoSigned(ry);
981
+ if (y === undefined || (y.lo <= 0 && y.hi >= 0)) {
982
+ return undefined;
983
+ }
984
+ const [least, most] = y.lo > 0 ? [y.lo, y.hi] : [-y.hi, -y.lo];
985
+ const x = rx === undefined ? undefined : intoSigned(rx);
986
+ if (x !== undefined && -x.lo < least && x.hi < least) {
987
+ return x;
988
+ }
989
+ const m = most - 1;
990
+ return point(
991
+ x === undefined || x.lo < 0 ? Math.max(-m, x?.lo ?? -m) : 0,
992
+ x === undefined || x.hi > 0 ? Math.min(m, x?.hi ?? m) : 0,
993
+ );
994
+ }
995
+
996
+ /** `stridedRange` before the move into one word: integers, every one of which is congruent modulo
997
+ * 2^32 to a word `v` may hold, and which cover them all. A block param is the union of what its
998
+ * in-edges pass, read one of two ways, each a sound cover on its own: as the integers they are
999
+ * (`wordFirst` false: a constant -1 beside a halfword reload is [-1, 65535], which no word
1000
+ * interval names but which excludes INT_MIN), or each moved into one word first where it fits one
1001
+ * (`wordFirst` true: a -1 beside a 0 is {0, 2^32 - 1}, one word interval where [-1, 0] is none).
1002
+ * The two never mix in one union. Where the integer union fits one word, both readings name the
1003
+ * same words (every edge then shares one multiple of 2^32), so `stridedRange` asks the integer
1004
+ * reading first and the moved one only where that crosses a multiple; `mayAlwaysHold` asks both,
1005
+ * and a word either excludes cannot be held. Where an in-edge names what its branch proves,
1006
+ * that edge's argument is read under it (`edgeRange`): a loop's latch `k + 1 <= 7` bounds the
1007
+ * `k + 1` its back edge passes, which no reading of `k` alone could, `k` depending on itself. */
1008
+ function integerRange(
1009
+ ctx: RangeCtx,
1010
+ v: Value,
1011
+ wordFirst: boolean,
1012
+ holds?: readonly EdgeTest[],
1013
+ ): StridedRange | undefined {
1014
+ if (question?.ctx === ctx) {
1015
+ return rangeIn(ctx, v, wordFirst, holds);
1016
+ }
1017
+ const outer = question; // a question of another context asked from inside one: its own
1018
+ question = { ctx, own: [new Map(), new Map()], left: RANGE_BUDGET, depth: 0 };
1019
+ try {
1020
+ return rangeIn(ctx, v, wordFirst, holds);
1021
+ } finally {
1022
+ question = outer;
1023
+ }
1024
+ }
1025
+
1026
+ /** `integerRange` inside the outermost question under way (`question`), whose budget it spends. */
1027
+ function rangeIn(ctx: RangeCtx, v: Value, wordFirst: boolean, holds?: readonly EdgeTest[]): StridedRange | undefined {
1028
+ const q = question!;
681
1029
  const open = new Set<Value>();
682
- let left = RANGE_BUDGET;
1030
+ /** one more term bounded: false once its question's budget is spent */
1031
+ const spend = (): boolean => --q.left >= 0;
1032
+ /** params met again while their own in-edges were being read */
1033
+ const cyclic = new Set<Value>();
1034
+ /** the induction hypotheses under test: each param holds only this range */
1035
+ const assumed = new Map<Value, StridedRange>();
683
1036
  const bits: BitsCtx = {
684
- defs: ctx.defs as Map<Value, Op>,
1037
+ defs: ctx.defs,
685
1038
  bound: (d) => (d.opcode === 'zext' ? (d.attrs.width as number) : null),
686
1039
  };
687
- const termRange = (t: Value): StridedRange | undefined => {
1040
+ /** `facts`: what the in-edge being read proves, by the value it bounds; each is read as a term.
1041
+ * `null` (here and from `rangeOf`) is NEVER: a fact no value of its term can meet, so the edge
1042
+ * that proves it is never taken and passes nothing. */
1043
+ const termRange = (t: Value, facts: Facts): StridedRange | undefined | null => {
1044
+ const fact = facts.get(t);
1045
+ if (fact !== undefined) {
1046
+ const own = rangeOf(t, new Map([...facts].filter(([x]) => x !== t)));
1047
+ return own === null ? null : meetBound(own, fact);
1048
+ }
688
1049
  const at = ctx.pointOf?.(t);
689
1050
  if (at !== undefined) {
690
1051
  return { lo: mod(at), hi: mod(at), stride: 0 };
691
1052
  }
692
- if (ctx.defs.has(t)) {
1053
+ const d = ctx.defs.get(t);
1054
+ if (d !== undefined) {
1055
+ const leaf = narrowLeaf(d);
1056
+ if (leaf !== undefined) {
1057
+ return leaf;
1058
+ }
1059
+ // what a shift or a mask leaves of its operand's range: agbcc's `(u8)k` of a counter that
1060
+ // stays in [0, 7] is [0, 7], not the [0, 255] the width alone leaves
1061
+ const moved = shiftMaskRange(ctx, d, (x) => {
1062
+ const r = spend() ? rangeOf(x, facts) : undefined;
1063
+ return r === undefined || r === null ? undefined : intoWord(r);
1064
+ });
693
1065
  const n = provableBits(bits, t);
694
- return n < 32 ? { lo: 0, hi: 2 ** n - 1, stride: n === 0 ? 0 : 1 } : undefined;
1066
+ const width = n < 32 ? { lo: 0, hi: 2 ** n - 1, stride: n === 0 ? 0 : 1 } : undefined;
1067
+ // a remainder is bounded by its divisor whatever its dividend holds: IDO's `i % 30`
1068
+ const rem = remainderRange(d, (x) => {
1069
+ const r = spend() ? rangeOf(x, facts) : undefined;
1070
+ return r === null ? undefined : r;
1071
+ });
1072
+ if (rem !== undefined) {
1073
+ return width !== undefined && width.hi - width.lo < rem.hi - rem.lo ? width : rem;
1074
+ }
1075
+ return moved !== undefined && (width === undefined || moved.hi - moved.lo <= width.hi - width.lo) ? moved : width;
1076
+ }
1077
+ const hypothesis = assumed.get(t);
1078
+ if (hypothesis !== undefined) {
1079
+ return hypothesis;
695
1080
  }
696
1081
  const ins = ctx.entry.has(t) ? undefined : ctx.incoming.get(t);
697
- if (ins === undefined || ins.length === 0 || open.has(t) || --left < 0) {
1082
+ if (open.has(t)) {
1083
+ cyclic.add(t);
1084
+ return undefined;
1085
+ }
1086
+ if (ins === undefined || ins.length === 0 || !spend()) {
698
1087
  return undefined;
699
1088
  }
700
1089
  open.add(t);
701
1090
  try {
702
- let out: StridedRange | undefined;
703
- for (const x of ins) {
704
- const r = x === undefined ? undefined : rangeOf(x);
705
- if (r === undefined) {
706
- return undefined;
1091
+ // each in-edge as it reads, and apart those that read without the param: an induction's start;
1092
+ // an edge that is never taken (`null`) passes nothing, and the param is what the others pass
1093
+ let readsItself = false;
1094
+ const seeds: (StridedRange | undefined)[] = [];
1095
+ const direct = ins.flatMap((e) => {
1096
+ const { range } = edgeRange(e);
1097
+ const self = cyclic.delete(t);
1098
+ readsItself ||= self;
1099
+ seeds.push(self || range === null ? undefined : range);
1100
+ return range === null ? [] : [range];
1101
+ });
1102
+ if (direct.length === 0) {
1103
+ return null; // no in-edge is ever taken: nor is any edge that reads the param
1104
+ }
1105
+ let out = direct.every((r) => r !== undefined)
1106
+ ? hullOf(direct.map((r) => (wordFirst ? (intoWord(r) ?? r) : r)))
1107
+ : undefined;
1108
+ // a param read through itself may still hold a range every in-edge keeps it in
1109
+ const proven = readsItself ? inductive(t, ins, seeds) : undefined;
1110
+ if (proven !== undefined && (out === undefined || proven.hi - proven.lo < out.hi - out.lo)) {
1111
+ out = wordFirst ? (intoWord(proven) ?? proven) : proven;
1112
+ }
1113
+ // met inside a loop through a param of its cycle that was asked first, the reading above saw
1114
+ // that param as nothing, and the induction that bounds the loop never ran: `k` read from
1115
+ // `k2 = phi(k + 1, k + 2)`, the value a signed latch tests, is only the latch's one side.
1116
+ // Its own question runs it from its own in-edges (`ownRange`); each answer covers every
1117
+ // value, and the narrower is kept.
1118
+ if ([...cyclic].some((x) => x !== t)) {
1119
+ const own = ownRange(ctx, t, wordFirst);
1120
+ if (own !== undefined && (out === undefined || own.hi - own.lo < out.hi - out.lo)) {
1121
+ out = own;
707
1122
  }
708
- out =
709
- out === undefined
710
- ? r
711
- : {
712
- lo: Math.min(out.lo, r.lo),
713
- hi: Math.max(out.hi, r.hi),
714
- stride: gcdOf(gcdOf(out.stride, r.stride), r.lo - out.lo),
715
- };
716
1123
  }
717
1124
  return out;
718
1125
  } finally {
719
1126
  open.delete(t);
720
1127
  }
721
1128
  };
722
- const rangeOf = (x: Value): StridedRange | undefined => {
723
- const form = linearTerms(ctx, x);
724
- if (form === undefined) {
1129
+ /** A range `r` for a param that reads itself, proven by induction: every in-edge, read with the
1130
+ * param anywhere in `r` and under what its branch proves, passes a value inside `r`, so no run
1131
+ * of the loop can leave it. A signed latch bounds its counter on one side only (`k + 1 <= 7`
1132
+ * holds of every negative k), and only the counter's start bounds the other. The hypothesis
1133
+ * starts at the in-edges that read without the param, takes in what they pass, and once the
1134
+ * bounds the branches name on values computed from the param; every sum is exact and a bound
1135
+ * read outside its window holds alone, so a counter that can wrap, an update on some paths only
1136
+ * or an unbounded start never settles and proves nothing. */
1137
+ const inductive = (
1138
+ t: Value,
1139
+ ins: readonly InEdge[],
1140
+ seeds: readonly (StridedRange | undefined)[],
1141
+ ): StridedRange | undefined => {
1142
+ let r = hullOf(seeds.filter((x): x is StridedRange => x !== undefined));
1143
+ for (let round = 0; r !== undefined && round < INDUCTION_ROUNDS; round++) {
1144
+ const passed: StridedRange[] = [];
1145
+ const named: StridedRange[] = [];
1146
+ assumed.set(t, r);
1147
+ try {
1148
+ for (const e of ins) {
1149
+ const { range, facts } = edgeRange(e);
1150
+ if (range === null) {
1151
+ continue; // never taken while the param is in `r`: it passes nothing
1152
+ }
1153
+ if (range === undefined) {
1154
+ return undefined;
1155
+ }
1156
+ passed.push(range);
1157
+ // only a bound on a value computed from the param can be where it settles: an edge also
1158
+ // carries what the tests above it prove of a loaded global or a dispatch value
1159
+ facts.forEach((b, x) => {
1160
+ if (computedFrom(x, t)) {
1161
+ named.push(...boundSides(b));
1162
+ }
1163
+ });
1164
+ }
1165
+ } finally {
1166
+ assumed.delete(t);
1167
+ }
1168
+ const next = hullOf([r, ...passed])!;
1169
+ if (next.lo === r.lo && next.hi === r.hi && next.stride === r.stride) {
1170
+ return r;
1171
+ }
1172
+ r = round === 0 ? hullOf([next, ...named]) : next;
1173
+ }
1174
+ return undefined;
1175
+ };
1176
+ /** Whether `x` is computed from the param `t` by arithmetic: through the operands of what defines
1177
+ * it, and through what a block param's in-edges pass (a join of `k + 1` and `k + 2` is computed
1178
+ * from `k`), but never through memory, whose word a load reads whatever address it took. */
1179
+ const computedFrom = (x: Value, t: Value): boolean => {
1180
+ const seen = new Set<Value>();
1181
+ const work = [x];
1182
+ while (work.length > 0 && seen.size < RANGE_BUDGET) {
1183
+ const y = work.pop()!;
1184
+ if (y === t) {
1185
+ return true;
1186
+ }
1187
+ if (seen.has(y)) {
1188
+ continue;
1189
+ }
1190
+ seen.add(y);
1191
+ const d = ctx.defs.get(y);
1192
+ if (d !== undefined) {
1193
+ if (!READS_MEMORY.has(d.opcode)) {
1194
+ work.push(...d.operands);
1195
+ }
1196
+ } else {
1197
+ ctx.incoming.get(y)?.forEach((e) => e.arg !== undefined && work.push(e.arg));
1198
+ }
1199
+ }
1200
+ return false;
1201
+ };
1202
+ /** What one in-edge passes, read under what its branch proves (`facts`, returned beside it).
1203
+ * Every value the argument reads is the instance the branch tested, so a bound on it holds
1204
+ * wherever the argument reads it; a param read on the way is read under its own in-edges'
1205
+ * facts, never these. */
1206
+ const edgeRange = (e: InEdge): { readonly range: StridedRange | undefined | null; readonly facts: Facts } => {
1207
+ const facts = e.holds === undefined ? NO_FACTS : factsOf(e.holds);
1208
+ return { range: e.arg === undefined ? undefined : rangeOf(e.arg, facts), facts };
1209
+ };
1210
+ /** The bound each compare puts on each side, from the other side's range (read with no facts);
1211
+ * of two bounds on one value, each holds, and the narrower is kept. */
1212
+ const factsOf = (hs: readonly EdgeTest[]): Facts => {
1213
+ const out = new Map<Value, EdgeBound>();
1214
+ for (const h of hs) {
1215
+ if (h.lhs === h.rhs) {
1216
+ continue;
1217
+ }
1218
+ for (const [x, y, opcode] of [
1219
+ [h.lhs, h.rhs, h.opcode],
1220
+ [h.rhs, h.lhs, SWAPPED_ICMP[h.opcode]],
1221
+ ] as const) {
1222
+ if (opcode === undefined || ctx.constOf(x) !== undefined) {
1223
+ continue;
1224
+ }
1225
+ const other = rangeOf(y, NO_FACTS);
1226
+ const b = other === undefined || other === null ? undefined : testBound(opcode, other);
1227
+ const had = out.get(x);
1228
+ if (b !== undefined && (had === undefined || b.r.hi - b.r.lo < had.r.hi - had.r.lo)) {
1229
+ out.set(x, b);
1230
+ }
1231
+ }
1232
+ }
1233
+ return out;
1234
+ };
1235
+ /** Two sets of facts on the same values, both holding: of two bounds in one window, their meet
1236
+ * (`null` where they meet nowhere, so no value passes both), else the narrower. */
1237
+ const bothFacts = (a: Facts, b: Facts): Facts | null => {
1238
+ if (a.size === 0 || b.size === 0) {
1239
+ return a.size === 0 ? b : a;
1240
+ }
1241
+ const out = new Map(a);
1242
+ for (const [x, bb] of b) {
1243
+ const had = out.get(x);
1244
+ if (had === undefined) {
1245
+ out.set(x, bb);
1246
+ } else if (had.window === bb.window) {
1247
+ const r = meetIn(had.r, bb.r);
1248
+ if (r === null) {
1249
+ return null;
1250
+ }
1251
+ out.set(x, { r, window: bb.window });
1252
+ } else if (bb.r.hi - bb.r.lo < had.r.hi - had.r.lo) {
1253
+ out.set(x, bb);
1254
+ }
1255
+ }
1256
+ return out;
1257
+ };
1258
+ /** A signed remainder by `2^k` the form holds as the pair `c * X - c * 2^k * (p >> k)`, `>>` the
1259
+ * sign-filling shift and `p` the operand `X + B` with a bias `B` in [0, 2^k), or a block param
1260
+ * whose every in-edge passes `X + B` for its own `B`: agbcc's (and gcc's) `X % 8` is `if (X < 0)
1261
+ * t = X + 7; else t = X; X - (t >> 3 << 3)`, and agbcc's `X % 2` is `X - ((X + (X >>> 31)) >> 1
1262
+ * << 1)`, its bias a term. The dividend `X` is any linear form: `(i + 1) % 8`
1263
+ * passes `i + 1` and `i + 8`, `(i * 2) % 8` passes `2 * i` and `2 * i + 7`. `X` is the terms the
1264
+ * outer form holds `c` times of the in-edge argument with the least constant, with that
1265
+ * argument's constant (moved out of the pair); every edge passes each of `X`'s terms as often,
1266
+ * so each is an SSA value defined above the join, the same word at the edge and at the use.
1267
+ *
1268
+ * Modulo 2^32, `X - 2^k * ((X + B) >> k)` is `(W mod 2^k) - B` of the signed word `W` the shift
1269
+ * reads, whether or not `X + B` wrapped to it: each edge's part is in [-B_hi, 2^k - 1 - B_lo]
1270
+ * with nothing known of `X`. Where every bias is a constant `b` and `X + b` stays inside the
1271
+ * signed window, the part is `X - 2^k * floor((X + b) / 2^k)`, exactly `X - 2^k * q` where the
1272
+ * floor `q` is one constant over `X`'s range: `X` is read under each in-edge's branch and under
1273
+ * `facts` (what holds where the form is read: `X`'s terms are the same words there), and the
1274
+ * parts hulled, an edge never taken passing nothing (`null` when none is taken). Undefined,
1275
+ * leaving the pair as terms, where `k` is outside [1, 30], an in-edge passes no linear form or
1276
+ * not each of `X`'s terms as often, a bias is out of range or holds the shift, or the outer form
1277
+ * holds none of the argument's terms `c` times.
1278
+ *
1279
+ * raise/divpow2.ts folds the same bias diamond into `sdiv`, but only in runPreRecovery, after the
1280
+ * frontends' frame audit has asked these ranges; and it is identity-or-decline about the shape
1281
+ * where this is a bound, which holds for any bias in range. */
1282
+ const remainderPair = (
1283
+ s: Value,
1284
+ cs: number,
1285
+ form: ReadonlyMap<Value | 1, number>,
1286
+ facts: Facts,
1287
+ ):
1288
+ | { readonly terms: readonly Value[]; readonly c: number; readonly moved: number; readonly r: StridedRange | null }
1289
+ | undefined => {
1290
+ const d = ctx.defs.get(s);
1291
+ if (d === undefined || d.opcode !== 'shr_s') {
725
1292
  return undefined;
726
1293
  }
1294
+ const k = d.operands.length === 1 ? (d.attrs.imm as number | undefined) : ctx.constOf(d.operands[1]);
1295
+ if (k === undefined || !(k > 0 && k < 31)) {
1296
+ return undefined;
1297
+ }
1298
+ const by = 2 ** k;
1299
+ const c = -cs / by;
1300
+ if (!Number.isSafeInteger(c) || c === 0) {
1301
+ return undefined;
1302
+ }
1303
+ const p = d.operands[0];
1304
+ const ins = ctx.defs.has(p) || ctx.entry.has(p) ? undefined : ctx.incoming.get(p);
1305
+ const edges = ins !== undefined && ins.length > 0 ? ins : [{ arg: p } as InEdge];
1306
+ const forms = edges.map((e) => (e.arg === undefined ? undefined : linearTerms(ctx, e.arg)));
1307
+ if (forms.some((f) => f === undefined)) {
1308
+ return undefined;
1309
+ }
1310
+ const constOf = (f: ReadonlyMap<Value | 1, number>) => f.get(1) ?? 0;
1311
+ const at = forms.reduce((m, f, i) => (constOf(f!) < constOf(forms[m]!) ? i : m), 0);
1312
+ const fx = forms[at]!;
1313
+ // X: the terms of that argument the outer form holds `c` times; X is not computed from the shift
1314
+ const terms = [...fx].filter(([t, n]) => t !== 1 && n !== 0 && form.get(t) === c * n) as [Value, number][];
1315
+ if (terms.length === 0 || terms.some(([t]) => t === s)) {
1316
+ return undefined;
1317
+ }
1318
+ // each edge's bias B: its form less X's, holding none of X's terms and not the shift
1319
+ const biases: Map<Value | 1, number>[] = [];
1320
+ for (const f of forms) {
1321
+ const b = new Map<Value | 1, number>();
1322
+ for (const [t, n] of f!) {
1323
+ if (n !== 0) {
1324
+ b.set(t, n);
1325
+ }
1326
+ }
1327
+ for (const [t, n] of terms) {
1328
+ if (b.get(t) !== n) {
1329
+ return undefined;
1330
+ }
1331
+ b.delete(t);
1332
+ }
1333
+ b.set(1, constOf(f!) - constOf(fx));
1334
+ if (b.has(s)) {
1335
+ return undefined;
1336
+ }
1337
+ biases.push(b);
1338
+ }
1339
+ // where every bias is a constant, X is the least argument itself
1340
+ const exact = biases.every((b) => b.size === 1);
1341
+ const x = edges[at].arg!;
1342
+ const parts: StridedRange[] = [];
1343
+ for (const [i, e] of edges.entries()) {
1344
+ const bias = biases[i];
1345
+ const own = e.holds === undefined ? NO_FACTS : factsOf(e.holds);
1346
+ // B is read where the edge leaves, under its branch alone: its terms need not be above the join
1347
+ const br =
1348
+ bias.size === 1
1349
+ ? { lo: bias.get(1)!, hi: bias.get(1)!, stride: 0 }
1350
+ : spend()
1351
+ ? formRange(bias, own)
1352
+ : undefined;
1353
+ if (br === null) {
1354
+ continue; // never taken
1355
+ }
1356
+ if (br === undefined || !(br.lo >= 0 && br.hi < by)) {
1357
+ return undefined;
1358
+ }
1359
+ const any = { lo: -br.hi, hi: by - 1 - br.lo, stride: 1 };
1360
+ if (!exact) {
1361
+ parts.push(any);
1362
+ continue;
1363
+ }
1364
+ const under = bothFacts(facts, own);
1365
+ if (under === null) {
1366
+ continue; // never taken where the form is read
1367
+ }
1368
+ const read = spend() ? rangeOf(x, under) : undefined;
1369
+ if (read === null) {
1370
+ continue; // never taken
1371
+ }
1372
+ const xs = read === undefined ? undefined : intoSigned(read);
1373
+ const b = br.lo;
1374
+ if (xs === undefined || xs.hi + b > S_MAX) {
1375
+ parts.push(any);
1376
+ continue;
1377
+ }
1378
+ const [q0, q1] = [Math.floor((xs.lo + b) / by), Math.floor((xs.hi + b) / by)];
1379
+ parts.push(q0 === q1 ? { lo: xs.lo - q0 * by, hi: xs.hi - q0 * by, stride: xs.stride } : any);
1380
+ }
1381
+ const moved = c * constOf(fx);
1382
+ return Number.isSafeInteger(moved)
1383
+ ? { terms: terms.map(([t]) => t), c, moved, r: hullOf(parts) ?? null }
1384
+ : undefined;
1385
+ };
1386
+ const rangeOf = (x: Value, facts: Facts): StridedRange | undefined | null => {
1387
+ const form = linearTerms(ctx, x, 0, facts);
1388
+ return form === undefined ? undefined : formRange(form, facts);
1389
+ };
1390
+ /** The integers a linear form holds, its terms read under `facts`. */
1391
+ const formRange = (form: ReadonlyMap<Value | 1, number>, facts: Facts): StridedRange | undefined | null => {
727
1392
  let lo = 0;
728
1393
  let hi = 0;
729
1394
  let stride = 0;
730
- for (const [t, c] of form) {
731
- if (c === 0) {
1395
+ // each remainder pair first, as one bounded term; the rest of the form after it
1396
+ const paired = new Set<Value | 1>();
1397
+ for (const [s, cs] of form) {
1398
+ const pair = s === 1 || cs === 0 || paired.has(s) ? undefined : remainderPair(s, cs, form, facts);
1399
+ if (pair === undefined || pair.terms.some((t) => paired.has(t))) {
732
1400
  continue;
733
1401
  }
734
- const r = t === 1 ? { lo: 1, hi: 1, stride: 0 } : termRange(t);
735
- if (r === undefined) {
1402
+ if (pair.r === null) {
1403
+ return null;
1404
+ }
1405
+ paired.add(s);
1406
+ pair.terms.forEach((t) => paired.add(t));
1407
+ // the pair is `c * X - c * 2^k * q`, and X's constant is already in the form's own constant
1408
+ const parts = [pair.c * pair.r.lo, pair.c * pair.r.hi, pair.c * pair.r.stride];
1409
+ lo += Math.min(parts[0], parts[1]) - pair.moved;
1410
+ hi += Math.max(parts[0], parts[1]) - pair.moved;
1411
+ if (![...parts, lo, hi].every(Number.isSafeInteger)) {
736
1412
  return undefined;
737
1413
  }
738
- lo += c > 0 ? c * r.lo : c * r.hi;
739
- hi += c > 0 ? c * r.hi : c * r.lo;
740
- stride = gcdOf(stride, c * r.stride);
1414
+ stride = gcdOf(stride, parts[2]);
741
1415
  }
742
- if (!Number.isSafeInteger(lo) || !Number.isSafeInteger(hi)) {
743
- return undefined;
1416
+ for (const [t, c] of form) {
1417
+ if (c === 0 || paired.has(t)) {
1418
+ continue;
1419
+ }
1420
+ const r = t === 1 ? { lo: 1, hi: 1, stride: 0 } : termRange(t, facts);
1421
+ if (r === undefined || r === null) {
1422
+ return r;
1423
+ }
1424
+ const parts = [c * r.lo, c * r.hi, c * r.stride];
1425
+ lo += c > 0 ? parts[0] : parts[1];
1426
+ hi += c > 0 ? parts[1] : parts[0];
1427
+ // every product and partial sum stays exact, or a rounded one could cancel to a false range
1428
+ if (![...parts, lo, hi].every(Number.isSafeInteger)) {
1429
+ return undefined;
1430
+ }
1431
+ stride = gcdOf(stride, parts[2]);
744
1432
  }
745
- const base = Math.floor(lo / WORD) * WORD;
746
- return hi - base >= WORD ? undefined : { lo: lo - base, hi: hi - base, stride };
1433
+ return Number.isSafeInteger(lo) && Number.isSafeInteger(hi) ? { lo, hi, stride } : undefined;
747
1434
  };
748
- return rangeOf(v);
1435
+ // asked at a block, `v` is read under what holds there, as an in-edge's argument is under what
1436
+ // its edge proves; a point no value can reach (`null`) is answered as nothing proven
1437
+ return rangeOf(v, holds === undefined || holds.length === 0 ? NO_FACTS : factsOf(holds)) ?? undefined;
1438
+ }
1439
+
1440
+ /** The outermost range question under way, which every question it opens shares: each block
1441
+ * param's range as its own question (`ownRange`), by reading (index 1: `wordFirst`), the terms
1442
+ * all its readings may still bound between them, and how many own questions are under way, one
1443
+ * inside another. Nothing outlives the question, so what it answers never depends on what was
1444
+ * asked before it. */
1445
+ let question:
1446
+ | {
1447
+ readonly ctx: RangeCtx;
1448
+ readonly own: [Map<Value, StridedRange | undefined>, Map<Value, StridedRange | undefined>];
1449
+ left: number;
1450
+ depth: number;
1451
+ }
1452
+ | undefined;
1453
+ /** The most `ownRange` questions under way at once, one inside another. */
1454
+ const OWN_DEPTH = 8;
1455
+
1456
+ /** A block param's range as its own question, asked from it with nothing open, holding nothing
1457
+ * assumed and read under no fact (`integerRange`), kept for the rest of the outermost question.
1458
+ * It covers every value the param takes, so the answer serves every reading that meets the
1459
+ * param, and one met inside a reading that opened another param of the same loop reads it here.
1460
+ * A param whose own question is already under way answers nothing to it, which only proves less. */
1461
+ function ownRange(ctx: RangeCtx, p: Value, wordFirst: boolean): StridedRange | undefined {
1462
+ const q = question!;
1463
+ const known = q.own[wordFirst ? 1 : 0];
1464
+ if (known.has(p)) {
1465
+ return known.get(p);
1466
+ }
1467
+ if (q.depth >= OWN_DEPTH) {
1468
+ known.set(p, undefined); // past the nesting it may run to: nothing, kept as any answer is
1469
+ return undefined;
1470
+ }
1471
+ known.set(p, undefined); // under way: met again, it answers nothing
1472
+ q.depth++;
1473
+ try {
1474
+ const r = rangeIn(ctx, p, wordFirst);
1475
+ known.set(p, r);
1476
+ return r;
1477
+ } finally {
1478
+ q.depth--;
1479
+ }
1480
+ }
1481
+
1482
+ /** The integers every range in a list holds, and the stride all of them sit on. */
1483
+ function hullOf(rs: readonly StridedRange[]): StridedRange | undefined {
1484
+ let out: StridedRange | undefined;
1485
+ for (const r of rs) {
1486
+ out =
1487
+ out === undefined
1488
+ ? r
1489
+ : {
1490
+ lo: Math.min(out.lo, r.lo),
1491
+ hi: Math.max(out.hi, r.hi),
1492
+ stride: gcdOf(gcdOf(out.stride, r.stride), r.lo - out.lo),
1493
+ };
1494
+ }
1495
+ return out;
1496
+ }
1497
+
1498
+ /** What a branch proves of a value: the integers of one window it may be, unsigned words (`u`,
1499
+ * from `icmp_u*` and `icmp_eq`) or the signed ones (`s`, from `icmp_s*`). */
1500
+ interface EdgeBound {
1501
+ readonly r: StridedRange;
1502
+ readonly window: 'u' | 's';
1503
+ }
1504
+
1505
+ /** What an in-edge's branch proves, by the value each bound is on. */
1506
+ type Facts = ReadonlyMap<Value, EdgeBound>;
1507
+ const NO_FACTS: Facts = new Map();
1508
+
1509
+ const S_MIN = -(2 ** 31);
1510
+ const S_MAX = 2 ** 31 - 1;
1511
+
1512
+ /** The ops whose result is a word read from memory or handed back by a callee (the registry's
1513
+ * `reads` and `effects`), not computed from their operands: `integerRange`'s `computedFrom` reads
1514
+ * no further. */
1515
+ const READS_MEMORY: ReadonlySet<string> = new Set(
1516
+ Object.entries(OPCODES as Record<string, OpSig>)
1517
+ .filter(([, sig]) => sig.reads === true || sig.effects === true)
1518
+ .map(([op]) => op),
1519
+ );
1520
+
1521
+ /** The rounds an induction hypothesis may grow before it proves nothing. */
1522
+ const INDUCTION_ROUNDS = 4;
1523
+
1524
+ /** The sides of a branch's bound that are not its window's edge, each as a one-value range. */
1525
+ function boundSides(b: EdgeBound): StridedRange[] {
1526
+ const [floor, ceil] = b.window === 'u' ? [0, WORD - 1] : [S_MIN, S_MAX];
1527
+ return [b.r.lo === floor ? undefined : b.r.lo, b.r.hi === ceil ? undefined : b.r.hi]
1528
+ .filter((x): x is number => x !== undefined)
1529
+ .map((x) => ({ lo: x, hi: x, stride: 0 }));
1530
+ }
1531
+
1532
+ /** An integer range as signed words, or undefined when it holds words on both sides of the sign. */
1533
+ function intoSigned(r: StridedRange): StridedRange | undefined {
1534
+ if (r.lo >= S_MIN && r.hi <= S_MAX) {
1535
+ return r;
1536
+ }
1537
+ const w = intoWord(r);
1538
+ if (w === undefined || w.hi <= S_MAX) {
1539
+ return w;
1540
+ }
1541
+ return w.lo > S_MAX ? { lo: w.lo - WORD, hi: w.hi - WORD, stride: w.stride } : undefined;
1542
+ }
1543
+
1544
+ /** What `x <opcode> y` proves of `x`, `y` being somewhere in `other`; undefined for `icmp_ne`, and
1545
+ * for a compare no `x` can pass (an edge that is never taken bounds nothing here). */
1546
+ function testBound(opcode: string, other: StridedRange): EdgeBound | undefined {
1547
+ const span = (lo: number, hi: number, window: 'u' | 's'): EdgeBound | undefined =>
1548
+ lo > hi ? undefined : { r: { lo, hi, stride: lo === hi ? 0 : 1 }, window };
1549
+ const w = intoWord(other);
1550
+ const s = intoSigned(other);
1551
+ switch (opcode) {
1552
+ case 'icmp_eq':
1553
+ return w === undefined ? undefined : { r: w, window: 'u' };
1554
+ case 'icmp_ult':
1555
+ return w && span(0, w.hi - 1, 'u');
1556
+ case 'icmp_ule':
1557
+ return w && span(0, w.hi, 'u');
1558
+ case 'icmp_ugt':
1559
+ return w && span(w.lo + 1, WORD - 1, 'u');
1560
+ case 'icmp_uge':
1561
+ return w && span(w.lo, WORD - 1, 'u');
1562
+ case 'icmp_slt':
1563
+ return s && span(S_MIN, s.hi - 1, 's');
1564
+ case 'icmp_sle':
1565
+ return s && span(S_MIN, s.hi, 's');
1566
+ case 'icmp_sgt':
1567
+ return s && span(s.lo + 1, S_MAX, 's');
1568
+ case 'icmp_sge':
1569
+ return s && span(s.lo, S_MAX, 's');
1570
+ default:
1571
+ return undefined;
1572
+ }
1573
+ }
1574
+
1575
+ /** A value's range where a branch also bounds it: the two met in the branch's window. A range with
1576
+ * words on both sides of that window's edge is two pieces there, each met with the bound: `d =
1577
+ * k - 1` of a k in [0, 10] is [-1, 9], which `d <=u 10` meets as [0, 9], the piece at 2^32 - 1
1578
+ * taking no branch. Where both pieces stay, their hull or the range itself, whichever is
1579
+ * narrower, the hull on a tie (it is a range of the window). A meet that holds nothing is `null`:
1580
+ * no word the value may hold takes the branch, so the edge is never taken — `if (k < 0) k += 7`
1581
+ * on a counter from 0. */
1582
+ function meetBound(r: StridedRange | undefined, b: EdgeBound): StridedRange | null {
1583
+ if (r === undefined) {
1584
+ return b.r;
1585
+ }
1586
+ const m = b.window === 'u' ? intoWord(r) : intoSigned(r);
1587
+ if (m !== undefined) {
1588
+ return meetIn(m, b.r);
1589
+ }
1590
+ if (r.hi - r.lo >= WORD || r.stride === 0) {
1591
+ return b.r; // every word: only the bound is left (one point always fits a window)
1592
+ }
1593
+ // the integer where the window ends inside `r`: the pieces below it and from it, each moved
1594
+ // into the window by its own multiple of 2^32
1595
+ const floor = b.window === 'u' ? 0 : S_MIN;
1596
+ const shift = Math.floor((r.lo - floor) / WORD) * WORD;
1597
+ const [below, from] = splitAt(r, floor + shift + WORD);
1598
+ const met = [
1599
+ below && meetIn({ ...below, lo: below.lo - shift, hi: below.hi - shift }, b.r),
1600
+ from && meetIn({ ...from, lo: from.lo - shift - WORD, hi: from.hi - shift - WORD }, b.r),
1601
+ ].filter((x): x is StridedRange => x !== undefined && x !== null);
1602
+ const hull = hullOf(met);
1603
+ if (hull === undefined) {
1604
+ return null;
1605
+ }
1606
+ return hull.hi - hull.lo <= r.hi - r.lo ? hull : r;
1607
+ }
1608
+
1609
+ /** The points of a strided range (stride above 0) below `cut` and from it, each keeping the
1610
+ * stride; a side with no point is undefined. */
1611
+ function splitAt(r: StridedRange, cut: number): [StridedRange | undefined, StridedRange | undefined] {
1612
+ const firstUp = r.lo + Math.ceil((cut - r.lo) / r.stride) * r.stride;
1613
+ const piece = (lo: number, hi: number): StridedRange | undefined =>
1614
+ lo > hi ? undefined : { lo, hi, stride: lo === hi ? 0 : r.stride };
1615
+ return [piece(r.lo, Math.min(firstUp - r.stride, r.hi)), piece(Math.max(firstUp, r.lo), r.hi)];
1616
+ }
1617
+
1618
+ /** A range inside one window met with a bound of that window; `null` when they share no point. */
1619
+ function meetIn(m: StridedRange, br: StridedRange): StridedRange | null {
1620
+ const lo = Math.max(m.lo, br.lo);
1621
+ const hi = Math.min(m.hi, br.hi);
1622
+ if (m.stride === 0) {
1623
+ return lo <= hi ? m : null;
1624
+ }
1625
+ const first = m.lo + Math.ceil((lo - m.lo) / m.stride) * m.stride;
1626
+ const last = m.lo + Math.floor((hi - m.lo) / m.stride) * m.stride;
1627
+ return first > last ? null : { lo: first, hi: last, stride: first === last ? 0 : m.stride };
749
1628
  }
750
1629
 
751
1630
  const mod = (w: number): number => ((w % WORD) + WORD) % WORD;
@@ -753,3 +1632,139 @@ const mod = (w: number): number => ((w % WORD) + WORD) % WORD;
753
1632
  function gcdOf(a: number, b: number): number {
754
1633
  return b === 0 ? Math.abs(a) : gcdOf(b, a % b);
755
1634
  }
1635
+
1636
+ const FULL = 0xffffffff;
1637
+
1638
+ /** The low `bits` bits, as a mask. */
1639
+ const lowBits = (bits: number): number => (bits >= 32 ? FULL : 2 ** bits - 1);
1640
+
1641
+ /** What `demandedBits` walks, built from the whole function: each value's defining op, the ops that
1642
+ * read it (each once), and the block parameters an edge hands it to. A caller asking about many
1643
+ * values of one unchanged function builds it once and hands it to each ask; each ask still walks
1644
+ * from its own root with its own memo, so the answer is the one a fresh index gives. */
1645
+ export interface DemandedBitsIndex {
1646
+ readonly defs: ReadonlyMap<Value, Op>;
1647
+ readonly readers: ReadonlyMap<Value, readonly Op[]>;
1648
+ readonly params: ReadonlyMap<Value, readonly Value[]>;
1649
+ }
1650
+
1651
+ export function demandedBitsIndex(fn: Fn): DemandedBitsIndex {
1652
+ const defs = defOpMap(fn);
1653
+ const readers = new Map<Value, Op[]>();
1654
+ const params = new Map<Value, Value[]>();
1655
+ const add = <T>(m: Map<Value, T[]>, v: Value, x: T) => (m.get(v) ?? m.set(v, []).get(v)!).push(x);
1656
+ for (const b of fn.blocks) {
1657
+ for (const op of b.ops) {
1658
+ for (const v of new Set(op.operands)) {
1659
+ add(readers, v, op);
1660
+ }
1661
+ for (const e of op.successors) {
1662
+ e.args.forEach((a, i) => add(params, a, e.block.params[i]));
1663
+ }
1664
+ }
1665
+ }
1666
+ return { defs, readers, params };
1667
+ }
1668
+
1669
+ /** THE BITS OF `root` ANY READER NEEDS, as a mask: a store of W bytes needs the low 8W bits of the
1670
+ * value it stores, a `zext` of W bits its operand's low W bits its own readers need, a `sext` those
1671
+ * and its sign bit where they need any bit above, a constant mask and the bitwise ops pass their
1672
+ * readers' bits through, a shift by a constant moves them, an add, a subtract or a multiply needs
1673
+ * every bit up to its readers' highest, since a carry only travels up, and a join needs what its
1674
+ * parameter's readers need. Every other reader — a compare, a call, an address, a shift's count —
1675
+ * needs all 32, and so does a join that reaches itself again. A `ret` needs none where the
1676
+ * function's own declaration returns nothing (`voidOwn`), all 32 otherwise. A value nothing reads
1677
+ * needs none: a caller for which an unseen reader may exist checks for a use itself.
1678
+ *
1679
+ * Asked whether a caller reads a register-returned struct's word past the struct
1680
+ * (frontend/register-struct-call.ts), and whether any use of a narrow load reads the bits its
1681
+ * extension fills (structure.ts `extensionImmaterial`). */
1682
+ export function demandedBits(
1683
+ fn: Fn,
1684
+ root: Value,
1685
+ voidOwn: boolean,
1686
+ index: DemandedBitsIndex = demandedBitsIndex(fn),
1687
+ ): number {
1688
+ const { defs, readers, params } = index;
1689
+ const memo = new Map<Value, number>();
1690
+ const constOf = (x: Value | undefined): number | undefined => {
1691
+ const d = x === undefined ? undefined : defs.get(x);
1692
+ return d?.opcode === 'const' && typeof d.attrs.value === 'number' ? d.attrs.value >>> 0 : undefined;
1693
+ };
1694
+ const upTo = (d: number): number => (d === 0 ? 0 : d >= 0x80000000 ? FULL : lowBits(32 - Math.clz32(d)));
1695
+ const of = (v: Value): number => {
1696
+ const known = memo.get(v);
1697
+ if (known !== undefined) {
1698
+ return known;
1699
+ }
1700
+ memo.set(v, FULL); // a reader reached again through a join needs everything
1701
+ let need = 0;
1702
+ for (const p of params.get(v) ?? []) {
1703
+ need |= of(p);
1704
+ }
1705
+ for (const op of readers.get(v) ?? []) {
1706
+ const out = () => (op.results.length === 1 ? of(op.results[0]) : FULL);
1707
+ const imm = typeof op.attrs.imm === 'number' && op.operands.length === 1 ? op.attrs.imm : undefined;
1708
+ const other = op.operands.length === 2 ? op.operands[op.operands[0] === v ? 1 : 0] : undefined;
1709
+ // a shift's constant count, where `v` is only what it shifts
1710
+ const count =
1711
+ other === v || (op.operands.length === 2 && op.operands[1] === v) ? undefined : (imm ?? constOf(other));
1712
+ const width = typeof op.attrs.width === 'number' ? op.attrs.width : undefined;
1713
+ switch (op.opcode) {
1714
+ case 'store':
1715
+ need |= op.operands[0] === v || width === undefined || width >= 4 ? FULL : lowBits(8 * width);
1716
+ break;
1717
+ case 'ret':
1718
+ need |= voidOwn ? 0 : FULL;
1719
+ break;
1720
+ case 'zext':
1721
+ need |= width === undefined ? FULL : out() & lowBits(width);
1722
+ break;
1723
+ case 'sext': {
1724
+ const d = out();
1725
+ need |=
1726
+ width === undefined || width < 1
1727
+ ? FULL
1728
+ : (d & lowBits(width)) | ((d & ~lowBits(width)) >>> 0 !== 0 ? 2 ** (width - 1) : 0);
1729
+ break;
1730
+ }
1731
+ case 'and': {
1732
+ const c = other === v ? undefined : (imm ?? constOf(other));
1733
+ need |= c === undefined ? out() : out() & c;
1734
+ break;
1735
+ }
1736
+ case 'or':
1737
+ case 'xor':
1738
+ case 'not':
1739
+ need |= other === v ? FULL : out();
1740
+ break;
1741
+ case 'add':
1742
+ case 'sub':
1743
+ case 'mul':
1744
+ case 'neg':
1745
+ need |= other === v ? FULL : upTo(out());
1746
+ break;
1747
+ case 'shl':
1748
+ need |= count === undefined || count >= 32 ? FULL : out() >>> count;
1749
+ break;
1750
+ case 'shr_u':
1751
+ need |= count === undefined || count >= 32 ? FULL : (out() << count) >>> 0;
1752
+ break;
1753
+ case 'shr_s': {
1754
+ const d = out();
1755
+ need |=
1756
+ count === undefined || count >= 32
1757
+ ? FULL
1758
+ : ((d << count) | (count > 0 && d >>> (32 - count) !== 0 ? 0x80000000 : 0)) >>> 0;
1759
+ break;
1760
+ }
1761
+ default:
1762
+ need |= FULL;
1763
+ }
1764
+ need >>>= 0;
1765
+ }
1766
+ memo.set(v, need);
1767
+ return need;
1768
+ };
1769
+ return of(root);
1770
+ }