kit

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

commit a82588275127da5cfee9aa081e1dc5722d2237a1
parent a06d4ad1cc6afeb50842a6a561718a8e4f6ea5dc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 10 May 2026 14:47:51 -0700

link/x64: test-link green — PLT32, 32S, TLS LE, ifunc stub

Bring x86_64 ELF up to the same path-R + path-E coverage as aa64 / rv64
in test/link.

  src/obj/elf_reloc_x86_64.c — round-trip R_X86_64_32S through a distinct
    R_X64_32S RelocKind (was collapsing to R_ABS32 → emit always wrote
    R_X86_64_32, breaking 23 / 25h / 34 readelf diffs).
  src/link/link_reloc.c — apply cases for R_X64_PLT32 (PC-rel 32 in a
    static link), R_X64_32S (shares ABS32 byte encoding), and
    R_X64_TPOFF32 / _TPOFF64 (caller pre-computes TP-rel S).
  src/link/link_elf.c — variant-II TP offset for x64 TLSLE: TP points
    just past the TLS image, so S = (tgt - tls_vaddr) - tls_memsz.
  src/link/link_layout.c — width entries for the new x64 kinds; iplt
    stub for CFREE_ARCH_X86_64 emits `jmpq *slot(%rip)` (FF 25 disp32)
    + 6-byte multibyte NOP, with disp32 encoded directly (stub→slot
    distance is invariant under image-base shift, like rv64).
    Also: SK_FILE (STT_FILE) is_def fix — file-name markers at SHN_ABS
    are defined locals, not undefs.

CFREE_TEST_ARCH=x64 bash test/link/run.sh: 77 pass, 0 fail. The 41
skips are all path J (in-process JIT requires host==x64; harness host
is darwin/arm64), matching the rv64 cross-test shape.

