@asmlift/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +22 -16
  2. package/package.json +1 -1
  3. package/src/backend/c.ts +1 -0
  4. package/src/backend/cfamily.ts +238 -167
  5. package/src/backend/cpp.ts +1 -0
  6. package/src/backend/pascal.ts +26 -12
  7. package/src/contracts.ts +194 -39
  8. package/src/declare.ts +41 -4
  9. package/src/frontend/mips.ts +11 -0
  10. package/src/frontend/ppc.ts +43 -7
  11. package/src/frontend/ssa.ts +404 -29
  12. package/src/frontend/thumb.ts +2176 -686
  13. package/src/ir/alias.ts +54 -0
  14. package/src/ir/bits.ts +75 -0
  15. package/src/ir/core.ts +337 -2
  16. package/src/ir/opcodes.ts +140 -21
  17. package/src/ir/parse.ts +19 -2
  18. package/src/ir/print.ts +27 -2
  19. package/src/ir/simplify.ts +190 -3
  20. package/src/ir/struct-names.ts +42 -0
  21. package/src/ir/verify.ts +43 -49
  22. package/src/l3/address.ts +62 -0
  23. package/src/l3/argbase.ts +2 -1
  24. package/src/l3/ast.ts +464 -57
  25. package/src/l3/basecse.ts +664 -76
  26. package/src/l3/coalesce.ts +429 -43
  27. package/src/l3/dce.ts +31 -9
  28. package/src/l3/gates.ts +21 -0
  29. package/src/l3/hoist.ts +293 -14
  30. package/src/l3/homesplit.ts +285 -0
  31. package/src/l3/initfirst.ts +301 -0
  32. package/src/l3/inlinebase.ts +193 -0
  33. package/src/l3/mentions.ts +113 -0
  34. package/src/l3/mulfirst.ts +42 -0
  35. package/src/l3/nearbase.ts +152 -0
  36. package/src/l3/offmember.ts +371 -0
  37. package/src/l3/parkfirst.ts +96 -0
  38. package/src/l3/pollguard.ts +154 -0
  39. package/src/l3/ptrfield.ts +227 -0
  40. package/src/l3/regspell.ts +110 -85
  41. package/src/l3/reindex.ts +715 -78
  42. package/src/l3/scopebase.ts +644 -218
  43. package/src/l3/sinkinit.ts +40 -0
  44. package/src/l3/slotorder.ts +123 -0
  45. package/src/l3/storage.ts +48 -0
  46. package/src/l3/symbol-refs.ts +41 -8
  47. package/src/l3/tailmerge.ts +15 -0
  48. package/src/l3/typing.ts +198 -9
  49. package/src/l3/unmerge.ts +263 -0
  50. package/src/l3/unreduce.ts +971 -0
  51. package/src/l3/volatileptr.ts +207 -0
  52. package/src/l3/volatileval.ts +130 -0
  53. package/src/l3/volstore.ts +229 -0
  54. package/src/l3/zerosub.ts +62 -0
  55. package/src/pattern/engine.ts +236 -13
  56. package/src/pipeline.ts +157 -56
  57. package/src/proto.ts +112 -14
  58. package/src/raise/arrays.ts +6 -1
  59. package/src/raise/divpow2.ts +2 -2
  60. package/src/raise/globalshape.ts +1038 -0
  61. package/src/raise/gvn.ts +33 -18
  62. package/src/raise/latch.ts +126 -0
  63. package/src/raise/memberarrays.ts +594 -0
  64. package/src/raise/narrow.ts +124 -0
  65. package/src/raise/narrowlocal.ts +556 -0
  66. package/src/raise/paramwidth.ts +179 -0
  67. package/src/raise/pre-recovery.ts +97 -14
  68. package/src/raise/recover.ts +56 -23
  69. package/src/raise/retsink.ts +210 -10
  70. package/src/raise/shortcircuit.ts +474 -74
  71. package/src/raise/struct-arrays.ts +19 -2
  72. package/src/raise/structs.ts +33 -3
  73. package/src/rank-axes.ts +630 -0
  74. package/src/rank-declare.ts +256 -0
  75. package/src/rank.ts +1723 -272
  76. package/src/structure/analysis.ts +1392 -141
  77. package/src/structure/bitfields.ts +332 -0
  78. package/src/structure/globalaccess.ts +274 -0
  79. package/src/structure/hazards.ts +411 -20
  80. package/src/structure/loops.ts +2 -49
  81. package/src/structure/namecoalesce.ts +435 -0
  82. package/src/structure/structure.ts +2678 -526
  83. package/src/structure/switch-recover.ts +616 -144
  84. package/src/symbols.ts +62 -1
  85. package/src/target.ts +367 -24
  86. package/src/trace.ts +111 -32
package/src/ir/verify.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  // 1. every block ends in exactly one terminator (and it is the last op)
4
4
  // 2. operands well-formed: opcode registered, correct arity/attrs
5
5
  // 3. SSA: each value defined once; every use is defined; def dominates use
