kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

link.c (73398B)


      1 /* link_emit_coff: write a PE32+ MH_EXECUTABLE-style image to the
      2  * caller-provided Writer.
      3  *
      4  * Phase 3.1 deliverable per doc/OBJ.md: skeleton + base-reloc
      5  * handling for the four standard PE sections.  Import-table synthesis
      6  * (.idata / IAT) lands in Phase 3.2; per-arch IAT stub bytes in 3.3;
      7  * TLS directory in 3.5; debug directory in 3.6 — those code paths
      8  * panic loudly here so the strict-by-default posture surfaces them.
      9  *
     10  * File layout (in write order):
     11  *
     12  *   [DOS stub IMAGE_DOS_HEADER]                  -- 64 bytes; e_lfanew=0x40
     13  *   [PE signature "PE\0\0"]                      -- 4 bytes
     14  *   [IMAGE_FILE_HEADER]                          -- 20 bytes
     15  *   [IMAGE_OPTIONAL_HEADER64]                    -- 240 bytes (PE32+)
     16  *   [IMAGE_SECTION_HEADER * nsec]                -- 40 bytes each
     17  *   [pad to FileAlignment]
     18  *   [.text bytes, padded to FileAlignment]
     19  *   [.rdata bytes, padded to FileAlignment]
     20  *   [.data bytes, padded to FileAlignment]
     21  *   [.reloc bytes, padded to FileAlignment]
     22  *
     23  * .bss is uninitialized — it has a section header (with VirtualSize)
     24  * but no file bytes and PointerToRawData=0.
     25  *
     26  * RVAs follow SectionAlignment (0x1000); FileAlignment is 0x200; the
     27  * first section starts at RVA 0x1000 (right after the headers map).
     28  * ImageBase is the Win64 convention 0x140000000.
     29  *
     30  * Reloc strategy.  The link layout pass has already placed every kept
     31  * input section into img->sections / img->segments under the ELF/Mach-O
     32  * coordinate system (image-relative vaddrs, often packed by permission
     33  * bucket).  COFF wants a different packing — the four standard
     34  * sections at SectionAlignment-aligned RVAs — so this writer re-derives
     35  * per-input-section vaddrs from scratch and shifts each LinkSection /
     36  * symbol / LinkRelocApply by its section's per-section delta before
     37  * applying relocations.  link_emit_macho takes the same tack for its
     38  * __DATA_CONST splits; the ELF writer leaves vaddrs alone because the
     39  * link layout already matches ELF's PT_LOAD shape. */
     40 
     41 #include "link/link.h"
     42 
     43 #include <stdlib.h>
     44 #include <string.h>
     45 
     46 #include "core/core.h"
     47 #include "core/heap.h"
     48 #include "core/pool.h"
     49 #include "core/slice.h"
     50 #include "core/util.h"
     51 #include "core/vec.h"
     52 #include "link/link_internal.h"
     53 #include "obj/coff/coff.h"
     54 #include "obj/format.h"
     55 
     56 /* ---- .idata layout constants ----
     57  *
     58  * Per doc/OBJ.md: the .idata content is a concatenation of an
     59  * IMAGE_IMPORT_DESCRIPTOR table (NULL-terminated), one ILT per DLL
     60  * (each NULL-terminated u64 array), one IAT per DLL (same shape),
     61  * a hint/name table, and a DLL-name string pool.  Each block is
     62  * pointer-sized aligned within the section.  AArch64 import thunks use
     63  * PAGEOFFSET_12L for 64-bit ILT/IAT slots, so those sub-blocks must be
     64  * 8-byte aligned. */
     65 #define PE_IDATA_BLOCK_ALIGN 8u
     66 /* Hint field on IMAGE_IMPORT_BY_NAME records.  kit never has a real
     67  * hint (the OS loader doesn't need one to do the bsearch on the DLL's
     68  * export name table), so 0 is the canonical "no hint" value. */
     69 #define PE_IMPORT_HINT_NONE 0u
     70 
     71 /* ---- PE/Win64 layout constants ----
     72  *
     73  * Centralised here so the wire-format numbers in this TU stay named
     74  * (and the magic-numbers rule in CLAUDE.md is honoured).  Values match
     75  * the PE/COFF spec + Win64 conventions; mingw-w64's ld defaults agree. */
     76 #define PE_IMAGE_BASE LINK_PE_IMAGE_BASE
     77 #define PE_SECTION_ALIGNMENT 0x1000u
     78 #define PE_FILE_ALIGNMENT 0x200u
     79 #define PE_FIRST_SECTION_RVA 0x1000u
     80 #define PE_DOS_E_LFANEW 0x40u
     81 #define PE_NUM_DATA_DIRS COFF_NUM_DATA_DIRECTORIES
     82 #define PE_OPT_HDR_SIZE COFF_OPT_HDR64_SIZE
     83 #define PE_LINKER_MAJOR 0u
     84 #define PE_LINKER_MINOR 1u
     85 #define PE_OS_MAJOR 6u /* Windows Vista+ — mingw default */
     86 #define PE_OS_MINOR 0u
     87 #define PE_SUBSYS_MAJOR 6u
     88 #define PE_SUBSYS_MINOR 0u
     89 #define PE_STACK_RESERVE 0x100000ULL
     90 #define PE_STACK_COMMIT 0x1000ULL
     91 #define PE_HEAP_RESERVE 0x100000ULL
     92 #define PE_HEAP_COMMIT 0x1000ULL
     93 /* DllCharacteristics bits that apply regardless of relocatability. */
     94 #define PE_DLL_CHARS_BASE               \
     95   (IMAGE_DLLCHARACTERISTICS_NX_COMPAT | \
     96    IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE)
     97 /* ASLR bits — only valid when the image carries base relocations (PIE).
     98  * Advertising DYNAMIC_BASE / HIGH_ENTROPY_VA alongside RELOCS_STRIPPED is
     99  * contradictory: the loader has nothing to fix up, so it can't relocate. */
    100 #define PE_DLL_CHARS_ASLR                     \
    101   (IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | \
    102    IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
    103 
    104 /* PE32+ DOS-stub-to-PE-signature offsets (manual, since we marshal
    105  * field-by-field rather than memcpy'ing the packed struct). */
    106 #define PE_DOS_HDR_SIZE COFF_DOS_HEADER_SIZE
    107 #define PE_SIG_SIZE 4u
    108 #define PE_FILE_HDR_SIZE COFF_FILE_HEADER_SIZE
    109 #define PE_SECTION_HDR_SIZE COFF_SECTION_HEADER_SIZE
    110 
    111 /* Standard PE output buckets, plus .idata (import directory) and
    112  * .reloc — both synthesised here rather than copied from input
    113  * sections.  Order matters: it's the on-image RVA order. */
    114 typedef enum CoffBucket {
    115   COFF_BUCKET_TEXT = 0,
    116   COFF_BUCKET_RDATA = 1,
    117   COFF_BUCKET_IDATA = 2,
    118   COFF_BUCKET_DATA = 3,
    119   COFF_BUCKET_TLS = 4,
    120   COFF_BUCKET_BSS = 5,
    121   COFF_BUCKET_PDATA = 6,
    122   COFF_BUCKET_RELOC = 7,
    123   COFF_NBUCKETS = 8,
    124 } CoffBucket;
    125 
    126 /* IMAGE_TLS_DIRECTORY64 wire size: u64*4 + u32*2 = 40 bytes. */
    127 #define COFF_TLS_DIRECTORY64_SIZE 40u
    128 /* Byte offsets of the four u64 VA fields within IMAGE_TLS_DIRECTORY64
    129  * — they need base relocations so ASLR can fix them up. */
    130 #define COFF_TLSDIR_OFF_START_ADDR 0u
    131 #define COFF_TLSDIR_OFF_END_ADDR 8u
    132 #define COFF_TLSDIR_OFF_INDEX_ADDR 16u
    133 #define COFF_TLSDIR_OFF_CALLBACKS 24u
    134 
    135 typedef struct CoffSection {
    136   const char* name; /* short ASCII; <= 8 bytes including NUL pad */
    137   u32 characteristics;
    138   u8* bytes;         /* NULL for .bss / .reloc-before-build */
    139   u32 size;          /* VirtualSize (real bytes; for .bss, mem size) */
    140   u32 size_raw;      /* SizeOfRawData (file size, FileAlignment-padded) */
    141   u32 rva;           /* VirtualAddress in image */
    142   u32 file_offset;   /* PointerToRawData; 0 for .bss */
    143   u8 in_image;       /* 1 if this bucket is emitted as a section */
    144   u8 has_file_bytes; /* 0 for .bss */
    145   u8 pad[2];
    146 } CoffSection;
    147 
    148 /* ---- byte writer helpers ---- */
    149 
    150 static void coff_write_zeroes(Writer* w, u64 n) {
    151   static const u8 zeroes[256] = {0};
    152   while (n) {
    153     u64 step = n > sizeof(zeroes) ? sizeof(zeroes) : n;
    154     kit_writer_write(w, zeroes, (size_t)step);
    155     n -= step;
    156   }
    157 }
    158 
    159 /* Return the COFF bucket for a kept LinkSection.  SF_TLS sections route
    160  * into the dedicated .tls bucket so SECREL relocations from TLS access
    161  * code resolve against the merged TLS image, not against .data.
    162  * Everything else partitions on SF_EXEC / SF_WRITE plus the SSEM_NOBITS
    163  * bit for .bss. */
    164 static CoffBucket coff_bucket_for(Compiler* c, const LinkSection* ls) {
    165   if (ls->name) {
    166     Slice nm = pool_slice(c->global, ls->name);
    167     if (nm.s && nm.len >= 6u && memcmp(nm.s, ".pdata", 6u) == 0)
    168       return COFF_BUCKET_PDATA;
    169   }
    170   if (ls->flags & SF_EXEC) return COFF_BUCKET_TEXT;
    171   if (ls->flags & SF_TLS) return COFF_BUCKET_TLS;
    172   if (ls->sem == SSEM_NOBITS) return COFF_BUCKET_BSS;
    173   if (ls->flags & SF_WRITE) return COFF_BUCKET_DATA;
    174   return COFF_BUCKET_RDATA;
    175 }
    176 
    177 /* True for relocation kinds that need an entry in .reloc so the OS
    178  * loader can patch the site after ASLR picks a runtime ImageBase.
    179  * PC-relative fixups don't need base-relocs — the displacement is
    180  * load-invariant. */
    181 static int coff_reloc_needs_base_reloc(RelocKind k) {
    182   return k == R_ABS64 || k == R_ABS32;
    183 }
    184 
    185 /* Look up the LinkSection whose [vaddr, vaddr+size] range covers the
    186  * given image-relative address `v`, or return NULL.  Used to attribute
    187  * symbol vaddrs to a containing section so we can apply per-section
    188  * vaddr deltas after re-laying out for PE. */
    189 static const LinkSection* coff_section_at(const LinkImage* img, u64 v) {
    190   u32 i;
    191   for (i = 0; i < img->nsections; ++i) {
    192     const LinkSection* ls = &img->sections[i];
    193     if (v >= ls->vaddr && v <= ls->vaddr + ls->size) return ls;
    194   }
    195   return NULL;
    196 }
    197 
    198 /* Per-input-section delta map.  Indexed by `LinkSection.id - 1`.
    199  * Populated by coff_build_buckets.  Consumed by every subsequent pass
    200  * that needs to translate input-coordinate offsets (the world that
    201  * img->sections / img->relocs live in) into PE-coordinate ones (where
    202  * the writer plants bytes).  delta is stored explicitly so callers
    203  * avoid recomputing (new_rva + bucket.rva - old_vaddr) for every
    204  * LinkRelocApply whose link_section_id points at the section. */
    205 typedef struct CoffSecMap {
    206   u32 new_rva;      /* image-relative RVA after PE relayout */
    207   u32 new_file_off; /* file offset of the patched byte */
    208   i64 delta;        /* new_rva - old_vaddr */
    209   u8 bucket;
    210   u8 pad[3];
    211 } CoffSecMap;
    212 
    213 /* TLS directory placement state. Populated when at least one SF_TLS
    214  * section survives dead-strip; consumed by the optional-header writer,
    215  * the .reloc builder (base-relocs for the four absolute VA fields),
    216  * and the .rdata emit pass that writes the final 40-byte record. */
    217 typedef struct CoffTlsLayout {
    218   int present;       /* 1 iff at least one TLS section was kept */
    219   u32 dir_rdata_off; /* byte offset of the IMAGE_TLS_DIRECTORY64 within .rdata
    220                       */
    221   u32 tls_size;      /* size of the merged .tls bucket */
    222   LinkSymId tls_index_sym; /* resolved _tls_index LinkSymbol */
    223   LinkSymId callbacks_sym; /* __xl_a when mingw's TLS callbacks are linked */
    224   u64 callbacks_addend;    /* mingw points past the leading NULL sentinel */
    225 } CoffTlsLayout;
    226 
    227 static LinkSymId coff_find_sym(LinkImage* img, const char* name) {
    228   Sym sym = pool_intern_cstr(img->c->global, name);
    229   u32 n = LinkSyms_count(&img->syms);
    230   u32 i;
    231   for (i = 0; i < n; ++i) {
    232     const LinkSymbol* s = LinkSyms_at(&img->syms, i);
    233     if (s->name == sym) return (LinkSymId)(i + 1);
    234   }
    235   return LINK_SYM_NONE;
    236 }
    237 
    238 /* Locate _tls_index by name in the resolved symbol table. mingw's
    239  * libmingwex defines it (as part of tlsmcrt); without a CRT the link
    240  * fails here with a clear message rather than producing a TLS
    241  * directory pointing at a stale address. */
    242 static LinkSymId coff_find_tls_index_sym(LinkImage* img) {
    243   return coff_find_sym(img, "_tls_index");
    244 }
    245 
    246 static const LinkSection* coff_symbol_section(const LinkImage* img,
    247                                               const LinkSymbol* s) {
    248   if (s->name) {
    249     Slice nm_s = pool_slice(img->c->global, s->name);
    250     const char* nm = nm_s.s;
    251     size_t n = nm_s.len;
    252     const char* sec_name = NULL;
    253     if (nm && n == 6 && memcmp(nm, "__xd_a", 6) == 0)
    254       sec_name = ".CRT$XDA";
    255     else if (nm && n == 6 && memcmp(nm, "__xd_z", 6) == 0)
    256       sec_name = ".CRT$XDZ";
    257     else if (nm && n == 6 && memcmp(nm, "__xl_a", 6) == 0)
    258       sec_name = ".CRT$XLA";
    259     else if (nm && n == 6 && memcmp(nm, "__xl_c", 6) == 0)
    260       sec_name = ".CRT$XLC";
    261     else if (nm && n == 6 && memcmp(nm, "__xl_d", 6) == 0)
    262       sec_name = ".CRT$XLD";
    263     else if (nm && n == 6 && memcmp(nm, "__xl_z", 6) == 0)
    264       sec_name = ".CRT$XLZ";
    265     if (sec_name) {
    266       u32 i;
    267       for (i = 0; i < img->nsections; ++i) {
    268         const LinkSection* ls = &img->sections[i];
    269         if (ls->name &&
    270             slice_eq_cstr(pool_slice(img->c->global, ls->name), sec_name))
    271           return ls;
    272       }
    273     }
    274   }
    275   if (s->section_id != LINK_SEC_NONE && s->section_id <= img->nsections)
    276     return &img->sections[s->section_id - 1];
    277   return coff_section_at(img, s->vaddr);
    278 }
    279 
    280 static u64 coff_symbol_final_va(const LinkImage* img,
    281                                 const CoffSection out[COFF_NBUCKETS],
    282                                 const CoffSecMap* map, LinkSymId id,
    283                                 const char* what) {
    284   const LinkSymbol* s = LinkSyms_at(&img->syms, id - 1);
    285   if (!s->defined || s->kind == SK_ABS) {
    286     compiler_panic(img->c, SRCLOC_NONE,
    287                    "link_emit_coff: `%.*s` is not a defined section-bound "
    288                    "symbol",
    289                    SLICE_ARG(slice_from_cstr(what)));
    290   }
    291   const LinkSection* sec = coff_symbol_section(img, s);
    292   if (!sec) {
    293     compiler_panic(img->c, SRCLOC_NONE,
    294                    "link_emit_coff: `%.*s` has no containing section",
    295                    SLICE_ARG(slice_from_cstr(what)));
    296   }
    297   u8 b = map[sec->id - 1].bucket;
    298   return PE_IMAGE_BASE + (u64)out[b].rva + (u64)map[sec->id - 1].new_rva +
    299          (s->vaddr - sec->vaddr);
    300 }
    301 
    302 /* Reserve 40 bytes at the tail of the .rdata bucket for the
    303  * IMAGE_TLS_DIRECTORY64 record. Records the offset for later emit and
    304  * grows the bucket if needed. The bytes start zeroed; coff_emit_tls_dir
    305  * fills them in once final RVAs are known. */
    306 static void coff_plan_tls_layout(LinkImage* img, CoffSection out[COFF_NBUCKETS],
    307                                  u32* rdata_cap, CoffTlsLayout* tls) {
    308   memset(tls, 0, sizeof(*tls));
    309   if (out[COFF_BUCKET_TLS].size == 0) return;
    310   tls->present = 1;
    311   tls->tls_size = out[COFF_BUCKET_TLS].size;
    312   tls->tls_index_sym = coff_find_tls_index_sym(img);
    313   if (tls->tls_index_sym == LINK_SYM_NONE) {
    314     compiler_panic(img->c, SRCLOC_NONE,
    315                    "link_emit_coff: .tls section requires `_tls_index` "
    316                    "(provided by mingw libmingwex / tlsmcrt.o) — none of "
    317                    "the linked inputs define it");
    318   }
    319   /* IMAGE_TLS_DIRECTORY64 needs 8-byte alignment for its u64 fields;
    320    * round the .rdata size up before reserving the 40-byte record. */
    321   tls->callbacks_sym = coff_find_sym(img, "__xl_a");
    322   if (tls->callbacks_sym != LINK_SYM_NONE) {
    323     tls->callbacks_addend = 8;
    324   } else {
    325     tls->callbacks_sym = coff_find_sym(img, "__xl_c");
    326     tls->callbacks_addend = 0;
    327   }
    328   u32 rdata_size = (u32)ALIGN_UP((u64)out[COFF_BUCKET_RDATA].size, 8ull);
    329   u32 need = rdata_size + COFF_TLS_DIRECTORY64_SIZE;
    330   if (need > *rdata_cap) {
    331     (void)VEC_GROW(img->heap, out[COFF_BUCKET_RDATA].bytes, *rdata_cap, need);
    332   }
    333   /* Zero any padding bytes introduced by the alignment bump and the
    334    * directory slot itself. */
    335   if (rdata_size > out[COFF_BUCKET_RDATA].size) {
    336     memset(out[COFF_BUCKET_RDATA].bytes + out[COFF_BUCKET_RDATA].size, 0,
    337            rdata_size - out[COFF_BUCKET_RDATA].size);
    338   }
    339   memset(out[COFF_BUCKET_RDATA].bytes + rdata_size, 0,
    340          COFF_TLS_DIRECTORY64_SIZE);
    341   tls->dir_rdata_off = rdata_size;
    342   out[COFF_BUCKET_RDATA].size = need;
    343 }
    344 
    345 /* Write the IMAGE_TLS_DIRECTORY64 bytes once all bucket RVAs are
    346  * final. Each u64 VA field gets ImageBase + RVA; the base-reloc pass
    347  * will emit IMAGE_REL_BASED_DIR64 entries so ASLR keeps them valid. */
    348 static void coff_emit_tls_dir(const LinkImage* img,
    349                               const CoffSection out[COFF_NBUCKETS],
    350                               const CoffSecMap* map, const CoffTlsLayout* tls) {
    351   if (!tls->present) return;
    352   u64 tls_start = PE_IMAGE_BASE + (u64)out[COFF_BUCKET_TLS].rva;
    353   u64 tls_end = tls_start + (u64)tls->tls_size;
    354   u64 idx_vaddr =
    355       coff_symbol_final_va(img, out, map, tls->tls_index_sym, "_tls_index");
    356   const char* callbacks_name = tls->callbacks_addend ? "__xl_a" : "__xl_c";
    357   u64 callbacks_vaddr =
    358       tls->callbacks_sym
    359           ? coff_symbol_final_va(img, out, map, tls->callbacks_sym,
    360                                  callbacks_name) +
    361                 tls->callbacks_addend
    362           : 0;
    363 
    364   u8* p = out[COFF_BUCKET_RDATA].bytes + tls->dir_rdata_off;
    365   wr_u64_le(p + COFF_TLSDIR_OFF_START_ADDR, tls_start);
    366   wr_u64_le(p + COFF_TLSDIR_OFF_END_ADDR, tls_end);
    367   wr_u64_le(p + COFF_TLSDIR_OFF_INDEX_ADDR, idx_vaddr);
    368   wr_u64_le(p + COFF_TLSDIR_OFF_CALLBACKS, callbacks_vaddr);
    369   wr_u32_le(p + 32, 0); /* SizeOfZeroFill */
    370   wr_u32_le(p + 36, 0); /* Characteristics */
    371 }
    372 
    373 static void coff_define_tls_used(LinkImage* img,
    374                                  const CoffSection out[COFF_NBUCKETS],
    375                                  const CoffTlsLayout* tls) {
    376   if (!tls->present) return;
    377   if (!img->linker) return;
    378   link_emit_boundary_sym(img->linker, img, "_tls_used",
    379                          PE_IMAGE_BASE + (u64)out[COFF_BUCKET_RDATA].rva +
    380                              (u64)tls->dir_rdata_off);
    381 }
    382 
    383 /* ---- import-table synthesis (Phase 3.2) ---------------------------
    384  *
    385  * Per doc/OBJ.md: every LinkSymbol with `imported = 1` gets
    386  * routed through an IAT slot synthesized in `.idata`.  Function
    387  * imports additionally receive a small per-arch stub in `.text`
    388  * (`ff 25 disp32` on x64 / `adrp;ldr;br` on aa64) so a direct CALL26
    389  * or PC32 against the symbol lands on a stub that indirects through
    390  * the IAT.  Data imports skip the stub — the symbol's final vaddr is
    391  * just the IAT slot vaddr, and code-gen emits a `mov rax, [slot]`
    392  * sequence the same way it would for any other GOT-style load.
    393  *
    394  * kit's COFF code-gen uses direct symbol references; there is no
    395  * separate `__imp_<name>` LinkSymbol consulted at link time.  The
    396  * IAT-slot rewrite happens entirely by overriding the imported
    397  * symbol's vaddr in apply_all_relocs. */
    398 
    399 typedef struct CoffImport {
    400   LinkSymId sym;   /* canonical LinkSymId from img->syms */
    401   Sym import_name; /* DLL export name override (short-import NameType); 0=use
    402                       sym */
    403   u32 dll_idx;     /* index into CoffImportTable.dlls */
    404   u32 stub_off;    /* offset in .text bucket (functions only) */
    405   u32 iat_off;     /* offset in .idata IAT block */
    406   u32 ilt_off;     /* offset in .idata ILT block */
    407   u32 hint_off;    /* offset in .idata hint/name table */
    408   u8 is_func;
    409   u8 pad[3];
    410 } CoffImport;
    411 
    412 typedef struct CoffImportDll {
    413   Sym soname;
    414   u32 first; /* index of first import in CoffImportTable.imports */
    415   u32 count;
    416   u32 ilt_off;  /* offset of this DLL's ILT block in .idata */
    417   u32 iat_off;  /* offset of this DLL's IAT block in .idata */
    418   u32 name_off; /* offset of DLL name string in .idata */
    419 } CoffImportDll;
    420 
    421 typedef struct CoffImportTable {
    422   CoffImport* imports;
    423   u32 nimports;
    424   u32 imports_cap;   /* heap-allocation size for cleanup */
    425   u32 nfunc_imports; /* subset of nimports that needs a .text stub */
    426   CoffImportDll* dlls;
    427   u32 ndlls;
    428   u32 dlls_cap; /* heap-allocation size for cleanup */
    429   /* Offsets within .idata of the five sub-blocks.  Filled in by
    430    * coff_plan_idata_layout once nimports / ndlls is known. */
    431   u32 desc_off; /* always 0 — descriptors come first */
    432   u32 desc_size;
    433   u32 ilt_base;
    434   u32 ilt_total;
    435   u32 iat_base;
    436   u32 iat_total;
    437   u32 hint_base;
    438   u32 hint_total;
    439   u32 name_base;
    440   u32 name_total;
    441   u32 idata_size;
    442   /* Stub region in .text bucket.  Stubs are appended after every
    443    * input .text section has been bucketed.  stub_text_off is the
    444    * bucket-local offset of the first stub; per-import stub offsets
    445    * are stored in CoffImport.stub_off. */
    446   u32 stub_text_off;
    447   u32 stub_total;
    448 } CoffImportTable;
    449 
    450 /* Sort comparator: imports grouped by DLL slot, stable on input
    451  * order within a DLL (sort is stable enough via secondary key). */
    452 static int coff_import_cmp(const void* a, const void* b) {
    453   const CoffImport* ia = (const CoffImport*)a;
    454   const CoffImport* ib = (const CoffImport*)b;
    455   if (ia->dll_idx < ib->dll_idx) return -1;
    456   if (ia->dll_idx > ib->dll_idx) return 1;
    457   /* Secondary: LinkSymId so the order is reproducible. */
    458   if (ia->sym < ib->sym) return -1;
    459   if (ia->sym > ib->sym) return 1;
    460   return 0;
    461 }
    462 
    463 static const char* coff_import_lookup_name(Compiler* c, const LinkSymbol* s,
    464                                            size_t* nlen_out) {
    465   Slice nm_s = s->name ? pool_slice(c->global, s->name) : SLICE_NULL;
    466   const char* nm = nm_s.s;
    467   size_t nlen = nm_s.len;
    468   static const char kImpPrefix[] = "__imp_";
    469   const size_t kImpPrefixLen = sizeof(kImpPrefix) - 1u;
    470   if (nm && nlen > kImpPrefixLen &&
    471       memcmp(nm, kImpPrefix, kImpPrefixLen) == 0) {
    472     nm += kImpPrefixLen;
    473     nlen -= kImpPrefixLen;
    474   }
    475   if (nlen_out) *nlen_out = nlen;
    476   return nm;
    477 }
    478 
    479 /* The name placed in the PE hint/name table for an import. Honors the
    480  * short-import NameType override (CoffImport.import_name, e.g. EXPORTAS's real
    481  * DLL export name) when present, else derives it from the symbol name. */
    482 static const char* coff_import_emit_name(Compiler* c, const CoffImport* imp,
    483                                          const LinkSymbol* s,
    484                                          size_t* nlen_out) {
    485   if (imp->import_name) {
    486     Slice nm_s = pool_slice(c->global, imp->import_name);
    487     if (nlen_out) *nlen_out = nm_s.len;
    488     return nm_s.s;
    489   }
    490   return coff_import_lookup_name(c, s, nlen_out);
    491 }
    492 
    493 /* True iff the import classifies as function-like.  Mirrors the ELF
    494  * `sym_is_func_import` heuristic: if the canonical kind is known
    495  * we trust it, otherwise we default to function (which matches the
    496  * COFF code-gen contract — direct calls are by far the common case
    497  * and a data import wrongly stubbed would still fail loudly via the
    498  * IAT-routed call). */
    499 static int coff_import_is_func(Compiler* c, const LinkSymbol* s) {
    500   if (s->name) {
    501     Slice nm_s = pool_slice(c->global, s->name);
    502     const char* nm = nm_s.s;
    503     size_t nlen = nm_s.len;
    504     if (nm && nlen > 6u && memcmp(nm, "__imp_", 6u) == 0) return 0;
    505   }
    506   if (s->kind == SK_FUNC || s->kind == SK_IFUNC) return 1;
    507   if (s->kind == SK_OBJ) return 0;
    508   /* SK_UNDEF / SK_NOTYPE: assume function (the common case). */
    509   return 1;
    510 }
    511 
    512 /* Walk LinkSyms, collect imports, group by DLL soname.  Returns 1 if
    513  * any imports were collected, 0 otherwise (caller skips the entire
    514  * .idata path). */
    515 static int coff_collect_imports(LinkImage* img, CoffImportTable* it) {
    516   Heap* heap = img->heap;
    517   Compiler* c = img->c;
    518   Linker* l = img->linker;
    519   u32 nsyms = LinkSyms_count(&img->syms);
    520   u32 imp_cap = 0;
    521   u32 dll_cap = 0;
    522   u32 i;
    523 
    524   memset(it, 0, sizeof(*it));
    525   if (!l) return 0;
    526   for (i = 0; i < nsyms; ++i) {
    527     LinkSymbol* s = LinkSyms_at(&img->syms, i);
    528     LinkInput* in;
    529     u32 dll_idx = (u32)-1;
    530     u32 d;
    531     if (!s->imported) continue;
    532     if (s->name == 0) continue;
    533     if (s->dso_input_id == LINK_INPUT_NONE) {
    534       compiler_panic(c, SRCLOC_NONE,
    535                      "link_emit_coff: imported symbol has no providing DSO");
    536     }
    537     /* img->globals only carries defined globals/weaks; imported undefs
    538      * never land there.  Dedup by name: skip if any earlier slot
    539      * already collected this name. */
    540     {
    541       int dup = 0;
    542       for (u32 k = 0; k < it->nimports; ++k) {
    543         LinkSymbol* prev = LinkSyms_at(&img->syms, it->imports[k].sym - 1);
    544         if (prev->name == s->name) {
    545           dup = 1;
    546           break;
    547         }
    548       }
    549       if (dup) continue;
    550     }
    551     if (s->dso_input_id - 1u >= LinkInputs_count(&l->inputs)) {
    552       compiler_panic(c, SRCLOC_NONE,
    553                      "link_emit_coff: import dso_input_id out of range");
    554     }
    555     in = LinkInputs_at(&l->inputs, s->dso_input_id - 1u);
    556     if (in->soname == 0) {
    557       compiler_panic(c, SRCLOC_NONE,
    558                      "link_emit_coff: providing DSO has no soname; cannot "
    559                      "emit IMAGE_IMPORT_DESCRIPTOR.Name");
    560     }
    561     /* Find-or-add the DLL slot. */
    562     for (d = 0; d < it->ndlls; ++d) {
    563       if (it->dlls[d].soname == in->soname) {
    564         dll_idx = d;
    565         break;
    566       }
    567     }
    568     if (dll_idx == (u32)-1) {
    569       if (VEC_GROW(heap, it->dlls, dll_cap, it->ndlls + 1u))
    570         compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom on import dlls");
    571       dll_idx = it->ndlls++;
    572       memset(&it->dlls[dll_idx], 0, sizeof(it->dlls[dll_idx]));
    573       it->dlls[dll_idx].soname = in->soname;
    574     }
    575     if (VEC_GROW(heap, it->imports, imp_cap, it->nimports + 1u))
    576       compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom on imports");
    577     memset(&it->imports[it->nimports], 0, sizeof(it->imports[it->nimports]));
    578     it->imports[it->nimports].sym = s->id;
    579     it->imports[it->nimports].import_name = in->coff_import_name;
    580     it->imports[it->nimports].dll_idx = dll_idx;
    581     it->imports[it->nimports].is_func = (u8)coff_import_is_func(c, s);
    582     if (it->imports[it->nimports].is_func) ++it->nfunc_imports;
    583     ++it->nimports;
    584     it->dlls[dll_idx].count++;
    585   }
    586   if (it->nimports == 0) return 0;
    587   /* Re-bucket the imports array so each DLL's run is contiguous. */
    588   qsort(it->imports, it->nimports, sizeof(*it->imports), coff_import_cmp);
    589   /* Fix up CoffImportDll.first now that imports[] is sorted. */
    590   {
    591     u32 cur = 0;
    592     for (u32 d = 0; d < it->ndlls; ++d) {
    593       it->dlls[d].first = cur;
    594       cur += it->dlls[d].count;
    595     }
    596   }
    597   it->imports_cap = imp_cap;
    598   it->dlls_cap = dll_cap;
    599   return 1;
    600 }
    601 
    602 static void coff_imports_free(LinkImage* img, CoffImportTable* it) {
    603   Heap* heap = img->heap;
    604   if (it->imports) {
    605     heap->free(heap, it->imports,
    606                (size_t)it->imports_cap * sizeof(*it->imports));
    607   }
    608   if (it->dlls) {
    609     heap->free(heap, it->dlls, (size_t)it->dlls_cap * sizeof(*it->dlls));
    610   }
    611 }
    612 
    613 /* Compute every per-block / per-import offset inside .idata and the
    614  * total .idata size in bytes.  Also assigns per-import hint/name and
    615  * dll-name offsets so the descriptor table can reference them by RVA
    616  * later (RVAs need the bucket's final RVA, added in coff_emit_idata). */
    617 static void coff_plan_idata_layout(LinkImage* img, CoffImportTable* it) {
    618   Compiler* c = img->c;
    619   u32 off;
    620 
    621   /* Block 1: import descriptors (one per DLL + zero terminator). */
    622   it->desc_off = 0;
    623   it->desc_size = (it->ndlls + 1u) * COFF_IMPORT_DESCRIPTOR_SIZE;
    624   off = (u32)ALIGN_UP((u64)it->desc_size, (u64)PE_IDATA_BLOCK_ALIGN);
    625 
    626   /* Block 2: ILTs.  Per DLL: count entries + 1 (terminator), 8 B each. */
    627   it->ilt_base = off;
    628   for (u32 d = 0; d < it->ndlls; ++d) {
    629     it->dlls[d].ilt_off = off;
    630     /* Per-import: assign ilt_off within this DLL's block. */
    631     for (u32 k = 0; k < it->dlls[d].count; ++k) {
    632       it->imports[it->dlls[d].first + k].ilt_off =
    633           off + k * (u32)COFF_THUNK_DATA64_SIZE;
    634     }
    635     off += (it->dlls[d].count + 1u) * (u32)COFF_THUNK_DATA64_SIZE;
    636   }
    637   it->ilt_total = off - it->ilt_base;
    638   off = (u32)ALIGN_UP((u64)off, (u64)PE_IDATA_BLOCK_ALIGN);
    639 
    640   /* Block 3: IATs (same shape as ILTs). */
    641   it->iat_base = off;
    642   for (u32 d = 0; d < it->ndlls; ++d) {
    643     it->dlls[d].iat_off = off;
    644     for (u32 k = 0; k < it->dlls[d].count; ++k) {
    645       it->imports[it->dlls[d].first + k].iat_off =
    646           off + k * (u32)COFF_THUNK_DATA64_SIZE;
    647     }
    648     off += (it->dlls[d].count + 1u) * (u32)COFF_THUNK_DATA64_SIZE;
    649   }
    650   it->iat_total = off - it->iat_base;
    651   off = (u32)ALIGN_UP((u64)off, (u64)PE_IDATA_BLOCK_ALIGN);
    652 
    653   /* Block 4: hint/name records.  Each: u16 hint + NUL-term name +
    654    * 1-byte pad if the resulting size is odd (PE/COFF spec). */
    655   it->hint_base = off;
    656   for (u32 i = 0; i < it->nimports; ++i) {
    657     LinkSymbol* s = LinkSyms_at(&img->syms, it->imports[i].sym - 1);
    658     size_t nlen = 0;
    659     const char* nm = coff_import_emit_name(c, &it->imports[i], s, &nlen);
    660     if (!nm || nlen == 0)
    661       compiler_panic(c, SRCLOC_NONE,
    662                      "link_emit_coff: imported symbol has empty name");
    663     it->imports[i].hint_off = off;
    664     /* hint (2 B) + name (nlen + 1) + optional pad to even. */
    665     u32 rec = 2u + (u32)nlen + 1u;
    666     if (rec & 1u) ++rec;
    667     off += rec;
    668   }
    669   it->hint_total = off - it->hint_base;
    670   off = (u32)ALIGN_UP((u64)off, (u64)PE_IDATA_BLOCK_ALIGN);
    671 
    672   /* Block 5: DLL name strings (NUL-terminated). */
    673   it->name_base = off;
    674   for (u32 d = 0; d < it->ndlls; ++d) {
    675     Slice nm_s = pool_slice(c->global, it->dlls[d].soname);
    676     const char* nm = nm_s.s;
    677     size_t nlen = nm_s.len;
    678     if (!nm || nlen == 0)
    679       compiler_panic(c, SRCLOC_NONE,
    680                      "link_emit_coff: providing DSO has empty soname");
    681     it->dlls[d].name_off = off;
    682     off += (u32)nlen + 1u;
    683   }
    684   it->name_total = off - it->name_base;
    685   it->idata_size = off;
    686 }
    687 
    688 /* Append the function-import stubs to the .text bucket.  Each stub is
    689  * the format arch descriptor's stub size. Records each stub's bucket-
    690  * local offset on the matching CoffImport so the per-symbol stub vaddr
    691  * can be computed once the .text bucket's RVA is final. */
    692 static void coff_append_stubs(LinkImage* img, CoffImportTable* it,
    693                               CoffSection* text_bucket, u32* text_bucket_cap) {
    694   Heap* heap = img->heap;
    695   Compiler* c = img->c;
    696   const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF);
    697   const ObjCoffArchOps* arch =
    698       fmt && fmt->coff_arch ? fmt->coff_arch(c->target.arch) : NULL;
    699   u32 stub_size;
    700   u32 stub_align;
    701   u64 cur;
    702   if (!arch || arch->stub_size == 0 || !arch->emit_iat_stub) {
    703     compiler_panic(c, SRCLOC_NONE,
    704                    "link_emit_coff: arch has no COFF IAT stub emitter");
    705   }
    706   stub_size = arch->stub_size;
    707   /* Stubs are pure code; aligning to instruction alignment is enough.
    708    * x64 wants byte-granular, aa64 wants 4 B; align to stub size as a
    709    * convenient upper bound. */
    710   stub_align = stub_size;
    711   cur = (u64)text_bucket->size;
    712   cur = ALIGN_UP(cur, (u64)stub_align);
    713   it->stub_text_off = (u32)cur;
    714   for (u32 i = 0; i < it->nimports; ++i) {
    715     if (!it->imports[i].is_func) continue;
    716     it->imports[i].stub_off = (u32)cur;
    717     cur += stub_size;
    718   }
    719   it->stub_total = (u32)cur - it->stub_text_off;
    720   if (it->stub_total == 0) return;
    721   /* Grow the .text bucket buffer to hold the new region. */
    722   u32 need = (u32)cur;
    723   if (need > *text_bucket_cap) {
    724     (void)VEC_GROW(heap, text_bucket->bytes, *text_bucket_cap, need);
    725   }
    726   /* Zero the alignment pad; stub bytes are written later by
    727    * coff_emit_stubs once vaddrs are known. */
    728   if ((u32)cur > text_bucket->size) {
    729     memset(text_bucket->bytes + text_bucket->size, 0,
    730            (size_t)((u32)cur - text_bucket->size));
    731   }
    732   text_bucket->size = (u32)cur;
    733 }
    734 
    735 /* Emit each function import's IAT stub into the .text bucket.  Must
    736  * run after coff_assign_layout has fixed both .text's RVA and
    737  * .idata's RVA, since the stub bakes in the post-shift IAT slot
    738  * displacement. */
    739 static void coff_emit_stubs(LinkImage* img, const CoffImportTable* it,
    740                             const CoffSection out[COFF_NBUCKETS]) {
    741   Compiler* c = img->c;
    742   const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF);
    743   const ObjCoffArchOps* arch =
    744       fmt && fmt->coff_arch ? fmt->coff_arch(c->target.arch) : NULL;
    745   u64 img_base = PE_IMAGE_BASE;
    746   u32 text_rva = out[COFF_BUCKET_TEXT].rva;
    747   u32 idata_rva = out[COFF_BUCKET_IDATA].rva;
    748   if (!arch || !arch->emit_iat_stub) {
    749     compiler_panic(c, SRCLOC_NONE,
    750                    "link_emit_coff: arch has no COFF IAT stub emitter");
    751   }
    752   for (u32 i = 0; i < it->nimports; ++i) {
    753     u64 stub_va, slot_va;
    754     if (!it->imports[i].is_func) continue;
    755     stub_va = img_base + (u64)text_rva + (u64)it->imports[i].stub_off;
    756     slot_va = img_base + (u64)idata_rva + (u64)it->imports[i].iat_off;
    757     arch->emit_iat_stub(out[COFF_BUCKET_TEXT].bytes + it->imports[i].stub_off,
    758                         stub_va, slot_va);
    759   }
    760 }
    761 
    762 /* Emit .idata content into the bucket buffer.  Allocates the buffer
    763  * here (size is already known from coff_plan_idata_layout). */
    764 static void coff_emit_idata(LinkImage* img, const CoffImportTable* it,
    765                             CoffSection out[COFF_NBUCKETS],
    766                             u32* idata_bucket_cap) {
    767   Heap* heap = img->heap;
    768   Compiler* c = img->c;
    769   CoffSection* idata = &out[COFF_BUCKET_IDATA];
    770   u32 idata_rva = idata->rva;
    771   u8* buf;
    772   /* Allocate the bucket buffer (idata_size is already block-aligned). */
    773   buf = (u8*)heap->alloc(heap, it->idata_size, _Alignof(u64));
    774   if (!buf)
    775     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom on .idata buffer");
    776   memset(buf, 0, it->idata_size);
    777   idata->bytes = buf;
    778   idata->size = it->idata_size;
    779   *idata_bucket_cap = it->idata_size;
    780 
    781   /* Block 1: IMAGE_IMPORT_DESCRIPTOR table. */
    782   for (u32 d = 0; d < it->ndlls; ++d) {
    783     u8* p = buf + d * (u32)COFF_IMPORT_DESCRIPTOR_SIZE;
    784     u32 ilt_rva = idata_rva + it->dlls[d].ilt_off;
    785     u32 iat_rva = idata_rva + it->dlls[d].iat_off;
    786     u32 name_rva = idata_rva + it->dlls[d].name_off;
    787     wr_u32_le(p + 0, ilt_rva);   /* OriginalFirstThunk */
    788     wr_u32_le(p + 4, 0u);        /* TimeDateStamp */
    789     wr_u32_le(p + 8, 0u);        /* ForwarderChain */
    790     wr_u32_le(p + 12, name_rva); /* Name */
    791     wr_u32_le(p + 16, iat_rva);  /* FirstThunk */
    792   }
    793   /* Trailing zero descriptor already zero-filled by memset. */
    794 
    795   /* Blocks 2+3: ILT + IAT.  Both initially point at the same hint/name
    796    * record for each import; the OS loader rewrites IAT entries at
    797    * load time. */
    798   for (u32 i = 0; i < it->nimports; ++i) {
    799     u64 hint_rva = (u64)idata_rva + (u64)it->imports[i].hint_off;
    800     wr_u64_le(buf + it->imports[i].ilt_off, hint_rva);
    801     wr_u64_le(buf + it->imports[i].iat_off, hint_rva);
    802   }
    803   /* Per-DLL ILT/IAT terminators are u64 0, already zero-filled. */
    804 
    805   /* Block 4: hint/name records. */
    806   for (u32 i = 0; i < it->nimports; ++i) {
    807     LinkSymbol* s = LinkSyms_at(&img->syms, it->imports[i].sym - 1);
    808     size_t nlen = 0;
    809     const char* nm = coff_import_emit_name(c, &it->imports[i], s, &nlen);
    810     u8* p = buf + it->imports[i].hint_off;
    811     wr_u16_le(p, PE_IMPORT_HINT_NONE);
    812     memcpy(p + 2, nm, nlen);
    813     /* NUL terminator + optional pad already zero. */
    814   }
    815 
    816   /* Block 5: DLL name strings. */
    817   for (u32 d = 0; d < it->ndlls; ++d) {
    818     Slice nm_s = pool_slice(c->global, it->dlls[d].soname);
    819     const char* nm = nm_s.s;
    820     size_t nlen = nm_s.len;
    821     memcpy(buf + it->dlls[d].name_off, nm, nlen);
    822     /* NUL already zero. */
    823   }
    824 }
    825 
    826 /* Per-LinkSymId vaddr override table for imports.  Indexed by
    827  * LinkSymId-1; 0 means "not an import".  Built once after the .idata
    828  * bucket RVA is final.  Consumed by coff_apply_all_relocs in lieu of
    829  * the symbol's own vaddr field (which is 0 for imports). */
    830 typedef struct CoffImportVaddr {
    831   u64* by_sym; /* size = nsyms; 0 entries mean "not imported" */
    832   u32 nsyms;
    833 } CoffImportVaddr;
    834 
    835 static void coff_import_vaddr_build(LinkImage* img, const CoffImportTable* it,
    836                                     const CoffSection out[COFF_NBUCKETS],
    837                                     CoffImportVaddr* iv) {
    838   Heap* heap = img->heap;
    839   u64 img_base = PE_IMAGE_BASE;
    840   u32 text_rva = out[COFF_BUCKET_TEXT].rva;
    841   u32 idata_rva = out[COFF_BUCKET_IDATA].rva;
    842   iv->nsyms = LinkSyms_count(&img->syms);
    843   iv->by_sym = (u64*)heap->alloc(heap, sizeof(u64) * (size_t)(iv->nsyms + 1u),
    844                                  _Alignof(u64));
    845   if (!iv->by_sym)
    846     compiler_panic(img->c, SRCLOC_NONE,
    847                    "link_emit_coff: oom on import vaddr table");
    848   memset(iv->by_sym, 0, sizeof(u64) * (size_t)(iv->nsyms + 1u));
    849   for (u32 i = 0; i < it->nimports; ++i) {
    850     LinkSymId sid = it->imports[i].sym;
    851     u64 va;
    852     if (it->imports[i].is_func) {
    853       va = img_base + (u64)text_rva + (u64)it->imports[i].stub_off;
    854     } else {
    855       va = img_base + (u64)idata_rva + (u64)it->imports[i].iat_off;
    856     }
    857     iv->by_sym[sid - 1u] = va;
    858     /* Fan out across every shadow LinkSymId with the same name so a
    859      * per-input undef reference resolves to the same import slot. */
    860     {
    861       LinkSymbol* canonical = LinkSyms_at(&img->syms, sid - 1u);
    862       for (u32 j = 0; j < iv->nsyms; ++j) {
    863         LinkSymbol* s = LinkSyms_at(&img->syms, j);
    864         if (s->name == canonical->name && s->imported) {
    865           iv->by_sym[s->id - 1u] = va;
    866         }
    867       }
    868     }
    869   }
    870 }
    871 
    872 static void coff_import_vaddr_free(LinkImage* img, CoffImportVaddr* iv) {
    873   Heap* heap = img->heap;
    874   if (iv->by_sym) {
    875     heap->free(heap, iv->by_sym, sizeof(u64) * (size_t)(iv->nsyms + 1u));
    876   }
    877 }
    878 
    879 /* Resolve Compiler.target.arch -> IMAGE_FILE_MACHINE_* via the per-arch
    880  * coff ops table.  Panic if the arch has no COFF descriptor or the
    881  * machine value is one kit doesn't ship (Phase 1 supports AMD64 and
    882  * ARM64 only). */
    883 static u16 coff_machine_or_panic(Compiler* c) {
    884   const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF);
    885   const ObjCoffArchOps* arch =
    886       fmt && fmt->coff_arch ? fmt->coff_arch(c->target.arch) : NULL;
    887   u16 m;
    888   if (!arch)
    889     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: no COFF arch descriptor");
    890   m = arch->machine;
    891   if (m != IMAGE_FILE_MACHINE_AMD64 && m != IMAGE_FILE_MACHINE_ARM64)
    892     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: unsupported machine 0x%x",
    893                    (unsigned)m);
    894   return m;
    895 }
    896 
    897 static int coff_section_name_starts(Compiler* c, const LinkSection* ls,
    898                                     const char* prefix) {
    899   size_t pn = slice_from_cstr(prefix).len;
    900   Slice s_s = ls->name ? pool_slice(c->global, ls->name) : SLICE_NULL;
    901   const char* s = s_s.s;
    902   size_t n = s_s.len;
    903   return s && n >= pn && memcmp(s, prefix, pn) == 0;
    904 }
    905 
    906 static int coff_section_name_cmp(Compiler* c, const LinkSection* a,
    907                                  const LinkSection* b) {
    908   Slice as_s = a->name ? pool_slice(c->global, a->name) : SLICE_NULL;
    909   Slice bs_s = b->name ? pool_slice(c->global, b->name) : SLICE_NULL;
    910   const char* as = as_s.s ? as_s.s : "";
    911   const char* bs = bs_s.s ? bs_s.s : "";
    912   size_t an = as_s.len, bn = bs_s.len;
    913   size_t n = an < bn ? an : bn;
    914   int cmp = n ? memcmp(as, bs, n) : 0;
    915   if (cmp) return cmp;
    916   if (an < bn) return -1;
    917   if (an > bn) return 1;
    918   if (a->id < b->id) return -1;
    919   if (a->id > b->id) return 1;
    920   return 0;
    921 }
    922 
    923 static void coff_place_section(LinkImage* img, CoffSection out[COFF_NBUCKETS],
    924                                CoffSecMap* map, u64 bucket_cur[COFF_NBUCKETS],
    925                                u32 bucket_cap[COFF_NBUCKETS],
    926                                const LinkSection* ls) {
    927   Heap* heap = img->heap;
    928   CoffBucket b2 = coff_bucket_for(img->c, ls);
    929   u32 align = ls->align ? ls->align : 1u;
    930   u64 cur = bucket_cur[b2];
    931   cur = ALIGN_UP(cur, (u64)align);
    932   map[ls->id - 1].bucket = (u8)b2;
    933   /* Record the bucket-local offset; the absolute RVA / file offset
    934    * are filled in after bucket placement (RVAs need
    935    * SectionAlignment, file offsets need FileAlignment). */
    936   map[ls->id - 1].new_rva = (u32)cur;
    937   if (b2 != COFF_BUCKET_BSS) {
    938     /* Copy bytes from the source segment buffer into the bucket. */
    939     if (ls->size) {
    940       u32 need = (u32)(cur + ls->size);
    941       if (need > bucket_cap[b2]) {
    942         (void)VEC_GROW(heap, out[b2].bytes, bucket_cap[b2], need);
    943       }
    944       memset(out[b2].bytes + bucket_cur[b2], 0, (size_t)(cur - bucket_cur[b2]));
    945       if (ls->sem != SSEM_NOBITS) {
    946         const LinkSegment* seg = &img->segments[ls->segment_id - 1];
    947         const u8* src = img->segment_bytes[seg->id - 1] +
    948                         (size_t)(ls->file_offset - seg->file_offset);
    949         memcpy(out[b2].bytes + cur, src, (size_t)ls->size);
    950       } else {
    951         memset(out[b2].bytes + cur, 0, (size_t)ls->size);
    952       }
    953     }
    954   }
    955   cur += ls->size;
    956   bucket_cur[b2] = cur;
    957   out[b2].size = (u32)cur;
    958 }
    959 
    960 static void coff_insert_sorted_section(Compiler* c, const LinkSection** a,
    961                                        u32* n, const LinkSection* ls) {
    962   u32 i = *n;
    963   while (i > 0 && coff_section_name_cmp(c, ls, a[i - 1u]) < 0) {
    964     a[i] = a[i - 1u];
    965     --i;
    966   }
    967   a[i] = ls;
    968   *n += 1u;
    969 }
    970 
    971 /* ---- pass 1: bucket input sections, assemble bytes, assign deltas ----
    972  * CoffSecMap is defined above (alongside CoffTlsLayout) because the
    973  * TLS planning helpers need to consume one. */
    974 
    975 /* Build the four payload buckets (.text/.rdata/.data/.bss).
    976  *
    977  * `map[secid-1]` is populated for every kept LinkSection with the
    978  * section's new RVA, new file offset, the bucket it landed in, and the
    979  * delta to add to in-section vaddrs.  Bucket buffers are
    980  * heap-allocated; the caller frees them after emit. */
    981 static void coff_build_buckets(LinkImage* img, CoffSection out[COFF_NBUCKETS],
    982                                CoffSecMap* map) {
    983   Heap* heap = img->heap;
    984   Compiler* c = img->c;
    985   const LinkSection** tls_sorted = NULL;
    986   const LinkSection** crt_sorted = NULL;
    987   u32 ntls_sorted = 0;
    988   u32 ncrt_sorted = 0;
    989   u32 i, b;
    990 
    991   for (b = 0; b < COFF_NBUCKETS; ++b) {
    992     memset(&out[b], 0, sizeof(out[b]));
    993   }
    994   out[COFF_BUCKET_TEXT].name = ".text";
    995   out[COFF_BUCKET_TEXT].characteristics =
    996       IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
    997   out[COFF_BUCKET_TEXT].has_file_bytes = 1;
    998   out[COFF_BUCKET_RDATA].name = ".rdata";
    999   out[COFF_BUCKET_RDATA].characteristics =
   1000       IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
   1001   out[COFF_BUCKET_RDATA].has_file_bytes = 1;
   1002   out[COFF_BUCKET_IDATA].name = ".idata";
   1003   out[COFF_BUCKET_IDATA].characteristics =
   1004       IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
   1005   out[COFF_BUCKET_IDATA].has_file_bytes = 1;
   1006   out[COFF_BUCKET_DATA].name = ".data";
   1007   out[COFF_BUCKET_DATA].characteristics =
   1008       IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
   1009   out[COFF_BUCKET_DATA].has_file_bytes = 1;
   1010   /* The Windows loader uses .tls as a *template*: the bytes on disk
   1011    * seed each thread's per-TLS copy at thread creation, and threads
   1012    * write to their copies, not the template.  The PE section is still
   1013    * marked writable because that's what mingw and link.exe emit; the
   1014    * loader special-cases it via the TLS directory. */
   1015   out[COFF_BUCKET_TLS].name = ".tls";
   1016   out[COFF_BUCKET_TLS].characteristics =
   1017       IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
   1018   out[COFF_BUCKET_TLS].has_file_bytes = 1;
   1019   out[COFF_BUCKET_BSS].name = ".bss";
   1020   out[COFF_BUCKET_BSS].characteristics = IMAGE_SCN_CNT_UNINITIALIZED_DATA |
   1021                                          IMAGE_SCN_MEM_READ |
   1022                                          IMAGE_SCN_MEM_WRITE;
   1023   out[COFF_BUCKET_BSS].has_file_bytes = 0;
   1024   out[COFF_BUCKET_PDATA].name = ".pdata";
   1025   out[COFF_BUCKET_PDATA].characteristics =
   1026       IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
   1027   out[COFF_BUCKET_PDATA].has_file_bytes = 1;
   1028   out[COFF_BUCKET_RELOC].name = ".reloc";
   1029   out[COFF_BUCKET_RELOC].characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA |
   1030                                            IMAGE_SCN_MEM_READ |
   1031                                            IMAGE_SCN_MEM_DISCARDABLE;
   1032   out[COFF_BUCKET_RELOC].has_file_bytes = 1;
   1033 
   1034   /* Track per-bucket cursors.  Bucket sizes are bounded by the sum of
   1035    * input section sizes plus per-section alignment padding; we grow
   1036    * lazily via VEC_GROW. */
   1037   u64 bucket_cur[COFF_NBUCKETS];
   1038   u32 bucket_cap[COFF_NBUCKETS];
   1039   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1040     bucket_cur[b] = 0;
   1041     bucket_cap[b] = 0;
   1042   }
   1043 
   1044   tls_sorted = img->nsections ? (const LinkSection**)heap->alloc(
   1045                                     heap, sizeof(*tls_sorted) * img->nsections,
   1046                                     _Alignof(const LinkSection*))
   1047                               : NULL;
   1048   crt_sorted = img->nsections ? (const LinkSection**)heap->alloc(
   1049                                     heap, sizeof(*crt_sorted) * img->nsections,
   1050                                     _Alignof(const LinkSection*))
   1051                               : NULL;
   1052   if (img->nsections && (!tls_sorted || !crt_sorted))
   1053     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom sorting sections");
   1054 
   1055   for (i = 0; i < img->nsections; ++i) {
   1056     const LinkSection* ls = &img->sections[i];
   1057     if (!(ls->flags & SF_ALLOC)) continue;
   1058     if (ls->flags & SF_TLS) {
   1059       coff_insert_sorted_section(c, tls_sorted, &ntls_sorted, ls);
   1060       continue;
   1061     }
   1062     if (coff_section_name_starts(c, ls, ".CRT$")) {
   1063       coff_insert_sorted_section(c, crt_sorted, &ncrt_sorted, ls);
   1064       continue;
   1065     }
   1066     coff_place_section(img, out, map, bucket_cur, bucket_cap, ls);
   1067   }
   1068 
   1069   for (i = 0; i < ntls_sorted; ++i) {
   1070     coff_place_section(img, out, map, bucket_cur, bucket_cap, tls_sorted[i]);
   1071   }
   1072   for (i = 0; i < ncrt_sorted; ++i) {
   1073     coff_place_section(img, out, map, bucket_cur, bucket_cap, crt_sorted[i]);
   1074   }
   1075 
   1076   /* Track caps so we can free with the right size later (heap->free
   1077    * needs the original allocation size).  Stash into size_raw
   1078    * temporarily — overwritten below with the proper PE value. */
   1079   for (b = 0; b < COFF_NBUCKETS; ++b) out[b].size_raw = bucket_cap[b];
   1080   if (tls_sorted)
   1081     heap->free(heap, tls_sorted, sizeof(*tls_sorted) * img->nsections);
   1082   if (crt_sorted)
   1083     heap->free(heap, crt_sorted, sizeof(*crt_sorted) * img->nsections);
   1084 }
   1085 
   1086 /* Assign RVAs and file offsets to the buckets that participate in the
   1087  * image.  Returns the file offset at which trailing pad-to-EOF should
   1088  * land (== file size). */
   1089 static u64 coff_assign_layout(CoffSection out[COFF_NBUCKETS],
   1090                               u32 headers_file_size, u32 first_section_rva) {
   1091   u32 rva = first_section_rva;
   1092   u64 file = ALIGN_UP((u64)headers_file_size, (u64)PE_FILE_ALIGNMENT);
   1093   u32 b;
   1094   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1095     if (out[b].size == 0) {
   1096       out[b].in_image = 0;
   1097       out[b].rva = 0;
   1098       out[b].file_offset = 0;
   1099       out[b].size_raw = 0;
   1100       continue;
   1101     }
   1102     out[b].in_image = 1;
   1103     out[b].rva = (u32)ALIGN_UP((u64)rva, (u64)PE_SECTION_ALIGNMENT);
   1104     if (out[b].has_file_bytes) {
   1105       out[b].file_offset = (u32)file;
   1106       out[b].size_raw = (u32)ALIGN_UP((u64)out[b].size, (u64)PE_FILE_ALIGNMENT);
   1107       file += out[b].size_raw;
   1108     } else {
   1109       out[b].file_offset = 0;
   1110       out[b].size_raw = 0;
   1111     }
   1112     rva = out[b].rva + out[b].size;
   1113   }
   1114   return file;
   1115 }
   1116 
   1117 /* Build the .reloc bytes by grouping absolute relocs by 4-KiB page.
   1118  * The map[] array maps LinkSectionId-1 to the per-section post-PE-relayout
   1119  * RVA, so we can compute each reloc's site_rva = section_rva + (orig
   1120  * write_vaddr - orig section_vaddr).
   1121  *
   1122  * Layout per page:
   1123  *   u32 page_rva
   1124  *   u32 size_of_block (8 + n_entries*2, padded to a multiple of 4)
   1125  *   u16 entries[]: (type << 12) | (offset & 0xfff)
   1126  *   optional trailing u16 = 0 (IMAGE_REL_BASED_ABSOLUTE) for u32 alignment */
   1127 typedef struct CoffRelocEntry {
   1128   u32 site_rva;
   1129   u16 type;
   1130   u16 pad;
   1131 } CoffRelocEntry;
   1132 
   1133 static int coff_reloc_entry_cmp(const void* a, const void* b) {
   1134   const CoffRelocEntry* ea = (const CoffRelocEntry*)a;
   1135   const CoffRelocEntry* eb = (const CoffRelocEntry*)b;
   1136   if (ea->site_rva < eb->site_rva) return -1;
   1137   if (ea->site_rva > eb->site_rva) return 1;
   1138   return 0;
   1139 }
   1140 
   1141 static void coff_build_reloc_section(LinkImage* img,
   1142                                      const CoffSection out[COFF_NBUCKETS],
   1143                                      const CoffSecMap* map, CoffSection* reloc,
   1144                                      const CoffRelocEntry* extras,
   1145                                      u32 n_extras) {
   1146   Heap* heap = img->heap;
   1147   Compiler* c = img->c;
   1148   u32 nrel = LinkRelocs_count(&img->relocs);
   1149   CoffRelocEntry* entries = NULL;
   1150   u32 nentries = 0;
   1151   u32 cap = 0;
   1152   u32 i;
   1153 
   1154   for (i = 0; i < nrel; ++i) {
   1155     const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
   1156     const LinkSection* ls;
   1157     u64 site_old_vaddr;
   1158     u32 site_rva;
   1159     u16 type;
   1160     if (!coff_reloc_needs_base_reloc(r->kind)) continue;
   1161     if (r->link_section_id == LINK_SEC_NONE) continue;
   1162     ls = &img->sections[r->link_section_id - 1];
   1163     /* r->write_vaddr is in the pre-relayout coordinate system (same as
   1164      * ls->vaddr), so the offset into the section is stable.  Add the
   1165      * containing bucket's final RVA to land at the image RVA. */
   1166     site_old_vaddr = r->write_vaddr;
   1167     u8 sb = map[ls->id - 1].bucket;
   1168     site_rva = out[sb].rva + map[ls->id - 1].new_rva +
   1169                (u32)(site_old_vaddr - ls->vaddr);
   1170     if (r->kind == R_ABS64) {
   1171       type = (u16)IMAGE_REL_BASED_DIR64;
   1172     } else {
   1173       type = (u16)IMAGE_REL_BASED_HIGHLOW;
   1174     }
   1175     if (nentries == cap) {
   1176       (void)VEC_GROW(heap, entries, cap, nentries + 1u);
   1177     }
   1178     entries[nentries].site_rva = site_rva;
   1179     entries[nentries].type = type;
   1180     entries[nentries].pad = 0;
   1181     ++nentries;
   1182   }
   1183   /* Append caller-supplied extras (TLS directory absolute-VA fields,
   1184    * etc.). These are already site-RVAs in the final image. */
   1185   for (i = 0; i < n_extras; ++i) {
   1186     if (nentries == cap) {
   1187       (void)VEC_GROW(heap, entries, cap, nentries + 1u);
   1188     }
   1189     entries[nentries] = extras[i];
   1190     ++nentries;
   1191   }
   1192   if (nentries == 0) {
   1193     reloc->bytes = NULL;
   1194     reloc->size = 0;
   1195     if (entries) heap->free(heap, entries, cap * sizeof(*entries));
   1196     (void)c;
   1197     return;
   1198   }
   1199   /* Sort entries by RVA so we can group runs sharing a 4-KiB page. */
   1200   qsort(entries, nentries, sizeof(*entries), coff_reloc_entry_cmp);
   1201 
   1202   /* Two-pass: first compute the total size (so we can allocate the
   1203    * blob exactly), then emit. */
   1204   u32 blob_size = 0;
   1205   u32 run_start = 0;
   1206   while (run_start < nentries) {
   1207     u32 page = entries[run_start].site_rva & ~0xfffu;
   1208     u32 run_end = run_start;
   1209     while (run_end < nentries &&
   1210            (entries[run_end].site_rva & ~0xfffu) == page) {
   1211       ++run_end;
   1212     }
   1213     u32 n = run_end - run_start;
   1214     u32 block = COFF_BASE_RELOCATION_SIZE + n * 2u;
   1215     block = (u32)ALIGN_UP((u64)block, 4ull);
   1216     blob_size += block;
   1217     run_start = run_end;
   1218   }
   1219   reloc->bytes = (u8*)heap->alloc(heap, blob_size, 4);
   1220   if (!reloc->bytes && blob_size)
   1221     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom on .reloc blob");
   1222   memset(reloc->bytes, 0, blob_size);
   1223   reloc->size = blob_size;
   1224   /* Stash allocation size for free path. */
   1225   reloc->size_raw = blob_size;
   1226 
   1227   u32 cursor = 0;
   1228   run_start = 0;
   1229   while (run_start < nentries) {
   1230     u32 page = entries[run_start].site_rva & ~0xfffu;
   1231     u32 run_end = run_start;
   1232     while (run_end < nentries &&
   1233            (entries[run_end].site_rva & ~0xfffu) == page) {
   1234       ++run_end;
   1235     }
   1236     u32 n = run_end - run_start;
   1237     u32 raw_size = COFF_BASE_RELOCATION_SIZE + n * 2u;
   1238     u32 block = (u32)ALIGN_UP((u64)raw_size, 4ull);
   1239     u8* p = reloc->bytes + cursor;
   1240     wr_u32_le(p, page);
   1241     wr_u32_le(p + 4, block);
   1242     u32 k;
   1243     for (k = 0; k < n; ++k) {
   1244       u16 entry = (u16)(((u16)entries[run_start + k].type << 12) |
   1245                         (entries[run_start + k].site_rva & 0xfffu));
   1246       wr_u16_le(p + 8 + k * 2u, entry);
   1247     }
   1248     /* Optional trailing pad: a single IMAGE_REL_BASED_ABSOLUTE (0). */
   1249     if (block > raw_size) {
   1250       wr_u16_le(p + 8 + n * 2u, 0);
   1251     }
   1252     cursor += block;
   1253     run_start = run_end;
   1254   }
   1255   heap->free(heap, entries, cap * sizeof(*entries));
   1256 }
   1257 
   1258 /* Patch each LinkRelocApply against the PE-relayout coordinates and
   1259  * apply.  `bucket_bytes[bucket]` gives the writable buffer for that
   1260  * bucket; the per-section delta in map[] turns the old in-section
   1261  * offsets into bucket-local offsets.
   1262  *
   1263  * Imported targets (LinkSymbol.imported == 1) have no vaddr of their
   1264  * own — instead the .idata pass populated `iv->by_sym[id-1]` with the
   1265  * function stub's vaddr (for callable imports) or the IAT slot's
   1266  * vaddr (for data imports).  This is the spot where that table is
   1267  * consulted in lieu of the symbol's own zero vaddr. */
   1268 static void coff_apply_all_relocs(LinkImage* img,
   1269                                   const CoffSection out[COFF_NBUCKETS],
   1270                                   const CoffSecMap* map,
   1271                                   const CoffImportVaddr* iv) {
   1272   Compiler* c = img->c;
   1273   u32 i;
   1274   u64 img_base = PE_IMAGE_BASE;
   1275   u32 nrel = LinkRelocs_count(&img->relocs);
   1276   for (i = 0; i < nrel; ++i) {
   1277     LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
   1278     const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1);
   1279     const LinkSection* sec;
   1280     const LinkSection* tgt_sec;
   1281     u64 S, P;
   1282     u8* P_bytes;
   1283     u8 bucket;
   1284     u32 site_off_in_sec;
   1285     u32 site_bucket_off;
   1286     if (r->link_section_id == LINK_SEC_NONE) continue;
   1287     sec = &img->sections[r->link_section_id - 1];
   1288     bucket = map[sec->id - 1].bucket;
   1289     if (!out[bucket].has_file_bytes || !out[bucket].bytes) {
   1290       /* Shouldn't happen — .bss has no relocations. */
   1291       continue;
   1292     }
   1293     site_off_in_sec = (u32)(r->write_vaddr - sec->vaddr);
   1294     site_bucket_off = map[sec->id - 1].new_rva + site_off_in_sec;
   1295     P_bytes = out[bucket].bytes + site_bucket_off;
   1296     /* P = ImageBase + bucket_rva + map[].new_rva + site_off_in_sec
   1297      * — i.e. the final runtime address of the patch site. */
   1298     P = img_base + (u64)out[bucket].rva + (u64)map[sec->id - 1].new_rva +
   1299         site_off_in_sec;
   1300 
   1301     /* Resolve S: target symbol's new image-relative address.  Look up
   1302      * the LinkSection that contains the symbol's original vaddr, then
   1303      * apply that section's delta. */
   1304     if (tgt->imported) {
   1305       /* IAT-routed: stub vaddr (functions) / slot vaddr (data). */
   1306       if (!iv || iv->by_sym[r->target - 1u] == 0)
   1307         compiler_panic(c, SRCLOC_NONE,
   1308                        "link_emit_coff: imported target lacks IAT slot");
   1309       S = iv->by_sym[r->target - 1u];
   1310     } else if (tgt->kind == SK_ABS) {
   1311       S = tgt->vaddr;
   1312     } else if (tgt->defined) {
   1313       tgt_sec = coff_symbol_section(img, tgt);
   1314       if (!tgt_sec) {
   1315         compiler_panic(c, SRCLOC_NONE,
   1316                        "link_emit_coff: symbol vaddr 0x%llx has no "
   1317                        "containing section",
   1318                        (unsigned long long)tgt->vaddr);
   1319       }
   1320       u8 tb = map[tgt_sec->id - 1].bucket;
   1321       u64 sym_off = tgt->vaddr - tgt_sec->vaddr;
   1322       S = img_base + (u64)out[tb].rva + (u64)map[tgt_sec->id - 1].new_rva +
   1323           sym_off;
   1324     } else {
   1325       /* Undef and not imported — shouldn't survive resolve_undefs. */
   1326       compiler_panic(c, SRCLOC_NONE,
   1327                      "link_emit_coff: unresolved non-imported symbol");
   1328     }
   1329     /* COFF-only section-relative kinds: the SECREL value is the
   1330      * symbol's offset from the start of its containing output section
   1331      * (PE bucket), and SECTION is the 1-based PE section index.
   1332      * link_reloc_apply only sees S and P, so we patch these inline
   1333      * before delegating common kinds. */
   1334     if (r->kind == R_COFF_SECREL || r->kind == R_COFF_SECTION ||
   1335         r->kind == R_COFF_AARCH64_SECREL_LOW12A ||
   1336         r->kind == R_COFF_AARCH64_SECREL_HIGH12A ||
   1337         r->kind == R_COFF_AARCH64_SECREL_LOW12L) {
   1338       if (!tgt->defined || tgt->kind == SK_ABS) {
   1339         compiler_panic(c, SRCLOC_NONE,
   1340                        "link_emit_coff: COFF SECREL/SECTION requires a "
   1341                        "defined section-bound target symbol");
   1342       }
   1343       u8 tb = map[tgt_sec->id - 1].bucket;
   1344       u64 sym_off_in_bucket =
   1345           (u64)map[tgt_sec->id - 1].new_rva + (tgt->vaddr - tgt_sec->vaddr);
   1346       if (r->kind == R_COFF_SECREL) {
   1347         u64 v = sym_off_in_bucket + (u64)r->addend;
   1348         wr_u32_le(P_bytes, (u32)(v & 0xffffffffu));
   1349       } else if (r->kind == R_COFF_SECTION) {
   1350         /* PE section indices are 1-based; buckets are 0-based, so add 1. */
   1351         wr_u16_le(P_bytes, (u16)((tb + 1u) & 0xffffu));
   1352       } else if (r->kind == R_COFF_AARCH64_SECREL_LOW12L) {
   1353         /* AArch64 SECREL_LOW12L: patch a load/store unsigned-immediate
   1354          * imm12. The encoded immediate is scaled by the access width, which
   1355          * is recoverable from the instruction's size/opc bits. */
   1356         u64 v = sym_off_in_bucket + (u64)r->addend;
   1357         u64 lo12 = v & 0xfffu;
   1358         u32 instr = rd_u32_le(P_bytes);
   1359         u32 sz = (instr >> 30) & 0x3u;
   1360         u32 shift = (((instr >> 26) & 0x1u) && ((instr >> 23) & 0x1u)) ? 4u : sz;
   1361         u32 align_mask = (1u << shift) - 1u;
   1362         if (lo12 & align_mask)
   1363           compiler_panic(c, SRCLOC_NONE,
   1364                          "link_emit_coff: ARM64 SECREL_LOW12L misaligned "
   1365                          "offset 0x%llx for scale %u",
   1366                          (unsigned long long)lo12, (unsigned)shift);
   1367         instr = (instr & ~(0xfffu << 10)) |
   1368                 ((u32)((lo12 >> shift) & 0xfffu) << 10);
   1369         wr_u32_le(P_bytes, instr);
   1370       } else {
   1371         /* AArch64 SECREL_{LOW,HIGH}12A: patch the imm12 field of an
   1372          * existing ADD-imm12 instruction. LOW12A = bits [11:0] of the
   1373          * SECREL; HIGH12A = bits [23:12]. The instruction's sh bit was
   1374          * already set by the codegen (0 for LOW, 1 for HIGH). */
   1375         u64 v = sym_off_in_bucket + (u64)r->addend;
   1376         u32 imm12 = (r->kind == R_COFF_AARCH64_SECREL_HIGH12A)
   1377                         ? (u32)((v >> 12) & 0xfffu)
   1378                         : (u32)(v & 0xfffu);
   1379         u32 instr = rd_u32_le(P_bytes);
   1380         instr = (instr & ~(0xfffu << 10)) | (imm12 << 10);
   1381         wr_u32_le(P_bytes, instr);
   1382       }
   1383       continue;
   1384     }
   1385     if (r->kind == R_COFF_ADDR32NB) {
   1386       u64 inline_addend = rd_u32_le(P_bytes);
   1387       u64 v = (S - img_base) + inline_addend + (u64)r->addend;
   1388       wr_u32_le(P_bytes, (u32)(v & 0xffffffffu));
   1389       continue;
   1390     }
   1391     if (tgt->bind == SB_WEAK && tgt->kind == SK_ABS && tgt->vaddr == 0) {
   1392       /* AArch64 cannot generally ADRP from a PE image base down to absolute
   1393        * NULL. Materialize the weak-undef address as zero directly; the paired
   1394        * ADD low-12 relocation is already a no-op. */
   1395       if (r->kind == R_AARCH64_ADR_PREL_PG_HI21 ||
   1396           r->kind == R_AARCH64_ADR_PREL_PG_HI21_NC) {
   1397         u32 instr = rd_u32_le(P_bytes);
   1398         u32 rd = instr & 0x1fu;
   1399         wr_u32_le(P_bytes, 0xd2800000u | rd); /* movz Xrd, #0 */
   1400         continue;
   1401       }
   1402       if (r->kind == R_AARCH64_ADD_ABS_LO12_NC) continue;
   1403     }
   1404     link_reloc_apply(c, r->kind, P_bytes, S, r->addend, P);
   1405   }
   1406 }
   1407 
   1408 /* ---- header marshalling ----
   1409  *
   1410  * Each helper streams its on-disk shape to the writer field-by-field;
   1411  * we avoid sizeof(struct) on the packed PE wire types since they carry
   1412  * implicit-padding hazards on hosts that disagree with #pragma pack(1)
   1413  * defaults. */
   1414 
   1415 static void coff_write_dos_stub(Writer* w) {
   1416   u8 buf[PE_DOS_HDR_SIZE];
   1417   memset(buf, 0, sizeof(buf));
   1418   /* e_magic ("MZ") + e_lfanew (offset of PE signature).  All other
   1419    * legacy fields zero. */
   1420   buf[0] = (u8)(IMAGE_DOS_SIGNATURE & 0xffu);
   1421   buf[1] = (u8)((IMAGE_DOS_SIGNATURE >> 8) & 0xffu);
   1422   wr_u32_le(buf + 0x3c, PE_DOS_E_LFANEW);
   1423   kit_writer_write(w, buf, sizeof(buf));
   1424 }
   1425 
   1426 static void coff_write_file_header(Writer* w, u16 machine, u16 nsec,
   1427                                    u16 characteristics) {
   1428   coff_wr_u16(w, machine);
   1429   coff_wr_u16(w, nsec);
   1430   coff_wr_u32(w, 0u);                   /* TimeDateStamp */
   1431   coff_wr_u32(w, 0u);                   /* PointerToSymbolTable */
   1432   coff_wr_u32(w, 0u);                   /* NumberOfSymbols */
   1433   coff_wr_u16(w, (u16)PE_OPT_HDR_SIZE); /* SizeOfOptionalHeader */
   1434   coff_wr_u16(w, characteristics);
   1435 }
   1436 
   1437 static void coff_write_optional_header(Writer* w, u32 entry_rva,
   1438                                        const CoffSection out[COFF_NBUCKETS],
   1439                                        u32 headers_size_padded, u32 image_size,
   1440                                        int dynamic_base, u16 subsystem,
   1441                                        const CoffImportTable* it,
   1442                                        const CoffTlsLayout* tls) {
   1443   /* Standard fields. */
   1444   coff_wr_u16(w, IMAGE_NT_OPTIONAL_HDR64_MAGIC);
   1445   coff_wr_u8(w, PE_LINKER_MAJOR);
   1446   coff_wr_u8(w, PE_LINKER_MINOR);
   1447   /* SizeOfCode / SizeOfInitializedData / SizeOfUninitializedData. */
   1448   u32 size_code =
   1449       out[COFF_BUCKET_TEXT].in_image ? out[COFF_BUCKET_TEXT].size_raw : 0;
   1450   u32 size_init =
   1451       (out[COFF_BUCKET_RDATA].in_image ? out[COFF_BUCKET_RDATA].size_raw : 0) +
   1452       (out[COFF_BUCKET_IDATA].in_image ? out[COFF_BUCKET_IDATA].size_raw : 0) +
   1453       (out[COFF_BUCKET_DATA].in_image ? out[COFF_BUCKET_DATA].size_raw : 0) +
   1454       (out[COFF_BUCKET_TLS].in_image ? out[COFF_BUCKET_TLS].size_raw : 0) +
   1455       (out[COFF_BUCKET_PDATA].in_image ? out[COFF_BUCKET_PDATA].size_raw : 0) +
   1456       (out[COFF_BUCKET_RELOC].in_image ? out[COFF_BUCKET_RELOC].size_raw : 0);
   1457   u32 size_uninit =
   1458       out[COFF_BUCKET_BSS].in_image ? out[COFF_BUCKET_BSS].size : 0;
   1459   coff_wr_u32(w, size_code);
   1460   coff_wr_u32(w, size_init);
   1461   coff_wr_u32(w, size_uninit);
   1462   coff_wr_u32(w, entry_rva);
   1463   coff_wr_u32(w,
   1464               out[COFF_BUCKET_TEXT].in_image ? out[COFF_BUCKET_TEXT].rva : 0);
   1465   /* Windows-specific fields. */
   1466   coff_wr_u64(w, PE_IMAGE_BASE);
   1467   coff_wr_u32(w, PE_SECTION_ALIGNMENT);
   1468   coff_wr_u32(w, PE_FILE_ALIGNMENT);
   1469   coff_wr_u16(w, PE_OS_MAJOR);
   1470   coff_wr_u16(w, PE_OS_MINOR);
   1471   coff_wr_u16(w, 0u); /* MajorImageVersion */
   1472   coff_wr_u16(w, 0u); /* MinorImageVersion */
   1473   coff_wr_u16(w, PE_SUBSYS_MAJOR);
   1474   coff_wr_u16(w, PE_SUBSYS_MINOR);
   1475   coff_wr_u32(w, 0u); /* Win32VersionValue */
   1476   coff_wr_u32(w, image_size);
   1477   coff_wr_u32(w, headers_size_padded);
   1478   coff_wr_u32(w, 0u); /* CheckSum */
   1479   coff_wr_u16(w, subsystem ? subsystem : IMAGE_SUBSYSTEM_WINDOWS_CUI);
   1480   coff_wr_u16(
   1481       w, (u16)(PE_DLL_CHARS_BASE | (dynamic_base ? PE_DLL_CHARS_ASLR : 0)));
   1482   coff_wr_u64(w, PE_STACK_RESERVE);
   1483   coff_wr_u64(w, PE_STACK_COMMIT);
   1484   coff_wr_u64(w, PE_HEAP_RESERVE);
   1485   coff_wr_u64(w, PE_HEAP_COMMIT);
   1486   coff_wr_u32(w, 0u); /* LoaderFlags */
   1487   coff_wr_u32(w, (u32)PE_NUM_DATA_DIRS);
   1488   /* DataDirectory[16].  Populated entries:
   1489    *   [1]  IMPORT    — descriptor table RVA + total descriptor bytes
   1490    *   [3]  EXCEPTION — .pdata runtime-function table
   1491    *   [5]  BASERELOC — when PIE and .reloc is in the image
   1492    *   [12] IAT       — first IAT block RVA + sum of per-DLL IAT sizes
   1493    * Everything else stays zero. */
   1494   u32 i;
   1495   int has_idata = it && it->nimports > 0 && out[COFF_BUCKET_IDATA].in_image;
   1496   for (i = 0; i < PE_NUM_DATA_DIRS; ++i) {
   1497     if (i == IMAGE_DIRECTORY_ENTRY_IMPORT && has_idata) {
   1498       coff_wr_u32(w, out[COFF_BUCKET_IDATA].rva + it->desc_off);
   1499       coff_wr_u32(w, it->desc_size);
   1500     } else if (i == IMAGE_DIRECTORY_ENTRY_EXCEPTION &&
   1501                out[COFF_BUCKET_PDATA].in_image) {
   1502       coff_wr_u32(w, out[COFF_BUCKET_PDATA].rva);
   1503       coff_wr_u32(w, out[COFF_BUCKET_PDATA].size);
   1504     } else if (i == IMAGE_DIRECTORY_ENTRY_IAT && has_idata) {
   1505       coff_wr_u32(w, out[COFF_BUCKET_IDATA].rva + it->iat_base);
   1506       coff_wr_u32(w, it->iat_total);
   1507     } else if (i == IMAGE_DIRECTORY_ENTRY_BASERELOC && dynamic_base &&
   1508                out[COFF_BUCKET_RELOC].in_image) {
   1509       coff_wr_u32(w, out[COFF_BUCKET_RELOC].rva);
   1510       coff_wr_u32(w, out[COFF_BUCKET_RELOC].size);
   1511     } else if (i == IMAGE_DIRECTORY_ENTRY_TLS && tls && tls->present) {
   1512       coff_wr_u32(w, out[COFF_BUCKET_RDATA].rva + tls->dir_rdata_off);
   1513       coff_wr_u32(w, COFF_TLS_DIRECTORY64_SIZE);
   1514     } else {
   1515       coff_wr_u32(w, 0u);
   1516       coff_wr_u32(w, 0u);
   1517     }
   1518   }
   1519 }
   1520 
   1521 static void coff_write_section_header(Writer* w, const char* name, u32 vsize,
   1522                                       u32 rva, u32 size_raw, u32 file_offset,
   1523                                       u32 characteristics) {
   1524   u8 nm[8] = {0, 0, 0, 0, 0, 0, 0, 0};
   1525   size_t n = slice_from_cstr(name).len;
   1526   if (n > 8) n = 8;
   1527   memcpy(nm, name, n);
   1528   kit_writer_write(w, nm, 8);
   1529   coff_wr_u32(w, vsize);
   1530   coff_wr_u32(w, rva);
   1531   coff_wr_u32(w, size_raw);
   1532   coff_wr_u32(w, file_offset);
   1533   coff_wr_u32(w, 0u); /* PointerToRelocations */
   1534   coff_wr_u32(w, 0u); /* PointerToLinenumbers */
   1535   coff_wr_u16(w, 0u); /* NumberOfRelocations */
   1536   coff_wr_u16(w, 0u); /* NumberOfLinenumbers */
   1537   coff_wr_u32(w, characteristics);
   1538 }
   1539 
   1540 /* ---- main entry ---- */
   1541 
   1542 void link_emit_coff(LinkImage* img, Writer* w) {
   1543   Heap* heap = img->heap;
   1544   Compiler* c = img->c;
   1545   u16 machine = coff_machine_or_panic(c);
   1546   if (img->entry_sym == LINK_SYM_NONE)
   1547     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: no resolved entry symbol");
   1548 
   1549   /* ---- pass 1: build buckets + per-section delta map ---- */
   1550   CoffSection out[COFF_NBUCKETS];
   1551   CoffSecMap* map = (CoffSecMap*)heap->alloc(
   1552       heap, sizeof(CoffSecMap) * (img->nsections + 1u), _Alignof(CoffSecMap));
   1553   if (!map && img->nsections)
   1554     compiler_panic(c, SRCLOC_NONE, "link_emit_coff: oom on section map");
   1555   memset(map, 0, sizeof(CoffSecMap) * (img->nsections + 1u));
   1556 
   1557   /* coff_build_buckets stashes per-bucket allocation caps in size_raw;
   1558    * we read them back into a local before size_raw is recomputed by
   1559    * coff_assign_layout so the cleanup path can free with the right
   1560    * size. */
   1561   coff_build_buckets(img, out, map);
   1562   /* coff_build_buckets stashes per-bucket allocation caps in size_raw
   1563    * (the only bucket field we own for the duration of layout); read
   1564    * them out before coff_assign_layout overwrites the field.  .reloc
   1565    * and .idata aren't touched by coff_build_buckets — their caps are
   1566    * filled in below once coff_build_reloc_section / coff_emit_idata
   1567    * run. */
   1568   u32 bucket_caps[COFF_NBUCKETS];
   1569   u32 b;
   1570   for (b = 0; b < COFF_NBUCKETS; ++b) bucket_caps[b] = out[b].size_raw;
   1571 
   1572   /* ---- pass 1b: collect imports and reserve .idata + .text stubs ----
   1573    *
   1574    * Builds the per-DLL / per-import layout and appends one IAT-routing
   1575    * stub per imported function to the .text bucket.  The .idata bucket
   1576    * size is set here (so it counts in nsec); the stub vaddrs and
   1577    * IAT-slot vaddrs are finalised after coff_assign_layout. */
   1578   CoffImportTable imports;
   1579   int have_imports = coff_collect_imports(img, &imports);
   1580   if (have_imports) {
   1581     coff_plan_idata_layout(img, &imports);
   1582     coff_append_stubs(img, &imports, &out[COFF_BUCKET_TEXT],
   1583                       &bucket_caps[COFF_BUCKET_TEXT]);
   1584     /* Reserve the .idata bucket size so coff_assign_layout / nsec
   1585      * accounting sees it.  Actual bytes are written by coff_emit_idata
   1586      * once the bucket RVA is known. */
   1587     out[COFF_BUCKET_IDATA].size = imports.idata_size;
   1588   }
   1589 
   1590   /* ---- pass 1c: plan the TLS directory record ----
   1591    *
   1592    * If any SF_TLS sections survived, reserve 40 bytes at the tail of
   1593    * .rdata for the IMAGE_TLS_DIRECTORY64. Bytes are zeroed now and
   1594    * filled in by coff_emit_tls_dir once the bucket RVAs are final. */
   1595   CoffTlsLayout tls;
   1596   coff_plan_tls_layout(img, out, &bucket_caps[COFF_BUCKET_RDATA], &tls);
   1597 
   1598   /* ---- pass 2: decide whether .reloc will be in the image ----
   1599    *
   1600    * The headers' file size (and therefore every section's file
   1601    * offset) depends on the section-table entry count, so we need to
   1602    * commit to "is .reloc emitted?" before laying out file offsets.
   1603    * .reloc lights up iff at least one absolute VA reloc points into a kept
   1604    * section, OR a TLS directory is emitted (its VA fields need base-relocs).
   1605    * ARM64 Windows rejects fixed images (/dynamicbase:no), and x64 Windows
   1606    * accepts ASLR images by default, so PE images advertise DYNAMIC_BASE when
   1607    * this table is present instead of tying the table to the generic ELF/Mach-O
   1608    * img->pie flag. */
   1609   int emit_reloc = 0;
   1610   {
   1611     u32 i;
   1612     u32 nrel = LinkRelocs_count(&img->relocs);
   1613     for (i = 0; i < nrel; ++i) {
   1614       const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
   1615       if (!coff_reloc_needs_base_reloc(r->kind)) continue;
   1616       if (r->link_section_id == LINK_SEC_NONE) continue;
   1617       emit_reloc = 1;
   1618       break;
   1619     }
   1620     if (!emit_reloc && tls.present) emit_reloc = 1;
   1621   }
   1622 
   1623   u32 nsec = 0;
   1624   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1625     if (b == COFF_BUCKET_RELOC) {
   1626       if (emit_reloc) ++nsec; /* tentative; size set below */
   1627       continue;
   1628     }
   1629     if (out[b].size) ++nsec;
   1630   }
   1631   u32 headers_size_unpadded = PE_DOS_HDR_SIZE + PE_SIG_SIZE + PE_FILE_HDR_SIZE +
   1632                               PE_OPT_HDR_SIZE + nsec * PE_SECTION_HDR_SIZE;
   1633   u32 headers_size_padded =
   1634       (u32)ALIGN_UP((u64)headers_size_unpadded, (u64)PE_FILE_ALIGNMENT);
   1635 
   1636   /* First layout pass: fixes RVAs / file offsets for buckets that
   1637    * already have a finalised size (.text, .rdata, .idata, .data, .bss).
   1638    * .reloc's RVA is provisional — it depends on .reloc's own size,
   1639    * which is still 0 at this point. */
   1640   (void)coff_assign_layout(out, headers_size_padded, PE_FIRST_SECTION_RVA);
   1641 
   1642   /* ---- pass 2b: emit .idata bytes + per-arch IAT stubs ----
   1643    *
   1644    * The .idata bucket's RVA is final after the first assign_layout;
   1645    * stubs need it (the indirect-jump displacement targets an IAT slot)
   1646    * and .idata's own descriptor / ILT / IAT records all carry RVAs.
   1647    * coff_import_vaddr_build builds the per-LinkSymId override table
   1648    * that apply_all_relocs consults in place of the (zero) symbol
   1649    * vaddr for imported targets. */
   1650   CoffImportVaddr import_vaddr;
   1651   memset(&import_vaddr, 0, sizeof(import_vaddr));
   1652   if (have_imports) {
   1653     coff_emit_idata(img, &imports, out, &bucket_caps[COFF_BUCKET_IDATA]);
   1654     coff_emit_stubs(img, &imports, out);
   1655     coff_import_vaddr_build(img, &imports, out, &import_vaddr);
   1656   }
   1657 
   1658   /* Write the TLS directory bytes now that bucket RVAs are final. */
   1659   coff_emit_tls_dir(img, out, map, &tls);
   1660 
   1661   /* ---- pass 3: build .reloc using the now-final bucket RVAs ----
   1662    *
   1663    * coff_build_reloc_section reads out[bucket].rva indirectly via
   1664    * map[].new_rva + (write_vaddr - sec->vaddr) → site offset within
   1665    * the bucket; the absolute site_rva is bucket.rva + that offset.
   1666    * Patch site RVAs are page-quantised in the emitted blob, so this
   1667    * is the spot where the bucket RVAs need to be already final.
   1668    *
   1669    * TLS directory's four absolute-VA fields ride into the entries via
   1670    * the `extras` array — they aren't ordinary symbol relocations, so
   1671    * they don't show up in img->relocs. */
   1672   if (emit_reloc) {
   1673     CoffRelocEntry tls_extras[4];
   1674     u32 n_tls_extras = 0;
   1675     if (tls.present) {
   1676       u32 dir_rva = out[COFF_BUCKET_RDATA].rva + tls.dir_rdata_off;
   1677       static const u32 field_offs[4] = {
   1678           COFF_TLSDIR_OFF_START_ADDR,
   1679           COFF_TLSDIR_OFF_END_ADDR,
   1680           COFF_TLSDIR_OFF_INDEX_ADDR,
   1681           COFF_TLSDIR_OFF_CALLBACKS,
   1682       };
   1683       u32 k;
   1684       for (k = 0; k < 4; ++k) {
   1685         if (field_offs[k] == COFF_TLSDIR_OFF_CALLBACKS && !tls.callbacks_sym)
   1686           continue;
   1687         tls_extras[n_tls_extras].site_rva = dir_rva + field_offs[k];
   1688         tls_extras[n_tls_extras].type = (u16)IMAGE_REL_BASED_DIR64;
   1689         tls_extras[n_tls_extras].pad = 0;
   1690         ++n_tls_extras;
   1691       }
   1692     }
   1693     coff_build_reloc_section(img, out, map, &out[COFF_BUCKET_RELOC], tls_extras,
   1694                              n_tls_extras);
   1695     bucket_caps[COFF_BUCKET_RELOC] = out[COFF_BUCKET_RELOC].size_raw;
   1696     /* size_raw was stashed by build; assign_layout below recomputes it
   1697      * as the FileAlignment-padded length. */
   1698     (void)coff_assign_layout(out, headers_size_padded, PE_FIRST_SECTION_RVA);
   1699   }
   1700 
   1701   /* `_tls_used` is the public mingw/PE name for the TLS directory
   1702    * record. Keep it in lockstep with the optional-header TLS data
   1703    * directory, rather than leaving references bound to mingw's tlssup.o
   1704    * placeholder record. */
   1705   coff_define_tls_used(img, out, &tls);
   1706 
   1707   /* ---- pass 4: resolve entry symbol's PE RVA ----
   1708    *
   1709    * Done before apply so the optional-header field has its final
   1710    * value. */
   1711   const LinkSymbol* entry_sym = LinkSyms_at(&img->syms, img->entry_sym - 1);
   1712   if (!entry_sym->defined || entry_sym->kind == SK_ABS)
   1713     compiler_panic(c, SRCLOC_NONE,
   1714                    "link_emit_coff: entry symbol is not a defined "
   1715                    "image-relative function");
   1716   const LinkSection* entry_sec = coff_section_at(img, entry_sym->vaddr);
   1717   if (!entry_sec)
   1718     compiler_panic(c, SRCLOC_NONE,
   1719                    "link_emit_coff: entry symbol has no containing "
   1720                    "section");
   1721   u8 entry_bucket = map[entry_sec->id - 1].bucket;
   1722   u32 entry_rva = out[entry_bucket].rva + map[entry_sec->id - 1].new_rva +
   1723                   (u32)(entry_sym->vaddr - entry_sec->vaddr);
   1724 
   1725   /* ---- pass 5: apply all relocations into bucket bytes ---- */
   1726   coff_apply_all_relocs(img, out, map, have_imports ? &import_vaddr : NULL);
   1727 
   1728   /* ---- pass 6: compute SizeOfImage (in-memory size) ---- */
   1729   u32 image_size = 0;
   1730   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1731     if (!out[b].in_image) continue;
   1732     u32 end = out[b].rva + out[b].size;
   1733     if (end > image_size) image_size = end;
   1734   }
   1735   image_size = (u32)ALIGN_UP((u64)image_size, (u64)PE_SECTION_ALIGNMENT);
   1736 
   1737   /* ---- pass 7: write everything ---- */
   1738   u16 file_chars = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE;
   1739   int dynamic_base = out[COFF_BUCKET_RELOC].in_image;
   1740   if (!dynamic_base) {
   1741     file_chars |= IMAGE_FILE_RELOCS_STRIPPED;
   1742   }
   1743 
   1744   coff_write_dos_stub(w);
   1745   /* PE signature. */
   1746   coff_wr_u32(w, IMAGE_NT_SIGNATURE);
   1747   coff_write_file_header(w, machine, (u16)nsec, file_chars);
   1748   u16 subsystem = img->linker ? img->linker->pe_subsystem : 0;
   1749   coff_write_optional_header(w, entry_rva, out, headers_size_padded, image_size,
   1750                              dynamic_base, subsystem,
   1751                              have_imports ? &imports : NULL, &tls);
   1752 
   1753   /* Section table. */
   1754   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1755     if (!out[b].in_image) continue;
   1756     coff_write_section_header(w, out[b].name, out[b].size, out[b].rva,
   1757                               out[b].size_raw, out[b].file_offset,
   1758                               out[b].characteristics);
   1759   }
   1760 
   1761   /* Pad to first section's file offset. */
   1762   u64 cur = (u64)headers_size_unpadded;
   1763   u64 first_file_off = headers_size_padded;
   1764   if (cur < first_file_off) {
   1765     coff_write_zeroes(w, first_file_off - cur);
   1766     cur = first_file_off;
   1767   }
   1768 
   1769   /* Section bodies. */
   1770   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1771     if (!out[b].in_image) continue;
   1772     if (!out[b].has_file_bytes) continue;
   1773     if (cur < out[b].file_offset) {
   1774       coff_write_zeroes(w, out[b].file_offset - cur);
   1775       cur = out[b].file_offset;
   1776     }
   1777     kit_writer_write(w, out[b].bytes, out[b].size);
   1778     cur += out[b].size;
   1779     if (out[b].size_raw > out[b].size) {
   1780       coff_write_zeroes(w, out[b].size_raw - out[b].size);
   1781       cur += out[b].size_raw - out[b].size;
   1782     }
   1783   }
   1784 
   1785   /* ---- cleanup ---- */
   1786   for (b = 0; b < COFF_NBUCKETS; ++b) {
   1787     if (out[b].bytes) heap->free(heap, out[b].bytes, bucket_caps[b]);
   1788   }
   1789   heap->free(heap, map, sizeof(CoffSecMap) * (img->nsections + 1u));
   1790   if (have_imports) {
   1791     coff_import_vaddr_free(img, &import_vaddr);
   1792     coff_imports_free(img, &imports);
   1793   }
   1794 }