@asmlift/core 0.4.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 (87) 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 -164
  5. package/src/backend/cpp.ts +1 -0
  6. package/src/backend/pascal.ts +26 -12
  7. package/src/contracts.ts +341 -22
  8. package/src/declare.ts +41 -4
  9. package/src/frontend/mips.ts +24 -6
  10. package/src/frontend/opaque.ts +31 -18
  11. package/src/frontend/ppc.ts +54 -7
  12. package/src/frontend/ssa.ts +632 -13
  13. package/src/frontend/thumb.ts +2786 -286
  14. package/src/ir/alias.ts +129 -0
  15. package/src/ir/bits.ts +75 -0
  16. package/src/ir/core.ts +337 -2
  17. package/src/ir/opcodes.ts +156 -27
  18. package/src/ir/parse.ts +19 -2
  19. package/src/ir/print.ts +27 -2
  20. package/src/ir/simplify.ts +190 -3
  21. package/src/ir/struct-names.ts +42 -0
  22. package/src/ir/verify.ts +43 -49
  23. package/src/l3/address.ts +62 -0
  24. package/src/l3/argbase.ts +8 -2
  25. package/src/l3/ast.ts +464 -49
  26. package/src/l3/basecse.ts +709 -88
  27. package/src/l3/coalesce.ts +521 -66
  28. package/src/l3/dce.ts +54 -19
  29. package/src/l3/gates.ts +88 -0
  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 +113 -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 +110 -85
  42. package/src/l3/reindex.ts +715 -78
  43. package/src/l3/scopebase.ts +649 -219
  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 +23 -4
  49. package/src/l3/typing.ts +198 -9
  50. package/src/l3/unmerge.ts +263 -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 +236 -13
  57. package/src/pipeline.ts +206 -49
  58. package/src/proto.ts +112 -14
  59. package/src/raise/arrays.ts +6 -1
  60. package/src/raise/divpow2.ts +4 -3
  61. package/src/raise/globalshape.ts +1038 -0
  62. package/src/raise/gvn.ts +44 -19
  63. package/src/raise/latch.ts +126 -0
  64. package/src/raise/memberarrays.ts +594 -0
  65. package/src/raise/narrow.ts +124 -0
  66. package/src/raise/narrowlocal.ts +556 -0
  67. package/src/raise/paramwidth.ts +179 -0
  68. package/src/raise/pre-recovery.ts +101 -16
  69. package/src/raise/recover.ts +56 -23
  70. package/src/raise/retsink.ts +215 -14
  71. package/src/raise/shortcircuit.ts +477 -79
  72. package/src/raise/struct-arrays.ts +21 -3
  73. package/src/raise/structs.ts +61 -3
  74. package/src/rank-axes.ts +630 -0
  75. package/src/rank-declare.ts +256 -0
  76. package/src/rank.ts +1726 -251
  77. package/src/structure/analysis.ts +1516 -220
  78. package/src/structure/bitfields.ts +332 -0
  79. package/src/structure/globalaccess.ts +274 -0
  80. package/src/structure/hazards.ts +411 -20
  81. package/src/structure/loops.ts +2 -49
  82. package/src/structure/namecoalesce.ts +435 -0
  83. package/src/structure/structure.ts +2850 -533
  84. package/src/structure/switch-recover.ts +688 -147
  85. package/src/symbols.ts +62 -1
  86. package/src/target.ts +367 -24
  87. package/src/trace.ts +111 -32
@@ -18,7 +18,9 @@
18
18
  // UNAMBIGUOUS here (the scaled operand is the index, read from the machine code) — the unscaled
19
19
  // `add(x, y)` byte form stays out of scope (genuinely ambiguous without types).
20
20
  import { Fn, Op, Value, defOpMap, mkOp } from '../ir/core';
21
+ import { nextStructIndex } from '../ir/struct-names';
21
22
  import { IrType, StructField, T, scalarTypeForAccess } from '../ir/types';
23
+ import { collectStructs } from './structs';
22
24
 