6
- import { Block, Fn, Value, predecessors } from './core';
6
+ // 4. side data: a fn that carries a write-order record carries one for EVERY block, with every
7
+ // ordinal inside that block's own write count (ir/core.ts `WriteOrder`)
8
+ import { Block, Fn, Value, dominators } from './core';
7
9
  import { opSig } from './opcodes';
8
10
 
9
11
  export class VerifyError extends Error {}
@@ -133,42 +135,8 @@ export function verify(fn: Fn): void {
133
135
  );
134
136
  }
135
137
 
136
- // --- dominance (iterative dominators over the CFG) ---
137
- const entry = fn.blocks[0];
138
- const preds = predecessors(fn);
139
- const dom = new Map<Block, Set<Block>>();
140
- const allBlocks = new Set(fn.blocks);
141
- for (const b of fn.blocks) {
142
- dom.set(b, b === entry ? new Set([entry]) : new Set(allBlocks));
143
- }
144
- let changed = true;
145
- while (changed) {
146
- changed = false;
147
- for (const b of fn.blocks) {
148
- if (b === entry) {
149
- continue;
150
- }
151
- let inter: Set<Block> | null = null;
152
- for (const p of preds.get(b)!) {
153
- const dp = dom.get(p)!;
154
- if (inter === null) {
155
- inter = new Set(dp);
156
- continue;
157
- }
158
- for (const x of inter) {
159
- if (!dp.has(x)) {
160
- inter.delete(x);
161
- }
162
- } // intersect in place (spec-safe delete-in-iter)
163
- }
164
- const next = new Set<Block>(inter ?? []);
165
- next.add(b);
166
- if (!setEq(next, dom.get(b)!)) {
167
- dom.set(b, next);
168
- changed = true;
169
- }
170
- }
171
- }
138
+ // --- dominance: def must dominate every use (ir/core.ts owns the analysis) ---
139
+ const dom = dominators(fn);
172
140
  const dominates = (a: Block, b: Block) => dom.get(b)!.has(a);
173
141
 
174
142
  for (const b of fn.blocks) {
@@ -193,6 +161,44 @@ export function verify(fn: Fn): void {
193
161
  ),
194
162
  );
195
163
  }
164
+
165
+ checkWriteOrder(fn);
166
+ }
167
+
168
+ /** THE WRITE-ORDER RECORD'S CROSS-PASS OBLIGATION, checked rather than trusted (ir/core.ts
169
+ * `WriteOrder`, `foldWriteOrder`), because the consequence of missing it is silent: an unmeasured
170
+ * block's edge copies sort with no records at all, changing the order the structurer emits them in
171
+ * and, on a cycle, which register it spills. No decline, no marker.
172
+ *
173
+ * WHAT IT CATCHES: a pass that MINTS a block into a measured fn, or drops a block's entry, leaving
174
+ * a hole indistinguishable from a fn nobody measured. Also an ordinal outside its block's own
175
+ * write count — a `foldWriteOrder` that moved records without growing the count. WHAT IT CANNOT:
176
+ * ops moved from one MEASURED block into another with the fold forgotten, since both blocks still
177
+ * have entries and no snapshot of the IR shows the move. That half stays a review obligation until
178
+ * the record hangs on `Successor` itself.
179
+ *
180
+ * A fn is "measured" iff it has any entry at all; one with none is parsed or hand-built IR, whose
181
+ * edges take the def-position proxy by design. */
182
+ function checkWriteOrder(fn: Fn): void {
183
+ const order = fn.writeOrder;
184
+ if (order === undefined || order.writes.size === 0) {
185
+ return;
186
+ }
187
+ for (const b of fn.blocks) {
188
+ const writes = order.writes.get(b);
189
+ if (writes === undefined) {
190
+ throw new VerifyError(
191
+ `fn '${fn.name}': block ^bb${fn.blocks.indexOf(b)} carries no write-order entry, but the fn is measured`,
192
+ );
193
+ }
194
+ for (const at of order.lastWrite.get(b)?.values() ?? []) {
195
+ if (at < 0 || at >= writes) {
196
+ throw new VerifyError(
197
+ `fn '${fn.name}': write-order ordinal ${at} on block ^bb${fn.blocks.indexOf(b)} is outside its ${writes} writes`,
198
+ );
199
+ }
200
+ }
201
+ }
196
202
  }
197
203
 
