commit e4406133899dbe7188e20d4f269d259b2df0d6a2
parent 98ca5ca79027bd63e9609ddcfc45d7ddcb020709
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 13:22:00 -0700
rv64: emit TLS Local-Exec only, matching aa64/x64
rv_tls_addr_of routed extern-via-GOT _Thread_local symbols through an
Initial-Exec sequence emitting R_RV_TLS_GOT_HI20. Under -fPIE (the hosted
default) this reloc is reachable for ordinary extern TLS access, but the
linker has no layout or apply for it (reloc_uses_got omits it, reloc_apply
hits the default panic), so linking failed with 'unsupported reloc kind 80'
rather than producing a binary. The JIT HI20-pairing also doesn't recognize
it. cfree links the whole module statically, so Local-Exec (TPREL) is always
correct — exactly what aa64 (aa_tls_addr_of) and x64 (x64_tls_addr_of) already
do unconditionally.
Drop the IE/GOT branch (and the now-dead rv_pcrel_anchor helper). Add
test-rv64-tls-link, a host-agnostic link-only regression, to the default set.
Verified: extern _Thread_local for riscv64-linux now emits RV_TPREL_HI20/
LO12_I and links cleanly.
Diffstat:
3 files changed, 63 insertions(+), 29 deletions(-)
diff --git a/src/arch/rv64/native.c b/src/arch/rv64/native.c
@@ -2123,40 +2123,19 @@ static void rv_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size,
/* ============================ TLS / bitfield / atomics ============================ */
-/* Define a fresh local .LpcrelHi anchor pointing at `ap`, for the
- * R_RV_PCREL_LO12_I follow-on that pairs with an AUIPC-relative HI20 reloc. */
-static ObjSymId rv_pcrel_anchor(RvNativeTarget* a, ObjSecId sec, u32 ap) {
- NativeTarget* t = &a->base;
- Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi"));
- return obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0);
-}
-
static void rv_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym,
i64 addend) {
- RvNativeTarget* a = rv_of(t);
MCEmitter* mc = t->mc;
u32 sec = mc->section_id;
u32 rd = loc_reg(dst);
- if (obj_symbol_extern_via_got(t->c, t->obj, sym)) {
- /* Initial-Exec: auipc t0, %tls_ie_pcrel_hi(sym)
- * ld t0, %pcrel_lo(.Ltmp)(t0)
- * add dst, tp, t0
- * GOT relocs disallow an addend, so apply it after the GOT load. */
- u32 ap = mc->pos(mc);
- rv64_emit32(mc, rv_auipc(RV_TMP0, 0));
- mc->emit_reloc_at(mc, sec, ap, R_RV_TLS_GOT_HI20, sym, 0, 0, 0);
- {
- ObjSymId anchor = rv_pcrel_anchor(a, sec, ap);
- u32 lp = mc->pos(mc);
- rv64_emit32(mc, rv_ld(RV_TMP0, RV_TMP0, 0));
- mc->emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0);
- }
- rv64_emit32(mc, rv_add(rd, RV_TP, RV_TMP0));
- if (addend) rv_emit_addr_adjust(mc, rd, rd, (i32)addend);
- return;
- }
- /* Local-Exec: lui t0, %tprel_hi(sym); add t0, tp, t0; addi dst, t0,
- * %tprel_lo(sym). */
+ /* Local-Exec only, matching aa64 (aa_tls_addr_of) and x64 (x64_tls_addr_of):
+ * cfree links the whole module statically, so every _Thread_local symbol is
+ * resolved within the image and TPREL is always valid. An Initial-Exec GOT
+ * path (R_RV_TLS_GOT_HI20) used to be emitted for extern-via-GOT symbols
+ * under -fPIE (the hosted default), but the linker has no layout/apply for
+ * that reloc, so it produced a hard "unsupported reloc kind" link failure
+ * rather than a working binary. */
+ /* lui t0, %tprel_hi(sym); add t0, tp, t0; addi dst, t0, %tprel_lo(sym). */
{
u32 hp = mc->pos(mc);
rv64_emit32(mc, rv_lui(RV_TMP0, 0));
diff --git a/test/smoke/rv64_tls_link.sh b/test/smoke/rv64_tls_link.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# test/smoke/rv64_tls_link.sh — regression for rv64 TLS Local-Exec lowering.
+#
+# rv64 used to emit R_RV_TLS_GOT_HI20 (Initial-Exec) for an extern
+# _Thread_local symbol under -fPIE (the hosted default). The linker has no
+# layout/apply for that reloc, so linking failed hard with
+# "link: unsupported reloc kind 80" instead of producing a binary. cfree
+# links whole-module/static, so the fix is to always emit Local-Exec
+# (R_RV_TPREL_HI20/LO12_I), matching the aa64 and x64 backends.
+#
+# This is link-only (no execution), so it runs on any host without qemu.
+
+set -u
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CFREE="${CFREE:-$ROOT/build/cfree}"
+WORK="$ROOT/build/test/rv64-tls-link"
+mkdir -p "$WORK"
+
+if [ ! -x "$CFREE" ]; then
+ echo "SKIP rv64_tls_link: $CFREE not built"
+ exit 0
+fi
+
+cat > "$WORK/tls_def.c" <<'EOF'
+_Thread_local int g = 7;
+EOF
+cat > "$WORK/tls_ie.c" <<'EOF'
+extern _Thread_local int g;
+int read_g(void) { return g; }
+int *addr_g(void) { return &g; }
+EOF
+
+fail() { printf 'FAIL rv64_tls_link: %s\n' "$1"; exit 1; }
+
+"$CFREE" cc -c -target riscv64-linux "$WORK/tls_ie.c" -o "$WORK/tls_ie.o" \
+ || fail "compile tls_ie.c"
+"$CFREE" cc -c -target riscv64-linux "$WORK/tls_def.c" -o "$WORK/tls_def.o" \
+ || fail "compile tls_def.c"
+
+# The extern _Thread_local access must lower to TPREL, never TLS_GOT.
+relocs="$("$CFREE" objdump -r "$WORK/tls_ie.o" 2>&1)"
+echo "$relocs" | grep -q 'RV_TPREL_HI20' || fail "expected RV_TPREL_HI20 reloc; got: $relocs"
+if echo "$relocs" | grep -q 'TLS_GOT'; then fail "unexpected TLS_GOT reloc (Initial-Exec regressed): $relocs"; fi
+
+# And the whole-module link must succeed (previously: unsupported reloc kind 80).
+"$CFREE" ld --entry read_g "$WORK/tls_ie.o" "$WORK/tls_def.o" -o "$WORK/tls_out" \
+ || fail "link extern _Thread_local"
+
+echo "OK rv64_tls_link"
diff --git a/test/test.mk b/test/test.mk
@@ -78,6 +78,7 @@ TEST_TARGETS = \
test-rt-runtime \
test-rv64-inline \
test-rv64-jit \
+ test-rv64-tls-link \
test-smoke-rv64 \
test-smoke-x64 \
test-toy \
@@ -105,6 +106,7 @@ DEFAULT_TEST_TARGETS = \
test-aa64-inline \
test-rv64-inline \
test-rv64-jit \
+ test-rv64-tls-link \
test-emu \
test-x64-inline \
test-x64-dbg \
@@ -346,6 +348,10 @@ $(RV64_JIT_TEST_BIN): test/link/rv64_jit_test.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(TEST_HOST_CFLAGS) test/link/rv64_jit_test.c $(LIB_AR) -o $@
+# Link-only regression for rv64 TLS Local-Exec lowering (runs on any host).
+test-rv64-tls-link: bin
+ @CFREE='$(abspath $(BIN))' bash test/smoke/rv64_tls_link.sh
+
X64_INLINE_TEST_BIN = build/test/x64_inline_test
test-x64-inline: $(X64_INLINE_TEST_BIN)