@asmlift/core 0.8.1 → 0.9.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 (105) hide show
  1. package/README.md +34 -30
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +248 -0
  4. package/src/backend/c.ts +5 -2
  5. package/src/backend/cfamily.ts +236 -37
  6. package/src/backend/cpp.ts +114 -9
  7. package/src/backend/pascal.ts +39 -0
  8. package/src/cdecl/constant.ts +59 -0
  9. package/src/cdecl/lex.ts +209 -0
  10. package/src/cdecl/parse.ts +975 -0
  11. package/src/cdecl/spell.ts +69 -0
  12. package/src/codegen-flags.ts +38 -6
  13. package/src/compiler-diagnostics.ts +194 -0
  14. package/src/contracts.ts +615 -79
  15. package/src/declare.ts +75 -5
  16. package/src/frontend/asmdata.ts +19 -5
  17. package/src/frontend/call-plan.ts +686 -0
  18. package/src/frontend/device-pins.ts +277 -0
  19. package/src/frontend/errors.ts +5 -0
  20. package/src/frontend/flags-edge.ts +101 -0
  21. package/src/frontend/fpu.ts +167 -0
  22. package/src/frontend/frame-objects.ts +2131 -0
  23. package/src/frontend/high-half.ts +2 -1
  24. package/src/frontend/local-object.ts +490 -0
  25. package/src/frontend/mips.ts +363 -40
  26. package/src/frontend/opaque.ts +97 -18
  27. package/src/frontend/ppc.ts +520 -129
  28. package/src/frontend/reloc-symbol.ts +94 -13
  29. package/src/frontend/splat.ts +108 -5
  30. package/src/frontend/ssa.ts +669 -76
  31. package/src/frontend/stackargs.ts +120 -39
  32. package/src/frontend/thumb.ts +2764 -2018
  33. package/src/ir/alias.ts +29 -0
  34. package/src/ir/core.ts +101 -3
  35. package/src/ir/discipline.ts +248 -0
  36. package/src/ir/float-bits.ts +47 -0
  37. package/src/ir/opcodes.ts +84 -112
  38. package/src/ir/parse.ts +1 -0
  39. package/src/ir/types.ts +118 -2
  40. package/src/ir/verify.ts +150 -3
  41. package/src/l3/address.ts +5 -0
  42. package/src/l3/argcopy.ts +38 -6
  43. package/src/l3/ast.ts +186 -14
  44. package/src/l3/coalesce.ts +21 -13
  45. package/src/l3/dce.ts +32 -10
  46. package/src/l3/gates.ts +9 -3
  47. package/src/l3/hoist.ts +8 -7
  48. package/src/l3/initfirst.ts +27 -9
  49. package/src/l3/intcell.ts +97 -0
  50. package/src/l3/mentions.ts +10 -4
  51. package/src/l3/narrowdecl.ts +218 -0
  52. package/src/l3/nearbase.ts +10 -5
  53. package/src/l3/parkfirst.ts +8 -5
  54. package/src/l3/pollguard.ts +3 -2
  55. package/src/l3/ptrcell.ts +357 -0
  56. package/src/l3/regspell.ts +17 -3
  57. package/src/l3/reindex.ts +10 -7
  58. package/src/l3/symbol-refs.ts +104 -23
  59. package/src/l3/tailmerge.ts +6 -19
  60. package/src/l3/typing.ts +137 -25
  61. package/src/l3/unreduce.ts +4 -4
  62. package/src/l3/volstore.ts +96 -7
  63. package/src/mangle.ts +11 -1
  64. package/src/pattern/engine.ts +19 -16
  65. package/src/pipeline.ts +57 -12
  66. package/src/proto-context.ts +788 -0
  67. package/src/proto.ts +689 -50
  68. package/src/raise/arrays.ts +4 -3
  69. package/src/raise/declared-volatile.ts +131 -0
  70. package/src/raise/divpow2.ts +2 -2
  71. package/src/raise/errors.ts +17 -0
  72. package/src/raise/floathelpers.ts +243 -0
  73. package/src/raise/globalshape.ts +11 -5
  74. package/src/raise/gvn.ts +26 -5
  75. package/src/raise/narrow.ts +29 -2
  76. package/src/raise/narrowlocal.ts +11 -6
  77. package/src/raise/offsetnames.ts +510 -0
  78. package/src/raise/pairparams.ts +81 -0
  79. package/src/raise/paramwidth.ts +7 -0
  80. package/src/raise/pre-recovery.ts +78 -4
  81. package/src/raise/recover.ts +14 -10
  82. package/src/raise/retsink.ts +1 -1
  83. package/src/raise/shortcircuit.ts +41 -19
  84. package/src/raise/softdiv.ts +21 -23
  85. package/src/raise/struct-arrays.ts +5 -4
  86. package/src/raise/structs.ts +311 -98
  87. package/src/raise/truncload.ts +366 -0
  88. package/src/raise/widehelpers.ts +106 -0
  89. package/src/rank-declare.ts +30 -5
  90. package/src/rank-variations.ts +36 -8
  91. package/src/rank.ts +267 -79
  92. package/src/runtime-helpers.ts +287 -0
  93. package/src/stillborn.ts +289 -0
  94. package/src/structure/analysis.ts +1285 -448
  95. package/src/structure/hazards.ts +633 -58
  96. package/src/structure/local-statics.ts +381 -0
  97. package/src/structure/namecoalesce.ts +1 -1
  98. package/src/structure/redundant-test.ts +99 -0
  99. package/src/structure/structure.ts +1772 -395
  100. package/src/structure/switch-recover.ts +159 -78
  101. package/src/symbols.ts +38 -3
  102. package/src/target.ts +662 -59
  103. package/src/trace.ts +44 -24
  104. package/src/variation-definitions.ts +119 -1
  105. package/src/variation-tokens.ts +20 -6