198
204
  /** Run a check body; a VerifyError it throws is re-thrown with the op's location appended —
@@ -207,15 +213,3 @@ function locate(body: () => void, where: () => string): void {
207
213
  throw e;
208
214
  }
209
215
  }
210
-
211
- function setEq(a: Set<Block>, b: Set<Block>): boolean {
212
- if (a.size !== b.size) {
213
- return false;
214
- }
215
- for (const x of a) {
216
- if (!b.has(x)) {
217
- return false;
218
- }
219
- }
220
- return true;
221
- }
@@ -0,0 +1,62 @@
1
+ // asmlift — THE THREE READINGS OF A CONSTANT ADDRESS, in one place because they are three and not
2
+ // one, and because the same four lines had been re-typed into five files at three strengths.
3
+ //
4
+ // A raw address reaches L3 as a cast chain over a `const`, and "which numeric cell is this" has
5
+ // three different right answers depending on what the caller intends to DO with the answer. The
6
+ // readings are deliberately not merged; what this module supplies is names for them, so a pass
7
+ // declares which one it means instead of restating four lines and drifting.
8
+ //
9
+ // • baseConst — a deref BASE, through SCALAR pointer casts only. A cast to a STRUCT pointer is
10
+ // the dot-form's base and is refused, because a lever that re-spells THROUGH it collapses the
11
+ // stride (`((struct S *)K)[i].f` is not `((u8 *)K)[…]`). This is the reading a lever that
12
+ // REWRITES the base needs: l3/nearbase.ts's clusters, l3/volstore.ts's qualifier.
13
+ // • addrConst — the address an expression IS, through ANY pointer cast. Wider, and safe
14
+ // because nothing re-spells through it: l3/volatileptr.ts counts volatility claims with it.
15
+ // • rootConst — the constant an ACCESS CHAIN is rooted at, through any cast and any number of
16
+ // subscripts and field selections. Wide enough to place an object whose element is not known,
17
+ // which is what a read with a runtime subscript needs (l3/unreduce.ts's aliasing gate).
18
+ //
19
+ // And `cellAddress`, which is not a fourth reading of a base but the WHOLE address an `index`
20
+ // denotes — base plus subscript × width — or null when any part of it is not constant.
21
+ //
22
+ // A CAUTION THE HISTORY EARNED: `rootConst` and `cellAddress` disagree, and a predicate that uses
23
+ // one on its write side and the other on its read side is not the same predicate on both. A read
24
+ // rooted at 0x03FFFFF0 whose element is `[8]` denotes 0x04000010 — a device register the root
25
+ // reports as EWRAM. Ask for both where both can be had.
26
+ import type { Expr } from './ast';
27
+
28
+ /** the numeric address behind a deref base, through SCALAR pointer casts only */
29
+ export const baseConst = (e: Expr): number | null =>
30
+ e.k === 'const'
31
+ ? e.value
32
+ : e.k === 'cast' && !(e.to.kind === 'ptr' && e.to.to.kind === 'struct')
33
+ ? baseConst(e.e)
34
+ : null;
35
+
36
+ /** the numeric address an expression IS, through any number of pointer casts */
37
+ export const addrConst = (e: Expr): number | null =>
38
+ e.k === 'const' ? e.value : e.k === 'cast' && e.to.kind === 'ptr' ? addrConst(e.e) : null;
39
+
40
+ /** the constant an ACCESS CHAIN is rooted at, through any cast, subscript and field selection */
41
+ export const rootConst = (e: Expr): number | null =>
42
+ e.k === 'const'
43
+ ? e.value
44
+ : e.k === 'cast'
45
+ ? rootConst(e.e)
46
+ : e.k === 'index' || e.k === 'field'
47
+ ? rootConst(e.base)
48
+ : null;
49
+
50
+ /** The WHOLE address an `index` access denotes, or null when any part of it is not constant. A
51
+ * `field` never resolves here: its offset is the struct's, which this module has no layout for. */
52
+ export function cellAddress(e: Expr): number | null {
53
+ if (e.k !== 'index' || e.lead !== undefined || e.idx.k !== 'const') {
54
+ return null; // a `lead` is a multidimensional global's leading dimension, not a numeric address
55
+ }
56
+ const base = baseConst(e.base);
57
+ return base === null ? null : base + e.idx.value * e.width;
58
+ }
59
+
60
+ /** is `a` inside the half-open range `w`? A null address, or no declared range, is never inside. */
61
+ export const inRange = (a: number | null, w?: readonly [number, number]): boolean =>
62
+ w !== undefined && a !== null && a >= w[0] && a < w[1];
package/src/l3/argbase.ts CHANGED
@@ -41,6 +41,7 @@ import { type IrType, T, scalarTypeForAccess } from '../ir/types';
41
41
  import type { Expr, SFn, Stmt } from './ast';
42
42
  import { mapExprChildren, stmtExprs } from './ast';
43
43
  import { nameAllocator } from './hoist';
44
+ import { declaredGlobals } from './storage';
44
45
 
45
46
  /** A base this pass may evaluate early: pure, and not something a store can change under us.
46
47
  *
@@ -103,7 +104,7 @@ function baseKey(n: Extract<Expr, { k: 'index' }>): string {
103
104
  * candidate at all rather than a duplicate of the primary).
104
105
  */
105
106
  export function materializeArgBases(sfn: SFn): SFn | null {
106
- const globals = new Set((sfn.globals ?? []).map((g) => g.name));
107
+ const globals = declaredGlobals(sfn);
107
108
  const fresh = nameAllocator(sfn);
108
109
  const newLocals: { name: string; type: IrType }[] = [];
109
110
  let fired = false;