@asmlift/core 0.8.1 → 0.10.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 (130) hide show
  1. package/README.md +36 -31
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +397 -0
  4. package/src/backend/c.ts +6 -2
  5. package/src/backend/cfamily.ts +370 -56
  6. package/src/backend/cpp.ts +116 -10
  7. package/src/backend/pascal.ts +69 -11
  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 +777 -84
  15. package/src/declare.ts +132 -13
  16. package/src/frontend/asmdata.ts +26 -6
  17. package/src/frontend/call-plan.ts +1590 -0
  18. package/src/frontend/dead-reads.ts +628 -0
  19. package/src/frontend/device-pins.ts +296 -0
  20. package/src/frontend/emit.ts +4 -3
  21. package/src/frontend/errors.ts +5 -0
  22. package/src/frontend/flags-edge.ts +101 -0
  23. package/src/frontend/fpu.ts +374 -0
  24. package/src/frontend/frame-objects.ts +3171 -0
  25. package/src/frontend/frontend.ts +64 -3
  26. package/src/frontend/high-half.ts +55 -5
  27. package/src/frontend/jump-table.ts +530 -0
  28. package/src/frontend/local-object.ts +616 -0
  29. package/src/frontend/mips.ts +2392 -208
  30. package/src/frontend/opaque.ts +97 -18
  31. package/src/frontend/own-struct-return.ts +500 -0
  32. package/src/frontend/ppc.ts +3137 -267
  33. package/src/frontend/register-struct-call.ts +484 -0
  34. package/src/frontend/reloc-symbol.ts +109 -13
  35. package/src/frontend/splat.ts +108 -5
  36. package/src/frontend/ssa.ts +2098 -89
  37. package/src/frontend/stackargs.ts +172 -42
  38. package/src/frontend/thumb.ts +4563 -2091
  39. package/src/ir/alias.ts +71 -0
  40. package/src/ir/bits.ts +681 -1
  41. package/src/ir/core.ts +199 -6
  42. package/src/ir/discipline.ts +313 -0
  43. package/src/ir/entry.ts +159 -0
  44. package/src/ir/float-bits.ts +47 -0
  45. package/src/{structure → ir}/loops.ts +6 -2
  46. package/src/ir/opcodes.ts +193 -121
  47. package/src/ir/parse.ts +1 -0
  48. package/src/ir/types.ts +202 -2
  49. package/src/ir/verify.ts +219 -3
  50. package/src/l3/address.ts +5 -0
  51. package/src/l3/argcopy.ts +38 -6
  52. package/src/l3/ast.ts +467 -23
  53. package/src/l3/basecse.ts +33 -5
  54. package/src/l3/coalesce.ts +21 -13
  55. package/src/l3/constinit.ts +382 -0
  56. package/src/l3/dce.ts +34 -12
  57. package/src/l3/gates.ts +9 -3
  58. package/src/l3/hoist.ts +8 -7
  59. package/src/l3/initfirst.ts +68 -19
  60. package/src/l3/intcell.ts +98 -0
  61. package/src/l3/mentions.ts +10 -4
  62. package/src/l3/narrowdecl.ts +259 -0
  63. package/src/l3/nearbase.ts +10 -5
  64. package/src/l3/parkfirst.ts +8 -5
  65. package/src/l3/pollguard.ts +11 -4
  66. package/src/l3/ptrcell.ts +354 -0
  67. package/src/l3/ptrfield.ts +123 -8
  68. package/src/l3/regspell.ts +26 -7
  69. package/src/l3/reindex.ts +293 -103
  70. package/src/l3/storage.ts +98 -1
  71. package/src/l3/symbol-refs.ts +182 -23
  72. package/src/l3/tailmerge.ts +9 -20
  73. package/src/l3/typing.ts +237 -32
  74. package/src/l3/unreduce.ts +10 -9
  75. package/src/l3/volatileptr.ts +17 -54
  76. package/src/l3/volstore.ts +96 -7
  77. package/src/mangle.ts +41 -1
  78. package/src/pattern/engine.ts +373 -82
  79. package/src/pipeline.ts +589 -27
  80. package/src/proto-context.ts +1648 -0
  81. package/src/proto.ts +1203 -49
  82. package/src/raise/aggmembers.ts +290 -0
  83. package/src/raise/arrays.ts +8 -3
  84. package/src/raise/declared-volatile.ts +307 -0
  85. package/src/raise/divpow2.ts +2 -2
  86. package/src/raise/errors.ts +17 -0
  87. package/src/raise/floathelpers.ts +464 -0
  88. package/src/raise/globalshape.ts +11 -5
  89. package/src/raise/gvn.ts +29 -8
  90. package/src/raise/latch.ts +162 -6
  91. package/src/raise/memberarrays.ts +10 -4
  92. package/src/raise/narrow.ts +29 -2
  93. package/src/raise/narrowlocal.ts +11 -6
  94. package/src/raise/offsetnames.ts +494 -0
  95. package/src/raise/pairparams.ts +114 -0
  96. package/src/raise/paramwidth.ts +203 -6
  97. package/src/raise/pre-recovery.ts +174 -17
  98. package/src/raise/pre-structure.ts +54 -0
  99. package/src/raise/recover.ts +26 -15
  100. package/src/raise/reroll.ts +694 -0
  101. package/src/raise/retsink.ts +1 -1
  102. package/src/raise/shortcircuit.ts +66 -30
  103. package/src/raise/softdiv.ts +21 -23
  104. package/src/raise/storesign.ts +153 -0
  105. package/src/raise/struct-arrays.ts +34 -14
  106. package/src/raise/structs.ts +674 -98
  107. package/src/raise/truncload.ts +367 -0
  108. package/src/raise/unthread.ts +400 -0
  109. package/src/raise/widehelpers.ts +106 -0
  110. package/src/rank-declare.ts +143 -12
  111. package/src/rank-variations.ts +117 -31
  112. package/src/rank.ts +563 -122
  113. package/src/runtime-helpers.ts +405 -0
  114. package/src/stillborn.ts +289 -0
  115. package/src/structure/analysis.ts +1449 -449
  116. package/src/structure/arith-ops.ts +23 -0
  117. package/src/structure/globalaccess.ts +111 -9
  118. package/src/structure/hazards.ts +641 -114
  119. package/src/structure/local-statics.ts +517 -0
  120. package/src/structure/namecoalesce.ts +2 -2
  121. package/src/structure/pointer-spelling.ts +1106 -0
  122. package/src/structure/redundant-test.ts +99 -0
  123. package/src/structure/structure.ts +3478 -975
  124. package/src/structure/switch-recover.ts +403 -178
  125. package/src/symbols.ts +44 -3
  126. package/src/target.ts +1387 -71
  127. package/src/trace.ts +66 -28
  128. package/src/variation-definitions.ts +243 -13
  129. package/src/variation-gates.ts +2 -0
  130. package/src/variation-tokens.ts +25 -7
