@scriptc/compiler 0.0.27 → 0.0.28

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.
@@ -0,0 +1,936 @@
1
+ /* Format-aware symbol localization for multi-instance library mode
2
+ * (abi.localize_runtime) — the in-process half of cc.ts's
3
+ * localizeLibraryObjects. Host toolchains cover the classic pairings
4
+ * (darwin's ld64 does combine+demote in one step; host-linux binutils runs
5
+ * `ld -r` + objcopy), but no portable toolchain performs the COFF
6
+ * equivalent at all — lld-link has no relocatable mode (mirroring MSVC
7
+ * link.exe) and llvm-objcopy rejects symbol-scope flags for COFF — and
8
+ * cross ELF demotion would otherwise require a host binutils/llvm-objcopy
9
+ * that neither `zig cc` nor a stock macOS ships. These transforms close
10
+ * both gaps with no external tool:
11
+ *
12
+ * localizeElfObject demote every defined global/weak symbol outside
13
+ * the keep set to a local symbol in a relocatable
14
+ * ELF64 object (the output of `zig cc -r`), and
15
+ * resolve COMDAT section groups the way binutils'
16
+ * --force-group-allocation does — a group whose
17
+ * signature repeats across archives sharing
18
+ * runtime objects must not be deduplicated against
19
+ * another archive's copy at the embedder's link.
20
+ *
21
+ * mergeAndLocalizeCoffObjects
22
+ * the COFF combine+demote in one pass: pull
23
+ * support objects on undefined-symbol demand (the
24
+ * staging-archive member semantics `ld -r` gives
25
+ * the other formats), concatenate the selected
26
+ * objects' sections, resolve cross-object symbol
27
+ * references by index, then demote every defined
28
+ * external outside the keep set to a static
29
+ * symbol.
30
+ *
31
+ * Shared demotion rule (GNU objcopy --keep-global-symbols semantics):
32
+ * undefined references keep their global binding (the target C/math runtime,
33
+ * system APIs, and sanitizer ABI are the embedder's by design), and COMMON
34
+ * symbols stay global — sanitizer image-registration guards are COMMONs whose
35
+ * merging across archives is the documented single-registration discipline.
36
+ * Windows embedders additionally link advapi32, iphlpapi, and ws2_32. */
37
+ const textDecoder = new TextDecoder();
38
+ const textEncoder = new TextEncoder();
39
+ class ObjectFormatError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = "ObjectFormatError";
43
+ }
44
+ }
45
+ function fail(message) {
46
+ throw new ObjectFormatError(message);
47
+ }
48
+ /* ────────────────────────────── ELF64 ─────────────────────────────────── */
49
+ const SHT_NULL = 0;
50
+ const SHT_SYMTAB = 2;
51
+ const SHT_RELA = 4;
52
+ const SHT_NOBITS = 8;
53
+ const SHT_REL = 9;
54
+ const SHT_GROUP = 17;
55
+ const SHT_SYMTAB_SHNDX = 18;
56
+ const SHT_LLVM_ADDRSIG = 0x6fff4c03;
57
+ const SHF_GROUP = 0x200n;
58
+ const SHN_UNDEF = 0;
59
+ const SHN_LORESERVE = 0xff00;
60
+ const SHN_COMMON = 0xfff2;
61
+ const SHN_XINDEX = 0xffff;
62
+ const STB_LOCAL = 0;
63
+ /** Demote every defined global/weak symbol not named in `keep` to a local
64
+ * symbol in a relocatable ELF64 little-endian object, resolving COMDAT
65
+ * section groups in the same pass. The input is the combined object a
66
+ * relocatable link produced; the output is byte-rebuilt (fresh layout,
67
+ * same section content). */
68
+ export function localizeElfObject(object, keep) {
69
+ const view = new DataView(object.buffer, object.byteOffset, object.byteLength);
70
+ if (object.byteLength < 64 || view.getUint32(0, false) !== 0x7f454c46) {
71
+ fail("not an ELF object");
72
+ }
73
+ if (object[4] !== 2 || object[5] !== 1) {
74
+ fail("unsupported ELF class/encoding (need ELF64, little-endian)");
75
+ }
76
+ if (view.getUint16(16, true) !== 1)
77
+ fail("not a relocatable ELF object (ET_REL)");
78
+ const machine = view.getUint16(18, true);
79
+ const flags = view.getUint32(48, true);
80
+ const shoff = Number(view.getBigUint64(40, true));
81
+ const shentsize = view.getUint16(58, true);
82
+ if (shentsize !== 64)
83
+ fail(`unsupported ELF section header size ${shentsize}`);
84
+ let shnum = view.getUint16(60, true);
85
+ let shstrndx = view.getUint16(62, true);
86
+ if (shnum === 0)
87
+ shnum = Number(view.getBigUint64(shoff + 32, true));
88
+ if (shstrndx === SHN_XINDEX)
89
+ shstrndx = view.getUint32(shoff + 40, true);
90
+ const sections = [];
91
+ for (let i = 0; i < shnum; i++) {
92
+ const at = shoff + i * 64;
93
+ const type = view.getUint32(at + 4, true);
94
+ const offset = Number(view.getBigUint64(at + 24, true));
95
+ const size = view.getBigUint64(at + 32, true);
96
+ sections.push({
97
+ nameOffset: view.getUint32(at, true),
98
+ type,
99
+ flags: view.getBigUint64(at + 8, true),
100
+ addr: view.getBigUint64(at + 16, true),
101
+ size,
102
+ link: view.getUint32(at + 40, true),
103
+ info: view.getUint32(at + 44, true),
104
+ addralign: view.getBigUint64(at + 48, true),
105
+ entsize: view.getBigUint64(at + 56, true),
106
+ data: type === SHT_NOBITS || type === SHT_NULL
107
+ ? new Uint8Array(0)
108
+ : object.subarray(offset, offset + Number(size)),
109
+ });
110
+ }
111
+ const symtabIndex = sections.findIndex((s) => s.type === SHT_SYMTAB);
112
+ if (symtabIndex < 0)
113
+ fail("ELF object has no symbol table");
114
+ const symtab = sections[symtabIndex];
115
+ const strtab = sections[symtab.link];
116
+ if (strtab === undefined)
117
+ fail("ELF symbol table names an invalid string table");
118
+ // SHT_SYMTAB_SHNDX (only present when section indices overflow st_shndx):
119
+ // resolve the real indices up front and drop the section — the rebuilt
120
+ // object's section count only shrinks, so inline indices always fit.
121
+ const shndxSection = sections.find((s) => s.type === SHT_SYMTAB_SHNDX && sections[s.link] === symtab);
122
+ const shndxView = shndxSection === undefined
123
+ ? null
124
+ : new DataView(shndxSection.data.buffer, shndxSection.data.byteOffset, shndxSection.data.byteLength);
125
+ const symbolName = (offset) => {
126
+ const bytes = strtab.data;
127
+ let end = offset;
128
+ while (end < bytes.length && bytes[end] !== 0)
129
+ end++;
130
+ return textDecoder.decode(bytes.subarray(offset, end));
131
+ };
132
+ const symCount = Number(symtab.size) / 24;
133
+ const symView = new DataView(symtab.data.buffer, symtab.data.byteOffset, symtab.data.byteLength);
134
+ const symbols = [];
135
+ for (let i = 0; i < symCount; i++) {
136
+ const at = i * 24;
137
+ let shndx = symView.getUint16(at + 6, true);
138
+ if (shndx === SHN_XINDEX) {
139
+ if (shndxView === null)
140
+ fail("ELF symbol uses SHN_XINDEX without a SHT_SYMTAB_SHNDX table");
141
+ shndx = shndxView.getUint32(i * 4, true);
142
+ }
143
+ symbols.push({
144
+ nameOffset: symView.getUint32(at, true),
145
+ name: symbolName(symView.getUint32(at, true)),
146
+ info: symView.getUint8(at + 4),
147
+ other: symView.getUint8(at + 5),
148
+ shndx,
149
+ });
150
+ }
151
+ // Section groups resolve away (the --force-group-allocation discipline),
152
+ // and .llvm_addrsig drops — its symbol indices go stale under the symbol
153
+ // reorder below, and its only consumer (ICF safety analysis) treats
154
+ // absence conservatively.
155
+ const sectionKept = sections.map((s) => s.type !== SHT_GROUP &&
156
+ s.type !== SHT_LLVM_ADDRSIG &&
157
+ !(shndxSection !== undefined && s === shndxSection));
158
+ const sectionMap = [];
159
+ {
160
+ let next = 0;
161
+ for (let i = 0; i < sections.length; i++)
162
+ sectionMap.push(sectionKept[i] === true ? next++ : -1);
163
+ }
164
+ for (let i = 0; i < sections.length; i++) {
165
+ if (sectionKept[i] === true)
166
+ sections[i].flags &= ~SHF_GROUP;
167
+ }
168
+ // Demote and drop decisions per symbol. A symbol anchored to a dropped
169
+ // section (a group's own section symbol) leaves with it; nothing may
170
+ // still reference it afterwards.
171
+ const symbolKept = [];
172
+ for (let i = 0; i < symCount; i++) {
173
+ const sym = symbols[i];
174
+ const regular = sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE;
175
+ symbolKept.push(!(regular && sectionKept[sym.shndx] !== true));
176
+ if (i === 0)
177
+ continue;
178
+ const binding = sym.info >> 4;
179
+ const defined = sym.shndx !== SHN_UNDEF && sym.shndx !== SHN_COMMON;
180
+ if (binding !== STB_LOCAL && defined && !keep.has(sym.name)) {
181
+ sym.info = (STB_LOCAL << 4) | (sym.info & 0xf);
182
+ }
183
+ }
184
+ // Reorder: locals first (the spec's symbol-table contract), stable within
185
+ // each half, index 0 pinned.
186
+ const localOrder = [];
187
+ const globalOrder = [];
188
+ for (let i = 0; i < symCount; i++) {
189
+ if (symbolKept[i] !== true)
190
+ continue;
191
+ ((symbols[i].info >> 4) === STB_LOCAL ? localOrder : globalOrder).push(i);
192
+ }
193
+ const symbolOrder = [...localOrder, ...globalOrder];
194
+ const symbolMap = new Array(symCount).fill(-1);
195
+ symbolOrder.forEach((oldIndex, newIndex) => (symbolMap[oldIndex] = newIndex));
196
+ const newSymtabData = new Uint8Array(symbolOrder.length * 24);
197
+ const newSymtabView = new DataView(newSymtabData.buffer);
198
+ symbolOrder.forEach((oldIndex, newIndex) => {
199
+ const sym = symbols[oldIndex];
200
+ const at = newIndex * 24;
201
+ newSymtabView.setUint32(at, sym.nameOffset, true);
202
+ newSymtabView.setUint8(at + 4, sym.info);
203
+ newSymtabView.setUint8(at + 5, sym.other);
204
+ const shndx = sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE ? sectionMap[sym.shndx] : sym.shndx;
205
+ newSymtabView.setUint16(at + 6, shndx, true);
206
+ newSymtabView.setBigUint64(at + 8, symView.getBigUint64(oldIndex * 24 + 8, true), true);
207
+ newSymtabView.setBigUint64(at + 16, symView.getBigUint64(oldIndex * 24 + 16, true), true);
208
+ });
209
+ symtab.data = newSymtabData;
210
+ symtab.size = BigInt(newSymtabData.length);
211
+ symtab.info = localOrder.length;
212
+ // Relocation sections against this symbol table: remap symbol indices.
213
+ for (const section of sections) {
214
+ if ((section.type !== SHT_RELA && section.type !== SHT_REL) || section.link !== symtabIndex) {
215
+ continue;
216
+ }
217
+ const entry = section.type === SHT_RELA ? 24 : 16;
218
+ const data = new Uint8Array(section.data); // private copy — inputs may be shared
219
+ const relView = new DataView(data.buffer);
220
+ for (let at = 0; at + entry <= data.length; at += entry) {
221
+ const info = relView.getBigUint64(at + 8, true);
222
+ const oldSym = Number(info >> 32n);
223
+ if (oldSym === 0)
224
+ continue;
225
+ const newSym = symbolMap[oldSym];
226
+ if (newSym === undefined || newSym < 0) {
227
+ fail("ELF relocation references a symbol dropped with its section group");
228
+ }
229
+ relView.setBigUint64(at + 8, (BigInt(newSym) << 32n) | (info & 0xffffffffn), true);
230
+ }
231
+ section.data = data;
232
+ }
233
+ // Rebuild. sh_link is a section index in every use this object can carry;
234
+ // sh_info is one only on relocation sections.
235
+ const kept = sections.filter((_, i) => sectionKept[i] === true);
236
+ let offset = 64;
237
+ const offsets = [];
238
+ for (const section of kept) {
239
+ if (section.type === SHT_NULL || section.type === SHT_NOBITS) {
240
+ offsets.push(0);
241
+ continue;
242
+ }
243
+ const align = Number(section.addralign > 1n ? section.addralign : 1n);
244
+ offset = Math.ceil(offset / align) * align;
245
+ offsets.push(offset);
246
+ offset += section.data.length;
247
+ }
248
+ const shoffOut = Math.ceil(offset / 8) * 8;
249
+ const out = new Uint8Array(shoffOut + kept.length * 64);
250
+ const outView = new DataView(out.buffer);
251
+ out.set(object.subarray(0, 64));
252
+ outView.setBigUint64(24, 0n, true); // e_entry
253
+ outView.setBigUint64(32, 0n, true); // e_phoff
254
+ outView.setUint16(18, machine, true);
255
+ outView.setUint32(48, flags, true);
256
+ outView.setBigUint64(40, BigInt(shoffOut), true);
257
+ outView.setUint16(54, 0, true); // e_phentsize
258
+ outView.setUint16(56, 0, true); // e_phnum
259
+ outView.setUint16(60, kept.length, true);
260
+ outView.setUint16(62, sectionMap[shstrndx], true);
261
+ kept.forEach((section, i) => {
262
+ if (section.type !== SHT_NULL && section.type !== SHT_NOBITS) {
263
+ out.set(section.data, offsets[i]);
264
+ }
265
+ const at = shoffOut + i * 64;
266
+ outView.setUint32(at, section.nameOffset, true);
267
+ outView.setUint32(at + 4, section.type, true);
268
+ outView.setBigUint64(at + 8, section.flags, true);
269
+ outView.setBigUint64(at + 16, section.addr, true);
270
+ outView.setBigUint64(at + 24, BigInt(offsets[i]), true);
271
+ outView.setBigUint64(at + 32, section.type === SHT_NOBITS ? section.size : BigInt(section.data.length), true);
272
+ outView.setUint32(at + 40, section.link === 0 ? 0 : sectionMap[section.link] ?? 0, true);
273
+ outView.setUint32(at + 44, section.type === SHT_RELA || section.type === SHT_REL
274
+ ? sectionMap[section.info] ?? 0
275
+ : section === symtab
276
+ ? symtab.info
277
+ : section.info, true);
278
+ outView.setBigUint64(at + 48, section.addralign, true);
279
+ outView.setBigUint64(at + 56, section.entsize, true);
280
+ });
281
+ return out;
282
+ }
283
+ /* ────────────────────────────── COFF ──────────────────────────────────── */
284
+ const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
285
+ const IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000;
286
+ const IMAGE_SCN_LNK_COMDAT = 0x00001000;
287
+ const IMAGE_SYM_UNDEFINED = 0;
288
+ const IMAGE_SYM_ABSOLUTE = -1;
289
+ const IMAGE_SYM_CLASS_EXTERNAL = 2;
290
+ const IMAGE_SYM_CLASS_STATIC = 3;
291
+ const IMAGE_SYM_CLASS_SECTION = 104;
292
+ const IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105;
293
+ const IMAGE_COMDAT_SELECT_NODUPLICATES = 1;
294
+ const IMAGE_COMDAT_SELECT_ANY = 2;
295
+ const IMAGE_COMDAT_SELECT_SAME_SIZE = 3;
296
+ const IMAGE_COMDAT_SELECT_EXACT_MATCH = 4;
297
+ const IMAGE_COMDAT_SELECT_ASSOCIATIVE = 5;
298
+ const IMAGE_COMDAT_SELECT_LARGEST = 6;
299
+ function readCString(bytes, offset) {
300
+ let end = offset;
301
+ while (end < bytes.length && bytes[end] !== 0)
302
+ end++;
303
+ return textDecoder.decode(bytes.subarray(offset, end));
304
+ }
305
+ function parseCoff(object, label) {
306
+ const view = new DataView(object.buffer, object.byteOffset, object.byteLength);
307
+ if (object.byteLength < 20)
308
+ fail(`${label}: not a COFF object`);
309
+ const machine = view.getUint16(0, true);
310
+ if (machine === 0 && view.getUint16(2, true) === 0xffff) {
311
+ fail(`${label}: bigobj/import-descriptor COFF members are not supported here`);
312
+ }
313
+ const sectionCount = view.getUint16(2, true);
314
+ const symtabOffset = view.getUint32(8, true);
315
+ const symbolCount = view.getUint32(12, true);
316
+ if (view.getUint16(16, true) !== 0)
317
+ fail(`${label}: unexpected optional header in a COFF object`);
318
+ const strtabOffset = symtabOffset + symbolCount * 18;
319
+ const strtab = object.subarray(strtabOffset);
320
+ const sectionName = (raw) => {
321
+ if (raw[0] === 0x2f /* '/' */) {
322
+ const spelled = textDecoder.decode(raw.subarray(1)).replace(/\0+$/, "").trim();
323
+ const offset = Number.parseInt(spelled, 10);
324
+ if (!Number.isFinite(offset))
325
+ fail(`${label}: malformed long section name`);
326
+ return readCString(strtab, offset);
327
+ }
328
+ let end = 0;
329
+ while (end < 8 && raw[end] !== 0)
330
+ end++;
331
+ return textDecoder.decode(raw.subarray(0, end));
332
+ };
333
+ const sections = [];
334
+ for (let i = 0; i < sectionCount; i++) {
335
+ const at = 20 + i * 40;
336
+ const rawSize = view.getUint32(at + 16, true);
337
+ const rawPointer = view.getUint32(at + 20, true);
338
+ const relocPointer = view.getUint32(at + 24, true);
339
+ let relocCount = view.getUint16(at + 32, true);
340
+ const characteristics = view.getUint32(at + 36, true);
341
+ let relocAt = relocPointer;
342
+ if ((characteristics & IMAGE_SCN_LNK_NRELOC_OVFL) !== 0 && relocCount === 0xffff) {
343
+ relocCount = view.getUint32(relocPointer, true) - 1;
344
+ relocAt += 10;
345
+ }
346
+ const relocs = [];
347
+ for (let r = 0; r < relocCount; r++) {
348
+ const rAt = relocAt + r * 10;
349
+ relocs.push({
350
+ virtualAddress: view.getUint32(rAt, true),
351
+ symbolIndex: view.getUint32(rAt + 4, true),
352
+ type: view.getUint16(rAt + 8, true),
353
+ });
354
+ }
355
+ sections.push({
356
+ name: sectionName(object.subarray(at, at + 8)),
357
+ virtualSize: view.getUint32(at + 8, true),
358
+ virtualAddress: view.getUint32(at + 12, true),
359
+ characteristics,
360
+ data: rawPointer === 0 ? null : object.subarray(rawPointer, rawPointer + rawSize),
361
+ rawSize,
362
+ relocs,
363
+ comdatSelection: 0,
364
+ comdatAssoc: 0,
365
+ comdatLeader: -1,
366
+ });
367
+ }
368
+ const symbols = [];
369
+ const primaryByRaw = new Map();
370
+ const rawByPrimary = [];
371
+ for (let i = 0; i < symbolCount;) {
372
+ const at = symtabOffset + i * 18;
373
+ const nameIsLong = view.getUint32(at, true) === 0;
374
+ const name = nameIsLong
375
+ ? readCString(strtab, view.getUint32(at + 4, true))
376
+ : (() => {
377
+ const raw = object.subarray(at, at + 8);
378
+ let end = 0;
379
+ while (end < 8 && raw[end] !== 0)
380
+ end++;
381
+ return textDecoder.decode(raw.subarray(0, end));
382
+ })();
383
+ const auxCount = view.getUint8(at + 17);
384
+ primaryByRaw.set(i, symbols.length);
385
+ rawByPrimary.push(i);
386
+ symbols.push({
387
+ name,
388
+ value: view.getUint32(at + 8, true),
389
+ sectionNumber: view.getInt16(at + 12, true),
390
+ type: view.getUint16(at + 14, true),
391
+ storageClass: view.getUint8(at + 16),
392
+ aux: object.slice(at + 18, at + 18 + auxCount * 18),
393
+ });
394
+ i += 1 + auxCount;
395
+ }
396
+ // COMDAT metadata rides the section symbol's aux record (selection,
397
+ // associated section) and the record after it (the COMDAT symbol — the
398
+ // linker's deduplication name).
399
+ for (let sectionIndex = 1; sectionIndex <= sections.length; sectionIndex++) {
400
+ const section = sections[sectionIndex - 1];
401
+ if ((section.characteristics & IMAGE_SCN_LNK_COMDAT) === 0)
402
+ continue;
403
+ let sawSectionSymbol = false;
404
+ for (let primary = 0; primary < symbols.length; primary++) {
405
+ const sym = symbols[primary];
406
+ if (sym.sectionNumber !== sectionIndex)
407
+ continue;
408
+ if (!sawSectionSymbol && sym.value === 0 && sym.name === section.name && sym.aux.length >= 18) {
409
+ sawSectionSymbol = true;
410
+ const auxView = new DataView(sym.aux.buffer, sym.aux.byteOffset, sym.aux.byteLength);
411
+ section.comdatSelection = auxView.getUint8(14);
412
+ section.comdatAssoc = auxView.getUint16(12, true);
413
+ continue;
414
+ }
415
+ section.comdatLeader = primary;
416
+ break;
417
+ }
418
+ if (!sawSectionSymbol)
419
+ fail(`${label}: COMDAT section ${section.name} has no section symbol`);
420
+ }
421
+ return { machine, sections, symbols, primaryByRaw, rawByPrimary, label };
422
+ }
423
+ function coffDefines(sym) {
424
+ return ((sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
425
+ (sym.sectionNumber > 0 || sym.sectionNumber === IMAGE_SYM_ABSOLUTE)) ||
426
+ coffIsCommon(sym));
427
+ }
428
+ function coffIsCommon(sym) {
429
+ return (sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
430
+ sym.sectionNumber === IMAGE_SYM_UNDEFINED &&
431
+ sym.value > 0);
432
+ }
433
+ function coffIsUndefined(sym) {
434
+ return ((sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
435
+ sym.sectionNumber === IMAGE_SYM_UNDEFINED &&
436
+ sym.value === 0) ||
437
+ sym.storageClass === IMAGE_SYM_CLASS_WEAK_EXTERNAL);
438
+ }
439
+ function coffSectionContentsEqual(a, b) {
440
+ if (a.rawSize !== b.rawSize)
441
+ return false;
442
+ if (a.data === null || b.data === null)
443
+ return a.data === b.data;
444
+ if (a.data.length !== b.data.length)
445
+ return false;
446
+ for (let i = 0; i < a.data.length; i++) {
447
+ if (a.data[i] !== b.data[i])
448
+ return false;
449
+ }
450
+ return true;
451
+ }
452
+ /** Combine a program object with the support objects it (transitively)
453
+ * needs into ONE COFF object, then demote every defined external outside
454
+ * `keep` to a static symbol. Support objects join on undefined-symbol
455
+ * demand — the staging-archive member semantics the other formats get from
456
+ * `ld -r` — so an unused member's undefined references never reach the
457
+ * embedder's link. x86_64 only (the one COFF target scriptc produces). */
458
+ export function mergeAndLocalizeCoffObjects(program, support, keep, labels) {
459
+ const objects = [
460
+ parseCoff(program, labels?.program ?? "program object"),
461
+ ...support.map((bytes, i) => parseCoff(bytes, labels?.support?.[i] ?? `support object ${i}`)),
462
+ ];
463
+ for (const object of objects) {
464
+ if (object.machine !== IMAGE_FILE_MACHINE_AMD64) {
465
+ fail(`${object.label}: unsupported COFF machine 0x${object.machine.toString(16)}`);
466
+ }
467
+ }
468
+ // Member selection: archive semantics over the support list. A definition
469
+ // (including a COMMON) satisfies an undefined reference; first definer in
470
+ // list order wins the pull, matching `ar` member order.
471
+ const definers = new Map();
472
+ objects.forEach((object, index) => {
473
+ if (index === 0)
474
+ return;
475
+ for (const sym of object.symbols) {
476
+ if (!coffDefines(sym))
477
+ continue;
478
+ const list = definers.get(sym.name);
479
+ if (list === undefined)
480
+ definers.set(sym.name, [index]);
481
+ else
482
+ list.push(index);
483
+ }
484
+ });
485
+ const included = objects.map((_, i) => i === 0);
486
+ // Archive extraction consults the linker's CURRENT symbol state: an
487
+ // undefined in a newly pulled member is already satisfied when the
488
+ // program object (or an earlier member) defines it. Record every
489
+ // selected definition as soon as its member joins, before that member's
490
+ // own undefineds are considered. Without this set, a support member can
491
+ // spuriously pull an alternate definition that a real archive link would
492
+ // leave untouched, and the merge later reports a false duplicate.
493
+ const selectedDefinitions = new Set();
494
+ const addDefinitions = (object) => {
495
+ for (const sym of object.symbols) {
496
+ if (coffDefines(sym))
497
+ selectedDefinitions.add(sym.name);
498
+ }
499
+ };
500
+ addDefinitions(objects[0]);
501
+ const queue = [0];
502
+ while (queue.length > 0) {
503
+ const object = objects[queue.shift()];
504
+ for (const sym of object.symbols) {
505
+ if (!coffIsUndefined(sym))
506
+ continue;
507
+ if (selectedDefinitions.has(sym.name))
508
+ continue;
509
+ for (const candidate of definers.get(sym.name) ?? []) {
510
+ if (included[candidate] !== true) {
511
+ included[candidate] = true;
512
+ addDefinitions(objects[candidate]);
513
+ queue.push(candidate);
514
+ }
515
+ break;
516
+ }
517
+ }
518
+ }
519
+ const selected = objects.filter((_, i) => included[i] === true);
520
+ // COMDAT resolution, the ELF arm's --force-group-allocation analog:
521
+ // resolve duplicate names inside THIS merge according to their selection
522
+ // contract (mingw's .refptr.<name> stubs use ANY), then clear the COMDAT
523
+ // flag on the survivors at emission. A section whose deduplication name
524
+ // repeats across archives sharing runtime objects must not be deduplicated
525
+ // against another archive's copy at the embedder's link, where its
526
+ // relocations bind that archive's private (demoted) definitions.
527
+ const sectionDropped = new Map();
528
+ const comdatKept = new Map();
529
+ for (const object of selected) {
530
+ const dropped = object.sections.map(() => false);
531
+ sectionDropped.set(object, dropped);
532
+ object.sections.forEach((section, i) => {
533
+ // .llvm_addrsig: its ULEB symbol indices go stale under the merge and
534
+ // its absence is read conservatively. .debug$S/.debug$T (CodeView):
535
+ // item indices resolve against the OWNING object's .debug$T, a
536
+ // per-object contract no single merged object can express — the
537
+ // localized member carries no CodeView, like an objcopy'd ELF member
538
+ // carries its DWARF untouched but a stripped one loses it.
539
+ if (section.name === ".llvm_addrsig" || section.name.startsWith(".debug$")) {
540
+ dropped[i] = true;
541
+ }
542
+ });
543
+ }
544
+ for (const object of selected) {
545
+ const dropped = sectionDropped.get(object);
546
+ object.sections.forEach((section, i) => {
547
+ if (dropped[i] === true || (section.characteristics & IMAGE_SCN_LNK_COMDAT) === 0)
548
+ return;
549
+ if (section.comdatSelection === IMAGE_COMDAT_SELECT_ASSOCIATIVE)
550
+ return;
551
+ if (section.comdatSelection !== IMAGE_COMDAT_SELECT_NODUPLICATES &&
552
+ section.comdatSelection !== IMAGE_COMDAT_SELECT_ANY &&
553
+ section.comdatSelection !== IMAGE_COMDAT_SELECT_SAME_SIZE &&
554
+ section.comdatSelection !== IMAGE_COMDAT_SELECT_EXACT_MATCH &&
555
+ section.comdatSelection !== IMAGE_COMDAT_SELECT_LARGEST) {
556
+ fail(`${object.label}: COMDAT section ${section.name} has unsupported selection ${section.comdatSelection}`);
557
+ }
558
+ const leader = section.comdatLeader >= 0 ? object.symbols[section.comdatLeader] : undefined;
559
+ if (leader === undefined || leader.storageClass !== IMAGE_SYM_CLASS_EXTERNAL) {
560
+ fail(`${object.label}: COMDAT section ${section.name} has no external leader symbol`);
561
+ }
562
+ const existing = comdatKept.get(leader.name);
563
+ if (existing === undefined) {
564
+ comdatKept.set(leader.name, { object, index: i });
565
+ return;
566
+ }
567
+ const existingSection = existing.object.sections[existing.index];
568
+ if (section.comdatSelection !== existingSection.comdatSelection) {
569
+ fail(`conflicting COMDAT selections for ${leader.name} ` +
570
+ `(${existing.object.label}: ${existingSection.comdatSelection}, ${object.label}: ${section.comdatSelection})`);
571
+ }
572
+ if (section.comdatSelection === IMAGE_COMDAT_SELECT_NODUPLICATES) {
573
+ fail(`duplicate IMAGE_COMDAT_SELECT_NODUPLICATES section ${section.name} (${existing.object.label} and ${object.label})`);
574
+ }
575
+ if (section.comdatSelection === IMAGE_COMDAT_SELECT_SAME_SIZE &&
576
+ section.rawSize !== existingSection.rawSize) {
577
+ fail(`IMAGE_COMDAT_SELECT_SAME_SIZE mismatch for ${leader.name} ` +
578
+ `(${existing.object.label}: ${existingSection.rawSize} bytes, ${object.label}: ${section.rawSize} bytes)`);
579
+ }
580
+ if (section.comdatSelection === IMAGE_COMDAT_SELECT_EXACT_MATCH &&
581
+ !coffSectionContentsEqual(existingSection, section)) {
582
+ fail(`IMAGE_COMDAT_SELECT_EXACT_MATCH mismatch for ${leader.name} ` +
583
+ `(${existing.object.label} and ${object.label})`);
584
+ }
585
+ if (section.comdatSelection === IMAGE_COMDAT_SELECT_LARGEST &&
586
+ section.rawSize > existingSection.rawSize) {
587
+ sectionDropped.get(existing.object)[existing.index] = true;
588
+ comdatKept.set(leader.name, { object, index: i });
589
+ }
590
+ else {
591
+ dropped[i] = true;
592
+ }
593
+ });
594
+ }
595
+ // Associative sections follow their target's final fate. Resolve these
596
+ // only after every non-associative winner is known: LARGEST can replace a
597
+ // section seen in an earlier object, whose associates must then leave too.
598
+ for (const object of selected) {
599
+ const dropped = sectionDropped.get(object);
600
+ object.sections.forEach((section, i) => {
601
+ if (dropped[i] === true || section.comdatSelection !== IMAGE_COMDAT_SELECT_ASSOCIATIVE)
602
+ return;
603
+ const seen = new Set([i]);
604
+ let target = section.comdatAssoc - 1;
605
+ while (true) {
606
+ if (target < 0 || target >= object.sections.length) {
607
+ fail(`${object.label}: associative COMDAT ${section.name} names an invalid section`);
608
+ }
609
+ if (seen.has(target)) {
610
+ fail(`${object.label}: associative COMDAT ${section.name} contains an association cycle`);
611
+ }
612
+ seen.add(target);
613
+ const targetSection = object.sections[target];
614
+ if ((targetSection.characteristics & IMAGE_SCN_LNK_COMDAT) === 0) {
615
+ fail(`${object.label}: associative COMDAT ${section.name} targets a non-COMDAT section`);
616
+ }
617
+ if (dropped[target] === true) {
618
+ dropped[i] = true;
619
+ break;
620
+ }
621
+ if (targetSection.comdatSelection !== IMAGE_COMDAT_SELECT_ASSOCIATIVE)
622
+ break;
623
+ target = targetSection.comdatAssoc - 1;
624
+ }
625
+ });
626
+ }
627
+ const canonical = new Map();
628
+ for (const object of selected) {
629
+ const dropped = sectionDropped.get(object);
630
+ for (const sym of object.symbols) {
631
+ if (sym.storageClass !== IMAGE_SYM_CLASS_EXTERNAL)
632
+ continue;
633
+ if (sym.sectionNumber > 0 && dropped[sym.sectionNumber - 1] === true)
634
+ continue;
635
+ const existing = canonical.get(sym.name);
636
+ if (coffDefines(sym) && !coffIsCommon(sym)) {
637
+ if (existing?.kind === "defined") {
638
+ fail(`duplicate external symbol ${sym.name} (${existing.definer ?? "?"} and ${object.label})`);
639
+ }
640
+ canonical.set(sym.name, {
641
+ kind: "defined",
642
+ definer: object.label,
643
+ commonSize: 0,
644
+ outIndex: -1,
645
+ });
646
+ }
647
+ else if (coffIsCommon(sym)) {
648
+ if (existing === undefined || existing.kind === "undefined") {
649
+ canonical.set(sym.name, { kind: "common", commonSize: sym.value, outIndex: -1 });
650
+ }
651
+ else if (existing.kind === "common" && sym.value > existing.commonSize) {
652
+ existing.commonSize = sym.value;
653
+ }
654
+ }
655
+ else if (existing === undefined) {
656
+ canonical.set(sym.name, { kind: "undefined", commonSize: 0, outIndex: -1 });
657
+ }
658
+ }
659
+ }
660
+ // A weak external's resolution happens at the embedder's link; a weak
661
+ // reference to a name this pass demotes would dangle there. scriptc's
662
+ // toolchains emit no weak externals in these objects — refuse rather than
663
+ // approximate if one ever appears against a demoted name.
664
+ for (const object of selected) {
665
+ for (const sym of object.symbols) {
666
+ if (sym.storageClass !== IMAGE_SYM_CLASS_WEAK_EXTERNAL)
667
+ continue;
668
+ const target = canonical.get(sym.name);
669
+ if (target?.kind === "defined" && !keep.has(sym.name)) {
670
+ fail(`${object.label}: weak external ${sym.name} would dangle after localization`);
671
+ }
672
+ }
673
+ }
674
+ const outSections = [];
675
+ const sectionMaps = new Map();
676
+ for (const object of selected) {
677
+ const map = [0];
678
+ const dropped = sectionDropped.get(object);
679
+ object.sections.forEach((section, i) => {
680
+ if (dropped[i] === true) {
681
+ map.push(0);
682
+ return;
683
+ }
684
+ outSections.push({ section, object });
685
+ map.push(outSections.length);
686
+ });
687
+ sectionMaps.set(object, map);
688
+ }
689
+ if (outSections.length > 0xfffe)
690
+ fail("merged COFF object exceeds the section-count limit");
691
+ const outSymbols = [];
692
+ const symbolMaps = new Map(); // raw old → raw new
693
+ let rawOut = 0;
694
+ for (const object of selected) {
695
+ const map = new Map();
696
+ symbolMaps.set(object, map);
697
+ const sectionMap = sectionMaps.get(object);
698
+ object.symbols.forEach((sym, primaryIndex) => {
699
+ const rawOld = object.rawByPrimary[primaryIndex];
700
+ const mapSection = () => sym.sectionNumber > 0 ? sectionMap[sym.sectionNumber] ?? 0 : sym.sectionNumber;
701
+ if (sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL) {
702
+ const inDroppedSection = sym.sectionNumber > 0 &&
703
+ sectionDropped.get(object)[sym.sectionNumber - 1] === true;
704
+ const entry = canonical.get(sym.name);
705
+ if (entry.outIndex >= 0) {
706
+ // Later appearance of a known external: reference the canonical
707
+ // entry, unless THIS record is the definition that must land (a
708
+ // dropped duplicate COMDAT's symbol is a reference to the kept
709
+ // copy, never a second landing).
710
+ const isTheDefinition = entry.kind === "defined" && coffDefines(sym) && !coffIsCommon(sym) && !inDroppedSection;
711
+ if (!isTheDefinition) {
712
+ map.set(rawOld, entry.outIndex);
713
+ return;
714
+ }
715
+ // The canonical slot was created by an earlier reference; the
716
+ // definition replaces it in place.
717
+ const slot = outSymbols[entry.outSlot];
718
+ slot.sym = sym;
719
+ slot.object = object;
720
+ slot.sectionNumber = mapSection();
721
+ slot.value = sym.value;
722
+ slot.storageClass = keep.has(sym.name)
723
+ ? IMAGE_SYM_CLASS_EXTERNAL
724
+ : IMAGE_SYM_CLASS_STATIC;
725
+ map.set(rawOld, entry.outIndex);
726
+ return;
727
+ }
728
+ const sectionNumber = entry.kind === "defined" && coffDefines(sym) && !coffIsCommon(sym)
729
+ ? mapSection()
730
+ : IMAGE_SYM_UNDEFINED;
731
+ const value = entry.kind === "common" ? entry.commonSize : entry.kind === "defined" && !coffIsCommon(sym) ? sym.value : 0;
732
+ const demote = entry.kind === "defined" &&
733
+ coffDefines(sym) &&
734
+ !coffIsCommon(sym) &&
735
+ !keep.has(sym.name);
736
+ entry.outIndex = rawOut;
737
+ entry.outSlot = outSymbols.length;
738
+ outSymbols.push({
739
+ sym,
740
+ object,
741
+ sectionNumber,
742
+ storageClass: demote ? IMAGE_SYM_CLASS_STATIC : IMAGE_SYM_CLASS_EXTERNAL,
743
+ value,
744
+ });
745
+ map.set(rawOld, rawOut);
746
+ rawOut += 1; // externals carry no aux records in this pipeline
747
+ if (sym.aux.length > 0) {
748
+ fail(`${object.label}: external symbol ${sym.name} carries aux records`);
749
+ }
750
+ return;
751
+ }
752
+ // A record anchored to a dropped section (.llvm_addrsig's section
753
+ // symbol) leaves with it; nothing may still reference it afterwards.
754
+ if (sym.sectionNumber > 0 && sectionMap[sym.sectionNumber] === 0)
755
+ return;
756
+ // Non-external record: copy through with a remapped section number.
757
+ map.set(rawOld, rawOut);
758
+ outSymbols.push({
759
+ sym,
760
+ object,
761
+ sectionNumber: mapSection(),
762
+ storageClass: sym.storageClass,
763
+ value: sym.value,
764
+ });
765
+ rawOut += 1 + sym.aux.length / 18;
766
+ });
767
+ }
768
+ // Pass 3 — remap relocations and aux records now every index is known.
769
+ for (const { section, object } of outSections) {
770
+ const map = symbolMaps.get(object);
771
+ for (const reloc of section.relocs) {
772
+ const mapped = map.get(reloc.symbolIndex);
773
+ if (mapped === undefined) {
774
+ // Relocations may address a record via its primary index only.
775
+ fail(`${object.label}: relocation in ${section.name} references a non-primary symbol`);
776
+ }
777
+ reloc.symbolIndex = mapped;
778
+ }
779
+ }
780
+ // Serialize.
781
+ const strings = [];
782
+ let strtabSize = 4;
783
+ const stringOffsets = new Map();
784
+ const internString = (name) => {
785
+ const existing = stringOffsets.get(name);
786
+ if (existing !== undefined)
787
+ return existing;
788
+ const bytes = textEncoder.encode(`${name}\0`);
789
+ strings.push(bytes);
790
+ const offset = strtabSize;
791
+ stringOffsets.set(name, offset);
792
+ strtabSize += bytes.length;
793
+ return offset;
794
+ };
795
+ const headerSize = 20 + outSections.length * 40;
796
+ let dataOffset = headerSize;
797
+ const dataOffsets = [];
798
+ for (const { section } of outSections) {
799
+ if (section.data === null) {
800
+ dataOffsets.push(0);
801
+ continue;
802
+ }
803
+ dataOffset = Math.ceil(dataOffset / 4) * 4;
804
+ dataOffsets.push(dataOffset);
805
+ dataOffset += section.rawSize;
806
+ }
807
+ const relocOffsets = [];
808
+ for (const { section } of outSections) {
809
+ if (section.relocs.length === 0) {
810
+ relocOffsets.push(0);
811
+ continue;
812
+ }
813
+ dataOffset = Math.ceil(dataOffset / 2) * 2;
814
+ relocOffsets.push(dataOffset);
815
+ dataOffset += (section.relocs.length + (section.relocs.length > 0xfffe ? 1 : 0)) * 10;
816
+ }
817
+ const symtabOffset = Math.ceil(dataOffset / 4) * 4;
818
+ const totalSymbols = rawOut;
819
+ const preStrtab = symtabOffset + totalSymbols * 18;
820
+ const buffers = [];
821
+ const header = new Uint8Array(headerSize);
822
+ const headerView = new DataView(header.buffer);
823
+ headerView.setUint16(0, IMAGE_FILE_MACHINE_AMD64, true);
824
+ headerView.setUint16(2, outSections.length, true);
825
+ headerView.setUint32(4, 0, true); // deterministic timestamp
826
+ headerView.setUint32(8, symtabOffset, true);
827
+ headerView.setUint32(12, totalSymbols, true);
828
+ outSections.forEach(({ section }, i) => {
829
+ const at = 20 + i * 40;
830
+ const nameBytes = textEncoder.encode(section.name);
831
+ if (nameBytes.length <= 8) {
832
+ header.set(nameBytes, at);
833
+ }
834
+ else {
835
+ header.set(textEncoder.encode(`/${internString(section.name)}`), at);
836
+ }
837
+ headerView.setUint32(at + 8, section.virtualSize, true);
838
+ headerView.setUint32(at + 12, section.virtualAddress, true);
839
+ headerView.setUint32(at + 16, section.rawSize, true);
840
+ headerView.setUint32(at + 20, dataOffsets[i], true);
841
+ headerView.setUint32(at + 24, relocOffsets[i], true);
842
+ headerView.setUint32(at + 28, 0, true); // line numbers (deprecated)
843
+ const overflow = section.relocs.length > 0xfffe;
844
+ headerView.setUint16(at + 32, overflow ? 0xffff : section.relocs.length, true);
845
+ headerView.setUint16(at + 34, 0, true);
846
+ const characteristics = section.characteristics & ~IMAGE_SCN_LNK_COMDAT;
847
+ headerView.setUint32(at + 36, overflow
848
+ ? characteristics | IMAGE_SCN_LNK_NRELOC_OVFL
849
+ : characteristics & ~IMAGE_SCN_LNK_NRELOC_OVFL, true);
850
+ });
851
+ buffers.push({ at: 0, bytes: header });
852
+ outSections.forEach(({ section }, i) => {
853
+ if (section.data !== null)
854
+ buffers.push({ at: dataOffsets[i], bytes: section.data });
855
+ if (section.relocs.length === 0)
856
+ return;
857
+ const overflow = section.relocs.length > 0xfffe;
858
+ const relocBytes = new Uint8Array((section.relocs.length + (overflow ? 1 : 0)) * 10);
859
+ const relocView = new DataView(relocBytes.buffer);
860
+ let at = 0;
861
+ if (overflow) {
862
+ relocView.setUint32(0, section.relocs.length + 1, true);
863
+ at = 10;
864
+ }
865
+ for (const reloc of section.relocs) {
866
+ relocView.setUint32(at, reloc.virtualAddress, true);
867
+ relocView.setUint32(at + 4, reloc.symbolIndex, true);
868
+ relocView.setUint16(at + 8, reloc.type, true);
869
+ at += 10;
870
+ }
871
+ buffers.push({ at: relocOffsets[i], bytes: relocBytes });
872
+ });
873
+ const symtabBytes = new Uint8Array(totalSymbols * 18);
874
+ const symtabView = new DataView(symtabBytes.buffer);
875
+ {
876
+ let raw = 0;
877
+ for (const out of outSymbols) {
878
+ const at = raw * 18;
879
+ const nameBytes = textEncoder.encode(out.sym.name);
880
+ if (nameBytes.length <= 8) {
881
+ symtabBytes.set(nameBytes, at);
882
+ }
883
+ else {
884
+ symtabView.setUint32(at, 0, true);
885
+ symtabView.setUint32(at + 4, internString(out.sym.name), true);
886
+ }
887
+ symtabView.setUint32(at + 8, out.value, true);
888
+ symtabView.setInt16(at + 12, out.sectionNumber, true);
889
+ symtabView.setUint16(at + 14, out.sym.type, true);
890
+ symtabView.setUint8(at + 16, out.storageClass);
891
+ symtabView.setUint8(at + 17, out.sym.aux.length / 18);
892
+ // Aux records: remap the fields that carry indices. Section
893
+ // definitions (on section symbols) carry an associated section
894
+ // number; weak externals carry a default-symbol index. Others copy
895
+ // raw (.file's aux is the filename bytes).
896
+ const aux = new Uint8Array(out.sym.aux); // private copy
897
+ if ((out.storageClass === IMAGE_SYM_CLASS_STATIC ||
898
+ out.storageClass === IMAGE_SYM_CLASS_SECTION) &&
899
+ aux.length === 18 &&
900
+ out.sym.value === 0 &&
901
+ out.sym.sectionNumber > 0) {
902
+ const auxView = new DataView(aux.buffer);
903
+ const assoc = auxView.getUint16(12, true);
904
+ const sectionMap = sectionMaps.get(out.object);
905
+ auxView.setUint16(12, assoc > 0 ? sectionMap[assoc] ?? 0 : 0, true);
906
+ auxView.setUint8(14, 0); // COMDAT selection cleared with the flag
907
+ }
908
+ else if (out.storageClass === IMAGE_SYM_CLASS_WEAK_EXTERNAL && aux.length >= 18) {
909
+ const auxView = new DataView(aux.buffer);
910
+ const tag = auxView.getUint32(0, true);
911
+ const mapped = symbolMaps.get(out.object).get(tag);
912
+ if (mapped === undefined)
913
+ fail(`${out.object.label}: weak external tag is not a primary symbol`);
914
+ auxView.setUint32(0, mapped, true);
915
+ }
916
+ symtabBytes.set(aux, at + 18);
917
+ raw += 1 + aux.length / 18;
918
+ }
919
+ }
920
+ buffers.push({ at: symtabOffset, bytes: symtabBytes });
921
+ const strtabBytes = new Uint8Array(strtabSize);
922
+ new DataView(strtabBytes.buffer).setUint32(0, strtabSize, true);
923
+ {
924
+ let at = 4;
925
+ for (const chunk of strings) {
926
+ strtabBytes.set(chunk, at);
927
+ at += chunk.length;
928
+ }
929
+ }
930
+ buffers.push({ at: preStrtab, bytes: strtabBytes });
931
+ const out = new Uint8Array(preStrtab + strtabSize);
932
+ for (const { at, bytes } of buffers)
933
+ out.set(bytes, at);
934
+ return out;
935
+ }
936
+ //# sourceMappingURL=object-localize.js.map