commit fc78a61c4c6dd2a8a1047b5e5b62037e82b0dca0 (tree)
parent b9eefe17af2c30855f229f5214679d3766966b85
Author: Mick Sayson <mick@sayson.com>
Date: Mon, 15 Dec 2025 17:07:34 -0800
Prevent register clobbering on x86_64 threadlocal access
On the x86_64 self hosted backend, thread locals are accessed through
__tls_get_addr on PIC. Usually this goes through a fast path which does
not lose any registers, however in some cases (notably any dlopened
library on my machine) this can take a slow path which calls out to C
ABI functions
Catch this case and backup registers as necessary
Fix a few other ones while we're here. Credit to mlugg
Fixes #30183
Diffstat:
1 file changed, 32 insertions(+), 4 deletions(-)
diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig
@@ -173048,11 +173048,39 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
const ty_nav = air_datas[@intFromEnum(inst)].ty_nav;
const nav = ip.getNav(ty_nav.nav);
const is_threadlocal = zcu.comp.config.any_non_single_threaded and nav.isThreadlocal(ip);
- if (is_threadlocal) if (cg.target.ofmt == .coff or cg.mod.pic) {
- try cg.spillRegisters(&.{ .rdi, .rax });
- } else {
- try cg.spillRegisters(&.{.rax});
+
+ if (is_threadlocal) switch (cg.target.ofmt) {
+ .elf => if (cg.mod.pic) {
+ // LD model: `__tls_get_addr` uses the standard ABI
+ try cg.spillEflagsIfOccupied();
+ try cg.spillRegisters(abi.getCallerPreservedRegs(.x86_64_sysv));
+ } else {
+ // LE model: manual lowering uses these two registers
+ try cg.spillRegisters(&.{ .rdi, .rax });
+ },
+
+ .coff => {
+ // manual lowering uses these registers
+ try cg.spillRegisters(&.{ .rdi, .rax });
+ },
+
+ .macho => switch (cg.target.cpu.arch) {
+ .x86 => {
+ // `tlv_get_addr` returns in eax, clobbers ecx, preserves other GPRs
+ try cg.spillEflagsIfOccupied();
+ try cg.spillRegisters(&.{ .rax, .rcx });
+ },
+ .x86_64 => {
+ // `tlv_get_addr` returns in rax, preserves other GPRs
+ try cg.spillEflagsIfOccupied();
+ try cg.spillRegisters(&.{.rax});
+ },
+ else => unreachable,
+ },
+
+ else => unreachable,
};
+
var res = try cg.tempInit(.fromInterned(ty_nav.ty), .{ .lea_nav = ty_nav.nav });
if (is_threadlocal) while (try res.toRegClass(true, .general_purpose, cg)) {};
try res.finish(inst, &.{}, &.{}, cg);