Diffstat:
Msrc/link/link_elf.c | 18++++++++++++++++++
Msrc/link/link_layout.c | 26+++++++++++++++++++++++++-
Msrc/link/link_reloc.c | 19+++++++++++++++----
Msrc/obj/elf_reloc_x86_64.c | 4+++-
4 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/src/link/link_elf.c b/src/link/link_elf.c @@ -216,6 +216,17 @@ static int reloc_is_tlsle(RelocKind k) { k == R_RV_TPREL_LO12_S; } +/* x86_64 SysV ABI: TLS variant II — the per-thread TLS image sits at + * *negative* offsets from %fs (which points at the TCB). start.c + * lays out [tdata | tbss | TCB] and arch_prctl(ARCH_SET_FS, &TCB), so + * a symbol at offset X within the TLS image is at fs-relative offset + * (X - tls_memsz). The two ELF reloc kinds R_X86_64_TPOFF32/_TPOFF64 + * encode that signed offset directly at the reloc site (no TCB bias — + * variant II's TCB sits *after* the image, so TPOFF is negative). */ +static int reloc_is_x64_tlsle(RelocKind k) { + return k == R_X64_TPOFF32 || k == R_X64_TPOFF64; +} + static int reloc_is_abs(RelocKind k) { return k == R_ABS32 || k == R_ABS64; } static int reloc_is_branch26(RelocKind k) { @@ -291,6 +302,13 @@ static void apply_all_relocs(LinkImage* img, u64 img_base) { * in the same (post-shift, image-relative) coordinate * system, so img_base cancels out. */ S = (tgt->vaddr - img->tls_vaddr) + TLS_TCB_SIZE; + } else if (reloc_is_x64_tlsle(r->kind)) { + /* x86_64 variant II: TP points just past the TLS image, so a + * symbol at offset X within the image is at TP-relative offset + * (X - tls_memsz). Cast through i64/u64 so the reloc apply + * writes the full 32- or 64-bit signed value. */ + i64 off = (i64)(tgt->vaddr - img->tls_vaddr) - (i64)img->tls_memsz; + S = (u64)off; } else if (r->kind == R_RV_PCREL_LO12_I || r->kind == R_RV_PCREL_LO12_S) { /* PCREL_LO12: rewrite S so that link_reloc_apply's existing diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -177,9 +177,12 @@ static void resolve_symbols(Linker* l, LinkImage* img) { * declaration's bookkeeping symbol with section_id = 0; those are * still undefs from the linker's perspective. ELF's read_elf * already normalizes those to SK_UNDEF; this check unifies the - * in-memory pipeline with that. */ + * in-memory pipeline with that. SK_FILE (STT_FILE) is a defined + * local marker carrying a source filename at SHN_ABS — it has no + * section, but it is not undef. */ int is_def = (s->kind != SK_UNDEF) && (s->kind == SK_ABS || s->kind == SK_COMMON || + s->kind == SK_FILE || s->section_id != OBJ_SEC_NONE); memset(&rec, 0, sizeof(rec)); @@ -1759,10 +1762,12 @@ static u8 reloc_width(RelocKind k) { case R_PLT32: case R_X64_PLT32: case R_X64_32S: + case R_X64_TPOFF32: return 4; case R_ABS64: case R_REL64: case R_PC64: + case R_X64_TPOFF64: return 8; case R_AARCH64_ABS16: case R_AARCH64_PREL16: @@ -2453,6 +2458,25 @@ static void layout_iplt(Linker* l, LinkImage* img) { wr_u32_le(stub_dst + 8, jr_t1); break; } + case CFREE_ARCH_X86_64: { + /* x86_64: `jmpq *slot(%rip)` (FF 25 disp32), 6 bytes, padded + * to 12 with a 6-byte multi-byte NOP. disp32 is measured from + * the end of the JMP (stub+6) to the slot. Like RV64, the + * stub→slot displacement is invariant under image-base shift + * so we encode it directly without a relocation. */ + i64 disp = (i64)slot_vaddr - (i64)(stub_vaddr + 6u); + stub_dst[0] = 0xffu; /* opcode: JMP r/m64 */ + stub_dst[1] = 0x25u; /* ModRM: 00 100 101 — RIP+disp32 */ + wr_u32_le(stub_dst + 2, (u32)((u64)disp & 0xffffffffu)); + /* 6-byte multibyte NOP: 66 0F 1F 44 00 00 (NOPW 0(%rax,%rax,1)). */ + stub_dst[6] = 0x66u; + stub_dst[7] = 0x0fu; + stub_dst[8] = 0x1fu; + stub_dst[9] = 0x44u; + stub_dst[10] = 0x00u; + stub_dst[11] = 0x00u; + break; + } default: compiler_panic(img->c, no_loc(), "link: ifunc iplt stub not implemented for arch %u", diff --git a/src/link/link_reloc.c b/src/link/link_reloc.c @@ -27,22 +27,33 @@ static SrcLoc no_loc(void) { void link_reloc_apply(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, u64 P) { switch (k) { - case R_ABS32: { + case R_ABS32: + case R_X64_32S: + case R_X64_TPOFF32: { + /* All three write a 32-bit value at the site. ABS32 / _32S + * take an absolute (unsigned / sign-extended) symbol address; + * TPOFF32 takes the (caller-precomputed) TP-relative offset. + * At the byte level the encoding is identical. */ u64 v = S + (u64)A; wr_u32_le(P_bytes, (u32)(v & 0xffffffffu)); return; } - case R_ABS64: { + case R_ABS64: + case R_X64_TPOFF64: { u64 v = S + (u64)A; wr_u64_le(P_bytes, v); return; } case R_REL32: - case R_PC32: { + case R_PC32: + case R_X64_PLT32: { /* AArch64 ELF: PREL32 maps to either of these; both encode a * 32-bit signed PC-relative displacement. The cfree-canonical * distinction (section-relative vs PC-relative) collapses on - * AArch64 because the linker resolves to absolute vaddrs. */ + * AArch64 because the linker resolves to absolute vaddrs. + * + * x86_64 PLT32: in a static link there is no PLT, so the + * displacement collapses to a plain 32-bit PC-relative call. */ i64 v = (i64)S + A - (i64)P; wr_u32_le(P_bytes, (u32)((u64)v & 0xffffffffu)); return; diff --git a/src/obj/elf_reloc_x86_64.c b/src/obj/elf_reloc_x86_64.c @@ -18,6 +18,8 @@ u32 elf_x86_64_reloc_to(u32 kind /* RelocKind */) { return ELF_R_X86_64_64; case R_ABS32: return ELF_R_X86_64_32; + case R_X64_32S: + return ELF_R_X86_64_32S; case R_PC32: return ELF_R_X86_64_PC32; case R_PC64: @@ -81,7 +83,7 @@ u32 elf_x86_64_reloc_from(u32 elf_type) { case ELF_R_X86_64_32: return R_ABS32; case ELF_R_X86_64_32S: - return R_ABS32; + return R_X64_32S; case ELF_R_X86_64_PC32: return R_PC32; case ELF_R_X86_64_PC64: