tls: fix local-exec addresses and external symbols
Some checks failed
build and test / test-x86_64-linux (push) Has been cancelled
build and test / test-x86_64-osx (push) Has been cancelled
build and test / test-aarch64-osx (push) Has been cancelled
build and test / test-x86_64-win32 (push) Has been cancelled
build and test / test-i386-win32 (push) Has been cancelled
build and test / test-arm64-win32 (push) Has been cancelled
build and test / test-armv7-linux (push) Has been cancelled
build and test / test-aarch64-linux (push) Has been cancelled
build and test / test-riscv64-linux (push) Has been cancelled
build and test / test-i386-linux (push) Has been cancelled

Preserve symbolic offsets when materializing i386 TLS addresses, generate ARM64 TLS addresses from the thread pointer, and carry RISC-V addends on both halves of the TPREL relocation pair.

Keep undefined ELF TLS symbols typed as STT_TLS so objects produced by TCC can be linked by the system linker. Extend the TLS regression coverage for narrow signed and unsigned accesses, address-taking, runtime indexing, thread isolation, large RISC-V offsets, unsupported runtimes, and external TLS definitions.
This commit is contained in:
Mounir IDRASSI 2026-07-10 23:58:23 +09:00
parent 04206d2903
commit 97a6d8c75f
10 changed files with 165 additions and 48 deletions

View File

