@asmlift/core 0.9.0 → 0.11.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 +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// asmlift — THE CALLER HALF OF A STRUCT RETURNED IN THE RETURN REGISTER.
|
|
2
|
+
//
|
|
3
|
+
// A callee the project declares to return a struct of one, two or four bytes, on a target whose
|
|
4
|
+
// rule (aggregate.ts `returnsInMemory`) hands it back in the return register — agbcc's r0 for a
|
|
5
|
+
// struct of one non-bitfield member, mwcc's r3 for any struct of eight bytes or less — fills that
|
|
6
|
+
// register with the struct's bytes as the target lays them in a word. The call plan lets the call
|
|
7
|
+
// write the register as its result, marked `REGISTER_STRUCT` (frontend/call-plan.ts
|
|
8
|
+
// `registerStructLocal` says which calls), so the frontend reads it as it reads any call's word.
|
|
9
|
+
//
|
|
10
|
+
// Here the word becomes what C says it is: the struct, assigned by the call to a local of its
|
|
11
|
+
// type, and re-read off that local. The call takes the local's address the way a call returning a
|
|
12
|
+
// struct through memory takes its destination (`sret`), so it prints `s = mk(x);`; the word is a
|
|
13
|
+
// load of the local at offset 0, of the struct's own size, and on a big-endian target shifted up to
|
|
14
|
+
// the top of the word, which is where the struct's first byte sits there (mwcc's `{u16}` member is
|
|
15
|
+
// `srwi r0,r3,16` off the call). Whatever the caller then takes out of the word — a truncating
|
|
16
|
+
// store, a shift, a mask — reads the local's bytes, and the member recovery spells it as the
|
|
17
|
+
// member it is: `gV = s.h;`. A 4-byte read of the local (a `{u32}`'s member, or agbcc's `{u16}`
|
|
18
|
+
// read whole by an add, `gH = *(s32 *)&s + gH;`) is spelled `*(s32 *)&s`, the frame-member spelling
|
|
19
|
+
// on every toolchain (ir/types.ts `spellsAccessType`): the value is right.
|
|
20
|
+
//
|
|
21
|
+
// WHAT THE IMAGE LEAVES OUT IS NO VALUE. A struct of one or two bytes — mwcc's; agbcc rounds every
|
|
22
|
+
// struct to four — fills only that much of the register, and the rest holds whatever the callee
|
|
23
|
+
// left. The load reads it as zero, which is right only where nothing reads it, so every reader of
|
|
24
|
+
// the register must need only the struct's bits (`demandedBits`): a truncating store, a shift that
|
|
25
|
+
// drops the rest, a mask. A function that reads past them — stores the whole word, compares it,
|
|
26
|
+
// returns it where its own declaration says it returns a value — declines; one whose return
|
|
27
|
+
// nothing declares returns no value instead, where that guessed return is the only such reader.
|
|
28
|
+
import { demandedBits } from '../ir/bits';
|
|
29
|
+
import {
|
|
30
|
+
type Block,
|
|
31
|
+
CALL_RETURN_OBJECT,
|
|
32
|
+
type Fn,
|
|
33
|
+
OWN_RETURN_OBJECT,
|
|
34
|
+
type Op,
|
|
35
|
+
REGISTER_STRUCT,
|
|
36
|
+
RETURNS_OBJECT,
|
|
37
|
+
STRUCT_HOMED,
|
|
38
|
+
type Value,
|
|
39
|
+
calleeName,
|
|
40
|
+
defOpMap,
|
|
41
|
+
mkOp,
|
|
42
|
+
mkValue,
|
|
43
|
+
replaceAllUsesWith,
|
|
44
|
+
} from '../ir/core';
|
|
45
|
+
import { qualified } from '../ir/discipline';
|
|
46
|
+
import { type IrType, T, typeEquals } from '../ir/types';
|
|
47
|
+
import { type FnProto, type Prototypes, declaresVoidReturn } from '../proto';
|
|
48
|
+
import type { TargetDescription } from '../target';
|
|
49
|
+
import { registerStructLocal } from './call-plan';
|
|
50
|
+
import { FrontendUnsupportedError } from './errors';
|
|
51
|
+
|
|
52
|
+
/** One call's struct, as `liftRegisterStructCalls` re-read it: the block it was assigned in, the
|
|
53
|
+
* local, the struct's size, the image word the frontend read, and the load of the struct's bytes
|
|
54
|
+
* (the word itself where no shift moves them). */
|
|
55
|
+
interface Image {
|
|
56
|
+
block: Block;
|
|
57
|
+
callee: string;
|
|
58
|
+
local: Value;
|
|
59
|
+
size: number;
|
|
60
|
+
word: Value;
|
|
61
|
+
loaded: Value;
|
|
62
|
+
/** the call as it was before it took the local, and the ops it added (`dropUnreadImage`) */
|
|
63
|
+
call: Op;
|
|
64
|
+
operands: Value[];
|
|
65
|
+
attrs: Op['attrs'];
|
|
66
|
+
added: Op[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** What `liftRegisterStructCalls` did to the function's own return. */
|
|
70
|
+
export interface OwnReturnOfCalls {
|
|
71
|
+
/** every `ret` hands back a call's struct whole, by value (`return mk(x);`) */
|
|
72
|
+
handedBack: boolean;
|
|
73
|
+
/** some call's struct is read in this function, so a word the function returns may be computed
|
|
74
|
+
* from it: through an op, a join, or a frame slot it is stored to and reloaded from (mwcc's home,
|
|
75
|
+
* an array, a local whose address escapes). The bytes do not say whether it is, and no pass can
|
|
76
|
+
* follow every such route through memory, so pipeline.ts `returnOwnStruct` refuses an own word
|
|
77
|
+
* return in any function that reads one. */
|
|
78
|
+
readsCallStruct: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Rewrites every call `REGISTER_STRUCT` marks into the struct assigned to a local of its declared
|
|
82
|
+
* type, its word re-read off that local. `prototypes` is the table the frontend planned the calls
|
|
83
|
+
* from, so each marked callee's declaration lays the local out; `ownDeclaration` is the function's
|
|
84
|
+
* own, stated or the context's, which says whether its return reads the register, and `ownStruct`
|
|
85
|
+
* the struct it declares it returns in that register, if it does.
|
|
86
|
+
*
|
|
87
|
+
* A FUNCTION THAT RETURNS WHAT A CALL RETURNED returns the call: where every `ret` hands back the
|
|
88
|
+
* very image a call to a callee declared to return the function's own struct left in the
|
|
89
|
+
* register, and nothing else reads that image, the call's value is the struct and each `ret`
|
|
90
|
+
* returns it, `return mk(x);`. Split into the local and a member copy instead, `ret.x = mk_ret.x`,
|
|
91
|
+
* it compiles to a frame copy the source never had (mwcc: `stw r3,8(r1)`). */
|
|
92
|
+
export function liftRegisterStructCalls(
|
|
93
|
+
fn: Fn,
|
|
94
|
+
prototypes: Prototypes,
|
|
95
|
+
target: TargetDescription,
|
|
96
|
+
ownDeclaration: FnProto | undefined,
|
|
97
|
+
ownStruct: Extract<IrType, { kind: 'struct' }> | undefined,
|
|
98
|
+
): OwnReturnOfCalls {
|
|
99
|
+
const bigEndian = target.capabilities.endianness === 'big';
|
|
100
|
+
const images: Image[] = [];
|
|
101
|
+
const rets = fn.blocks.map((b) => b.ops[b.ops.length - 1]).filter((op) => op?.opcode === 'ret');
|
|
102
|
+
const marked = new Map<Value, Op>();
|
|
103
|
+
for (const op of fn.blocks.flatMap((b) => b.ops)) {
|
|
104
|
+
if (op.opcode === 'call' && op.attrs[REGISTER_STRUCT] !== undefined) {
|
|
105
|
+
marked.set(op.results[0], op);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (marked.size === 0) {
|
|
109
|
+
return { handedBack: false, readsCallStruct: false };
|
|
110
|
+
}
|
|
111
|
+
const defs = defOpMap(fn);
|
|
112
|
+
const isFrameAddress = (v: Value): boolean => {
|
|
113
|
+
const d = defs.get(v);
|
|
114
|
+
return d?.opcode === 'laddr' && d.attrs[OWN_RETURN_OBJECT] !== true && d.attrs[CALL_RETURN_OBJECT] === undefined;
|
|
115
|
+
};
|
|
116
|
+
// a store of the image into a frame word is its home (`foldFrameHome`), which reads no member
|
|
117
|
+
const homeStore = (op: Op, word: Value): boolean =>
|
|
118
|
+
op.opcode === 'store' &&
|
|
119
|
+
op.operands[1] === word &&
|
|
120
|
+
op.operands[0] !== word &&
|
|
121
|
+
isFrameAddress(op.operands[0]) &&
|
|
122
|
+
!qualified(op);
|
|
123
|
+
const handedBack =
|
|
124
|
+
ownStruct !== undefined &&
|
|
125
|
+
rets.length > 0 &&
|
|
126
|
+
rets.every((r) => {
|
|
127
|
+
const word = r.operands.length === 1 ? r.operands[0] : undefined;
|
|
128
|
+
const call = word === undefined ? undefined : marked.get(word);
|
|
129
|
+
const type = call === undefined ? undefined : registerStructLocal(prototypes[calleeName(call)!], target);
|
|
130
|
+
return (
|
|
131
|
+
type !== undefined &&
|
|
132
|
+
typeEquals(type, ownStruct) &&
|
|
133
|
+
fn.blocks.every((b) =>
|
|
134
|
+
b.ops.every(
|
|
135
|
+
(op) =>
|
|
136
|
+
(op === r || homeStore(op, word!) || !op.operands.includes(word!)) &&
|
|
137
|
+
op.successors.every((e) => !e.args.includes(word!)),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
// a struct the source names — it has a home — is returned as that local, `return mk_ret;`; one
|
|
143
|
+
// with none is the call itself, `return mk(x);`
|
|
144
|
+
const returnedWhole = new Map<Value, Op>();
|
|
145
|
+
if (handedBack) {
|
|
146
|
+
for (const r of rets) {
|
|
147
|
+
const word = r.operands[0];
|
|
148
|
+
const call = marked.get(word)!;
|
|
149
|
+
const homed =
|
|
150
|
+
call.attrs[REGISTER_STRUCT] === STRUCT_HOMED || fn.blocks.some((b) => b.ops.some((op) => homeStore(op, word)));
|
|
151
|
+
if (homed) {
|
|
152
|
+
returnedWhole.set(word, r);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const { [REGISTER_STRUCT]: _, ...attrs } = call.attrs;
|
|
156
|
+
call.attrs = attrs;
|
|
157
|
+
call.results = [mkValue(ownStruct)];
|
|
158
|
+
r.operands = [call.results[0]];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const b of fn.blocks) {
|
|
162
|
+
for (let i = 0; i < b.ops.length; i++) {
|
|
163
|
+
const call = b.ops[i];
|
|
164
|
+
if (call.opcode !== 'call' || call.attrs[REGISTER_STRUCT] === undefined) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const callee = calleeName(call);
|
|
168
|
+
const own = callee !== undefined && Object.hasOwn(prototypes, callee) ? prototypes[callee] : undefined;
|
|
169
|
+
// the type the plan marked the call by (`registerStructLocal` is its one derivation)
|
|
170
|
+
const type = registerStructLocal(own, target);
|
|
171
|
+
if (callee === undefined || type?.size === undefined) {
|
|
172
|
+
throw new Error(`'${fn.name}': a call marked to return a struct in a register has no declared struct`);
|
|
173
|
+
}
|
|
174
|
+
const size = type.size;
|
|
175
|
+
const word = call.results[0];
|
|
176
|
+
const { [REGISTER_STRUCT]: _, ...attrs } = call.attrs;
|
|
177
|
+
// a struct nothing reads is the call's statement alone, as a discarded scalar is
|
|
178
|
+
const read = fn.blocks.some(
|
|
179
|
+
(x) =>
|
|
180
|
+
x.ops.some((op) => op.operands.includes(word)) ||
|
|
181
|
+
x.ops.some((op) => op.successors.some((e) => e.args.includes(word))),
|
|
182
|
+
);
|
|
183
|
+
if (!read) {
|
|
184
|
+
call.attrs = attrs;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const object = mkOp('laddr', {
|
|
188
|
+
results: [mkValue(T.ptr(type))],
|
|
189
|
+
// one local per call, which `nth` keeps apart: two calls' structs are two objects, and one
|
|
190
|
+
// read of the first may come after the second
|
|
191
|
+
attrs: {
|
|
192
|
+
off: 0,
|
|
193
|
+
width: 1,
|
|
194
|
+
signed: false,
|
|
195
|
+
count: size,
|
|
196
|
+
aggregate: true,
|
|
197
|
+
[CALL_RETURN_OBJECT]: callee,
|
|
198
|
+
nth: images.length,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
const local = object.results[0];
|
|
202
|
+
// the local's address is the call's first operand, as a hidden pointer's is, so every index
|
|
203
|
+
// the call names into its operands moves up one
|
|
204
|
+
const shifted = (key: string, step: number) =>
|
|
205
|
+
Array.isArray(attrs[key])
|
|
206
|
+
? { [key]: (attrs[key] as number[]).map((v, k) => (k % step === 0 ? v + 1 : v)) }
|
|
207
|
+
: {};
|
|
208
|
+
call.attrs = {
|
|
209
|
+
...attrs,
|
|
210
|
+
sret: true,
|
|
211
|
+
...shifted('doubles', 1),
|
|
212
|
+
...shifted('byValue', 2),
|
|
213
|
+
...shifted('byValueWords', 2),
|
|
214
|
+
};
|
|
215
|
+
const operands = call.operands;
|
|
216
|
+
call.operands = [local, ...call.operands];
|
|
217
|
+
call.results = [mkValue(type)];
|
|
218
|
+
const lift = 32 - 8 * size;
|
|
219
|
+
const loaded = bigEndian && lift > 0 ? mkValue(T.unk(32)) : word;
|
|
220
|
+
const reads = [
|
|
221
|
+
mkOp('load', { operands: [local], results: [loaded], attrs: { off: 0, width: size, signed: false } }),
|
|
222
|
+
];
|
|
223
|
+
if (loaded !== word) {
|
|
224
|
+
reads.push(mkOp('shl', { operands: [loaded], results: [word], attrs: { imm: lift } }));
|
|
225
|
+
}
|
|
226
|
+
b.ops.splice(i + 1, 0, ...reads);
|
|
227
|
+
// the locals open the entry block in the order of their calls
|
|
228
|
+
fn.blocks[0].ops.splice(images.length, 0, object);
|
|
229
|
+
if (b === fn.blocks[0]) {
|
|
230
|
+
i++;
|
|
231
|
+
}
|
|
232
|
+
images.push({ block: b, callee, local, size, word, loaded, call, operands, attrs, added: [object, ...reads] });
|
|
233
|
+
const ret = returnedWhole.get(word);
|
|
234
|
+
if (ret !== undefined) {
|
|
235
|
+
ret.operands = [local];
|
|
236
|
+
ret.attrs = { ...ret.attrs, [RETURNS_OBJECT]: true };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// THE MEMBER TAKEN BACK DOWN IS THE LOAD. A big-endian caller reads a sub-word struct's first
|
|
242
|
+
// member by shifting the register down by exactly what the image shifted it up (`srwi r0,r3,16`
|
|
243
|
+
// for a `{u16}`), and the load is narrower than the shift leaves room for, so the two shifts
|
|
244
|
+
// cancel: what that shift computes is the load itself.
|
|
245
|
+
for (const { word, loaded, size } of images) {
|
|
246
|
+
for (const op of fn.blocks.flatMap((x) => x.ops)) {
|
|
247
|
+
if (
|
|
248
|
+
loaded !== word &&
|
|
249
|
+
op.opcode === 'shr_u' &&
|
|
250
|
+
op.operands.length === 1 &&
|
|
251
|
+
op.operands[0] === word &&
|
|
252
|
+
op.attrs.imm === 32 - 8 * size
|
|
253
|
+
) {
|
|
254
|
+
replaceAllUsesWith(fn, op.results[0], loaded);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const image of images) {
|
|
259
|
+
foldFrameHome(fn, image);
|
|
260
|
+
}
|
|
261
|
+
// what the image leaves out must be what no reader reads (`demandedBits`)
|
|
262
|
+
const structBits = (size: number): number =>
|
|
263
|
+
size >= 4 ? FULL : bigEndian ? (FULL << (32 - 8 * size)) >>> 0 : (1 << (8 * size)) - 1;
|
|
264
|
+
const readsPast = ({ word, size }: Image, voidOwn: boolean): boolean =>
|
|
265
|
+
(demandedBits(fn, word, voidOwn) & ~structBits(size)) >>> 0 !== 0;
|
|
266
|
+
// A RETURN NOTHING DECLARES IS NO READER OF WHAT THE IMAGE LEAVES OUT. With no own declaration,
|
|
267
|
+
// the `ret` reading the return register is the frontend's guess (a scalar callee's word is
|
|
268
|
+
// returned the same way, `v0 = g(); return v0;`), and the bytes do not decide it, as they do not
|
|
269
|
+
// decide a guessed arity (frontend/ssa.ts `noteStructImage`). Where that guess is the only reader
|
|
270
|
+
// past the struct's bits, and every `ret` hands back a call's image as it came back, the function
|
|
271
|
+
// returns no value: the register below a struct of one or two bytes holds none, so no C return
|
|
272
|
+
// can be what it holds, and `void` is the spelling those bytes compile from.
|
|
273
|
+
if (
|
|
274
|
+
ownDeclaration === undefined &&
|
|
275
|
+
images.some((image) => readsPast(image, false)) &&
|
|
276
|
+
images.every((image) => !readsPast(image, true)) &&
|
|
277
|
+
rets.every((r) => r.operands.length === 1 && images.some(({ word }) => word === r.operands[0]))
|
|
278
|
+
) {
|
|
279
|
+
for (const r of rets) {
|
|
280
|
+
r.operands = [];
|
|
281
|
+
}
|
|
282
|
+
// …and a struct only that guess read is now one nothing reads: the call's statement alone
|
|
283
|
+
for (const image of [...images]) {
|
|
284
|
+
if (dropUnreadImage(fn, image)) {
|
|
285
|
+
images.splice(images.indexOf(image), 1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const voidOwn = declaresVoidReturn(ownDeclaration);
|
|
290
|
+
for (const image of images) {
|
|
291
|
+
const { size, callee, word } = image;
|
|
292
|
+
if (readsPast(image, voidOwn)) {
|
|
293
|
+
// a home `foldFrameHome` left — one read outside the call's block (mwcc 2.3.3 stores the
|
|
294
|
+
// word whole in each arm of an `if` and reads the member in the join), or written again
|
|
295
|
+
// before it is read — is no read past the struct, only one this fold does not follow
|
|
296
|
+
if (fn.blocks.some((b) => b.ops.some((op) => homeStore(op, word) && op.attrs.width === 4))) {
|
|
297
|
+
throw new FrontendUnsupportedError(
|
|
298
|
+
`cannot lift '${fn.name}': local stack frames not supported — \`${callee}\` hands back a struct of ` +
|
|
299
|
+
`${size} byte${size === 1 ? '' : 's'} in ${target.returnReg}, and its frame home is written again or ` +
|
|
300
|
+
"read where the fold into the call's local does not reach",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
throw new FrontendUnsupportedError(
|
|
304
|
+
`cannot lift '${fn.name}': \`${callee}\` hands back a struct of ${size} byte${size === 1 ? '' : 's'} in ` +
|
|
305
|
+
`${target.returnReg}, and this function reads bits of the register past it — what the callee left there is no value`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { handedBack, readsCallStruct: images.length > 0 };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Put back the call `image` was built from, where nothing outside the ops it added reads its
|
|
313
|
+
* word, its load or its local. */
|
|
314
|
+
function dropUnreadImage(fn: Fn, image: Image): boolean {
|
|
315
|
+
const own = new Set([image.call, ...image.added]);
|
|
316
|
+
const read = (v: Value): boolean =>
|
|
317
|
+
fn.blocks.some((b) =>
|
|
318
|
+
b.ops.some((op) => !own.has(op) && (op.operands.includes(v) || op.successors.some((e) => e.args.includes(v)))),
|
|
319
|
+
);
|
|
320
|
+
if (read(image.word) || read(image.loaded) || read(image.local)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
image.call.operands = image.operands;
|
|
324
|
+
image.call.attrs = image.attrs;
|
|
325
|
+
image.call.results = [image.word];
|
|
326
|
+
for (const b of fn.blocks) {
|
|
327
|
+
b.ops = b.ops.filter((op) => !image.added.includes(op));
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** THE STRUCT'S FRAME HOME IS THE LOCAL. A compiler that gives the struct a home in the frame stores
|
|
333
|
+
* the register there as it comes back (mwcc: `bl mk; stw r3,8(r1); lhz r0,8(r1)` for `s = mk(x);
|
|
334
|
+
* gOut = s.a;`) and reads its members off the home, so the home's bytes are the local's. Each load
|
|
335
|
+
* of them is rewritten to read the local, which spells it as the member it is, and the store goes
|
|
336
|
+
* where nothing else touches those bytes — the C's own assignment is that store.
|
|
337
|
+
*
|
|
338
|
+
* Only what the bytes prove is folded. The home is a frame object whose address is no value — only
|
|
339
|
+
* ever the base of a load or a store — so nothing but those accesses reaches its bytes; the store
|
|
340
|
+
* writes the image whole (the word, or on big-endian the struct's own bytes, `sth`), unqualified,
|
|
341
|
+
* in the call's block; and only the loads that follow it there, before any other store to the
|
|
342
|
+
* home's bytes, and that lie inside the struct, read what it stored. A load of the pad past a
|
|
343
|
+
* struct of one or two bytes reads no member, and stops the walk. */
|
|
344
|
+
function foldFrameHome(fn: Fn, { block, local, size, word, loaded }: Image): void {
|
|
345
|
+
const defs = defOpMap(fn);
|
|
346
|
+
const frameOff = (v: Value): number | undefined => {
|
|
347
|
+
const d = defs.get(v);
|
|
348
|
+
return d?.opcode === 'laddr' &&
|
|
349
|
+
d.attrs[CALL_RETURN_OBJECT] === undefined &&
|
|
350
|
+
d.attrs[OWN_RETURN_OBJECT] !== true &&
|
|
351
|
+
typeof d.attrs.off === 'number'
|
|
352
|
+
? d.attrs.off
|
|
353
|
+
: undefined;
|
|
354
|
+
};
|
|
355
|
+
const at = block.ops.findIndex(
|
|
356
|
+
(op) =>
|
|
357
|
+
op.opcode === 'store' &&
|
|
358
|
+
frameOff(op.operands[0]) !== undefined &&
|
|
359
|
+
!qualified(op) &&
|
|
360
|
+
((op.operands[1] === word && op.attrs.width === 4) || (op.operands[1] === loaded && op.attrs.width === size)),
|
|
361
|
+
);
|
|
362
|
+
if (at < 0) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const home = block.ops[at];
|
|
366
|
+
const base = frameOff(home.operands[0])! + (home.attrs.off as number);
|
|
367
|
+
const touches = (op: Op): boolean => {
|
|
368
|
+
const off = frameOff(op.operands[0]);
|
|
369
|
+
const from = (off ?? NaN) + (op.attrs.off as number);
|
|
370
|
+
return from < base + (home.attrs.width as number) && from + (op.attrs.width as number) > base;
|
|
371
|
+
};
|
|
372
|
+
// every frame address in the function is only the base of an access, or nothing may be folded
|
|
373
|
+
const ops = fn.blocks.flatMap((b) => b.ops);
|
|
374
|
+
for (const op of ops) {
|
|
375
|
+
const bases = op.opcode === 'load' || op.opcode === 'store' ? 1 : 0;
|
|
376
|
+
if (op.operands.slice(bases).some((v) => frameOff(v) !== undefined)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const op of block.ops.slice(at + 1)) {
|
|
381
|
+
if (op.opcode === 'store' && touches(op)) {
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
if (op.opcode !== 'load' || !touches(op)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const from = frameOff(op.operands[0])! + (op.attrs.off as number) - base;
|
|
388
|
+
if (from < 0 || from + (op.attrs.width as number) > size || qualified(op)) {
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
op.operands = [local, ...op.operands.slice(1)];
|
|
392
|
+
op.attrs = { ...op.attrs, off: from };
|
|
393
|
+
}
|
|
394
|
+
if (!ops.some((op) => op !== home && (op.opcode === 'load' || op.opcode === 'store') && touches(op))) {
|
|
395
|
+
block.ops.splice(block.ops.indexOf(home), 1);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const FULL = 0xffffffff;
|
|
@@ -13,13 +13,18 @@
|
|
|
13
13
|
// name, and every refusing kind gets its own sentence naming what was seen.
|
|
14
14
|
//
|
|
15
15
|
// Consumed by frontend/ppc.ts, which refuses before it recovers, and by frontend/thumb.ts's
|
|
16
|
-
// literal-pool reader.
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
16
|
+
// literal-pool reader. Two kinds are names a lift may still WRITE, because the object's definition
|
|
17
|
+
// is in hand and the definition is what the source wrote:
|
|
18
|
+
// • a function-scope static (`local-static`): the function DEFINES it, so both frontends read its
|
|
19
|
+
// definition out of the target (frontend/local-object.ts) and the lift writes it in the body
|
|
20
|
+
// under the name `localStaticName` takes apart, refusing only where the definition cannot be read;
|
|
21
|
+
// • the compiler's anonymous constant (`anon-pool`): no source names it, but a local aggregate's
|
|
22
|
+
// initializer image is what the source wrote. Thumb reads it as `LocalObject.anonymous`, which
|
|
23
|
+
// l3/constinit.ts spells back as that initializer or refuses. ppc still refuses `@N` by name:
|
|
24
|
+
// every one a lift reaches is a float or a string handed out by address, and mwcc copies an
|
|
25
|
+
// image inline into a frame local its frontend refuses first.
|
|
26
|
+
// Thumb asks about those kinds and the predefined names alone rather than taking
|
|
27
|
+
// `unspellableReason`'s answer for every kind, because a pool word is not a relocation.
|
|
23
28
|
//
|
|
24
29
|
// A kind is listed only when it behaves differently: the decomp projects' generated labels
|
|
25
30
|
// (`lbl_1_bss_2464`, `fn_1_458`) are ordinary identifiers that the project's own headers declare
|
|
@@ -55,6 +60,9 @@ export type RelocSymbolKind =
|
|
|
55
60
|
* {@link classifyRelocSymbol}. */
|
|
56
61
|
const LOCAL_STATIC = /^([A-Za-z_][A-Za-z0-9_]*)[.$](\d+)$/;
|
|
57
62
|
|
|
63
|
+
/** A GNU assembler local label, `.L` and the rest of an identifier: `.LC0`, `.L5`. */
|
|
64
|
+
const GAS_LOCAL = /^\.L[\w.$]*$/;
|
|
65
|
+
|
|
58
66
|
/** The identifiers a compiler defines inside every function, whose objects it names like a static. */
|
|
59
67
|
const PREDEFINED = new Set(['__func__', '__FUNCTION__', '__PRETTY_FUNCTION__']);
|
|
60
68
|
|
|
@@ -69,6 +77,13 @@ export function classifyRelocSymbol(sym: string): RelocSymbolKind {
|
|
|
69
77
|
if (sym.startsWith('@')) {
|
|
70
78
|
return 'anon-pool'; // `@193` — mwcc's anonymous string/constant pool entries
|
|
71
79
|
}
|
|
80
|
+
// `.LC0` — a GNU assembler LOCAL label (the `.L` prefix keeps it out of the symbol table), which
|
|
81
|
+
// is how gcc, and so agbcc, names an anonymous constant (varasm.c `output_constant_def`). Asked
|
|
82
|
+
// before `section-local`, whose leading dot it shares: it labels a datum, not a section. A `.L`
|
|
83
|
+
// label may label code as well; reading the definition is what tells the two apart.
|
|
84
|
+
if (GAS_LOCAL.test(sym)) {
|
|
85
|
+
return 'anon-pool';
|
|
86
|
+
}
|
|
72
87
|
if (sym.startsWith('.')) {
|
|
73
88
|
return 'section-local'; // `...bss.0`, `.rodata` — an offset into a section
|
|
74
89
|
}
|