@asmlift/core 0.9.0 → 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.
Files changed (101) hide show
  1. package/README.md +25 -24
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +149 -0
  4. package/src/backend/c.ts +1 -0
  5. package/src/backend/cfamily.ts +165 -50
  6. package/src/backend/cpp.ts +2 -1
  7. package/src/backend/pascal.ts +31 -12
  8. package/src/contracts.ts +173 -16
  9. package/src/declare.ts +69 -20
  10. package/src/frontend/asmdata.ts +7 -1
  11. package/src/frontend/call-plan.ts +964 -60
  12. package/src/frontend/dead-reads.ts +628 -0
  13. package/src/frontend/device-pins.ts +23 -4
  14. package/src/frontend/emit.ts +4 -3
  15. package/src/frontend/fpu.ts +224 -17
  16. package/src/frontend/frame-objects.ts +1232 -192
  17. package/src/frontend/frontend.ts +64 -3
  18. package/src/frontend/high-half.ts +53 -4
  19. package/src/frontend/jump-table.ts +530 -0
  20. package/src/frontend/local-object.ts +158 -32
  21. package/src/frontend/mips.ts +2076 -215
  22. package/src/frontend/own-struct-return.ts +500 -0
  23. package/src/frontend/ppc.ts +2711 -232
  24. package/src/frontend/register-struct-call.ts +484 -0
  25. package/src/frontend/reloc-symbol.ts +22 -7
  26. package/src/frontend/ssa.ts +1460 -44
  27. package/src/frontend/stackargs.ts +106 -57
  28. package/src/frontend/thumb.ts +2044 -318
  29. package/src/ir/alias.ts +42 -0
  30. package/src/ir/bits.ts +681 -1
  31. package/src/ir/core.ts +100 -5
  32. package/src/ir/discipline.ts +86 -21
  33. package/src/ir/entry.ts +159 -0
  34. package/src/{structure → ir}/loops.ts +6 -2
  35. package/src/ir/opcodes.ts +109 -9
  36. package/src/ir/types.ts +86 -2
  37. package/src/ir/verify.ts +80 -11
  38. package/src/l3/ast.ts +290 -18
  39. package/src/l3/basecse.ts +33 -5
  40. package/src/l3/constinit.ts +382 -0
  41. package/src/l3/dce.ts +2 -2
  42. package/src/l3/initfirst.ts +42 -11
  43. package/src/l3/intcell.ts +2 -1
  44. package/src/l3/narrowdecl.ts +77 -36
  45. package/src/l3/pollguard.ts +8 -2
  46. package/src/l3/ptrcell.ts +31 -34
  47. package/src/l3/ptrfield.ts +123 -8
  48. package/src/l3/regspell.ts +9 -4
  49. package/src/l3/reindex.ts +283 -96
  50. package/src/l3/storage.ts +98 -1
  51. package/src/l3/symbol-refs.ts +84 -6
  52. package/src/l3/tailmerge.ts +4 -2
  53. package/src/l3/typing.ts +108 -15
  54. package/src/l3/unreduce.ts +6 -5
  55. package/src/l3/volatileptr.ts +17 -54
  56. package/src/mangle.ts +30 -0
  57. package/src/pattern/engine.ts +361 -73
  58. package/src/pipeline.ts +541 -24
  59. package/src/proto-context.ts +905 -45
  60. package/src/proto.ts +555 -40
  61. package/src/raise/aggmembers.ts +290 -0
  62. package/src/raise/arrays.ts +5 -1
  63. package/src/raise/declared-volatile.ts +207 -31
  64. package/src/raise/floathelpers.ts +254 -33
  65. package/src/raise/gvn.ts +3 -3
  66. package/src/raise/latch.ts +162 -6
  67. package/src/raise/memberarrays.ts +10 -4
  68. package/src/raise/offsetnames.ts +10 -26
  69. package/src/raise/pairparams.ts +33 -0
  70. package/src/raise/paramwidth.ts +196 -6
  71. package/src/raise/pre-recovery.ts +102 -19
  72. package/src/raise/pre-structure.ts +54 -0
  73. package/src/raise/recover.ts +12 -5
  74. package/src/raise/reroll.ts +694 -0
  75. package/src/raise/shortcircuit.ts +26 -12
  76. package/src/raise/softdiv.ts +1 -1
  77. package/src/raise/storesign.ts +153 -0
  78. package/src/raise/struct-arrays.ts +30 -11
  79. package/src/raise/structs.ts +396 -33
  80. package/src/raise/truncload.ts +4 -3
  81. package/src/raise/unthread.ts +400 -0
  82. package/src/rank-declare.ts +118 -12
  83. package/src/rank-variations.ts +89 -31
  84. package/src/rank.ts +307 -54
  85. package/src/runtime-helpers.ts +140 -22
  86. package/src/structure/analysis.ts +165 -2
  87. package/src/structure/arith-ops.ts +23 -0
  88. package/src/structure/globalaccess.ts +111 -9
  89. package/src/structure/hazards.ts +16 -64
  90. package/src/structure/local-statics.ts +145 -9
  91. package/src/structure/namecoalesce.ts +1 -1
  92. package/src/structure/pointer-spelling.ts +1106 -0
  93. package/src/structure/redundant-test.ts +1 -1
  94. package/src/structure/structure.ts +2180 -1054
  95. package/src/structure/switch-recover.ts +244 -100
  96. package/src/symbols.ts +6 -0
  97. package/src/target.ts +765 -52
  98. package/src/trace.ts +26 -8
  99. package/src/variation-definitions.ts +126 -14
  100. package/src/variation-gates.ts +2 -0
  101. package/src/variation-tokens.ts +5 -1
