@asmlift/core 0.8.0 → 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.
Files changed (105) hide show
  1. package/README.md +34 -30
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +248 -0
  4. package/src/backend/c.ts +5 -2
  5. package/src/backend/cfamily.ts +236 -37
  6. package/src/backend/cpp.ts +114 -9
  7. package/src/backend/pascal.ts +39 -0
  8. package/src/cdecl/constant.ts +59 -0
  9. package/src/cdecl/lex.ts +209 -0
  10. package/src/cdecl/parse.ts +975 -0
  11. package/src/cdecl/spell.ts +69 -0
  12. package/src/codegen-flags.ts +38 -6
  13. package/src/compiler-diagnostics.ts +194 -0
  14. package/src/contracts.ts +615 -79
  15. package/src/declare.ts +75 -5
  16. package/src/frontend/asmdata.ts +19 -5
  17. package/src/frontend/call-plan.ts +686 -0
  18. package/src/frontend/device-pins.ts +277 -0
  19. package/src/frontend/errors.ts +5 -0
  20. package/src/frontend/flags-edge.ts +101 -0
  21. package/src/frontend/fpu.ts +167 -0
  22. package/src/frontend/frame-objects.ts +2131 -0
  23. package/src/frontend/high-half.ts +2 -1
  24. package/src/frontend/local-object.ts +490 -0
  25. package/src/frontend/mips.ts +363 -40
  26. package/src/frontend/opaque.ts +97 -18
  27. package/src/frontend/ppc.ts +520 -129
  28. package/src/frontend/reloc-symbol.ts +94 -13
  29. package/src/frontend/splat.ts +108 -5
  30. package/src/frontend/ssa.ts +669 -76
  31. package/src/frontend/stackargs.ts +120 -39
  32. package/src/frontend/thumb.ts +2764 -2018
  33. package/src/ir/alias.ts +29 -0
  34. package/src/ir/core.ts +101 -3
  35. package/src/ir/discipline.ts +248 -0
  36. package/src/ir/float-bits.ts +47 -0
  37. package/src/ir/opcodes.ts +84 -112
  38. package/src/ir/parse.ts +1 -0
  39. package/src/ir/types.ts +118 -2
  40. package/src/ir/verify.ts +150 -3
  41. package/src/l3/address.ts +5 -0
  42. package/src/l3/argcopy.ts +38 -6
  43. package/src/l3/ast.ts +186 -14
  44. package/src/l3/coalesce.ts +21 -13
  45. package/src/l3/dce.ts +32 -10
  46. package/src/l3/gates.ts +9 -3
  47. package/src/l3/hoist.ts +8 -7
  48. package/src/l3/initfirst.ts +27 -9
  49. package/src/l3/intcell.ts +97 -0
  50. package/src/l3/mentions.ts +10 -4
  51. package/src/l3/narrowdecl.ts +218 -0
  52. package/src/l3/nearbase.ts +10 -5
  53. package/src/l3/parkfirst.ts +8 -5
  54. package/src/l3/pollguard.ts +3 -2
  55. package/src/l3/ptrcell.ts +357 -0
  56. package/src/l3/regspell.ts +17 -3
  57. package/src/l3/reindex.ts +10 -7
  58. package/src/l3/symbol-refs.ts +104 -23
  59. package/src/l3/tailmerge.ts +6 -19
  60. package/src/l3/typing.ts +137 -25
  61. package/src/l3/unreduce.ts +4 -4
  62. package/src/l3/volstore.ts +96 -7
  63. package/src/mangle.ts +11 -1
  64. package/src/pattern/engine.ts +19 -16
  65. package/src/pipeline.ts +57 -12
  66. package/src/proto-context.ts +788 -0
  67. package/src/proto.ts +689 -50
  68. package/src/raise/arrays.ts +4 -3
  69. package/src/raise/declared-volatile.ts +131 -0
  70. package/src/raise/divpow2.ts +2 -2
  71. package/src/raise/errors.ts +17 -0
  72. package/src/raise/floathelpers.ts +243 -0
  73. package/src/raise/globalshape.ts +11 -5
  74. package/src/raise/gvn.ts +26 -5
  75. package/src/raise/narrow.ts +29 -2
  76. package/src/raise/narrowlocal.ts +11 -6
  77. package/src/raise/offsetnames.ts +510 -0
  78. package/src/raise/pairparams.ts +81 -0
  79. package/src/raise/paramwidth.ts +7 -0
  80. package/src/raise/pre-recovery.ts +78 -4
  81. package/src/raise/recover.ts +14 -10
  82. package/src/raise/retsink.ts +1 -1
  83. package/src/raise/shortcircuit.ts +41 -19
  84. package/src/raise/softdiv.ts +21 -23
  85. package/src/raise/struct-arrays.ts +5 -4
  86. package/src/raise/structs.ts +311 -98
  87. package/src/raise/truncload.ts +366 -0
  88. package/src/raise/widehelpers.ts +106 -0
  89. package/src/rank-declare.ts +30 -5
  90. package/src/rank-variations.ts +36 -8
  91. package/src/rank.ts +276 -80
  92. package/src/runtime-helpers.ts +287 -0
  93. package/src/stillborn.ts +289 -0
  94. package/src/structure/analysis.ts +1285 -448
  95. package/src/structure/hazards.ts +633 -58
  96. package/src/structure/local-statics.ts +381 -0
  97. package/src/structure/namecoalesce.ts +1 -1
  98. package/src/structure/redundant-test.ts +99 -0
  99. package/src/structure/structure.ts +1772 -395
  100. package/src/structure/switch-recover.ts +159 -78
  101. package/src/symbols.ts +38 -3
  102. package/src/target.ts +662 -59
  103. package/src/trace.ts +44 -24
  104. package/src/variation-definitions.ts +119 -1
  105. 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(message: string): never;
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
+ }