@@ -0,0 +1,405 @@
1
+ // asmlift — the COMPILER RUNTIME HELPERS a target's codegen calls, and what each one computes.
2
+ //
3
+ // A compiler lowers an operation its ISA cannot do to a call into its own runtime: agbcc emits
4
+ // `bl __divsi3` for `a / b` because ARMv4T has no divider, and `bl __muldi3` for a 64-bit multiply
5
+ // because ARMv4T has no 64-bit anything. Two things are needed to recover either, and they are
6
+ // different things:
7
+ // • the SIGNATURE, so the call's arguments are recovered at all (the frontend's arity lookup
8
+ // falls back to this behind any caller-supplied prototype);
9
+ // • the OPERATION, so the call is rewritten to the op it computes and the structurer prints
10
+ // `a / b` rather than `__divsi3(a, b)`.
11
+ //
12
+ // AN ENTRY HERE IS ALSO A REFUSAL. Where no recogniser folds a call to a name in this table,
13
+ // `raise/widehelpers.ts` turns it into a gap instead of letting the backend spell it. Re-emitting
14
+ // the compiler's own runtime call as source is not a recovery — it is the one shape that MATCHES
15
+ // for free, because a compiler handed `__div2i(a, b)` emits the `bl __div2i` it was lifted from.
16
+ // A helper nothing here can fold therefore declines; a helper nothing here NAMES still passes
17
+ // through, which is what makes adding a name a decision rather than a note.
18
+ //
19
+ // WHICH HELPERS A COMPILER EMITS IS A COMPILER FACT, so the table hangs off `TargetDescription`
20
+ // rather than off a pass. `proto.ts` holds signatures fixed by the C STANDARD, which a runtime
21
+ // helper is precisely not — agbcc calls `__muldi3`, CodeWarrior calls `__div2i`, IDO calls
22
+ // `__ll_mul`, and nothing about the C standard predicts any of them. A scan that assumes one
23
+ // family reports ZERO on the others.
24
+ //
25
+ // `params` is a list of C parameter WIDTHS in bits, not a word count, and the difference is the
26
+ // whole point of the field: `__ashrdi3` takes a 64-bit value and a 32-bit count — two C parameters
27
+ // occupying THREE argument registers. A stated width is read two ways and both conversions live
28
+ // here: `wordsOf` (proto.ts, because a C declaration asks the same question) counts the argument
29
+ // REGISTERS the list occupies, and `irWidthOf` gives the IR width one parameter ARRIVES at once a
30
+ // frontend has paired those registers up.
31
+ import { type Op, type Value, mkOp } from './ir/core';
32
+ import { FLOAT_COMPUTING, FLOAT_TO_INT, INT_TO_FLOAT, NEGATED_FCMP, type Opcode, WIDE_BITS } from './ir/opcodes';
33
+ import { type IrType, intWidth } from './ir/types';
34
+ import { type Prototypes, wordsOf } from './proto';
35
+ import type { TargetDescription } from './target';
36
+
37
+ export interface RuntimeHelper {
38
+ /** the op this helper computes, where asmlift has one. Absent for a helper it can only SIGN. */
39
+ op?: Opcode;
40
+ /** each C parameter's width in bits, in argument order. */
41
+ params: readonly number[];
42
+ /** the returned width: 0 (void), 32, or 64 (a register PAIR). */
43
+ returns: 0 | 32 | 64;
44
+ /** A THREE-WAY FLOAT COMPARE: its result's SIGN orders its two operands (negative, zero, positive
45
+ * for less, equal, greater), and this is the value it returns where either is a NaN. A compare
46
+ * folds through the integer test its caller applies to the result (raise/floathelpers.ts), so it
47
+ * carries this in place of an `op`. */
48
+ unordered?: 1 | -1;
49
+ /** THE RELATION THE SOURCE SPELLED, for a compare helper whose relation and its negation are both
50
+ * C operators — `__eqsf2` for `==`, `__nesf2` for `!=`. agbcc's gcc folds `!(a == b)` into
51
+ * `a != b` before it picks a helper, so the helper names the relation the source wrote, and a
52
+ * lift that spells its negation (or the negation's `!`) recompiles to the other helper. The fold
53
+ * stamps it on the compare as `attrs.relation` (raise/floathelpers.ts), and the structurer keeps
54
+ * that relation's spelling by choosing the arm order instead (l3/ast.ts `Expr.helperRel`). The
55
+ * ordered four need none: `!(a > b)` keeps `__gtsf2`, and no other helper computes it. */
56
+ relation?: 'fcmp_oeq' | 'fcmp_une';
57
+ }
58
+
59
+ /** The entry a target's table holds for `callee`, or undefined. THE ONE READER, because the table
60
+ * is an object literal and a C function may be named `toString`: a bare index or an `in` answers
61
+ * with a member of `Object.prototype` for eight names, and what each caller then does with that
62
+ * `Function` differs — one reads `params` off it and throws a TypeError, another refuses while
63
+ * naming a runtime helper the target does not have. `Object.hasOwn` is stated here so that no
64
+ * caller can be the one that forgot it. (`proto.ts` states the same rule for the prototype
65
+ * tables, which are read the same way for the same reason.) */
66
+ export function lookupHelper(
67
+ table: Readonly<Record<string, RuntimeHelper>> | undefined,
68
+ callee: string,
69
+ ): RuntimeHelper | undefined {
70
+ return table && Object.hasOwn(table, callee) ? table[callee] : undefined;
71
+ }
72
+
73
+ /** The IR width a C parameter of `bits` arrives at: one 64-bit value for anything wider than a
74
+ * register, because a frontend that reads such a parameter fuses its registers into a single
75
+ * value; a machine word for everything else, whatever narrower width the C type carries. The
76
+ * counterpart of `wordsOf` — same threshold, different vocabulary. */
77
+ export function irWidthOf(bits: number): number {
78
+ return bits > 32 ? WIDE_BITS : 32;
79
+ }
80
+
81
+ /** Whether a call ARRIVED in the shape this helper's signature states: one result at the returned
82
+ * width, and each operand at the IR width its C parameter carries.
83
+ *
84
+ * A COUNT IS NOT A WIDTH, and the gap between them is where a 64-bit operation gets invented over
85
+ * two 32-bit words. Two ordinary shapes reach a fold with the right operand count and the wrong
86
+ * contents: a call whose arity came off a caller-supplied prototype rather than off this table, so
87
+ * no frontend ever built the pair; and a call on a target whose frontend does not pair registers
88
+ * at all, where an argument register that is not live at the call gets trimmed and the survivors
89
+ * happen to number what the table states. Either one folded publishes an operation over one half
90
+ * of each value, at exit 0 and with no gap — so the pair construction IS the precondition, and a
91
+ * shape that lacks it belongs to `refuseUnmodelledHelpers`.
92
+ *
93
+ * AN OPERAND THAT CARRIES NO INTEGER WIDTH REFUSES, which is `intWidth` returning null for a
94
+ * pointer or an aggregate. `ir/verify.ts` reads that same null as "does not take part in the width
95
+ * rule"; here it means there is no width to check the claim against, and a fold may not proceed on
96
+ * a claim it cannot check.
97
+ *
98
+ * A `returns: 0` entry can never satisfy this, because no result carries width 0. A void helper
99
+ * computing on a 64-bit value is a shape there is no operation to fold it into. */
100
+ export function arrivesAsDeclared(
101
+ helper: RuntimeHelper,
102
+ operands: readonly IrType[],
103
+ results: readonly IrType[],
104
+ ): boolean {
105
+ if (results.length !== 1 || intWidth(results[0]) !== helper.returns) {
106
+ return false;
107
+ }
108
+ return (
109
+ operands.length === helper.params.length && operands.every((t, i) => intWidth(t) === irWidthOf(helper.params[i]))
110
+ );
111
+ }
112
+
113
+ /** Whether a helper computes on a value wider than a register — the ones the 64-bit representation
114
+ * is for, and the ones no hardware capability can make unnecessary. */
115
+ export function isWideHelper(h: RuntimeHelper): boolean {
116
+ return h.returns > 32 || h.params.some((w) => w > 32);
117
+ }
118
+
119
+ /** The op a recognised helper call is rewritten to, STAMPED with the helper it was: `helper` names
120
+ * the callee. An integer helper's op takes the call's own operands and the SAME result value, so
121
+ * every use already points at it; a float helper's takes them re-typed as floats
122
+ * (`raise/floathelpers.ts`). `raise/softdiv.ts`, `raise/widehelpers.ts` and `raise/floathelpers.ts`
123
+ * build every one through here.
124
+ *
125
+ * The stamp carries the one fact the op's opcode loses: the asm CALLED something here. A
126
+ * `sdiv` is a `divw` on mwcc and a `bl __divsi3` on agbcc, and an `shr_s` is a shift everywhere
127
+ * but a `bl __ashrdi3` where it is 64-bit on agbcc — which ops a compiler calls a helper for is a
128
+ * compiler fact, not an ISA one, and it was known exactly once, at the rewrite. What reads it is
129
+ * every rule that places a call: a call runs once, where the asm ran it (ir/discipline.ts
130
+ * `placedAt`), and a pass that moves the op elsewhere drops the stamp (`forgetHelperPlacement`). */
131
+ export function helperOp(opcode: Opcode, helper: string, operands: readonly Value[], result: Value): Op {
132
+ return mkOp(opcode, { operands: [...operands], results: [result], attrs: { helper } });
133
+ }
134
+
135
+ /** Whether a helper computes on floats: its op is a float op, a conversion or a compare, so the
136
+ * operands and the result that op takes as floats are floats of the widths its signature states,
137
+ * carried in integer registers by a soft-float runtime. Every reader of the table asks this and
138
+ * not the width, because the two families fold into different IR: an integer helper into its op
139
+ * over the same values, a float helper into its op over values re-typed as floats where the op
140
+ * takes one (`raise/floathelpers.ts`). */
141
+ export function isFloatHelper(h: RuntimeHelper): boolean {
142
+ return h.unordered !== undefined || (h.op !== undefined && FLOAT_COMPUTING.has(h.op));
143
+ }
144
+
145
+ /** Which sides of a float helper are floats: its operands (one kind for all of them, in every op
146
+ * here) and its result. A conversion has an integer on one side; a compare returns one. */
147
+ export function floatSidesOf(h: RuntimeHelper): { operands: boolean; result: boolean } {
148
+ const op = h.op ?? '';
149
+ return {
150
+ operands: !INT_TO_FLOAT.has(op),
151
+ result: h.unordered === undefined && !FLOAT_TO_INT.has(op) && NEGATED_FCMP[op] === undefined,
152
+ };
153
+ }
154
+
155
+ /** Signatures for a target's helpers, in the WORD arity the frontend's prototype lookup speaks.
156
+ * Consumed behind any caller-supplied prototype — the project's own headers win. */
157
+ export function helperPrototypes(
158
+ target: Pick<TargetDescription, 'runtimeHelpers' | 'evenWordAlignedArgs'>,
159
+ ): Prototypes {
160
+ return Object.fromEntries(
161
+ Object.entries(target.runtimeHelpers ?? {}).map(([sym, h]) => [sym, { params: wordsOf(h.params, target) }]),
162
+ );
163
+ }
164
+
165
+ /** agbcc's runtime, which is GCC 2.9's libgcc for ARM.
166
+ *
167
+ * THE 64-BIT MULTIPLY IS SIGNEDNESS-BLIND, and that is a fact about this libgcc rather than a
168
+ * simplification here: `optabs.c` initialises the only integer multiply libfunc from the MODE
169
+ * alone, and `expmed.c` routes every `MULT` through it whatever the operands' signedness, so
170
+ * `__umuldi3` does not exist and both spellings emit `bl __muldi3`. Signedness comes from the
171
+ * operand SET-UP instead — `asr rN,rM,#31` per half for signed, `mov rN,#0` for unsigned. The
172
+ * DIVISIONS do split, and their entries say so.
173
+ *
174
+ * THE FLOAT ARITHMETIC OF BOTH PRECISIONS IS HERE, AND FOLDS. `optabs.c:4022`
175
+ * `init_floating_libfuncs` names each mode's add, sub, mul, div (4129-4140) and neg (4155), and
176
+ * `thumb.md` has no SF or DF pattern but the move, so every one of them is a call; a single
177
+ * travels as an int's word and a double as a long long's pair (thumb.h:632, 655).
178
+ * `raise/floathelpers.ts` folds them to the float ops over a `float` or a `double`.
179
+ *
180
+ * NAMING THEM IS A TRADE, and these ten make it: a call the fold refuses declines where an unnamed
181
+ * helper would publish as a pass-through. So a function whose float arithmetic feeds a compare, a
182
+ * conversion or an ordinary call declines, where unnamed it would print `__muldf3(); g();`. For
183
+ * the singles that pass-through is wrong C: unnamed, `__mulsf3`'s arity is GUESSED (frontend/ssa.ts
184
+ * `fallbackArgc`), and a guess drops what the function passes on unchanged — `a * b + c` as
185
+ * `__addsf3(__mulsf3(), a2)`, `a + b` as `__addsf3()`.
186
+ *
187
+ * THE CONVERSIONS FOLD TOO, to the cast each computes (`fptosi`, `sitofp`, `fpext` …), with an
188
+ * integer on one side that `raise/floathelpers.ts` takes as any integer. THE COMPARES fold through
189
+ * the test their caller applies to the integer they return, by libgcc's contract for it
190
+ * (`unordered`), and one whose result is read any other way refuses (`refuseUnmodelledHelpers`).
191
+ * Unnamed, each would be the same guess, dropping the argument passed on unchanged and matching
192
+ * byte for byte: `(int)x` as `__fixsfsi()`, `(float)i` as `__floatsisf()`, `a > b` as
193
+ * `__gtsf2() > 0`. The names are the ones agbcc
194
+ * emits for every compare, every conversion between `int`, `unsigned`, `long long` and either
195
+ * precision, and every widening and narrowing between the two precisions (compiled evidence: one
196
+ * function per operation at the canonical flags). There is no conversion of an unsigned integer
197
+ * INTO a float: agbcc open-codes `(float)u` around `__floatsisf` and `__addsf3`. `__cmpdi2`, which
198
+ * it also calls converting an `unsigned long long`, is an integer compare and outside this
199
+ * family. */
200
+ export const AGBCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
201
+ // 32-bit software division — the ops `raise/softdiv.ts` rewrites, gated on the target having no
202
+ // hardware divider, which is what those four are about.
203
+ __divsi3: { op: 'sdiv', params: [32, 32], returns: 32 },
204
+ __udivsi3: { op: 'udiv', params: [32, 32], returns: 32 },
205
+ __modsi3: { op: 'smod', params: [32, 32], returns: 32 },
206
+ __umodsi3: { op: 'umod', params: [32, 32], returns: 32 },
207
+ // 64-bit arithmetic. NOT gated on a hardware capability: no ISA this repo targets has 64-bit
208
+ // integer arithmetic at all, so a call here is software on every one of them.
209
+ __muldi3: { op: 'mul', params: [64, 64], returns: 64 },
210
+ __divdi3: { op: 'sdiv', params: [64, 64], returns: 64 },
211
+ __udivdi3: { op: 'udiv', params: [64, 64], returns: 64 },
212
+ __moddi3: { op: 'smod', params: [64, 64], returns: 64 },
213
+ __umoddi3: { op: 'umod', params: [64, 64], returns: 64 },
214
+ __ashldi3: { op: 'shl', params: [64, 32], returns: 64 },
215
+ __ashrdi3: { op: 'shr_s', params: [64, 32], returns: 64 },
216
+ __lshrdi3: { op: 'shr_u', params: [64, 32], returns: 64 },
217
+ __negdi2: { op: 'neg', params: [64], returns: 64 },
218
+ // Double arithmetic, which the same pair carries (`raise/floathelpers.ts`).
219
+ __adddf3: { op: 'fadd', params: [64, 64], returns: 64 },
220
+ __subdf3: { op: 'fsub', params: [64, 64], returns: 64 },
221
+ __muldf3: { op: 'fmul', params: [64, 64], returns: 64 },
222
+ __divdf3: { op: 'fdiv', params: [64, 64], returns: 64 },
223
+ __negdf2: { op: 'fneg', params: [64], returns: 64 },
224
+ // Single arithmetic, one float in one register each way (thumb.h:632's word).
225
+ __addsf3: { op: 'fadd', params: [32, 32], returns: 32 },
226
+ __subsf3: { op: 'fsub', params: [32, 32], returns: 32 },
227
+ __mulsf3: { op: 'fmul', params: [32, 32], returns: 32 },
228
+ __divsf3: { op: 'fdiv', params: [32, 32], returns: 32 },
229
+ __negsf2: { op: 'fneg', params: [32], returns: 32 },
230
+ // The compares, one int out of two floats of either precision, each libgcc's three-way
231
+ // `__fpcmp_parts` when both are ordered (fp-bit.c:1117-1188). On a NaN each returns the value its
232
+ // own relation tests FALSE on — `== 0`, `< 0`, `<= 0` are false on 1, `> 0`, `>= 0` on -1 — except
233
+ // `__nesf2`, whose 1 makes `!=` true (fp-bit.c:1214-1309). agbcc tests the result against 0 with
234
+ // the relation the helper is named for.
235
+ __eqsf2: { params: [32, 32], returns: 32, unordered: 1, relation: 'fcmp_oeq' },
236
+ __nesf2: { params: [32, 32], returns: 32, unordered: 1, relation: 'fcmp_une' },
237
+ __gtsf2: { params: [32, 32], returns: 32, unordered: -1 },
238
+ __gesf2: { params: [32, 32], returns: 32, unordered: -1 },
239
+ __ltsf2: { params: [32, 32], returns: 32, unordered: 1 },
240
+ __lesf2: { params: [32, 32], returns: 32, unordered: 1 },
241
+ __eqdf2: { params: [64, 64], returns: 32, unordered: 1, relation: 'fcmp_oeq' },
242
+ __nedf2: { params: [64, 64], returns: 32, unordered: 1, relation: 'fcmp_une' },
243
+ __gtdf2: { params: [64, 64], returns: 32, unordered: -1 },
244
+ __gedf2: { params: [64, 64], returns: 32, unordered: -1 },
245
+ __ltdf2: { params: [64, 64], returns: 32, unordered: 1 },
246
+ __ledf2: { params: [64, 64], returns: 32, unordered: 1 },
247
+ // The conversions: to and from a word, to and from a long long, and between the precisions.
248
+ // `__fix*` truncates toward zero, as C's conversion out of a float does.
249
+ __fixsfsi: { op: 'fptosi', params: [32], returns: 32 },
250
+ __fixunssfsi: { op: 'fptoui', params: [32], returns: 32 },
251
+ __floatsisf: { op: 'sitofp', params: [32], returns: 32 },
252
+ __fixdfsi: { op: 'fptosi', params: [64], returns: 32 },
253
+ __fixunsdfsi: { op: 'fptoui', params: [64], returns: 32 },
254
+ __floatsidf: { op: 'sitofp', params: [32], returns: 64 },
255
+ __fixsfdi: { op: 'fptosi', params: [32], returns: 64 },
256
+ __fixunssfdi: { op: 'fptoui', params: [32], returns: 64 },
257
+ __floatdisf: { op: 'sitofp', params: [64], returns: 32 },
258
+ __fixdfdi: { op: 'fptosi', params: [64], returns: 64 },
259
+ __fixunsdfdi: { op: 'fptoui', params: [64], returns: 64 },
260
+ __floatdidf: { op: 'sitofp', params: [64], returns: 64 },
261
+ __extendsfdf2: { op: 'fpext', params: [32], returns: 64 },
262
+ __truncdfsf2: { op: 'fptrunc', params: [64], returns: 32 },
263
+ };
264
+
265
+ /** CodeWarrior's PowerPC runtime (`Runtime.PPCEABI.H`), as the GameCube projects vendor it.
266
+ *
267
+ * A DIFFERENT FAMILY ENTIRELY, which is the reason this table is per target and not per repo: a
268
+ * scan for `__*di3` reports zero on every one of these. These seven are the integer set, agreed
269
+ * on by all three PPC checkouts' own vendored `runtime.c` (each at a different path), where every
270
+ * one is an `ASM` function whose body states its register contract: `__div2i` takes r3:r4 and
271
+ * r5:r6 and returns r3:r4, `__shl2i` takes r3:r4 and a count in r5.
272
+ *
273
+ * ONE SHIFT LEFT AND TWO RIGHT, which is the same signedness rule agbcc's `__ashldi3` follows:
274
+ * the bits a left shift brings in are zero whatever the operand is, so there is nothing for a
275
+ * `__shl2u` to do differently.
276
+ *
277
+ * NO MULTIPLY, and that is a fact about this compiler rather than a hole here: mwcc open-codes a
278
+ * 64-bit multiply. `synthetic:llmul:mwcc_242_81` is the evidence and it is in the corpus — its
279
+ * target is 28 bytes of `mulhwu` and two `mullw` with no relocation at all, against 32 bytes and
280
+ * an `R_PPC_REL24` to `__div2i` for `lldivs` beside it. The float and decimal helpers of the same
281
+ * runtime (`__cvt_sll_flt`, `__num2dec`) are left out for the reason the agbcc table leaves out
282
+ * its own.
283
+ *
284
+ * ⚠ WHAT THIS TABLE DOES ON THIS TARGET TODAY IS REFUSE, and the `op` column is not reachable
285
+ * from a lift. `frontend/ppc.ts` reads guessed argument registers and fuses none of them into a
286
+ * pair, so no value on this target is ever 64 bits wide, so `arrivesAsDeclared` can never be
287
+ * satisfied here and `recognizeWideHelpers` cannot fold. THAT absent pairing is the gate bounding
288
+ * the path — not anything in this file, and not `refuseUnmodelledHelpers`, which is what these
289
+ * names reach instead. The ops below are measured off the vendored `runtime.c` and asserted
290
+ * directly by `runtime-helper-refusal.test.ts`, because a claim no lift can reach is a claim no
291
+ * lift can check either; naming which operation each helper computes is the half of the frontend
292
+ * work that is done. */
293
+ export const PPC_MWCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
294
+ __div2i: { op: 'sdiv', params: [64, 64], returns: 64 },
295
+ __div2u: { op: 'udiv', params: [64, 64], returns: 64 },
296
+ __mod2i: { op: 'smod', params: [64, 64], returns: 64 },
297
+ __mod2u: { op: 'umod', params: [64, 64], returns: 64 },
298
+ __shl2i: { op: 'shl', params: [64, 32], returns: 64 },
299
+ __shr2i: { op: 'shr_s', params: [64, 32], returns: 64 },
300
+ __shr2u: { op: 'shr_u', params: [64, 32], returns: 64 },
301
+ };
302
+
303
+ /** A compiler runtime's OUT-OF-LINE REGISTER SAVE AND RESTORE ROUTINES: a prologue that saves a run
304
+ * of callee-saved registers by calling into the runtime instead of storing each one inline. A
305
+ * compiler fact, like the table above, and it hangs off `TargetDescription` for the same reason:
306
+ * which compiler emits the family, at which level, is measured per build, and a frontend that
307
+ * hard-coded the names would recognise them on a target whose runtime has no such routine.
308
+ *
309
+ * A call to one is FRAME BOOKKEEPING, not a call: it writes no register but the link register, and
310
+ * the frame address in `frameReg` is the only thing it reads (frontend/ppc.ts `saveRoutine`). */
311
+ export interface RegisterSaveRoutines {
312
+ /** the register a routine takes its frame address in. The save area is the words just BELOW it. */
313
+ frameReg: string;
314
+ /** every routine name, with three groups: `save` or `rest`, the register file (`gpr`/`fpr`), and
315
+ * the first register the routine moves — it moves that one up to the file's last, 31. */
316
+ name: RegExp;
317
+ }
318
+
319
+ /** The routine a callee symbol names under `routines`, or null when it names none. */
320
+ export function registerSaveRoutineOf(
321
+ routines: RegisterSaveRoutines | undefined,
322
+ sym: string,
323
+ ): { sym: string; restore: boolean; fpr: boolean; first: number } | null {
324
+ const m = routines?.name.exec(sym);
325
+ return m ? { sym: m[0], restore: m[1] === 'rest', fpr: m[2] === 'fpr', first: Number(m[3]) } : null;
326
+ }
327
+
328
+ /** CodeWarrior's `_savegpr_NN` / `_restgpr_NN` and `_savefpr_NN` / `_restfpr_NN`, called after
329
+ * `addi r11,r1,N`. `_savegpr_NN` stores rNN..r31 into the 4*(32-NN) bytes just below r11, and
330
+ * `_savefpr_NN` stores fNN..f31 into the 8*(32-NN) bytes below it.
331
+ *
332
+ * COMPILED, per build: 2.4.2 and 2.4.7 emit the family at every level (`-O4,p`, `-O4,s`, `-O0,p`).
333
+ * 2.3.3 never does at Pikmin's `-O4,p`, where it saves with `stmw` and inline `stfd`, but does at
334
+ * `-O0,p` and `-O4,s`; all three builds share `PPC_MWCC`, so the description carries it. The bench's
335
+ * feature tagger reads this same pattern (apps/benchmark `features.ts`). */
336
+ export const PPC_MWCC_SAVE_ROUTINES: RegisterSaveRoutines = {
337
+ frameReg: 'r11',
338
+ name: /^_(save|rest)(gpr|fpr)_(\d+)$/,
339
+ };
340
+
341
+ /** IDO 7.1's runtime for the 64-bit operations MIPS II has no instruction for, a family of its own
342
+ * (`__ll_*` for signed, `__ull_*` for unsigned): a scan for `__*di3` reports zero on it.
343
+ *
344
+ * Compiled at `-mips2 -O2 -32 -non_shared -G 0` (compiled evidence only): `a * b` calls `__ll_mul`
345
+ * for both signednesses, the divisions and remainders split, and a left shift is one helper while
346
+ * the right shift splits. A SHIFT'S COUNT TRAVELS AS A PAIR: `a << n` with `int n` builds
347
+ * `a2 = n >> 31, a3 = n` before `jal __ll_lshift`, so both parameters are 64 bits wide.
348
+ *
349
+ * EVERY CONVERSION BETWEEN A 64-BIT INTEGER AND A FLOAT IS A CALL TOO, one per direction, precision
350
+ * and signedness. The float side travels in the FPU's registers (`$f12` in, `$f0` out), so these
351
+ * carry no op: a guessed call to one passes and returns nothing it can see.
352
+ *
353
+ * SO IS A 64-BIT BITFIELD: a read hands the word's address, start bit and length to
354
+ * `__ll_bit_extract` (`__ull_bit_extract` unsigned), and a write of either signedness calls
355
+ * `__ll_bit_insert` with the value as a fourth parameter. No op folds either.
356
+ *
357
+ * THE NAMES ARE THE COMPILER'S OWN: these are every `__ll_*`, `__ull_*` and `__*_to_*ll` that a pass
358
+ * of IDO 7.1 carries in its string table (`cfe` carries them all). Its libc defines more
359
+ * (`__ll_mod`, `__ull_divremi`, `__ull_divrem_*`), which no pass names, so no compiled code calls them.
360
+ *
361
+ * ⚠ WHAT THIS TABLE DOES ON THIS TARGET IS REFUSE, as `PPC_MWCC_RUNTIME_HELPERS` does and for the
362
+ * same reason: `frontend/mips.ts` fuses no register pair, so a call to one of these arrives with
363
+ * word operands and `refuseUnmodelledHelpers` gaps it by name. */
364
+ export const IDO_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
365
+ __ll_mul: { op: 'mul', params: [64, 64], returns: 64 },
366
+ __ll_div: { op: 'sdiv', params: [64, 64], returns: 64 },
367
+ __ull_div: { op: 'udiv', params: [64, 64], returns: 64 },
368
+ __ll_rem: { op: 'smod', params: [64, 64], returns: 64 },
369
+ __ull_rem: { op: 'umod', params: [64, 64], returns: 64 },
370
+ __ll_lshift: { op: 'shl', params: [64, 64], returns: 64 },
371
+ __ll_rshift: { op: 'shr_s', params: [64, 64], returns: 64 },
372
+ __ull_rshift: { op: 'shr_u', params: [64, 64], returns: 64 },
373
+ __ll_to_d: { params: [64], returns: 64 },
374
+ __ll_to_f: { params: [64], returns: 32 },
375
+ __ull_to_d: { params: [64], returns: 64 },
376
+ __ull_to_f: { params: [64], returns: 32 },
377
+ __d_to_ll: { params: [64], returns: 64 },
378
+ __f_to_ll: { params: [32], returns: 64 },
379
+ __d_to_ull: { params: [64], returns: 64 },
380
+ __f_to_ull: { params: [32], returns: 64 },
381
+ __ll_bit_extract: { params: [32, 32, 32], returns: 64 },
382
+ __ull_bit_extract: { params: [32, 32, 32], returns: 64 },
383
+ __ll_bit_insert: { params: [32, 32, 32, 64], returns: 64 },
384
+ };
385
+
386
+ /** GCC's libgcc for MIPS, as KMC GCC and GCC 2.7.2 call it at the N64 projects' `-mips3 -mgp32`
387
+ * (compiled evidence only, both toolchains agreeing): the four divisions are calls, while a
388
+ * 64-bit multiply is `mult`/`multu` and a shift is open-coded, so neither has an entry. The
389
+ * conversions between a 64-bit integer and a float are calls, with no `__floatundi*`: an unsigned
390
+ * one converts as signed and corrects the result when `__cmpdi2` finds the value negative. The
391
+ * conversions carry no op, for the reason `IDO_RUNTIME_HELPERS` gives. It refuses on this target
392
+ * for that table's reason too. */
393
+ export const MIPS_GCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
394
+ __divdi3: { op: 'sdiv', params: [64, 64], returns: 64 },
395
+ __udivdi3: { op: 'udiv', params: [64, 64], returns: 64 },
396
+ __moddi3: { op: 'smod', params: [64, 64], returns: 64 },
397
+ __umoddi3: { op: 'umod', params: [64, 64], returns: 64 },
398
+ __floatdidf: { params: [64], returns: 64 },
399
+ __floatdisf: { params: [64], returns: 32 },
400
+ __fixdfdi: { params: [64], returns: 64 },
401
+ __fixsfdi: { params: [32], returns: 64 },
402
+ __fixunsdfdi: { params: [64], returns: 64 },
403
+ __fixunssfdi: { params: [32], returns: 64 },
404
+ __cmpdi2: { params: [64, 64], returns: 32 },
405
+ };