23
25
  interface Scaled {
24
26
  base: Value;
@@ -87,12 +89,22 @@ function withPadding(dataFields: StructField[], stride: number): StructField[] {
87
89
  * rematerializes the same element address (several `add(base, i*stride)` ops for one logical
88
90
  * array), and recovering them one-by-one would let the first claim the base and force its
89
91
  * twins to decline — a mixed spelling that is worse than either pure form (found live on
90
- * pokeemerald:GetGender, whose address is materialized twice). A base whose element pointers
92
+ * pokeemerald:GetGenderFromSpeciesAndPersonality, whose address is materialized twice). A base
93
+ * whose element pointers
91
94
  * disagree on stride declines entirely: two strides over one base is a reinterpreted view or
92
95
  * a 2D layout, genuinely ambiguous — decline over guess. */
93
96
  export function recognizeStructArrays(fn: Fn): number {
94
97
  const defs = defOpMap(fn);
95
98
  let count = 0;
99
+ // The NAME index is not the recognition count: `count` is this function's return value — how
100
+ // many groups it recognized — and seeding it would report the wrong number. The index starts
101
+ // past every `Elem<N>` the graph already mentions; over the corpus that scan returns 0 every
102
+ // time, because this pass runs once per function and nothing else mints the prefix, and
103
+ // ir/struct-names.ts states why the computed 0 is kept over the assumed one.
104
+ let name = nextStructIndex(
105
+ [...collectStructs(fn)].map((s) => s.name),
106
+ 'Elem',
107
+ );
96
108
 
97
109
  // group candidate element pointers by base, tracking each add's own index and stride
98
110
  const byBase = new Map<Value, { add: Op; index: Value; stride: number }[]>();
@@ -221,7 +233,7 @@ export function recognizeStructArrays(fn: Fn): number {
221
233
  type: scalarTypeForAccess(width, loadSigned ?? width === 4),
222
234
  name: `field_${off}`,
223
235
  }));
224
- const elemStruct = T.struct(`Elem${count}`, withPadding(dataFields, stride), stride);
236
+ const elemStruct = T.struct(`Elem${name++}`, withPadding(dataFields, stride), stride);
225
237
  base.type = T.ptr(elemStruct);
226
238
 
227
239
  // Rewrite each field load/store into an aload/astore carrying base, ITS elem's index,
