@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.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- 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
|
+
};
|