@asmlift/core 0.5.0 → 0.7.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 (94) 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 +270 -171
  5. package/src/backend/cpp.ts +1 -0
  6. package/src/backend/pascal.ts +26 -12
  7. package/src/contracts.ts +243 -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 +78 -0
  14. package/src/ir/bits.ts +75 -0
  15. package/src/ir/core.ts +345 -2
  16. package/src/ir/opcodes.ts +176 -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/advance.ts +373 -0
  24. package/src/l3/argbase.ts +6 -5
  25. package/src/l3/ast.ts +510 -59
  26. package/src/l3/basecse.ts +686 -78
  27. package/src/l3/coalesce.ts +432 -46
  28. package/src/l3/dce.ts +31 -9
  29. package/src/l3/gates.ts +96 -1
  30. package/src/l3/hoist.ts +293 -14
  31. package/src/l3/homesplit.ts +285 -0
  32. package/src/l3/initfirst.ts +301 -0
  33. package/src/l3/inlinebase.ts +193 -0
  34. package/src/l3/mentions.ts +176 -0
  35. package/src/l3/mulfirst.ts +42 -0
  36. package/src/l3/nearbase.ts +152 -0
  37. package/src/l3/offmember.ts +371 -0
  38. package/src/l3/parkfirst.ts +96 -0
  39. package/src/l3/pollguard.ts +154 -0
  40. package/src/l3/ptrfield.ts +227 -0
  41. package/src/l3/regspell.ts +114 -89
  42. package/src/l3/reindex.ts +722 -80
  43. package/src/l3/scopebase.ts +649 -220
  44. package/src/l3/sinkinit.ts +40 -0
  45. package/src/l3/slotorder.ts +123 -0
  46. package/src/l3/storage.ts +48 -0
  47. package/src/l3/symbol-refs.ts +41 -8
  48. package/src/l3/tailmerge.ts +16 -1
  49. package/src/l3/typing.ts +198 -9
  50. package/src/l3/unmerge.ts +687 -0
  51. package/src/l3/unreduce.ts +971 -0
  52. package/src/l3/volatileptr.ts +207 -0
  53. package/src/l3/volatileval.ts +130 -0
  54. package/src/l3/volstore.ts +229 -0
  55. package/src/l3/zerosub.ts +62 -0
  56. package/src/pattern/engine.ts +239 -16
  57. package/src/pipeline.ts +173 -60
  58. package/src/proto.ts +112 -14
  59. package/src/raise/arrays.ts +6 -1
  60. package/src/raise/const.ts +203 -3
  61. package/src/raise/divpow2.ts +4 -4
  62. package/src/raise/extscale.ts +342 -0
  63. package/src/raise/globalshape.ts +1058 -0
  64. package/src/raise/gvn.ts +33 -18
  65. package/src/raise/latch.ts +126 -0
  66. package/src/raise/magicdiv.ts +2 -2
  67. package/src/raise/memberarrays.ts +594 -0
  68. package/src/raise/narrow.ts +124 -0
  69. package/src/raise/narrowlocal.ts +572 -0
  70. package/src/raise/paramwidth.ts +201 -0
  71. package/src/raise/pre-recovery.ts +169 -21
  72. package/src/raise/recover.ts +56 -23
  73. package/src/raise/retsink.ts +585 -19
  74. package/src/raise/shortcircuit.ts +1050 -89
  75. package/src/raise/struct-arrays.ts +19 -2
  76. package/src/raise/structs.ts +34 -4
  77. package/src/raise/tailsink.ts +126 -0
  78. package/src/rank-declare.ts +256 -0
  79. package/src/rank-variations.ts +760 -0
  80. package/src/rank.ts +2122 -326
  81. package/src/structure/analysis.ts +1398 -150
  82. package/src/structure/bitfields.ts +432 -0
  83. package/src/structure/globalaccess.ts +300 -0
  84. package/src/structure/hazards.ts +411 -20
  85. package/src/structure/loops.ts +2 -49
  86. package/src/structure/namecoalesce.ts +454 -0
  87. package/src/structure/structure.ts +3979 -612
  88. package/src/structure/switch-recover.ts +710 -145
  89. package/src/symbols.ts +188 -6
  90. package/src/target.ts +495 -32
  91. package/src/trace.ts +112 -33
  92. package/src/variation-definitions.ts +1540 -0
  93. package/src/variation-gates.ts +89 -0
  94. package/src/variation-tokens.ts +355 -0
