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:
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: