@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,616 @@
|
|
|
1
|
+
// asmlift — a function-scope static's DEFINITION, read out of the target.
|
|
2
|
+
//
|
|
3
|
+
// A relocation (PPC) or a literal-pool word (Thumb) names a function-scope static by the linker's
|
|
4
|
+
// name, `sprHideTbl$797` or `tide.3`, and the counter in it is the compiler's
|
|
5
|
+
// (frontend/reloc-symbol.ts). No declaration elsewhere in the project describes the object: the
|
|
6
|
+
// source DEFINED it inside the function. So a lift that names it must also define it, and
|
|
7
|
+
// everything a definition needs is in the target: the section says the qualifier and whether it
|
|
8
|
+
// has an initializer, the size says how big it is, and the bytes are the initializer.
|
|
9
|
+
//
|
|
10
|
+
// This module reads that, and what an assembler listing shows beyond it — the directives the
|
|
11
|
+
// compiler wrote the initializer with and the alignment it placed the object at, which are facts
|
|
12
|
+
// about the source's type. Which C type the definition gets is decided where the accesses are
|
|
13
|
+
// spelled (structure/local-statics.ts).
|
|
14
|
+
//
|
|
15
|
+
// It REFUSES rather than guesses — with the tail of a sentence the caller opens by naming the
|
|
16
|
+
// static — when:
|
|
17
|
+
// • the target does not carry the definition (the symbol is absent, or its label heads no data);
|
|
18
|
+
// • the object sits in a section that is not read-only data, initialized data or bss;
|
|
19
|
+
// • a relocation falls inside its bytes that is no pointer-width absolute address of an ordinary
|
|
20
|
+
// global: one narrower or wider than a pointer, or off the object's words, or of another type,
|
|
21
|
+
// or naming what no declaration outside the function can (a section, the compiler's anonymous
|
|
22
|
+
// constant, another function-scope static — reloc-symbol.ts's kinds). Such an address is
|
|
23
|
+
// RECORDED (`LocalObject.relocs`) and left to the reader of the object to spell or refuse
|
|
24
|
+
// (structure/local-statics.ts);
|
|
25
|
+
// • its extent cannot be read: a directive inside its data run this reader does not parse, a
|
|
26
|
+
// size that disagrees with the bytes under the label, or an empty object;
|
|
27
|
+
// • its alignment cannot be read: an `.align` form this reader does not parse, or an object file
|
|
28
|
+
// whose `.comment` section does not record it;
|
|
29
|
+
// • another function names it too. A static of an inlined same-unit function is named by every
|
|
30
|
+
// function the compiler inlined it into — agbcc 2.9 puts `static inline counter`'s `n.3`
|
|
31
|
+
// ahead of its first caller, and mwcc -inline auto has `A` and `B` both address `n$4` — so
|
|
32
|
+
// re-declaring it inside one of them would split one object in two. Only the functions this
|
|
33
|
+
// input shows can be checked, which is the limit of this refusal: a single-function input
|
|
34
|
+
// cannot show a second referrer, and one that ends at this function cannot show a later one.
|
|
35
|
+
import {
|
|
36
|
+
type LocalObject,
|
|
37
|
+
type LocalObjectReloc,
|
|
38
|
+
type LocalObjects,
|
|
39
|
+
LOCAL_OBJECT_ADDRESS_BYTES as POINTER_BYTES,
|
|
40
|
+
} from '../ir/core';
|
|
41
|
+
import type { AsmData } from './asmdata';
|
|
42
|
+
import { classifyRelocSymbol, localStaticName, unspellableReason } from './reloc-symbol';
|
|
43
|
+
|
|
44
|
+
/** A definition, or why there is none: the tail of a refusal sentence the caller opens with
|
|
45
|
+
* "names a function-scope static ('<symbol>')", so every refusal of the kind reads the same. */
|
|
46
|
+
export type LocalObjectRead = LocalObject | { refused: string };
|
|
47
|
+
|
|
48
|
+
const refused = (why: string): { refused: string } => ({ refused: why });
|
|
49
|
+
|
|
50
|
+
/** Why an initializer's address of `symbol` cannot be recorded, as the tail of the refusal
|
|
51
|
+
* sentence — or null for an ordinary global's, which a declaration outside the function names.
|
|
52
|
+
* Every other kind is the naming policy's own (reloc-symbol.ts), with its own sentence. */
|
|
53
|
+
function addressRefusal(symbol: string): string | null {
|
|
54
|
+
const why = unspellableReason(symbol);
|
|
55
|
+
return why === null ? null : `whose initializer holds an address that ${why}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── GNU as text (agbcc) ──────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/** A line without its `@` comment, where the `@` is not inside a string literal. */
|
|
61
|
+
function code(line: string): string {
|
|
62
|
+
let quoted = false;
|
|
63
|
+
for (let i = 0; i < line.length; i++) {
|
|
64
|
+
const c = line[i];
|
|
65
|
+
if (c === '\\' && quoted) {
|
|
66
|
+
i++;
|
|
67
|
+
} else if (c === '"') {
|
|
68
|
+
quoted = !quoted;
|
|
69
|
+
} else if (c === '@' && !quoted) {
|
|
70
|
+
return line.slice(0, i).trim();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return line.trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The section a GNU as section directive switches to, or null for a line that is not one. */
|
|
77
|
+
function sectionSwitch(line: string): string | null {
|
|
78
|
+
const m = line.match(/^\.(?:section\s+([^\s,]+)|(text|data|bss)\b)/);
|
|
79
|
+
return m ? (m[1] ?? `.${m[2]}`) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const GAS_SECTIONS: Readonly<Record<string, LocalObject['section']>> = {
|
|
83
|
+
'.rodata': 'rodata',
|
|
84
|
+
'.data': 'data',
|
|
85
|
+
'.bss': 'bss',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** A GNU as integer literal: decimal, `0x` hex or `0` octal, optionally negated. */
|
|
89
|
+
function gasInteger(s: string): number | null {
|
|
90
|
+
const m = s.trim().match(/^(-?)(0x[0-9a-f]+|0[0-7]*|[1-9]\d*)$/i);
|
|
91
|
+
if (!m) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const radix = /^0x/i.test(m[2]) ? 16 : /^0\d/.test(m[2]) ? 8 : 10;
|
|
95
|
+
const mag = parseInt(radix === 16 ? m[2].slice(2) : m[2], radix);
|
|
96
|
+
return m[1] ? -mag : mag;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The named escapes agbcc's ASM_OUTPUT_ASCII writes (agbcc/gcc/thumb.h:195-260); every other
|
|
100
|
+
* unprintable byte it writes as `\ooo` octal. */
|
|
101
|
+
const GAS_ESCAPES: Readonly<Record<string, number>> = { n: 10, t: 9, r: 13, f: 12, b: 8, '\\': 92, '"': 34 };
|
|
102
|
+
|
|
103
|
+
/** The bytes of a GNU as string literal body (between its quotes), or null for an escape this
|
|
104
|
+
* reader does not decode. */
|
|
105
|
+
function gasString(body: string): number[] | null {
|
|
106
|
+
const out: number[] = [];
|
|
107
|
+
for (let i = 0; i < body.length; i++) {
|
|
108
|
+
const c = body[i];
|
|
109
|
+
if (c !== '\\') {
|
|
110
|
+
out.push(c.charCodeAt(0) & 0xff);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const n = body[++i];
|
|
114
|
+
const oct = body.slice(i).match(/^[0-7]{1,3}/);
|
|
115
|
+
if (oct) {
|
|
116
|
+
out.push(parseInt(oct[0], 8) & 0xff);
|
|
117
|
+
i += oct[0].length - 1;
|
|
118
|
+
} else if (n in GAS_ESCAPES) {
|
|
119
|
+
out.push(GAS_ESCAPES[n]);
|
|
120
|
+
} else {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Little-endian bytes of `value` at `width` bytes — the only byte order agbcc's target has. */
|
|
128
|
+
const le = (value: number, width: number): number[] =>
|
|
129
|
+
Array.from({ length: width }, (_, i) => (value >>> (8 * i)) & 0xff);
|
|
130
|
+
|
|
131
|
+
/** What one data directive writes: its bytes, the width of each value it writes (null for
|
|
132
|
+
* padding, which writes none), whether one of those values is negative, whether it writes a
|
|
133
|
+
* string, and the addresses among its values, at their byte offsets in the directive. */
|
|
134
|
+
interface DirectiveData {
|
|
135
|
+
bytes: number[];
|
|
136
|
+
width: number | null;
|
|
137
|
+
negative: boolean;
|
|
138
|
+
string: boolean;
|
|
139
|
+
relocs: LocalObjectReloc[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `sym`, `sym+K` or `sym-K` — the operand GNU as relocates to a symbol's address plus a constant. */
|
|
143
|
+
const GAS_ADDRESS = /^([A-Za-z_.$][\w.$]*)\s*(?:([+-])\s*(\S+))?$/;
|
|
144
|
+
|
|
145
|
+
/** One data directive's data; `{ refused }` for an operand that is no number and no address this
|
|
146
|
+
* reader records, null when the directive is not a data directive this reader parses. */
|
|
147
|
+
function dataBytes(directive: string, operands: string): DirectiveData | { refused: string } | null {
|
|
148
|
+
const width = /^(byte)$/.test(directive)
|
|
149
|
+
? 1
|
|
150
|
+
: /^(short|hword|2byte)$/.test(directive)
|
|
151
|
+
? 2
|
|
152
|
+
: /^(word|long|int|4byte)$/.test(directive)
|
|
153
|
+
? 4
|
|
154
|
+
: 0;
|
|
155
|
+
if (width > 0) {
|
|
156
|
+
const out: number[] = [];
|
|
157
|
+
const relocs: LocalObjectReloc[] = [];
|
|
158
|
+
let negative = false;
|
|
159
|
+
for (const op of operands.split(',')) {
|
|
160
|
+
const v = gasInteger(op);
|
|
161
|
+
if (v !== null) {
|
|
162
|
+
negative ||= v < 0;
|
|
163
|
+
out.push(...le(v, width));
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Not a number, so the assembler relocates the value: an address, which the bytes do not hold.
|
|
167
|
+
const a = op.trim().match(GAS_ADDRESS);
|
|
168
|
+
// agbcc writes a negative addend `garr+-0x4` (compiled: `&garr[-1]`), so the constant keeps a sign
|
|
169
|
+
const k = a === null ? null : a[3] === undefined ? 0 : gasInteger(a[3]);
|
|
170
|
+
if (a === null || k === null) {
|
|
171
|
+
return refused(`whose initializer holds '${op.trim()}', an expression this reader does not read as an address`);
|
|
172
|
+
}
|
|
173
|
+
if (width !== POINTER_BYTES) {
|
|
174
|
+
return refused(
|
|
175
|
+
`whose initializer holds the address '${op.trim()}' in a ${width}-byte value — not a pointer's ${POINTER_BYTES}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const why = addressRefusal(a[1]);
|
|
179
|
+
if (why !== null) {
|
|
180
|
+
return refused(why);
|
|
181
|
+
}
|
|
182
|
+
relocs.push({ offset: out.length, symbol: a[1], addend: a[2] === '-' ? -k : k });
|
|
183
|
+
out.push(...le(0, width));
|
|
184
|
+
}
|
|
185
|
+
return { bytes: out, width, negative, string: false, relocs };
|
|
186
|
+
}
|
|
187
|
+
if (/^(space|skip|zero)$/.test(directive)) {
|
|
188
|
+
const [n, fill, ...rest] = operands.split(',').map((s) => gasInteger(s));
|
|
189
|
+
return n === null || n < 0 || rest.length > 0 || fill === null
|
|
190
|
+
? null
|
|
191
|
+
: {
|
|
192
|
+
bytes: new Array<number>(n).fill((fill ?? 0) & 0xff),
|
|
193
|
+
width: null,
|
|
194
|
+
negative: false,
|
|
195
|
+
string: false,
|
|
196
|
+
relocs: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (/^(ascii|asciz|string)$/.test(directive)) {
|
|
200
|
+
const m = operands.trim().match(/^"((?:[^"\\]|\\.)*)"$/);
|
|
201
|
+
const s = m ? gasString(m[1]) : null;
|
|
202
|
+
return s === null
|
|
203
|
+
? null
|
|
204
|
+
: { bytes: directive === 'ascii' ? s : [...s, 0], width: 1, negative: false, string: true, relocs: [] };
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The alignment written ahead of the label on line `at`, in bytes. agbcc writes `.align N` there
|
|
210
|
+
* exactly when the object's alignment exceeds a byte — after the section switch, before `.type`,
|
|
211
|
+
* `.size` and the label (varasm.c:1327-1329 `assemble_variable`) — so none means 1. Null for an
|
|
212
|
+
* alignment directive this reader does not read. */
|
|
213
|
+
function alignAhead(lines: readonly string[], at: number): number | null {
|
|
214
|
+
for (let i = at - 1; i >= 0; i--) {
|
|
215
|
+
const line = lines[i];
|
|
216
|
+
if (line === '' || /^\.(type|size|globl)\b/.test(line)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const a = line.match(/^\.align\s+(\d+)\s*(?:,.*)?$/);
|
|
220
|
+
if (a) {
|
|
221
|
+
return 2 ** Number(a[1]);
|
|
222
|
+
}
|
|
223
|
+
return /^\.(balign|p2align)\b/.test(line) ? null : 1;
|
|
224
|
+
}
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The directives that end an object's data run without being part of it: the next object's
|
|
229
|
+
* alignment or bss allocation, and what ends a file or opens code. Any other directive inside the
|
|
230
|
+
* run is one this reader cannot size, so it refuses there rather than end the object short — an
|
|
231
|
+
* unsized object has no `.size` to catch the truncation (telf.h:297-316 prints one only when the
|
|
232
|
+
* declaration knows the size). */
|
|
233
|
+
const RUN_ENDS: ReadonlySet<string> = new Set([
|
|
234
|
+
'align',
|
|
235
|
+
'balign',
|
|
236
|
+
'p2align',
|
|
237
|
+
'lcomm',
|
|
238
|
+
'comm',
|
|
239
|
+
'ident',
|
|
240
|
+
'end',
|
|
241
|
+
'code',
|
|
242
|
+
'thumb',
|
|
243
|
+
'thumb_func',
|
|
244
|
+
'arm',
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
/** Read a function-scope static's definition out of GNU as text (agbcc's `.s`).
|
|
248
|
+
*
|
|
249
|
+
* Two shapes, both agbcc 2.9's (varasm.c `assemble_variable`): an object with NO initializer is
|
|
250
|
+
* `.lcomm name.N,size` — bss, with the exact size; an initialized one is a label under
|
|
251
|
+
* `.section .rodata` or `.data`, followed by its data directives. `.size name.N,K` is emitted for
|
|
252
|
+
* a sized declarator and NOT for an unsized one (`static const u8 tide[] = {…}` has none), so the
|
|
253
|
+
* extent is the data run under the label, which ends at the next label, section switch,
|
|
254
|
+
* instruction or one of {@link RUN_ENDS}, and `.size` where present must agree with it. */
|
|
255
|
+
export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead {
|
|
256
|
+
const parts = localStaticName(symbol);
|
|
257
|
+
if (parts === null) {
|
|
258
|
+
return refused('whose name has no source spelling this reader knows');
|
|
259
|
+
}
|
|
260
|
+
const { name, counter: order } = parts;
|
|
261
|
+
const read = readGasDefinition(asm, symbol);
|
|
262
|
+
return 'refused' in read ? read : { name, symbol, order, ...read };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Read an ANONYMOUS constant out of GNU as text: the data run under a compiler-generated `.L`
|
|
266
|
+
* label (agbcc's `.LC0`), which is how agbcc 2.9 places the image a local aggregate's constant
|
|
267
|
+
* initializer is copied from (`u8 a[16] = {…}` is `ldr r1,=.LC0; mov r0,sp; mov r2,#16; bl memcpy`,
|
|
268
|
+
* varasm.c `output_constant_def`). Nothing in the source names it, so no declaration can spell it:
|
|
269
|
+
* the object is carried {@link LocalObject.anonymous} and spelled only as the initializer of the
|
|
270
|
+
* local it is copied into (l3/constinit.ts), or refused there.
|
|
271
|
+
*
|
|
272
|
+
* Read-only data only, with the same refusals as a static's definition: a `.L` label anywhere else
|
|
273
|
+
* is code, or data this reader has no reason to believe is a constant image. That gate is agbcc's
|
|
274
|
+
* placement, not every compiler's: IDO 7.1 places the same image in `.data` (compiled). Which
|
|
275
|
+
* names are anonymous is reloc-symbol.ts's decision (`anon-pool`). */
|
|
276
|
+
export function readGasAnonymousObject(asm: string, label: string): LocalObjectRead {
|
|
277
|
+
if (classifyRelocSymbol(label) !== 'anon-pool') {
|
|
278
|
+
return refused('whose label is not a compiler-generated `.L` label');
|
|
279
|
+
}
|
|
280
|
+
const read = readGasDefinition(asm, label);
|
|
281
|
+
if ('refused' in read) {
|
|
282
|
+
return read;
|
|
283
|
+
}
|
|
284
|
+
if (read.section !== 'rodata') {
|
|
285
|
+
return refused(`defined in ${read.section}, which is not the read-only data a constant image sits in`);
|
|
286
|
+
}
|
|
287
|
+
return { name: label, symbol: label, order: -1, anonymous: true, ...read };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** A definition's section, size, bytes and directives, read from the label down. */
|
|
291
|
+
function readGasDefinition(
|
|
292
|
+
asm: string,
|
|
293
|
+
symbol: string,
|
|
294
|
+
): Omit<LocalObject, 'name' | 'symbol' | 'order'> | { refused: string } {
|
|
295
|
+
const lines = asm.split('\n').map(code);
|
|
296
|
+
let declaredSize: number | null = null;
|
|
297
|
+
let section: string | null = null;
|
|
298
|
+
let at = -1;
|
|
299
|
+
let labelSection: string | null = null;
|
|
300
|
+
for (let i = 0; i < lines.length; i++) {
|
|
301
|
+
const line = lines[i];
|
|
302
|
+
const lcomm = line.match(/^\.lcomm\s+([^\s,]+)\s*,\s*(\S+?)(?:\s*,.*)?$/);
|
|
303
|
+
if (lcomm && lcomm[1] === symbol) {
|
|
304
|
+
const size = gasInteger(lcomm[2]);
|
|
305
|
+
if (size === null || size <= 0) {
|
|
306
|
+
return refused(`whose '.lcomm' size '${lcomm[2]}' is not a positive number`);
|
|
307
|
+
}
|
|
308
|
+
return { section: 'bss', size, bigEndian: false };
|
|
309
|
+
}
|
|
310
|
+
const sz = line.match(/^\.size\s+([^\s,]+)\s*,\s*(\S+)$/);
|
|
311
|
+
if (sz && sz[1] === symbol) {
|
|
312
|
+
declaredSize = gasInteger(sz[2]);
|
|
313
|
+
}
|
|
314
|
+
const sw = sectionSwitch(line);
|
|
315
|
+
if (sw !== null) {
|
|
316
|
+
section = sw;
|
|
317
|
+
}
|
|
318
|
+
const lab = line.match(/^([A-Za-z_.$][\w.$]*):/);
|
|
319
|
+
if (lab && lab[1] === symbol) {
|
|
320
|
+
if (at !== -1) {
|
|
321
|
+
return refused('whose label is defined twice in this asm');
|
|
322
|
+
}
|
|
323
|
+
at = i;
|
|
324
|
+
labelSection = section;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (at === -1) {
|
|
328
|
+
return refused('whose definition this asm does not carry');
|
|
329
|
+
}
|
|
330
|
+
const kind = labelSection === null ? undefined : GAS_SECTIONS[labelSection];
|
|
331
|
+
if (kind === undefined) {
|
|
332
|
+
return refused(`defined in section '${labelSection ?? '(none)'}', which is not data this reader defines`);
|
|
333
|
+
}
|
|
334
|
+
const bytes: number[] = [];
|
|
335
|
+
const widths = new Set<number>();
|
|
336
|
+
let negative = false;
|
|
337
|
+
const strings = new Set<boolean>();
|
|
338
|
+
const gaps: [number, number][] = [];
|
|
339
|
+
const relocs: LocalObjectReloc[] = [];
|
|
340
|
+
const first = lines[at].replace(/^[A-Za-z_.$][\w.$]*:\s*/, '');
|
|
341
|
+
for (let i = at; i < lines.length; i++) {
|
|
342
|
+
const line = i === at ? first : lines[i];
|
|
343
|
+
if (line === '') {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (/^[A-Za-z_.$][\w.$]*:/.test(line) || sectionSwitch(line) !== null) {
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
const d = line.match(/^\.(\w+)\s*(.*)$/);
|
|
350
|
+
if (!d || RUN_ENDS.has(d[1])) {
|
|
351
|
+
break; // an instruction, or what comes after an object
|
|
352
|
+
}
|
|
353
|
+
if (d[1] === 'type' || d[1] === 'size' || d[1] === 'globl') {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const b = dataBytes(d[1], d[2]);
|
|
357
|
+
if (b === null) {
|
|
358
|
+
return refused(`whose data run holds '.${d[1]}', a directive this reader does not read as bytes`);
|
|
359
|
+
}
|
|
360
|
+
if ('refused' in b) {
|
|
361
|
+
return b;
|
|
362
|
+
}
|
|
363
|
+
// A word lands wherever the run has reached: agbcc aligns an object's words, never one word
|
|
364
|
+
// inside it, so one off a pointer boundary is no pointer of the object's.
|
|
365
|
+
if (b.relocs.length > 0 && bytes.length % POINTER_BYTES !== 0) {
|
|
366
|
+
return refused(`whose initializer holds the address '${b.relocs[0].symbol}' off its ${POINTER_BYTES}-byte words`);
|
|
367
|
+
}
|
|
368
|
+
relocs.push(...b.relocs.map((r) => ({ ...r, offset: r.offset + bytes.length })));
|
|
369
|
+
if (b.width === null && b.bytes.length > 0 && b.bytes.every((x) => x === 0)) {
|
|
370
|
+
const last = gaps[gaps.length - 1];
|
|
371
|
+
if (last !== undefined && last[0] + last[1] === bytes.length) {
|
|
372
|
+
last[1] += b.bytes.length;
|
|
373
|
+
} else {
|
|
374
|
+
gaps.push([bytes.length, b.bytes.length]);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
bytes.push(...b.bytes);
|
|
378
|
+
if (b.width !== null) {
|
|
379
|
+
widths.add(b.width);
|
|
380
|
+
negative ||= b.negative && b.width < 4;
|
|
381
|
+
strings.add(b.string);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (bytes.length === 0) {
|
|
385
|
+
return refused('whose label heads no data');
|
|
386
|
+
}
|
|
387
|
+
if (declaredSize !== null && declaredSize !== bytes.length) {
|
|
388
|
+
return refused(`whose '.size' (${declaredSize}) disagrees with the ${bytes.length} bytes under its label`);
|
|
389
|
+
}
|
|
390
|
+
if (kind === 'bss') {
|
|
391
|
+
return bytes.every((b) => b === 0) && relocs.length === 0
|
|
392
|
+
? { section: 'bss', size: bytes.length, bigEndian: false }
|
|
393
|
+
: refused('defined in bss with non-zero bytes');
|
|
394
|
+
}
|
|
395
|
+
const align = alignAhead(lines, at);
|
|
396
|
+
if (align === null) {
|
|
397
|
+
return refused('whose alignment directive this reader does not read');
|
|
398
|
+
}
|
|
399
|
+
// A word's negative value says nothing: agbcc's constants are 32-bit host integers (machmode.h:30),
|
|
400
|
+
// so an unsigned word past 0x7fffffff prints negative too, while a narrower constant keeps its
|
|
401
|
+
// type's extension (varasm.c:1706-1710 `immed_double_const`) — `.short 0xffff` is a u16,
|
|
402
|
+
// `.short -0x1` an s16. And it says so only of the ELEMENT when every value is one width: in a
|
|
403
|
+
// struct's mixed directives a negative belongs to one field, which says nothing of another.
|
|
404
|
+
const unit = widths.size === 1 && bytes.length % [...widths][0] === 0 ? [...widths][0] : undefined;
|
|
405
|
+
return {
|
|
406
|
+
section: kind,
|
|
407
|
+
size: bytes.length,
|
|
408
|
+
bytes: Uint8Array.from(bytes),
|
|
409
|
+
bigEndian: false,
|
|
410
|
+
directives: {
|
|
411
|
+
align,
|
|
412
|
+
...(unit !== undefined ? { unit } : {}),
|
|
413
|
+
negative: unit !== undefined && negative,
|
|
414
|
+
string: strings.size === 1 && strings.has(true),
|
|
415
|
+
...(widths.size > 0 && gaps.length > 0 ? { gaps } : {}),
|
|
416
|
+
},
|
|
417
|
+
...(relocs.length > 0 ? { relocs } : {}),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** The functions whose literal pools name `symbol` in GNU as text, by the function each pool
|
|
422
|
+
* follows: a pool word belongs to the most recent function start (`.thumb_func` then a label, or
|
|
423
|
+
* a pret `thumb_func_start NAME`).
|
|
424
|
+
*
|
|
425
|
+
* Only words in code count. A word in a data section is another object's initializer, and one
|
|
426
|
+
* naming a function-scope static belongs to a static of the same function, the only scope that
|
|
427
|
+
* can spell that name — so it is no second referrer, and it sits wherever the compiler emitted
|
|
428
|
+
* that object, which is ahead of the function's own code. */
|
|
429
|
+
export function gasPoolReferrers(asm: string, symbol: string): Set<string> {
|
|
430
|
+
const out = new Set<string>();
|
|
431
|
+
let fn: string | null = null;
|
|
432
|
+
let pendingFn = false;
|
|
433
|
+
let inCode = true;
|
|
434
|
+
for (const raw of asm.split('\n')) {
|
|
435
|
+
const line = code(raw);
|
|
436
|
+
const sw = sectionSwitch(line);
|
|
437
|
+
if (sw !== null) {
|
|
438
|
+
inCode = sw === '.text' || sw.startsWith('.text.');
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const start = line.match(/^(?:non_word_aligned_)?thumb_func_start\s+(\S+)$/);
|
|
442
|
+
if (start) {
|
|
443
|
+
fn = start[1];
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (line === '.thumb_func') {
|
|
447
|
+
pendingFn = true;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const lab = line.match(/^([A-Za-z_.$][\w.$]*):\s*(.*)$/);
|
|
451
|
+
if (lab && pendingFn) {
|
|
452
|
+
fn = lab[1];
|
|
453
|
+
pendingFn = false;
|
|
454
|
+
}
|
|
455
|
+
const rest = lab ? lab[2] : line;
|
|
456
|
+
const w = rest.match(/^\.(?:word|4byte|long)\s+(.+)$/);
|
|
457
|
+
if (
|
|
458
|
+
inCode &&
|
|
459
|
+
w &&
|
|
460
|
+
fn !== null &&
|
|
461
|
+
w[1].split(',').some((op) => op.trim().match(/^[A-Za-z_.$][\w.$]*/)?.[0] === symbol)
|
|
462
|
+
) {
|
|
463
|
+
out.add(fn);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ── An object file's side table (mwcc) ───────────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
const OBJECT_SECTIONS: Readonly<Record<string, LocalObject['section']>> = {
|
|
472
|
+
'.rodata': 'rodata',
|
|
473
|
+
'.sdata2': 'rodata',
|
|
474
|
+
'.data': 'data',
|
|
475
|
+
'.sdata': 'data',
|
|
476
|
+
'.bss': 'bss',
|
|
477
|
+
'.sbss': 'bss',
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/** The relocations that fill a pointer-width word with a symbol's absolute address — the only kind
|
|
481
|
+
* an initializer's address constant is. Each is listed where a frontend reads its objects through
|
|
482
|
+
* this reader: mwcc's (frontend/ppc.ts); MIPS addresses a function-scope static section-relative
|
|
483
|
+
* and does not reach it. RELA types only: the addend is read off the relocation entry alone, and a
|
|
484
|
+
* REL type (whose addend is the word it fills) joins only with a reader of that word. */
|
|
485
|
+
const ABSOLUTE_WORD: ReadonlySet<string> = new Set(['R_PPC_ADDR32']);
|
|
486
|
+
|
|
487
|
+
/** Sections whose relocations describe the program for a debugger, not for the machine. */
|
|
488
|
+
const isDebugSection = (s: string): boolean => /^\.(debug|line)/.test(s);
|
|
489
|
+
|
|
490
|
+
/** mwcc's `.comment` section: `CodeWarrior`, a version byte (0x0a from 2.4.2 and 2.4.7, 0x08 from
|
|
491
|
+
* 2.3.3), a header to 0x2c, then one 8-byte record per symbol-table entry, the null one first,
|
|
492
|
+
* whose first word is the alignment mwcc gave that symbol (compiled on all three builds: `s64`
|
|
493
|
+
* records 8 and lands at 8 after an `s32`, `aligned(32)` records 32, a scalar `u8` 1). No section
|
|
494
|
+
* header or symbol value says the same: a section's alignment is its widest object's, and an
|
|
495
|
+
* object at offset 0 of one is aligned to anything. The magic is the eleven letters alone, so the
|
|
496
|
+
* version byte never decides whether the records are read. */
|
|
497
|
+
const MW_COMMENT_MAGIC = 'CodeWarrior';
|
|
498
|
+
const MW_COMMENT_RECORDS = 0x2c;
|
|
499
|
+
|
|
500
|
+
/** The alignment mwcc's `.comment` records for the symbol at `index`, or null when the section is
|
|
501
|
+
* absent, is not mwcc's, or holds another number of records than the listing has symbols — the
|
|
502
|
+
* index would then name another symbol's. */
|
|
503
|
+
function recordedAlign(ad: AsmData, index: number): number | null {
|
|
504
|
+
const c = ad.sections.get('.comment');
|
|
505
|
+
if (
|
|
506
|
+
c === undefined ||
|
|
507
|
+
String.fromCharCode(...c.subarray(0, MW_COMMENT_MAGIC.length)) !== MW_COMMENT_MAGIC ||
|
|
508
|
+
c.length !== MW_COMMENT_RECORDS + 8 * (ad.symbolCount + 1)
|
|
509
|
+
) {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
const at = MW_COMMENT_RECORDS + 8 * index;
|
|
513
|
+
const align = ((c[at] << 24) | (c[at + 1] << 16) | (c[at + 2] << 8) | c[at + 3]) >>> 0;
|
|
514
|
+
return align > 0 && (align & (align - 1)) === 0 ? align : null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Read a function-scope static's definition out of an object's `objdump -s -r -t` side table:
|
|
518
|
+
* the symbol table gives its section, offset and size, the section contents its bytes, and mwcc's
|
|
519
|
+
* `.comment` its alignment. `fn` is the function reading it — a relocation naming the static from
|
|
520
|
+
* any other code, or from data, is another referrer. */
|
|
521
|
+
export function readObjectLocalObject(ad: AsmData, symbol: string, fn: string): LocalObjectRead {
|
|
522
|
+
const parts = localStaticName(symbol);
|
|
523
|
+
if (parts === null) {
|
|
524
|
+
return refused('whose name has no source spelling this reader knows');
|
|
525
|
+
}
|
|
526
|
+
const { name, counter: order } = parts;
|
|
527
|
+
const sym = ad.symbols.get(symbol);
|
|
528
|
+
if (sym === undefined) {
|
|
529
|
+
return refused("whose definition the object's symbol table does not carry");
|
|
530
|
+
}
|
|
531
|
+
const kind = OBJECT_SECTIONS[sym.section];
|
|
532
|
+
if (kind === undefined) {
|
|
533
|
+
return refused(`defined in section '${sym.section}', which is not data this reader defines`);
|
|
534
|
+
}
|
|
535
|
+
if (sym.size <= 0) {
|
|
536
|
+
return refused('whose symbol-table size is 0, so its extent is unknown');
|
|
537
|
+
}
|
|
538
|
+
const self = ad.symbols.get(fn);
|
|
539
|
+
const inside: typeof ad.relocs = [];
|
|
540
|
+
for (const r of ad.relocs) {
|
|
541
|
+
if (r.section === sym.section && r.offset >= sym.value && r.offset < sym.value + sym.size) {
|
|
542
|
+
inside.push(r);
|
|
543
|
+
}
|
|
544
|
+
if (r.sym !== symbol || isDebugSection(r.section)) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const inSelf =
|
|
548
|
+
self !== undefined && r.section === self.section && r.offset >= self.value && r.offset < self.value + self.size;
|
|
549
|
+
if (!inSelf) {
|
|
550
|
+
return refused(`that ${r.section} also names at 0x${r.offset.toString(16)} — it is not this function's alone`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const align = recordedAlign(ad, sym.index);
|
|
554
|
+
if (align === null) {
|
|
555
|
+
return refused("whose alignment the object's `.comment` section does not record");
|
|
556
|
+
}
|
|
557
|
+
const placement = { align, section: sym.section, offset: sym.value };
|
|
558
|
+
if (kind === 'bss') {
|
|
559
|
+
return inside.length === 0
|
|
560
|
+
? { name, symbol, order, section: 'bss', size: sym.size, bigEndian: ad.bigEndian, placement }
|
|
561
|
+
: refused('defined in bss with a relocation inside it');
|
|
562
|
+
}
|
|
563
|
+
const contents = ad.sections.get(sym.section);
|
|
564
|
+
if (contents === undefined || sym.value + sym.size > contents.length) {
|
|
565
|
+
return refused(`whose bytes the side table's '${sym.section}' contents do not hold`);
|
|
566
|
+
}
|
|
567
|
+
const relocs: LocalObjectReloc[] = [];
|
|
568
|
+
for (const r of inside.sort((a, b) => a.offset - b.offset)) {
|
|
569
|
+
const at = r.offset - sym.value;
|
|
570
|
+
if (!ABSOLUTE_WORD.has(r.type)) {
|
|
571
|
+
return refused(
|
|
572
|
+
`whose initializer holds the address '${r.sym}' by a ${r.type} relocation — no pointer's absolute address`,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (at % POINTER_BYTES !== 0 || at + POINTER_BYTES > sym.size) {
|
|
576
|
+
return refused(`whose initializer holds the address '${r.sym}' off its ${POINTER_BYTES}-byte words`);
|
|
577
|
+
}
|
|
578
|
+
const why = addressRefusal(r.sym);
|
|
579
|
+
if (why !== null) {
|
|
580
|
+
return refused(why);
|
|
581
|
+
}
|
|
582
|
+
// a RELA relocation (ABSOLUTE_WORD): the linker replaces the word, so its bytes add nothing
|
|
583
|
+
relocs.push({ offset: at, symbol: r.sym, addend: r.addend | 0 });
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
name,
|
|
587
|
+
symbol,
|
|
588
|
+
order,
|
|
589
|
+
section: kind,
|
|
590
|
+
size: sym.size,
|
|
591
|
+
bytes: contents.slice(sym.value, sym.value + sym.size),
|
|
592
|
+
bigEndian: ad.bigEndian,
|
|
593
|
+
placement,
|
|
594
|
+
...(relocs.length > 0 ? { relocs } : {}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** The statics one lift names, gathered as the frontend meets them. */
|
|
599
|
+
export interface LocalStatics {
|
|
600
|
+
/** Record a static this function names, and answer the name the IR calls it by: its linker name,
|
|
601
|
+
* which no C identifier and no declared symbol can equal. */
|
|
602
|
+
define(obj: LocalObject): string;
|
|
603
|
+
/** Every static, or undefined for none. */
|
|
604
|
+
finish(): LocalObjects | undefined;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function makeLocalStatics(): LocalStatics {
|
|
608
|
+
const bySymbol = new Map<string, LocalObject>();
|
|
609
|
+
return {
|
|
610
|
+
define(obj) {
|
|
611
|
+
bySymbol.set(obj.symbol, obj);
|
|
612
|
+
return obj.symbol;
|
|
613
|
+
},
|
|
614
|
+
finish: () => (bySymbol.size > 0 ? bySymbol : undefined),
|
|
615
|
+
};
|
|
616
|
+
}
|