@@ -50,13 +50,70 @@ export interface OpaquePolicy {
50
50
  * destination — so there is nothing to degrade and the instruction is REFUSED, not skipped
51
51
  * (`teq zero, zero` is a trap). Default: nothing is zero. */
52
52
  isZero?: (r: string) => boolean;
53
+ /** The FPU's register operands in this ISA, and the FPU's ALONE — the message this produces
54
+ * names the floating-point register file in so many words, so a second unmodelled file (MIPS
55
+ * CP0, `hi`/`lo`, PowerPC's condition registers and SPRs) needs its own field and its own
56
+ * sentence rather than one more alternative here. An unmodelled instruction touching one is
57
+ * refused with a message naming that file, because the missing capability is the FILE, not the
58
+ * instruction: there is no value kind to hold what it computes, no ABI home to receive it, and
59
+ * no C spelling to print it. `docs/floating-point.md` measures the gap and prices the layers.
60
+ *
61
+ * Tested against EVERY operand, not just `ops[0]`, and the two shapes that force it are on
62
+ * opposite sides: `fcmpo cr0,f1,f2` has a destination `isReg` REJECTS (a condition register) and
63
+ * `mfc1 v0,$f12` one it ACCEPTS. A destination-only test files the first under "no register
64
+ * destination to degrade" and lets the second fabricate an opaque whose sources silently drop
65
+ * the FP operand it actually read. Both are floating point and neither is about the destination.
66
+ *
67
+ * IT MUST COVER EVERY DIALECT THE FRONTEND READS AND EVERY SPELLING EACH OF THEM USES, because
68
+ * a frontend can be fed more than one name for the same register: MIPS objdump numbers the file
69
+ * (`$f12`), the Splat trees use o32 ABI names (`$ft2`), and those names take a trailing `f` for
70
+ * the odd half of a double-precision pair (`$ft0f`) — so a pattern anchored on a final digit
71
+ * covers two of the three. Every miss is SILENT in the same way: the reader strips the sigil
72
+ * off a token it did not recognise, `isMipsReg` ACCEPTS the bare result, and the gap ends as an
73
+ * opaque on a register in a file nothing models. MIPS answers this with ONE predicate — the
74
+ * exported `MIPS_FP_REG` in `frontend/splat.ts`, which the reader and the frontend both use —
75
+ * because which tokens keep their sigil and which tokens are the FPU's are two halves of one
76
+ * decision, and two copies of it can disagree silently in either direction.
77
+ *
78
+ * A CONTROL TRANSFER IS NOT THIS MODULE'S, even when the FPU is what it is missing. MIPS
79
+ * `bc1t`/`bc1f` test an FP condition code, and PowerPC branch targets are bare lower-case hex
80
+ * that PowerPC's own `fpReg` matches — but a branch has no destination to degrade, so nothing
81
+ * here could answer one, and both frontends refuse unmodelled control flow in a whole-function
82
+ * pre-pass before a block is filled. The report keeps them as their own class for the same
83
+ * reason (`fp-cond-branch` in `apps/web/src/pages/benchmark/lib/declines.ts`): the missing file
84
+ * is shared, the mechanism is not.
85
+ *
86
+ * It is consulted FIRST, ahead of `storeClass`, so `swc1`/`stfs` are named by the file they move
87
+ * rather than by the fact that they move it to memory. That ordering is what the web report's
88
+ * decline table already assumes, and it could only assume it by re-deriving "is this floating
89
+ * point?" from a list of mnemonics this module never told it.
90
+ *
91
+ * REQUIRED, and `null` is the way to say "this ISA has no FPU" — an optional refusal is one the
92
+ * next frontend switches off by forgetting it, and the symptom is the message this field exists
93
+ * to remove. */
94
+ fpReg: RegExp | null;
95
+ /** FPU instructions that name no `fpReg` operand at all, matched on the MNEMONIC because there
96
+ * is nothing else to match: they address the FPU's CONTROL register, which is spelled in the
97
+ * ISA's OTHER namespace — MIPS `cfc1 rt,$31` / `ctc1 rt,$31` take a GPR and a coprocessor
98
+ * control number, PowerPC's `mtfsfi`/`mtfsb0`/`mtfsb1`/`mcrfs` take a field number and a
99
+ * condition register. A register-name predicate cannot see them, so without this they fall to
100
+ * the general arms and are filed as the generic opaque gap — and `ctc1`'s `ops[0]` is the
101
+ * instruction's SOURCE, so an opaque would be fabricated on a register it only reads.
102
+ *
103
+ * The FPU moves that DO name a data register (`mfc1`, `mtc1`, `mffs`, `mtfsf`) are `fpReg`'s,
104
+ * not this one's. Same message phrase either way, because it is the same missing capability.
105
+ *
106
+ * REQUIRED for the same reason as `fpReg`; `null` says the ISA has no FPU control register to
107
+ * reach. */
108
+ fpControl: RegExp | null;
53
109
  /** Mnemonics that WRITE MEMORY in this ISA: the "no register destination ⇒ safe to fall
54
110
  * through" premise is FALSE for stores — skipping one silently deletes the write (Thumb
55
111
  * `stmia r0!, {…}`), and a store whose FIRST token is a register (MIPS `swl rt, off(base)`)
56
112
  * would otherwise fabricate an opaque write to what is actually a SOURCE. An unmodelled
57
- * instruction matching this pattern throws instead. Default: none (a frontend that models
58
- * every store may omit it, but registered frontends are held to supplying one). */
59
- storeClass?: RegExp;
113
+ * instruction matching this pattern throws instead. REQUIRED, with `null` for a frontend that
114
+ * models every store in its ISA — held to it by the type, because the sentence that said
115
+ * "registered frontends are held to supplying one" was held to it by nothing. */
116
+ storeClass: RegExp | null;
60
117
  /** attribution for thrown declines: the function being lifted (optionally "+ site"). */
61
118
  context?: string;
62
119
  /** How to SPELL the mnemonic in messages, when that differs from the name used to classify it.
@@ -80,10 +137,14 @@ export interface OpaqueDest {
80
137
 
81
138
  /** Decide the opaque destination + register source operands for an unmodelled instruction
82
139
  * `mnemonic` with operand list `ops` (operands in objdump order — destination first). Returns
83
- * `null` only for a policy.skipSafe mnemonic. An unmodelled STORE-CLASS instruction
84
- * (policy.storeClass) throws loud (a skipped memory write is a silent miscompile), and so does any
85
- * instruction with no degradable destination: with no register to carry the live-`?` sentinel,
86
- * skipping would silently delete a side effect (swi/syscall/sync/cache).
140
+ * `null` only for a policy.skipSafe mnemonic. Three arms throw instead, in this order: an
141
+ * instruction that touches the FLOATING-POINT unit — naming one of its registers (policy.fpReg)
142
+ * or, for the control-register moves that name none, matching policy.fpControl — is refused
143
+ * first, because the file is the capability and the instruction is only where it surfaced; an
144
+ * unmodelled STORE-CLASS instruction (policy.storeClass) throws next (a skipped memory write is a
145
+ * silent miscompile); and so does any instruction with no degradable destination: with no register
146
+ * to carry the live-`?` sentinel, skipping would silently delete a side effect (swi/syscall/sync/
147
+ * cache).
87
148
  *
88
149
  * When non-null, the caller MUST emit an `opaque` op that writes `dst` and consumes `srcRegs`
89
150
  * (read through the frontend's own SSA). It reaches structuring as the sentinel `?` and trips
@@ -97,23 +158,42 @@ export interface OpaqueDest {
97
158
  * So `skipSafe`, a short per-ISA list of provably transparent mnemonics, is the only way an
98
159
  * unmodelled instruction leaves no trace.
99
160
  *
100
- * `storeClass` is not load-bearing for soundness — a missed store degrades loudly like anything
101
- * else — but it throws naming the memory write instead of an unresolvable value, and it catches
102
- * the shape where `ops[0]` is a SOURCE (MIPS `swl rt, off(base)`) before a `dst` is fabricated
103
- * from it. It and `skipSafe` match case-INSENSITIVELY: mnemonic case is a property of the
104
- * disassembler, not of the instruction. */
161
+ * None of `fpReg`, `fpControl` and `storeClass` is load-bearing for SOUNDNESS — every instruction
162
+ * that reaches this function declines whatever arm takes it, because an `opaque` carries
163
+ * `effects: true` and reaches `assertResolved` whether or not anything reads it. What they buy is
164
+ * WHICH capability the decline names, and that is the whole content of a decline. Two of them buy
165
+ * one thing more: they catch the shapes where `ops[0]` is a SOURCE (MIPS `swl rt, off(base)`,
166
+ * `ctc1 rt, $31`) before a `dst` is fabricated from a register the instruction only reads.
167
+ * They and `skipSafe` match case-INSENSITIVELY: mnemonic case is a property of the disassembler,
168
+ * not of the instruction. */
105
169
  export function opaqueDest(mnemonic: string, ops: string[], policy: OpaquePolicy): OpaqueDest | null {
106
170
  const shown = policy.display ?? mnemonic;
171
+ const norm = policy.normalize ?? ((s) => s);
172
+ // `context` names the function (and, where the ISA has addresses, the site) — every message
173
+ // below lands verbatim in annotate-mode stub headers, where an un-attributed decline is
174
+ // unactionable in a multi-function run.
175
+ const where = policy.context ? `cannot lift '${policy.context}': ` : '';
176
+ const fpReg = policy.fpReg;
177
+ // DEDUPED: `fadds f1,f1,f2` names two registers, not three, and the message is read by a human
178
+ // asking which file is missing.
179
+ const fp = fpReg ? [...new Set(ops.map(norm).filter((o) => fpReg.test(o)))] : [];
180
+ if (fp.length > 0) {
181
+ throw new FrontendUnsupportedError(
182
+ `${where}unmodelled floating-point instruction '${shown}' — it uses the floating-point ` +
183
+ `register file (${fp.join(', ')}), which this frontend does not model`,
184
+ );
185
+ }
186
+ if (policy.fpControl?.test(mnemonic)) {
187
+ throw new FrontendUnsupportedError(
188
+ `${where}unmodelled floating-point instruction '${shown}' — it uses the floating-point ` +
189
+ `control register, which this frontend does not model`,
190
+ );
191
+ }
107
192
  if (policy.storeClass?.test(mnemonic)) {
108
- // `context` names the function (and, where the ISA has addresses, the site) — this message
109
- // lands verbatim in annotate-mode stub headers, where an un-attributed decline is
110
- // unactionable in a multi-function run.
111
- const where = policy.context ? `cannot lift '${policy.context}': ` : '';
112
193
  throw new FrontendUnsupportedError(
113
194
  `${where}unmodelled store-class instruction '${shown}' — a memory write cannot be skipped or degraded to a register opaque`,
114
195
  );
115
196
  }
116
- const norm = policy.normalize ?? ((s) => s);
117
197
  const dst = norm(ops[0] ?? '');
118
198
  // No DEGRADABLE destination: `ops[0]` is not a register, or it is the hardwired zero. Same answer
119
199
  // for both — a zero write is a genuine no-op only for a MODELLED instruction, and by construction
@@ -122,7 +202,6 @@ export function opaqueDest(mnemonic: string, ops: string[], policy: OpaquePolicy
122
202
  if (policy.skipSafe?.test(mnemonic)) {
123
203
  return null;
124
204
  } // explicitly transparent for this ISA
125
- const where = policy.context ? `cannot lift '${policy.context}': ` : '';
126
205
  throw new FrontendUnsupportedError(
127
206
  `${where}unmodelled effect instruction '${shown}' — no register destination to degrade, and skipping it would silently delete its effect`,
128
207
  );