package/src/symbols.ts CHANGED
@@ -52,9 +52,30 @@ export interface SymbolStructField {
52
52
  /** ARRAY field only: the element count (absent for a flexible array member, which declares a
53
53
  * stride but no bound) — types the synthesized `T name[n];` field decl */
54
54
  length?: number;
55
+ /** ARRAY field only: the RANK — the per-dimension extents, outermost first (`u8 x[6][8]` →
56
+ * `[6, 8]`), `null` for an unbounded one. {@link SymbolInfo.dims}'s law, one indirection down,
57
+ * and for the same reason: `length` is the PRODUCT of the dimensions, so it cannot say how many
58
+ * subscripts reach an ELEMENT. `->x[i]` on a `[6][8]` member is a ROW, and against the
59
+ * project's own header that is a type error — or, where the row address flows into an integer
60
+ * context, silently the wrong address.
61
+ *
62
+ * ABSENCE READS DIFFERENTLY HERE THAN IT DOES FOR A GLOBAL, and the difference is the whole
63
+ * point of the field. {@link arrayInnerExtents} may read an absent `SymbolInfo.dims` as rank 1
64
+ * because the ELF provider's capability gate refuses a @gba-kit/debug-info that cannot report
65
+ * a global's rank. No such gate covers a MEMBER's rank — a member simply omits the key both
66
+ * when the DWARF carries no subranges and when the package is too old to look — so absence
67
+ * here means "the map could not say". A consumer that must type-check against a FOREIGN
68
+ * declaration (the indexed member spelling, structure.ts's `pointeeElement`) refuses on
69
+ * absence; a consumer synthesizing its OWN declaration may still flatten, because its access
70
+ * and its declaration then agree by construction. That is why {@link structFieldInnerExtents}
71
+ * answers `[]` for absence and the refusal lives at the access site instead. */
72
+ dims?: (number | null)[];
55
73
  /** BITFIELD field only: the field's width in BITS. Its PRESENCE is what marks a field a
56
- * bitfield — `size` above stays the byte span its bits touch (the read width the compiler
57
- * uses), which is why the exact (offset,size) scalar-field rules must exclude it. The
74
+ * bitfield — `size` above stays the byte SPAN its bits touch, which is why the exact
75
+ * (offset,size) scalar-field rules must exclude it. The span is NOT the access the compiler
76
+ * uses: measured with the pinned agbcc, `u32 x : 8` at bit 12 spans 2 bytes and is reached by
77
+ * a WORD, `u32 a : 20` spans 3, and `u16 a : 2` spans 1 in a 2-byte container. A consumer
78
+ * needing the access width computes it from `bitOffset`/`bitWidth` (structure/bitfields.ts). The
58
79
  * provider only emits these for LITTLE-ENDIAN ELFs: both the extract equation the access
59
80
  * recognizer solves and the `u32 name : n` layout model the synthesis verifies are LE-GCC
60
81
  * semantics, so a big-endian map carries no bitfield members at all (today's behavior). */
@@ -90,7 +111,12 @@ export interface SymbolPointee {
90
111
 
91
112
  /** One declared type in a signature — width, signedness, pointer-ness. Deliberately the same
92
113
  * vocabulary a struct member uses, so a parameter and a field of the same C type describe
93
- * identically. `size: null` = the DWARF did not size it. */
114
+ * identically. `size: null` = the DWARF did not size it.
115
+ *
116
+ * NO POINTEE, and the absence is UPSTREAM's rather than a shape asmlift dropped:
117
+ * `@gba-kit/debug-info`'s `TypeFacts` — what a `FunctionSignature`'s params are made of — is
118
+ * exactly these three fields. {@link SymbolInfo.pointee} exists only for a symbol AT AN ADDRESS.
119
+ * Widening this is priced in docs/level-tower.md and pinned in test/param-pointee-variation.test.ts. */
94
120
  export interface SymbolTypeFacts {
95
121
  size: number | null;
96
122
  signed: boolean | null;
@@ -192,6 +218,28 @@ export function arrayInnerExtents(info: SymbolInfo): number[] | null {
192
218
  return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
193
219
  }
194
220
 
221
+ /** {@link arrayInnerExtents}, one indirection down: the INNER extents of an array MEMBER, shared
222
+ * by the access side (structure.ts's indexed member spelling) and the declaration side
223
+ * (symbolFieldType) so the two cannot disagree about a member's shape.
224
+ *
225
+ * `[]` is the rank-1 answer — one subscript, `u8 x[48];`, the spelling every map has had. It is
226
+ * ALSO what an absent `dims` answers, because the declaration synthesis that reads this renders
227
+ * the member itself and so cannot be wrong about it. A consumer type-checking against a header it
228
+ * did NOT write must test `f.dims === undefined` first and decline; see {@link
229
+ * SymbolStructField.dims} for why that asymmetry is real rather than a shortcut.
230
+ *
231
+ * Null means NO consistent pair is available (a stated rank with an unknown or non-positive inner
232
+ * extent, which neither a declaration nor a subscript can spell), and both sides honour it the
233
+ * same way: the declaration falls back to the flat member, the access to the cast form. */
234
+ export function structFieldInnerExtents(f: SymbolStructField): number[] | null {
235
+ const dims = isArrayField(f) ? f.dims : undefined;
236
+ if (dims === undefined || dims.length <= 1) {
237
+ return [];
238
+ }
239
+ const inner = dims.slice(1);
240
+ return inner.every((d) => typeof d === 'number' && d > 0) ? (inner as number[]) : null;
241
+ }
242
+
195
243
  /** address → symbols at that address; `[0]` is the provider's canonical pick. */
196
244
  export type SymbolMap = Map<number, SymbolInfo[]>;
197
245
 
@@ -202,6 +250,17 @@ export function isArrayField(f: SymbolStructField): boolean {
202
250
  return f.elemSize !== undefined;
203
251
  }
204
252
 
253
+ /** THE one test for "is this field a POINTER", and it is a test of TWO facts. The flag alone is
254
+ * not enough: {@link symbolFieldType} declares a pointer only at `size === 4`, so a `pointer`
255
+ * member of any other size declares as a scalar cell or a byte array — and a consumer trusting
256
+ * the flag alone would spell pointer arithmetic on a value the very declaration beside it calls a
257
+ * `u16`. The two answers must be the SAME answer, for the same reason declaredFields and the
258
+ * synthesis must: core reasoning about a member as something the declaration does not declare is
259
+ * non-compiling C. */
260
+ export function isPtrField(f: SymbolStructField): boolean {
261
+ return f.pointer === true && f.size === 4;
262
+ }
263
+
205
264
  /** THE one test for "is this field a bitfield" — the PRESENCE of `bitWidth` (see the field doc).
206
265
  * The exact (offset,size) scalar-field rules must exclude these: a 7-bit field whose bits span
207
266
  * 2 bytes carries `size: 2` and would otherwise match a plain u16 read at its offset. */
@@ -209,6 +268,13 @@ export function isBitfieldField(f: SymbolStructField): boolean {
209
268
  return f.bitWidth !== undefined;
210
269
  }
211
270
 
271
+ /** Does the map declare a bitfield member anywhere, in a symbol's own layout or its pointee's? */
272
+ export function declaresBitfields(symbols: SymbolMap): boolean {
273
+ return [...symbols.values()].some((infos) =>
274
+ infos.some((i) => [...(i.layout ?? []), ...(i.pointee?.layout ?? [])].some(isBitfieldField)),
275
+ );
276
+ }
277
+
212
278
  /** A layout member that {@link declaredFields} passed: sizable, and seated at an offset no
213
279
  * earlier member already covers. */
214
280
  export type DeclaredField = SymbolStructField & { size: number };
@@ -228,6 +294,31 @@ function wellFormedField(f: unknown): f is SymbolStructField {
228
294
  ) {
229
295
  return false;
230
296
  }
297
+ // `dims`, when stated, must be an array of positive extents or nulls whose product is the
298
+ // member's own `length` — three facts that contradict each other cannot all be trusted, exactly
299
+ // as `elemSize * length !== size` declines the layout below. A member with no `elemSize` is not
300
+ // an array and may not carry a rank at all.
301
+ if (m.dims !== undefined) {
302
+ if (
303
+ !Array.isArray(m.dims) ||
304
+ m.dims.length === 0 ||
305
+ m.elemSize === undefined ||
306
+ !m.dims.every((d) => d === null || (typeof d === 'number' && Number.isInteger(d) && d > 0))
307
+ ) {
308
+ return false;
309
+ }
310
+ // The product test has to hold over the NUMERIC extents alone, not only when every extent is
311
+ // numeric. A partly-null rank still constrains `length`: `[null, 5]` says the member's
312
+ // elements come in rows of five, so a `length` of 48 contradicts it exactly as `[6, 9]` and 48
313
+ // do — and symbolFieldType, which recovers a missing outer extent as `length / prod(inner)`,
314
+ // would spell that pair `u8 grid[9.6][5];`. A non-integral quotient is not a spelling defect
315
+ // to patch downstream: it is the same three-facts-disagree malformation, declined here.
316
+ const numeric = (m.dims as (number | null)[]).filter((d): d is number => typeof d === 'number');
317
+ const stated = numeric.reduce((a, b) => a * b, 1);
318
+ if (m.length !== undefined && (numeric.length === m.dims.length ? stated !== m.length : m.length % stated !== 0)) {
319
+ return false;
320
+ }
321
+ }
231
322
  // A bitfield's two facts must be present TOGETHER and internally consistent — a bitWidth with
232
323
  // no bitOffset (or bits outside the byte span `size` claims) leaves the field unseatable, so
233
324
  // the member is malformed and the layout declines whole like any other malformed member.
@@ -325,9 +416,29 @@ export function declaredFields(layout: SymbolStructField[] | undefined): Declare
325
416
  export function symbolFieldType(f: DeclaredField): IrType {
326
417
  if (isArrayField(f)) {
327
418
  const scalarElem = f.elemSigned !== undefined && (f.elemSize === 1 || f.elemSize === 2 || f.elemSize === 4);
328
- return scalarElem && f.length !== undefined && f.elemSize! * f.length === f.size
329
- ? T.array(T.int(f.elemSize! * 8, f.elemSigned!), f.length)
330
- : T.array(T.u(8), f.size);
419
+ if (!(scalarElem && f.length !== undefined && f.elemSize! * f.length === f.size)) {
420
+ return T.array(T.u(8), f.size);
421
+ }
422
+ // The declared RANK, when the map states one: `u8 x[6][8]` rather than `u8 x[48]`. Same
423
+ // object and same size either way — but a foreign header declares the member ONE of those two
424
+ // ways, and an access spelled against the other does not type-check (see
425
+ // SymbolStructField.dims). An unspellable rank (null) keeps the flat member.
426
+ //
427
+ // The outer extent is RECOVERED as `length / prod(inner)` — the map may leave the outermost
428
+ // subrange null where it knows the total. wellFormedField already declines a `length` the
429
+ // inner extents do not divide, so the quotient is a whole number for any layout that reached
430
+ // here; it is re-checked rather than assumed because this function is exported and a
431
+ // fractional extent prints `u8 grid[9.6][5];`, which is not C. The flat member is the honest
432
+ // fallback, the same one an unspellable inner extent takes.
433
+ const inner = structFieldInnerExtents(f);
434
+ const elem = T.int(f.elemSize! * 8, f.elemSigned!);
435
+ if (inner === null || inner.length === 0) {
436
+ return T.array(elem, f.length);
437
+ }
438
+ const outer = f.length / inner.reduce((a, b) => a * b, 1);
439
+ return Number.isInteger(outer) && outer > 0
440
+ ? [outer, ...inner].reverse().reduce<IrType>((t, n) => T.array(t, n), elem)
441
+ : T.array(elem, f.length);
331
442
  }
332
443
  if (f.pointer && f.size === 4) {
333
444
  // The pointee width is byte-load-bearing: arithmetic on the loaded pointer scales by it, so
@@ -349,6 +460,32 @@ export function symbolFieldType(f: DeclaredField): IrType {
349
460
  }
350
461
  return T.array(T.u(8), f.size);
351
462
  }
463
+
464
+ /** The array shape the DECLARATION actually spells for a member — the extents outermost first and
465
+ * the element type left at the bottom — read back out of {@link symbolFieldType} rather than
466
+ * re-derived from the map's own facts. It answers whatever that declaration is, which for a
467
+ * member with no spellable element type is the flat `u8 name[size]`: extents `[size]` over `u8`,
468
+ * not the empty shape.
469
+ *
470
+ * Exists because "how many subscripts does this member take, and of what" was being answered
471
+ * twice: once here (which needs `length` and a base-type `elemSigned`, and declares the flat byte
472
+ * array without either) and once straight off `dims` at the access site. `dims` and `length` are
473
+ * INDEPENDENT facts — a flexible array member states a stride and no bound (see
474
+ * SymbolStructField.length), and a member whose OUTERMOST subrange is unbounded (`u8 data[][8]`,
475
+ * legal C) reaches a map as `dims: [null, 8]` with no `length` at all. The declaration then spells
476
+ * `u8 grid[48];` while an access reading the rank off `dims` spells `gPtr->grid[0][a0];`, which is
477
+ * not C (`subscripted value is not an array, pointer, or vector`). An access side that asks THIS
478
+ * question cannot diverge from the declaration whatever gate symbolFieldType grows next. */
479
+ export function declaredArrayShape(f: DeclaredField): { extents: number[]; elem: IrType } {
480
+ let t = symbolFieldType(f);
481
+ const extents: number[] = [];
482
+ while (t.kind === 'array') {
483
+ extents.push(t.count);
484
+ t = t.elem;
485
+ }
486
+ return { extents, elem: t };
487
+ }
488
+
352
489
  /** A 4-byte member/scalar with NO base-type signedness is the enum idiom — C89 says int. */
353
490
  export const ENUM_IS_SIGNED = true;
354
491
 
@@ -539,3 +676,48 @@ export function symbolMapFromJson(obj: Record<string, SymbolInfo[]>): SymbolMap
539
676
  }
540
677
  return map;
541
678
  }
679
+
680
+ const HEX_KEY = /^(0x)?[0-9a-fA-F]+$/;
681
+
682
+ /** STRUCTURAL VALIDATION of the vendored-map JSON, in core because BOTH readers of that format
683
+ * need it and a second hand-rolled copy is how they come to disagree about what a map is: the
684
+ * webapp's Symbols pane (which degrades — a bad map is reported and the run proceeds without
685
+ * one) and the cli's `tools.asmlift.symbols` (which is LOUD — an explicit config that failed to
686
+ * load must never read like a row that never had a map).
687
+ *
688
+ * `symbolMapFromJson` alone cannot serve either: it is a total function over `Object.entries`,
689
+ * so `[]` and `{}` and `{"nope": []}` all yield an EMPTY map and no error at all. An empty map
690
+ * is exactly what a map-less run holds, and the two produce different source — so the shapes
691
+ * that silently reduce to it are the ones this checks. Only the load-bearing minimum is
692
+ * validated (hex keys, non-empty arrays, string `name`, `kind` ∈ code|data); the declaration
693
+ * SHAPE fields are typed by this module and fail soft downstream.
694
+ *
695
+ * Emptiness is REPORTED, not judged: a caller that tolerates an empty map reads `map.size`
696
+ * itself. */
697
+ export function parseSymbolMapJson(obj: unknown): { map: SymbolMap } | { error: string } {
698
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
699
+ return {
700
+ error: 'expected an object of hex addresses, e.g. {"0x03001234": [{"name": "gCounter", "kind": "data"}]}',
701
+ };
702
+ }
703
+ for (const [key, infos] of Object.entries(obj)) {
704
+ if (!HEX_KEY.test(key)) {
705
+ return { error: `"${key}" is not a hex address key (e.g. "0x03001234")` };
706
+ }
707
+ if (!Array.isArray(infos) || infos.length === 0) {
708
+ return { error: `"${key}" must map to a non-empty array of symbols` };
709
+ }
710
+ for (const info of infos as unknown[]) {
711
+ const si = info as Partial<SymbolInfo> | null;
712
+ if (
713
+ typeof si !== 'object' ||
714
+ si === null ||
715
+ typeof si.name !== 'string' ||
716
+ (si.kind !== 'code' && si.kind !== 'data')
717
+ ) {
718
+ return { error: `"${key}": every symbol needs a string "name" and "kind": "code" | "data"` };
719
+ }
720
+ }
721
+ }
722
+ return { map: symbolMapFromJson(obj as Record<string, SymbolInfo[]>) };
723
+ }