@@ -16,8 +16,12 @@
16
16
  // static — when:
17
17
  // • the target does not carry the definition (the symbol is absent, or its label heads no data);
18
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;
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);
21
25
  // • its extent cannot be read: a directive inside its data run this reader does not parse, a
22
26
  // size that disagrees with the bytes under the label, or an empty object;
23
27
  // • its alignment cannot be read: an `.align` form this reader does not parse, or an object file
@@ -28,9 +32,14 @@
28
32
  // re-declaring it inside one of them would split one object in two. Only the functions this
29
33
  // input shows can be checked, which is the limit of this refusal: a single-function input
30
34
  // 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';
35
+ import {
36
+ type LocalObject,
37
+ type LocalObjectReloc,
38
+ type LocalObjects,
39
+ LOCAL_OBJECT_ADDRESS_BYTES as POINTER_BYTES,
40
+ } from '../ir/core';
32
41
  import type { AsmData } from './asmdata';
33
- import { localStaticName } from './reloc-symbol';
42
+ import { classifyRelocSymbol, localStaticName, unspellableReason } from './reloc-symbol';
34
43
 
35
44
  /** A definition, or why there is none: the tail of a refusal sentence the caller opens with
36
45
  * "names a function-scope static ('<symbol>')", so every refusal of the kind reads the same. */
@@ -38,6 +47,14 @@ export type LocalObjectRead = LocalObject | { refused: string };
38
47
 
39
48
  const refused = (why: string): { refused: string } => ({ refused: why });
40
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
+
41
58
  // ── GNU as text (agbcc) ──────────────────────────────────────────────────────────────────────────
42
59
 
43
60
  /** A line without its `@` comment, where the `@` is not inside a string literal. */
@@ -112,18 +129,22 @@ const le = (value: number, width: number): number[] =>
112
129
  Array.from({ length: width }, (_, i) => (value >>> (8 * i)) & 0xff);
113
130
 
114
131
  /** 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. */
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. */
117
134
  interface DirectiveData {
118
135
  bytes: number[];
119
136
  width: number | null;
120
137
  negative: boolean;
121
138
  string: boolean;
139
+ relocs: LocalObjectReloc[];
122
140
  }
