@asmlift/core 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- 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 +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +267 -79
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// Either way `addend` is "the part of the address this producer contributed", and the consumer adds
|
|
24
24
|
// its own.
|
|
25
25
|
import type { Block, Value } from '../ir/core';
|
|
26
|
+
import type { FrontendRefusal } from './errors';
|
|
26
27
|
|
|
27
28
|
/** A pending high half: what it names, what it contributed, and the instruction that produced it,
|
|
28
29
|
* so a refusal can point a reader at it. Whether it has been consumed is deliberately NOT here: it
|
|
@@ -45,7 +46,7 @@ export interface HighHalfDialect {
|
|
|
45
46
|
/** The low-half marker: `@l` (PowerPC), `%lo` (MIPS). */
|
|
46
47
|
lo: string;
|
|
47
48
|
/** The frontend's designed loud-failure signal (`PpcUnsupportedError`, `FrontendUnsupportedError`). */
|
|
48
|
-
fail
|
|
49
|
+
fail: FrontendRefusal;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface HighHalves {
|
|
@@ -0,0 +1,490 @@
|
|
|
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 — the initializer holds an address, which is a
|
|
20
|
+
// declaration of another object this reader does not make;
|
|
21
|
+
// • its extent cannot be read: a directive inside its data run this reader does not parse, a
|
|
22
|
+
// size that disagrees with the bytes under the label, or an empty object;
|
|
23
|
+
// • its alignment cannot be read: an `.align` form this reader does not parse, or an object file
|
|
24
|
+
// whose `.comment` section does not record it;
|
|
25
|
+
// • another function names it too. A static of an inlined same-unit function is named by every
|
|
26
|
+
// function the compiler inlined it into — agbcc 2.9 puts `static inline counter`'s `n.3`
|
|
27
|
+
// ahead of its first caller, and mwcc -inline auto has `A` and `B` both address `n$4` — so
|
|
28
|
+
// re-declaring it inside one of them would split one object in two. Only the functions this
|
|
29
|
+
// input shows can be checked, which is the limit of this refusal: a single-function input
|
|
30
|
+
// cannot show a second referrer, and one that ends at this function cannot show a later one.
|
|
31
|
+
import type { LocalObject, LocalObjects } from '../ir/core';
|
|
32
|
+
import type { AsmData } from './asmdata';
|
|
33
|
+
import { localStaticName } from './reloc-symbol';
|
|
34
|
+
|
|
35
|
+
/** A definition, or why there is none: the tail of a refusal sentence the caller opens with
|
|
36
|
+
* "names a function-scope static ('<symbol>')", so every refusal of the kind reads the same. */
|
|
37
|
+
export type LocalObjectRead = LocalObject | { refused: string };
|
|
38
|
+
|
|
39
|
+
const refused = (why: string): { refused: string } => ({ refused: why });
|
|
40
|
+
|
|
41
|
+
// ── GNU as text (agbcc) ──────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/** A line without its `@` comment, where the `@` is not inside a string literal. */
|
|
44
|
+
function code(line: string): string {
|
|
45
|
+
let quoted = false;
|
|
46
|
+
for (let i = 0; i < line.length; i++) {
|
|
47
|
+
const c = line[i];
|
|
48
|
+
if (c === '\\' && quoted) {
|
|
49
|
+
i++;
|
|
50
|
+
} else if (c === '"') {
|
|
51
|
+
quoted = !quoted;
|
|
52
|
+
} else if (c === '@' && !quoted) {
|
|
53
|
+
return line.slice(0, i).trim();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return line.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The section a GNU as section directive switches to, or null for a line that is not one. */
|
|
60
|
+
function sectionSwitch(line: string): string | null {
|
|
61
|
+
const m = line.match(/^\.(?:section\s+([^\s,]+)|(text|data|bss)\b)/);
|
|
62
|
+
return m ? (m[1] ?? `.${m[2]}`) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const GAS_SECTIONS: Readonly<Record<string, LocalObject['section']>> = {
|
|
66
|
+
'.rodata': 'rodata',
|
|
67
|
+
'.data': 'data',
|
|
68
|
+
'.bss': 'bss',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** A GNU as integer literal: decimal, `0x` hex or `0` octal, optionally negated. */
|
|
72
|
+
function gasInteger(s: string): number | null {
|
|
73
|
+
const m = s.trim().match(/^(-?)(0x[0-9a-f]+|0[0-7]*|[1-9]\d*)$/i);
|
|
74
|
+
if (!m) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const radix = /^0x/i.test(m[2]) ? 16 : /^0\d/.test(m[2]) ? 8 : 10;
|
|
78
|
+
const mag = parseInt(radix === 16 ? m[2].slice(2) : m[2], radix);
|
|
79
|
+
return m[1] ? -mag : mag;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The named escapes agbcc's ASM_OUTPUT_ASCII writes (agbcc/gcc/thumb.h:195-260); every other
|
|
83
|
+
* unprintable byte it writes as `\ooo` octal. */
|
|
84
|
+
const GAS_ESCAPES: Readonly<Record<string, number>> = { n: 10, t: 9, r: 13, f: 12, b: 8, '\\': 92, '"': 34 };
|
|
85
|
+
|
|
86
|
+
/** The bytes of a GNU as string literal body (between its quotes), or null for an escape this
|
|
87
|
+
* reader does not decode. */
|
|
88
|
+
function gasString(body: string): number[] | null {
|
|
89
|
+
const out: number[] = [];
|
|
90
|
+
for (let i = 0; i < body.length; i++) {
|
|
91
|
+
const c = body[i];
|
|
92
|
+
if (c !== '\\') {
|
|
93
|
+
out.push(c.charCodeAt(0) & 0xff);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const n = body[++i];
|
|
97
|
+
const oct = body.slice(i).match(/^[0-7]{1,3}/);
|
|
98
|
+
if (oct) {
|
|
99
|
+
out.push(parseInt(oct[0], 8) & 0xff);
|
|
100
|
+
i += oct[0].length - 1;
|
|
101
|
+
} else if (n in GAS_ESCAPES) {
|
|
102
|
+
out.push(GAS_ESCAPES[n]);
|
|
103
|
+
} else {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Little-endian bytes of `value` at `width` bytes — the only byte order agbcc's target has. */
|
|
111
|
+
const le = (value: number, width: number): number[] =>
|
|
112
|
+
Array.from({ length: width }, (_, i) => (value >>> (8 * i)) & 0xff);
|
|
113
|
+
|
|
114
|
+
/** What one data directive writes: its bytes, the width of each value it writes (null for
|
|
115
|
+
* padding, which writes none), whether one of those values is negative, and whether it writes a
|
|
116
|
+
* string. */
|
|
117
|
+
interface DirectiveData {
|
|
118
|
+
bytes: number[];
|
|
119
|
+
width: number | null;
|
|
120
|
+
negative: boolean;
|
|
121
|
+
string: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** One data directive's data; `{ address }` when an operand is not a number (a symbol — the word is
|
|
125
|
+
* relocated), null when the directive is not a data directive this reader parses. */
|
|
126
|
+
function dataBytes(directive: string, operands: string): DirectiveData | { address: string } | null {
|
|
127
|
+
const width = /^(byte)$/.test(directive)
|
|
128
|
+
? 1
|
|
129
|
+
: /^(short|hword|2byte)$/.test(directive)
|
|
130
|
+
? 2
|
|
131
|
+
: /^(word|long|int|4byte)$/.test(directive)
|
|
132
|
+
? 4
|
|
133
|
+
: 0;
|
|
134
|
+
if (width > 0) {
|
|
135
|
+
const out: number[] = [];
|
|
136
|
+
let negative = false;
|
|
137
|
+
for (const op of operands.split(',')) {
|
|
138
|
+
const v = gasInteger(op);
|
|
139
|
+
if (v === null) {
|
|
140
|
+
return { address: op.trim() };
|
|
141
|
+
}
|
|
142
|
+
negative ||= v < 0;
|
|
143
|
+
out.push(...le(v, width));
|
|
144
|
+
}
|
|
145
|
+
return { bytes: out, width, negative, string: false };
|
|
146
|
+
}
|
|
147
|
+
if (/^(space|skip|zero)$/.test(directive)) {
|
|
148
|
+
const [n, fill, ...rest] = operands.split(',').map((s) => gasInteger(s));
|
|
149
|
+
return n === null || n < 0 || rest.length > 0 || fill === null
|
|
150
|
+
? null
|
|
151
|
+
: { bytes: new Array<number>(n).fill((fill ?? 0) & 0xff), width: null, negative: false, string: false };
|
|
152
|
+
}
|
|
153
|
+
if (/^(ascii|asciz|string)$/.test(directive)) {
|
|
154
|
+
const m = operands.trim().match(/^"((?:[^"\\]|\\.)*)"$/);
|
|
155
|
+
const s = m ? gasString(m[1]) : null;
|
|
156
|
+
return s === null
|
|
157
|
+
? null
|
|
158
|
+
: { bytes: directive === 'ascii' ? s : [...s, 0], width: 1, negative: false, string: true };
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The alignment written ahead of the label on line `at`, in bytes. agbcc writes `.align N` there
|
|
164
|
+
* exactly when the object's alignment exceeds a byte — after the section switch, before `.type`,
|
|
165
|
+
* `.size` and the label (varasm.c:1327-1329 `assemble_variable`) — so none means 1. Null for an
|
|
166
|
+
* alignment directive this reader does not read. */
|
|
167
|
+
function alignAhead(lines: readonly string[], at: number): number | null {
|
|
168
|
+
for (let i = at - 1; i >= 0; i--) {
|
|
169
|
+
const line = lines[i];
|
|
170
|
+
if (line === '' || /^\.(type|size|globl)\b/.test(line)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const a = line.match(/^\.align\s+(\d+)\s*(?:,.*)?$/);
|
|
174
|
+
if (a) {
|
|
175
|
+
return 2 ** Number(a[1]);
|
|
176
|
+
}
|
|
177
|
+
return /^\.(balign|p2align)\b/.test(line) ? null : 1;
|
|
178
|
+
}
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The directives that end an object's data run without being part of it: the next object's
|
|
183
|
+
* alignment or bss allocation, and what ends a file or opens code. Any other directive inside the
|
|
184
|
+
* run is one this reader cannot size, so it refuses there rather than end the object short — an
|
|
185
|
+
* unsized object has no `.size` to catch the truncation (telf.h:297-316 prints one only when the
|
|
186
|
+
* declaration knows the size). */
|
|
187
|
+
const RUN_ENDS: ReadonlySet<string> = new Set([
|
|
188
|
+
'align',
|
|
189
|
+
'balign',
|
|
190
|
+
'p2align',
|
|
191
|
+
'lcomm',
|
|
192
|
+
'comm',
|
|
193
|
+
'ident',
|
|
194
|
+
'end',
|
|
195
|
+
'code',
|
|
196
|
+
'thumb',
|
|
197
|
+
'thumb_func',
|
|
198
|
+
'arm',
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
/** Read a function-scope static's definition out of GNU as text (agbcc's `.s`).
|
|
202
|
+
*
|
|
203
|
+
* Two shapes, both agbcc 2.9's (varasm.c `assemble_variable`): an object with NO initializer is
|
|
204
|
+
* `.lcomm name.N,size` — bss, with the exact size; an initialized one is a label under
|
|
205
|
+
* `.section .rodata` or `.data`, followed by its data directives. `.size name.N,K` is emitted for
|
|
206
|
+
* a sized declarator and NOT for an unsized one (`static const u8 tide[] = {…}` has none), so the
|
|
207
|
+
* extent is the data run under the label, which ends at the next label, section switch,
|
|
208
|
+
* instruction or one of {@link RUN_ENDS}, and `.size` where present must agree with it. */
|
|
209
|
+
export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead {
|
|
210
|
+
const parts = localStaticName(symbol);
|
|
211
|
+
if (parts === null) {
|
|
212
|
+
return refused('whose name has no source spelling this reader knows');
|
|
213
|
+
}
|
|
214
|
+
const { name, counter: order } = parts;
|
|
215
|
+
const lines = asm.split('\n').map(code);
|
|
216
|
+
let declaredSize: number | null = null;
|
|
217
|
+
let section: string | null = null;
|
|
218
|
+
let at = -1;
|
|
219
|
+
let labelSection: string | null = null;
|
|
220
|
+
for (let i = 0; i < lines.length; i++) {
|
|
221
|
+
const line = lines[i];
|
|
222
|
+
const lcomm = line.match(/^\.lcomm\s+([^\s,]+)\s*,\s*(\S+?)(?:\s*,.*)?$/);
|
|
223
|
+
if (lcomm && lcomm[1] === symbol) {
|
|
224
|
+
const size = gasInteger(lcomm[2]);
|
|
225
|
+
if (size === null || size <= 0) {
|
|
226
|
+
return refused(`whose '.lcomm' size '${lcomm[2]}' is not a positive number`);
|
|
227
|
+
}
|
|
228
|
+
return { name, symbol, order, section: 'bss', size, bigEndian: false };
|
|
229
|
+
}
|
|
230
|
+
const sz = line.match(/^\.size\s+([^\s,]+)\s*,\s*(\S+)$/);
|
|
231
|
+
if (sz && sz[1] === symbol) {
|
|
232
|
+
declaredSize = gasInteger(sz[2]);
|
|
233
|
+
}
|
|
234
|
+
const sw = sectionSwitch(line);
|
|
235
|
+
if (sw !== null) {
|
|
236
|
+
section = sw;
|
|
237
|
+
}
|
|
238
|
+
const lab = line.match(/^([A-Za-z_.$][\w.$]*):/);
|
|
239
|
+
if (lab && lab[1] === symbol) {
|
|
240
|
+
if (at !== -1) {
|
|
241
|
+
return refused('whose label is defined twice in this asm');
|
|
242
|
+
}
|
|
243
|
+
at = i;
|
|
244
|
+
labelSection = section;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (at === -1) {
|
|
248
|
+
return refused('whose definition this asm does not carry');
|
|
249
|
+
}
|
|
250
|
+
const kind = labelSection === null ? undefined : GAS_SECTIONS[labelSection];
|
|
251
|
+
if (kind === undefined) {
|
|
252
|
+
return refused(`defined in section '${labelSection ?? '(none)'}', which is not data this reader defines`);
|
|
253
|
+
}
|
|
254
|
+
const bytes: number[] = [];
|
|
255
|
+
const widths = new Set<number>();
|
|
256
|
+
let negative = false;
|
|
257
|
+
const strings = new Set<boolean>();
|
|
258
|
+
const first = lines[at].replace(/^[A-Za-z_.$][\w.$]*:\s*/, '');
|
|
259
|
+
for (let i = at; i < lines.length; i++) {
|
|
260
|
+
const line = i === at ? first : lines[i];
|
|
261
|
+
if (line === '') {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (/^[A-Za-z_.$][\w.$]*:/.test(line) || sectionSwitch(line) !== null) {
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
const d = line.match(/^\.(\w+)\s*(.*)$/);
|
|
268
|
+
if (!d || RUN_ENDS.has(d[1])) {
|
|
269
|
+
break; // an instruction, or what comes after an object
|
|
270
|
+
}
|
|
271
|
+
if (d[1] === 'type' || d[1] === 'size' || d[1] === 'globl') {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const b = dataBytes(d[1], d[2]);
|
|
275
|
+
if (b === null) {
|
|
276
|
+
return refused(`whose data run holds '.${d[1]}', a directive this reader does not read as bytes`);
|
|
277
|
+
}
|
|
278
|
+
if ('address' in b) {
|
|
279
|
+
return refused(`whose initializer holds the address '${b.address}' — a relocation inside the object`);
|
|
280
|
+
}
|
|
281
|
+
bytes.push(...b.bytes);
|
|
282
|
+
if (b.width !== null) {
|
|
283
|
+
widths.add(b.width);
|
|
284
|
+
negative ||= b.negative && b.width < 4;
|
|
285
|
+
strings.add(b.string);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (bytes.length === 0) {
|
|
289
|
+
return refused('whose label heads no data');
|
|
290
|
+
}
|
|
291
|
+
if (declaredSize !== null && declaredSize !== bytes.length) {
|
|
292
|
+
return refused(`whose '.size' (${declaredSize}) disagrees with the ${bytes.length} bytes under its label`);
|
|
293
|
+
}
|
|
294
|
+
if (kind === 'bss') {
|
|
295
|
+
return bytes.every((b) => b === 0)
|
|
296
|
+
? { name, symbol, order, section: 'bss', size: bytes.length, bigEndian: false }
|
|
297
|
+
: refused('defined in bss with non-zero bytes');
|
|
298
|
+
}
|
|
299
|
+
const align = alignAhead(lines, at);
|
|
300
|
+
if (align === null) {
|
|
301
|
+
return refused('whose alignment directive this reader does not read');
|
|
302
|
+
}
|
|
303
|
+
// A word's negative value says nothing: agbcc's constants are 32-bit host integers (machmode.h:30),
|
|
304
|
+
// so an unsigned word past 0x7fffffff prints negative too, while a narrower constant keeps its
|
|
305
|
+
// type's extension (varasm.c:1706-1710 `immed_double_const`) — `.short 0xffff` is a u16,
|
|
306
|
+
// `.short -0x1` an s16. And it says so only of the ELEMENT when every value is one width: in a
|
|
307
|
+
// struct's mixed directives a negative belongs to one field, which says nothing of another.
|
|
308
|
+
const unit = widths.size === 1 && bytes.length % [...widths][0] === 0 ? [...widths][0] : undefined;
|
|
309
|
+
return {
|
|
310
|
+
name,
|
|
311
|
+
symbol,
|
|
312
|
+
order,
|
|
313
|
+
section: kind,
|
|
314
|
+
size: bytes.length,
|
|
315
|
+
bytes: Uint8Array.from(bytes),
|
|
316
|
+
bigEndian: false,
|
|
317
|
+
directives: {
|
|
318
|
+
align,
|
|
319
|
+
...(unit !== undefined ? { unit } : {}),
|
|
320
|
+
negative: unit !== undefined && negative,
|
|
321
|
+
string: strings.size === 1 && strings.has(true),
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** The functions whose literal pools name `symbol` in GNU as text, by the function each pool
|
|
327
|
+
* follows: a pool word belongs to the most recent function start (`.thumb_func` then a label, or
|
|
328
|
+
* a pret `thumb_func_start NAME`).
|
|
329
|
+
*
|
|
330
|
+
* Only words in code count. A word in a data section is another object's initializer, and one
|
|
331
|
+
* naming a function-scope static belongs to a static of the same function, the only scope that
|
|
332
|
+
* can spell that name — so it is no second referrer, and it sits wherever the compiler emitted
|
|
333
|
+
* that object, which is ahead of the function's own code. */
|
|
334
|
+
export function gasPoolReferrers(asm: string, symbol: string): Set<string> {
|
|
335
|
+
const out = new Set<string>();
|
|
336
|
+
let fn: string | null = null;
|
|
337
|
+
let pendingFn = false;
|
|
338
|
+
let inCode = true;
|
|
339
|
+
for (const raw of asm.split('\n')) {
|
|
340
|
+
const line = code(raw);
|
|
341
|
+
const sw = sectionSwitch(line);
|
|
342
|
+
if (sw !== null) {
|
|
343
|
+
inCode = sw === '.text' || sw.startsWith('.text.');
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const start = line.match(/^(?:non_word_aligned_)?thumb_func_start\s+(\S+)$/);
|
|
347
|
+
if (start) {
|
|
348
|
+
fn = start[1];
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (line === '.thumb_func') {
|
|
352
|
+
pendingFn = true;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const lab = line.match(/^([A-Za-z_.$][\w.$]*):\s*(.*)$/);
|
|
356
|
+
if (lab && pendingFn) {
|
|
357
|
+
fn = lab[1];
|
|
358
|
+
pendingFn = false;
|
|
359
|
+
}
|
|
360
|
+
const rest = lab ? lab[2] : line;
|
|
361
|
+
const w = rest.match(/^\.(?:word|4byte|long)\s+(.+)$/);
|
|
362
|
+
if (
|
|
363
|
+
inCode &&
|
|
364
|
+
w &&
|
|
365
|
+
fn !== null &&
|
|
366
|
+
w[1].split(',').some((op) => op.trim().match(/^[A-Za-z_.$][\w.$]*/)?.[0] === symbol)
|
|
367
|
+
) {
|
|
368
|
+
out.add(fn);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── An object file's side table (mwcc) ───────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
const OBJECT_SECTIONS: Readonly<Record<string, LocalObject['section']>> = {
|
|
377
|
+
'.rodata': 'rodata',
|
|
378
|
+
'.sdata2': 'rodata',
|
|
379
|
+
'.data': 'data',
|
|
380
|
+
'.sdata': 'data',
|
|
381
|
+
'.bss': 'bss',
|
|
382
|
+
'.sbss': 'bss',
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** Sections whose relocations describe the program for a debugger, not for the machine. */
|
|
386
|
+
const isDebugSection = (s: string): boolean => /^\.(debug|line)/.test(s);
|
|
387
|
+
|
|
388
|
+
/** mwcc's `.comment` section: `CodeWarrior\n`, a header to 0x2c, then one 8-byte record per
|
|
389
|
+
* symbol-table entry, the null one first, whose first word is the alignment mwcc gave that symbol
|
|
390
|
+
* (compiled: `s64` records 8 and lands at 8 after an `s32`, `aligned(32)` records 32, a scalar
|
|
391
|
+
* `u8` 1). No section header or symbol value says the same: a section's alignment is its widest
|
|
392
|
+
* object's, and an object at offset 0 of one is aligned to anything. */
|
|
393
|
+
const MW_COMMENT_MAGIC = 'CodeWarrior\n';
|
|
394
|
+
const MW_COMMENT_RECORDS = 0x2c;
|
|
395
|
+
|
|
396
|
+
/** The alignment mwcc's `.comment` records for the symbol at `index`, or null when the section is
|
|
397
|
+
* absent, is not mwcc's, or holds another number of records than the listing has symbols — the
|
|
398
|
+
* index would then name another symbol's. */
|
|
399
|
+
function recordedAlign(ad: AsmData, index: number): number | null {
|
|
400
|
+
const c = ad.sections.get('.comment');
|
|
401
|
+
if (
|
|
402
|
+
c === undefined ||
|
|
403
|
+
String.fromCharCode(...c.subarray(0, MW_COMMENT_MAGIC.length)) !== MW_COMMENT_MAGIC ||
|
|
404
|
+
c.length !== MW_COMMENT_RECORDS + 8 * (ad.symbolCount + 1)
|
|
405
|
+
) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const at = MW_COMMENT_RECORDS + 8 * index;
|
|
409
|
+
const align = ((c[at] << 24) | (c[at + 1] << 16) | (c[at + 2] << 8) | c[at + 3]) >>> 0;
|
|
410
|
+
return align > 0 && (align & (align - 1)) === 0 ? align : null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Read a function-scope static's definition out of an object's `objdump -s -r -t` side table:
|
|
414
|
+
* the symbol table gives its section, offset and size, the section contents its bytes, and mwcc's
|
|
415
|
+
* `.comment` its alignment. `fn` is the function reading it — a relocation naming the static from
|
|
416
|
+
* any other code, or from data, is another referrer. */
|
|
417
|
+
export function readObjectLocalObject(ad: AsmData, symbol: string, fn: string): LocalObjectRead {
|
|
418
|
+
const parts = localStaticName(symbol);
|
|
419
|
+
if (parts === null) {
|
|
420
|
+
return refused('whose name has no source spelling this reader knows');
|
|
421
|
+
}
|
|
422
|
+
const { name, counter: order } = parts;
|
|
423
|
+
const sym = ad.symbols.get(symbol);
|
|
424
|
+
if (sym === undefined) {
|
|
425
|
+
return refused("whose definition the object's symbol table does not carry");
|
|
426
|
+
}
|
|
427
|
+
const kind = OBJECT_SECTIONS[sym.section];
|
|
428
|
+
if (kind === undefined) {
|
|
429
|
+
return refused(`defined in section '${sym.section}', which is not data this reader defines`);
|
|
430
|
+
}
|
|
431
|
+
if (sym.size <= 0) {
|
|
432
|
+
return refused('whose symbol-table size is 0, so its extent is unknown');
|
|
433
|
+
}
|
|
434
|
+
const self = ad.symbols.get(fn);
|
|
435
|
+
for (const r of ad.relocs) {
|
|
436
|
+
if (r.section === sym.section && r.offset >= sym.value && r.offset < sym.value + sym.size) {
|
|
437
|
+
return refused(`whose initializer holds the address '${r.sym}' — a relocation inside the object`);
|
|
438
|
+
}
|
|
439
|
+
if (r.sym !== symbol || isDebugSection(r.section)) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const inSelf =
|
|
443
|
+
self !== undefined && r.section === self.section && r.offset >= self.value && r.offset < self.value + self.size;
|
|
444
|
+
if (!inSelf) {
|
|
445
|
+
return refused(`that ${r.section} also names at 0x${r.offset.toString(16)} — it is not this function's alone`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const align = recordedAlign(ad, sym.index);
|
|
449
|
+
if (align === null) {
|
|
450
|
+
return refused("whose alignment the object's `.comment` section does not record");
|
|
451
|
+
}
|
|
452
|
+
const placement = { align, section: sym.section, offset: sym.value };
|
|
453
|
+
if (kind === 'bss') {
|
|
454
|
+
return { name, symbol, order, section: 'bss', size: sym.size, bigEndian: ad.bigEndian, placement };
|
|
455
|
+
}
|
|
456
|
+
const contents = ad.sections.get(sym.section);
|
|
457
|
+
if (contents === undefined || sym.value + sym.size > contents.length) {
|
|
458
|
+
return refused(`whose bytes the side table's '${sym.section}' contents do not hold`);
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
name,
|
|
462
|
+
symbol,
|
|
463
|
+
order,
|
|
464
|
+
section: kind,
|
|
465
|
+
size: sym.size,
|
|
466
|
+
bytes: contents.slice(sym.value, sym.value + sym.size),
|
|
467
|
+
bigEndian: ad.bigEndian,
|
|
468
|
+
placement,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** The statics one lift names, gathered as the frontend meets them. */
|
|
473
|
+
export interface LocalStatics {
|
|
474
|
+
/** Record a static this function names, and answer the name the IR calls it by: its linker name,
|
|
475
|
+
* which no C identifier and no declared symbol can equal. */
|
|
476
|
+
define(obj: LocalObject): string;
|
|
477
|
+
/** Every static, or undefined for none. */
|
|
478
|
+
finish(): LocalObjects | undefined;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function makeLocalStatics(): LocalStatics {
|
|
482
|
+
const bySymbol = new Map<string, LocalObject>();
|
|
483
|
+
return {
|
|
484
|
+
define(obj) {
|
|
485
|
+
bySymbol.set(obj.symbol, obj);
|
|
486
|
+
return obj.symbol;
|
|
487
|
+
},
|
|
488
|
+
finish: () => (bySymbol.size > 0 ? bySymbol : undefined),
|
|
489
|
+
};
|
|
490
|
+
}
|