@ -468,20 +468,8 @@ static void arm64_strv(int sz_, int dst, int bas, uint64_t off)
}
}
static void arm64_sym(int r, Sym *sym, unsigned long addend)
static void arm64_add_offset(int r, unsigned long addend)
{
#ifdef TCC_TARGET_PE
/* PE links symbol addresses directly; there is no ELF-style GOT here. */
greloca(cur_text_section, sym, ind, R_AARCH64_ADR_PREL_PG_HI21, 0);
o(ARM64_ADRP | r); // adrp xr, #sym
greloca(cur_text_section, sym, ind, R_AARCH64_ADD_ABS_LO12_NC, 0);
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | r); // add xr, xr, #sym
#else
greloca(cur_text_section, sym, ind, R_AARCH64_ADR_GOT_PAGE, 0);
o(ARM64_ADRP | r); // adrp xr, #sym
greloca(cur_text_section, sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0);
o(ARM64_LDR_X | ARM64_RN(r) | r); // ld xr,[xr, #sym]
#endif
if (addend) {
// add xr, xr, #addend
if (addend & 0xffful)
@ -504,17 +492,35 @@ static void arm64_sym(int r, Sym *sym, unsigned long addend)
}
}
static void arm64_sym(int r, Sym *sym, unsigned long addend)
{
#ifdef TCC_TARGET_PE
/* PE links symbol addresses directly; there is no ELF-style GOT here. */
greloca(cur_text_section, sym, ind, R_AARCH64_ADR_PREL_PG_HI21, 0);
o(ARM64_ADRP | r); // adrp xr, #sym
greloca(cur_text_section, sym, ind, R_AARCH64_ADD_ABS_LO12_NC, 0);
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | r); // add xr, xr, #sym
#else
greloca(cur_text_section, sym, ind, R_AARCH64_ADR_GOT_PAGE, 0);
o(ARM64_ADRP | r); // adrp xr, #sym
greloca(cur_text_section, sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0);
o(ARM64_LDR_X | ARM64_RN(r) | r); // ld xr,[xr, #sym]
#endif
arm64_add_offset(r, addend);
}
static void arm64_load_cmp(int r, SValue *sv);
static void arm64_tls_x30(SValue *sv)
static void arm64_tls_sym(int r, Sym *sym, unsigned long addend)
{
o(0xd53bd05e); /* mrs x30, tpidr_el0 */
greloca(cur_text_section, sv->sym, ind, R_AARCH64_TLSLE_ADD_TPREL_HI12, 0);
/* add x30, x30, #0, lsl #12 */
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) | ARM64_RN(30) | ARM64_RD(30));
greloca(cur_text_section, sv->sym, ind, R_AARCH64_TLSLE_ADD_TPREL_LO12, 0);
/* add x30, x30, #0 */
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(30) | ARM64_RD(30));
o(0xd53bd040 | r); /* mrs xr, tpidr_el0 */
greloca(cur_text_section, sym, ind, R_AARCH64_TLSLE_ADD_TPREL_HI12, 0);
/* add xr, xr, #0, lsl #12 */
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) | ARM64_RN(r) | ARM64_RD(r));
greloca(cur_text_section, sym, ind, R_AARCH64_TLSLE_ADD_TPREL_LO12, 0);
/* add xr, xr, #0 */
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | ARM64_RD(r));
arm64_add_offset(r, addend);
}
ST_FUNC void load(int r, SValue *sv)
@ -537,7 +543,9 @@ ST_FUNC void load(int r, SValue *sv)
if (svr == (VT_CONST | VT_LVAL)) {
uint64_t i = sv->c.i;
if (sv->sym)
if (sv->sym && (sv->sym->type.t & VT_TLS))
arm64_tls_sym(30, sv->sym, 0);
else if (sv->sym)
arm64_sym(30, sv->sym, // use x30 for address
arm64_check_offset(0, arm64_type_size(svtt), i));
else
@ -564,7 +572,7 @@ ST_FUNC void load(int r, SValue *sv)
if (svr == (VT_CONST | VT_LVAL | VT_SYM)) {
if (sv->sym->type.t & VT_TLS)
arm64_tls_x30(sv);
arm64_tls_sym(30, sv->sym, 0);
else
arm64_sym(30, sv->sym, // use x30 for address
arm64_check_offset(0, arm64_type_size(svtt), svcoff));
@ -578,6 +586,9 @@ ST_FUNC void load(int r, SValue *sv)
}
if (svr == (VT_CONST | VT_SYM)) {
if (sv->sym->type.t & VT_TLS)
arm64_tls_sym(intr(r), sv->sym, svcul);
else
arm64_sym(intr(r), sv->sym, svcul);
return;
}
@ -659,7 +670,7 @@ ST_FUNC void store(int r, SValue *sv)
if (svr == (VT_CONST | VT_LVAL)) {
uint64_t i = sv->c.i;
if (sv->sym && (sv->sym->type.t & VT_TLS))
arm64_tls_x30(sv);
arm64_tls_sym(30, sv->sym, 0);
else
if (sv->sym)
arm64_sym(30, sv->sym, // use x30 for address
@ -686,15 +697,7 @@ ST_FUNC void store(int r, SValue *sv)
if (svr == (VT_CONST | VT_LVAL | VT_SYM)) {
if (sv->sym->type.t & VT_TLS) {
o(0xd53bd05e); /* mrs x30, tpidr_el0 */
greloca(cur_text_section, sv->sym, ind,
R_AARCH64_TLSLE_ADD_TPREL_HI12, 0);
o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) |
ARM64_RN(30) | ARM64_RD(30)); /* add x30, x30, #0, lsl #12 */
greloca(cur_text_section, sv->sym, ind,
R_AARCH64_TLSLE_ADD_TPREL_LO12, 0);
o(ARM64_ADD_IMM | ARM64_SF(1) |
ARM64_RN(30) | ARM64_RD(30)); /* add x30, x30, #0 */
arm64_tls_sym(30, sv->sym, 0);
if (IS_FREG(r))
arm64_strv(arm64_type_size(svtt), fltr(r), 30, svcoff);
else

View File

@ -354,7 +354,7 @@ ST_FUNC void load(int r, SValue *sv)
} else {
if ((fr & VT_SYM) && (sv->sym->type.t & VT_TLS)) {
oad(0x058b65 | REG_VALUE(r) << 19, 0); /* mov gs:0,r */
oad(0xC081 | REG_VALUE(r) << 8, 0); /* add tpoffs,r */
oad(0xC081 | REG_VALUE(r) << 8, fc); /* add tpoffs,r */
greloc(cur_text_section, sv->sym, ind - 4, R_386_TLS_LE);
#if defined CONFIG_TCC_PIC
} else if ((fr & (VT_VALMASK|VT_SYM)) == (VT_CONST|VT_SYM)) {

View File

@ -186,10 +186,8 @@ static int load_symofs(int r, SValue *sv, int forstore, int *new_fc)
R_RISCV_TPREL_HI20, sv->c.i);
o(0x37 | (rr << 7)); // lui RR, 0 %tprel_hi(sym)
greloca(cur_text_section, sv->sym, ind,
R_RISCV_TPREL_LO12_I, 0);
R_RISCV_TPREL_LO12_I, sv->c.i);
EI(0x13, 0, rr, rr, 0); // addi RR, RR, 0 %tprel_lo(sym)
if (fc)
EI(0x13, 0, rr, rr, fc); // addi RR, RR, fc
ER(0x33, 0, rr, rr, 4, 0); // add RR, RR, tp
*new_fc = 0;
return rr;

View File

@ -205,7 +205,9 @@ ST_FUNC void tccelf_end_file(TCCState *s1)
#ifndef TCC_TARGET_PE
if (sym_bind == STB_GLOBAL && s1->output_type == TCC_OUTPUT_OBJ) {
/* undefined symbols with STT_FUNC are confusing gnu ld when
linking statically to STT_GNU_IFUNC */
linking statically to STT_GNU_IFUNC. Keep TLS typed,
otherwise gnu ld rejects non-TLS refs to TLS definitions. */
if (sym_type != STT_TLS)
sym_type = STT_NOTYPE;
}
#endif

View File

@ -6,6 +6,11 @@
typedef enum { tls_a, tls_b, tls_c } tls_enum_type;
typedef struct { int tls_d, tls_e, tls_f; } tls_struct_type;
typedef struct {
int *init;
signed short *element;
} tls_address_type;
__thread char tls_char = 42;
__thread short tls_short = 43;
__thread int tls_init = 44;
@ -17,6 +22,41 @@ __thread long double tls_long_double = 48.0;
__thread int *tls_ptr = (int *)49;
__thread tls_enum_type tls_enum = tls_b;
__thread tls_struct_type tls_struct = { 50, 51, 52 };
__thread signed char tls_signed_char = -5;
__thread unsigned char tls_unsigned_char = 250;
__thread signed short tls_signed_short = -1234;
__thread unsigned short tls_unsigned_short = 60000;
__thread signed short tls_array[4] = { -1000, 2000, -3000, 4000 };
#if defined __riscv && __riscv_xlen == 64
typedef struct {
char padding[4096];
int value;
} tls_large_type;
__thread tls_large_type tls_large = { { 0 }, 123456 };
#endif
static int check_extra(int changed)
{
volatile int index = 2;
if (changed)
return tls_signed_char != -7 || tls_unsigned_char != 42
|| tls_signed_short != -2222 || tls_unsigned_short != 12345
|| tls_array[1] != 2222 || tls_array[index] != -3333;
if (tls_signed_char != -5 || tls_unsigned_char != 250
|| tls_signed_short != -1234 || tls_unsigned_short != 60000
|| tls_array[1] != 2000 || tls_array[index] != -3000)
return 1;
#if defined __riscv && __riscv_xlen == 64
if (tls_large.value != 123456)
return 1;
#endif
return 0;
}
static int check(void)
{
@ -40,9 +80,18 @@ static int check(void)
static void *thread_func(void *arg)
{
(void)arg;
tls_address_type *main_addresses = arg;
int *init_address = &tls_init;
signed short *element_address = &tls_array[1];
volatile int index = 2;
long errors = check();
if (init_address == main_addresses->init
|| element_address == main_addresses->element
|| *init_address != 44 || *element_address != 2000
|| tls_array[index] != -3000 || check_extra(0))
errors = 1;
tls_char = 10;
tls_short = 20;
tls_init = 30;
@ -56,6 +105,12 @@ static void *thread_func(void *arg)
tls_struct.tls_d = 100;
tls_struct.tls_e = 110;
tls_struct.tls_f = 120;
*element_address = 2222;
tls_array[index] = -3333;
tls_signed_char = -7;
tls_unsigned_char = 42;
tls_signed_short = -2222;
tls_unsigned_short = 12345;
CHECK(tls_char, "%d", 10);
CHECK(tls_short, "%d", 20);
@ -71,6 +126,10 @@ static void *thread_func(void *arg)
CHECK(tls_struct.tls_e, "%d", 110);
CHECK(tls_struct.tls_f, "%d", 120);
if (*init_address != 30 || *element_address != 2222
|| tls_array[index] != -3333 || check_extra(1))
errors = 1;
return (void *)errors;
}
@ -79,9 +138,20 @@ int main()
pthread_t t;
void *ret;
int errors = check();
tls_address_type addresses = { &tls_init, &tls_array[1] };
pthread_create(&t, NULL, thread_func, NULL);
if (*addresses.init != 44 || *addresses.element != 2000
|| check_extra(0))
errors = 1;
pthread_create(&t, NULL, thread_func, &addresses);
pthread_join(t, &ret);
return (ret ? 1 : 0) | errors | check();
errors |= (ret ? 1 : 0) | check();
if (*addresses.init != 44 || *addresses.element != 2000
|| check_extra(0))
errors = 1;
printf("errors: %d\n", errors);
return errors;
}

View File

@ -50,3 +50,4 @@
50
51
52
errors: 0

View File

@ -0,0 +1,2 @@
__thread signed short extern_tls_short = 1234;
__thread float extern_tls_float = 1.5f;

View File

@ -0,0 +1,22 @@
#include <stdio.h>
extern __thread signed short extern_tls_short;
extern __thread float extern_tls_float;
int main(void)
{
int errors = 0;
if (extern_tls_short != 1234 || extern_tls_float != 1.5f)
errors = 1;
extern_tls_short = -2222;
extern_tls_float = 2.5f;
extern_tls_float += 1.0f;
if (extern_tls_short != -2222 || extern_tls_float != 3.5f)
errors = 1;
printf("extern tls errors: %d\n", errors);
return errors;
}

View File

@ -0,0 +1 @@
extern tls errors: 0

View File

@ -15,6 +15,9 @@ endif
ifeq (,$(filter i386,$(ARCH)))
SKIP += 98_al_ax_extend.test 99_fastcall.test
endif
ifeq (,$(filter x86_64 riscv64 arm64 i386,$(ARCH)))
SKIP += 146_tls_extern.test # TLS only implemented on these architectures
endif
ifeq (,$(filter i386 x86_64,$(ARCH)))
SKIP += 85_asm-outside-function.test # x86 asm
SKIP += 127_asm_goto.test # hardcodes x86 asm
@ -39,6 +42,9 @@ endif
ifeq (-$(findstring gcc,$(CC))-,--)
SKIP += $(patsubst %.expect,%.test,$(GEN-ALWAYS))
endif
ifeq ($(CC_NAME),tcc)
SKIP += 146_tls_extern.test # needs an external system linker
endif
ifeq (-$(CONFIG_WIN32)-$(CONFIG_i386)$(CONFIG_arm)-,--yes-)
SKIP += 95_bitfields%.test # type_align is different on 32bit-non-windows
endif
@ -47,19 +53,22 @@ ifeq (-$(CONFIG_WIN32)-,-yes-)
SKIP += 114_bound_signal.test # No pthread support
SKIP += 117_builtins.test # win32 port doesn't define __builtins
SKIP += 124_atomic_counter.test # No pthread support
SKIP += 144_tls.test # No pthread support
SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported
endif
ifneq (,$(filter OpenBSD FreeBSD NetBSD,$(TARGETOS)))
SKIP += 106_versym.test # no pthread_condattr_setpshared
SKIP += 114_bound_signal.test # libc problem signal/fork
SKIP += 116_bound_setjmp2.test # No TLS_FUNC/TLS_VAR in bcheck.c
SKIP += 144_tls.test # No tls support
SKIP += 144_tls.test 146_tls_extern.test # No tls support
endif
ifeq ($(TARGETOS),DragonFly)
SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported
endif
ifeq ($(CONFIG_OSX),yes)
SKIP += 144_tls.test # TLS runtime not supported on Mach-O
SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported on Mach-O
endif
ifeq (-$(ARCH)-,-arm-)
SKIP += 144_tls.test # doesn't work currently
SKIP += 144_tls.test 146_tls_extern.test # doesn't work currently
endif
ifneq (-$(ARCH)-,-arm64-)
SKIP += 138_arm64_encoding.test
@ -122,6 +131,15 @@ GEN-ALWAYS =
144_tls.test: FLAGS += -pthread
144_tls.test: NORUN = true
# Verify that undefined TLS symbols in TCC objects work with the system linker.
146_tls_extern.test: NORUN = true
146_tls_extern.test: T1 = \
$(TCC) -c $1 -o 146_tls_extern-main.o && \
$(TCC) -c $(subst 146,146+,$1) -o 146_tls_extern-def.o && \
$(CC) -no-pie -Wl,-z,noexecstack 146_tls_extern-main.o \
146_tls_extern-def.o -o $(basename $@).exe && \
./$(basename $@).exe
112_backtrace.test: FLAGS += -dt -b
112_backtrace.test 113_btdll.test 126_bound_global.test: FILTER += \
-e 's;[0-9A-Fa-fx]\{5,\};........;g' \
@ -204,4 +222,4 @@ $(sort $(GEN-ALWAYS) $(UPDATE)) : force
force:
clean :
rm -f fred.txt *.output *.exe *.dll *.so *.def $(GEN-ALWAYS)
rm -f fred.txt *.output *.o *.exe *.dll *.so *.def $(GEN-ALWAYS)