123
141
 
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 {
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 {
127
148
  const width = /^(byte)$/.test(directive)
128
149
  ? 1
129
150
  : /^(short|hword|2byte)$/.test(directive)
@@ -133,29 +154,54 @@ function dataBytes(directive: string, operands: string): DirectiveData | { addre
133
154
  : 0;
134
155
  if (width > 0) {
135
156
  const out: number[] = [];
157
+ const relocs: LocalObjectReloc[] = [];
136
158
  let negative = false;
137
159
  for (const op of operands.split(',')) {
138
160
  const v = gasInteger(op);
139
- if (v === null) {
140
- return { address: op.trim() };
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`);
141
172
  }
142
- negative ||= v < 0;
143
- out.push(...le(v, width));
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));
144
184
  }
145
- return { bytes: out, width, negative, string: false };
185
+ return { bytes: out, width, negative, string: false, relocs };
146
186
  }
147
187
  if (/^(space|skip|zero)$/.test(directive)) {
148
188
  const [n, fill, ...rest] = operands.split(',').map((s) => gasInteger(s));
149
189
  return n === null || n < 0 || rest.length > 0 || fill === null
150
190
  ? null
151
- : { bytes: new Array<number>(n).fill((fill ?? 0) & 0xff), width: null, negative: false, string: false };
191
+ : {
192
+ bytes: new Array<number>(n).fill((fill ?? 0) & 0xff),
193
+ width: null,
194
+ negative: false,
195
+ string: false,
196
+ relocs: [],
197
+ };
152
198
  }
153
199
  if (/^(ascii|asciz|string)$/.test(directive)) {
154
200
  const m = operands.trim().match(/^"((?:[^"\\]|\\.)*)"$/);
155
201
  const s = m ? gasString(m[1]) : null;
156
202
  return s === null
157
203
  ? null
158
- : { bytes: directive === 'ascii' ? s : [...s, 0], width: 1, negative: false, string: true };
204
+ : { bytes: directive === 'ascii' ? s : [...s, 0], width: 1, negative: false, string: true, relocs: [] };
159
205
  }
160
206
  return null;
161
207
  }
@@ -212,6 +258,40 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
212
258
  return refused('whose name has no source spelling this reader knows');
213
259
  }
214
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 } {
215
295
  const lines = asm.split('\n').map(code);
216
296
  let declaredSize: number | null = null;
217
297
  let section: string | null = null;
@@ -225,7 +305,7 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
225
305
  if (size === null || size <= 0) {
226
306
  return refused(`whose '.lcomm' size '${lcomm[2]}' is not a positive number`);
227
307
  }
228
- return { name, symbol, order, section: 'bss', size, bigEndian: false };
308
+ return { section: 'bss', size, bigEndian: false };
229
309
  }
230
310
  const sz = line.match(/^\.size\s+([^\s,]+)\s*,\s*(\S+)$/);
231
311
  if (sz && sz[1] === symbol) {
@@ -255,6 +335,8 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
255
335
  const widths = new Set<number>();
256
336
  let negative = false;
257
337
  const strings = new Set<boolean>();
338
+ const gaps: [number, number][] = [];
339
+ const relocs: LocalObjectReloc[] = [];
258
340
  const first = lines[at].replace(/^[A-Za-z_.$][\w.$]*:\s*/, '');
259
341
  for (let i = at; i < lines.length; i++) {
260
342
  const line = i === at ? first : lines[i];
@@ -275,8 +357,22 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
275
357
  if (b === null) {
276
358
  return refused(`whose data run holds '.${d[1]}', a directive this reader does not read as bytes`);
277
359
  }
278
- if ('address' in b) {
279
- return refused(`whose initializer holds the address '${b.address}' — a relocation inside the object`);
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
+ }
280
376
  }
281
377
  bytes.push(...b.bytes);
282
378
  if (b.width !== null) {
@@ -292,8 +388,8 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
292
388
  return refused(`whose '.size' (${declaredSize}) disagrees with the ${bytes.length} bytes under its label`);
293
389
  }
294
390
  if (kind === 'bss') {
295
- return bytes.every((b) => b === 0)
296
- ? { name, symbol, order, section: 'bss', size: bytes.length, bigEndian: false }
391
+ return bytes.every((b) => b === 0) && relocs.length === 0
392
+ ? { section: 'bss', size: bytes.length, bigEndian: false }
297
393
  : refused('defined in bss with non-zero bytes');
298
394
  }
299
395
  const align = alignAhead(lines, at);
@@ -307,9 +403,6 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
307
403
  // struct's mixed directives a negative belongs to one field, which says nothing of another.
308
404
  const unit = widths.size === 1 && bytes.length % [...widths][0] === 0 ? [...widths][0] : undefined;
309
405
  return {
310
- name,
311
- symbol,
312
- order,
313
406
  section: kind,
314
407
  size: bytes.length,
315
408
  bytes: Uint8Array.from(bytes),
@@ -319,7 +412,9 @@ export function readGasLocalObject(asm: string, symbol: string): LocalObjectRead
319
412
  ...(unit !== undefined ? { unit } : {}),
320
413
  negative: unit !== undefined && negative,
321
414
  string: strings.size === 1 && strings.has(true),
415
+ ...(widths.size > 0 && gaps.length > 0 ? { gaps } : {}),
322
416
  },
417
+ ...(relocs.length > 0 ? { relocs } : {}),
323
418
  };
324
419
  }
325
420
 
@@ -382,15 +477,24 @@ const OBJECT_SECTIONS: Readonly<Record<string, LocalObject['section']>> = {
382
477
  '.sbss': 'bss',
383
478
  };
384
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
+
385
487
  /** Sections whose relocations describe the program for a debugger, not for the machine. */
386
488
  const isDebugSection = (s: string): boolean => /^\.(debug|line)/.test(s);
387
489
 
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';
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';
394
498
  const MW_COMMENT_RECORDS = 0x2c;
395
499
 
396
500
  /** The alignment mwcc's `.comment` records for the symbol at `index`, or null when the section is
@@ -432,9 +536,10 @@ export function readObjectLocalObject(ad: AsmData, symbol: string, fn: string):
432
536
  return refused('whose symbol-table size is 0, so its extent is unknown');
433
537
  }
434
538
  const self = ad.symbols.get(fn);
539
+ const inside: typeof ad.relocs = [];
435
540
  for (const r of ad.relocs) {
436
541
  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`);
542
+ inside.push(r);
438
543
  }
439
544
  if (r.sym !== symbol || isDebugSection(r.section)) {
440
545
  continue;
@@ -451,12 +556,32 @@ export function readObjectLocalObject(ad: AsmData, symbol: string, fn: string):
451
556
  }
452
557
  const placement = { align, section: sym.section, offset: sym.value };
453
558
  if (kind === 'bss') {
454
- return { name, symbol, order, section: 'bss', size: sym.size, bigEndian: ad.bigEndian, placement };
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');
455
562
  }
456
563
  const contents = ad.sections.get(sym.section);
457
564
  if (contents === undefined || sym.value + sym.size > contents.length) {
458
565
  return refused(`whose bytes the side table's '${sym.section}' contents do not hold`);
459
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
+ }
460
585
  return {
461
586
  name,
462
587
  symbol,
@@ -466,6 +591,7 @@ export function readObjectLocalObject(ad: AsmData, symbol: string, fn: string):
466
591
  bytes: contents.slice(sym.value, sym.value + sym.size),
467
592
  bigEndian: ad.bigEndian,
468
593
  placement,
594
+ ...(relocs.length > 0 ? { relocs } : {}),
469
595
  };
470
596
  }
471
597