@@ -241,7 +253,13 @@ export function recognizeStructArrays(fn: Fn): number {
241
253
  bb.ops[i] = mkOp('aload', {
242
254
  operands: [base, index],
243
255
  results: [op.results[0]],
244
- attrs: { elemSize: stride, signed: op.attrs.signed as boolean, fieldOff: op.attrs.off as number },
256
+ // listOrder rides along: an ldmia-expanded load keeps its stream-order caveat as an aload
257
+ attrs: {
258
+ elemSize: stride,
259
+ signed: op.attrs.signed as boolean,
260
+ fieldOff: op.attrs.off as number,
261
+ ...(op.attrs.listOrder === true && { listOrder: true }),
262
+ },
245
263
  });
246
264
  } else if (op.opcode === 'store') {
247
265
  bb.ops[i] = mkOp('astore', {
@@ -22,8 +22,22 @@
22
22
  // base @ {off8 w4} -> array (single aligned access — no struct evidence)
23
23
  // base @ aload(index) -> array (variable index — untouched)
24
24
  //
25
- // This recovery is BYTE-NEUTRAL — `->field_N` and `[idx]` compile identically, so it is a
26
- // representation upgrade driven by access evidence, not a scored lever. GAPS between accessed
25
+ // This recovery is USUALLY BYTE-NEUTRAL — `->field_N` and `[idx]` mostly compile identically, so it
26
+ // is a representation upgrade driven by access evidence rather than a scored lever. TWO
27
+ // MEASUREMENTS SAY "USUALLY" IS THE RIGHT WORD, and both were made on agbcc against a real target
28
+ // object, each pair differing in ONE token:
29
+ // • THE SPELLING. `synthetic:dmanest`'s reference compiles from `((struct Elem0 *)K)[a1].field_4`
30
+ // to a byte-exact match and from `((s32 *)((a1 << 3) + K))[1]` to a 2-point diff — a
31
+ // COMPONENT_REF keeps the offset in the load displacement, an index folds it into the pool
32
+ // literal. The row's own dataset entry carries the reproduction.
33
+ // • THE FIELD TYPE, which this file assigns from the ACCESS WIDTH alone. A word field declared
34
+ // `void *` rather than `s32` changes agbcc's alias set and lets a loop-invariant load leave the
35
+ // loop: `synthetic:dmaptrsrc` matches with the pointer declaration and diffs by 35 without it
36
+ // (its own fan: `/vol-store/unreduce/ptr-field` 0, `/vol-store/unreduce` 35).
37
+ // That is what `l3/ptrfield.ts` offers as a differ-ranked lever.
38
+ // So the neutrality claim is CONDITIONAL, and nothing here says on what. Until it does, read it as
39
+ // "no candidate is enumerated for this axis", not as "the differ could not referee one" — the
40
+ // second reading is the one both measurements above falsify. GAPS between accessed
27
41
  // offsets (unaccessed leading/interior fields) are filled with `u8[N]` PAD fields so the declared
28
42
  // struct reproduces the observed offsets byte-for-byte and is self-describing (the same
29
43
  // discipline raise/struct-arrays.ts withPadding uses). Each accessed field must still be
@@ -31,6 +45,7 @@
31
45
  // at an offset natural C alignment could not place it at) is rejected LOUD, as is an
32
46
  // overlap/union.
33
47
  import { Fn, Op, Value } from '../ir/core';
48
+ import { nextStructIndex } from '../ir/struct-names';
34
49
  import { IrType, StructField, T, scalarTypeForAccess } from '../ir/types';
35
50
  import type { StructType } from '../l3/ast';
36
51
  import { RaiseUnsupportedError } from './errors';
@@ -178,6 +193,21 @@ export function recognizeStructs(fn: Fn): number {
178
193
  }
179
194
  }
180
195
 
196
+ // Which values are the address of a NAMED global (`gaddr`)? Consulted only when synthesis
197
+ // DECLINES: see the catch below.
198
+ const namedGlobal = new Set<Value>();
199
+ for (const b of fn.blocks) {
200
+ for (const op of b.ops as Op[]) {
201
+ if (op.opcode === 'gaddr') {
202
+ namedGlobal.add(op.results[0]);
203
+ }
204
+ }
205
+ }
206
+
207
+ // The NAME counter is seeded from the names already in the graph, not from this pass's own
208
+ // success count: raise/memberarrays.ts runs first and mints `Struct<N>` types of its own, and two
209
+ // different layouts under one name would leave `collectStructs` declaring only one of them.
210
+ let name = firstFreeStructIndex(fn);
181
211
  let count = 0;
182
212
  for (const base of order) {
183
213
  if (arrayBases.has(base)) {
@@ -186,16 +216,44 @@ export function recognizeStructs(fn: Fn): number {
186
216
  if (base.type.kind !== 'unknown') {
187
217
  continue;
188
218
  } // already typed (not a bare recovery target)
219
+
189
220
  const accesses = accessesOf.get(base)!;
190
221
  if (isArray(accesses)) {
191
222
  continue;
192
223
  } // uniform stride / single aligned access → array
193
- base.type = T.ptr(buildStruct(`Struct${count}`, accesses));
224
+ try {
225
+ base.type = T.ptr(buildStruct(`Struct${name}`, accesses));
226
+ } catch (e) {
227
+ // A NAMED global whose accesses synthesis cannot reconcile is not a reason to decline the
228
+ // function: its declaration belongs to the project's own headers, and its constant-offset
229
+ // accesses render at L3 through the symbol context (member spelling when the map knows the
230
+ // layout, the honest cast spelling when it does not). The inhabitant is agbcc FUSING two
231
+ // adjacent u8 compares into one ldrh — `s.level == 8 && s.world == 6` reads offset 12 at
232
+ // widths 1 AND 2, which is not a union, just two spellings of declared bytes. An ANONYMOUS
233
+ // base (a loaded pointer, a parameter) has no other source of truth, so for it the decline
234
+ // stands exactly as before — this catch narrows nothing for the shapes that already worked,
235
+ // because a base synthesis succeeds on takes the same path it always took.
236
+ if (e instanceof RaiseUnsupportedError && namedGlobal.has(base)) {
237
+ continue;
238
+ }
239
+ throw e;
240
+ }
241
+ name++;
194
242
  count++;
195
243
  }
196
244
  return count;
197
245
  }
198
246
 
247
+ /** The next `Struct<N>` index past every one this function's graph already uses — the shared name
248
+ * allocator for the two passes that synthesize a struct pointee. The scan itself is
249
+ * `ir/struct-names.ts`, which the three struct minters share; this names the prefix. */
250
+ export function firstFreeStructIndex(fn: Fn): number {
251
+ return nextStructIndex(
252
+ [...collectStructs(fn)].map((s) => s.name),
253
+ 'Struct',
254
+ );
255
+ }
256
+
199
257
  /** The distinct struct types this function's L2 GRAPH mentions (unwrapping struct pointers on every
200
258
  * value), deduped by name and sorted, for the backend to declare above the function.
201
259
  *