diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index ef87253cec80f6fb526994bcca2570cb998789aa..e02ba39840497e2dec5d55ed1a7bb4a05b17ad01 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -14,7 +14,8 @@ const mem = std.mem; const elf = std.elf; const math = std.math; const assert = std.debug.assert; -const native_arch = @import("builtin").cpu.arch; +const builtin = @import("builtin"); +const native_arch = builtin.cpu.arch; const linux = std.os.linux; const page_size_min = std.heap.page_size_min; @@ -41,13 +42,13 @@ const Variant = enum { I_original, /// The modified Variant I: /// - /// --------------------------------------------------- - /// | DTV | Zig TCB | ABI TCB | [Offset] | TLS Blocks | - /// -------------------------------------^------------- - /// `-- The TP register points here. + /// -------------------------------------------- + /// | DTV | Zig TCB | ABI TCB | TLS Blocks | + /// ------------------------------^------------- + /// `-- The TP register points here (*inside* the TLS blocks). /// - /// The offset (which can be zero) is applied to the TP only; there is never a physical gap - /// between the ABI TCB and the TLS blocks. This implies that we only need to align the TP. + /// The offset from the start of the TLS blocks to the TP register is `current_tp_offset`. It + /// may be zero, in which case the TP register points to the start of the TLS blocks. /// /// The first (and only) word in the ABI TCB points to the DTV. I_modified, @@ -106,7 +107,7 @@ const current_variant: Variant = switch (native_arch) { else => @compileError("undefined TLS variant for this architecture"), }; -/// The Offset value for the modified Variant I. +/// The offset value for the modified Variant I. const current_tp_offset = switch (native_arch) { .m68k, .mips, @@ -379,6 +380,106 @@ pub fn setThreadPointer(addr: usize) void { } } +pub fn getThreadPointer() usize { + @setRuntimeSafety(false); + @disableInstrumentation(); + + return switch (native_arch) { + .aarch64, .aarch64_be => asm ( + \\ mrs %[ret], tpidr_el0 + : [ret] "=r" (-> usize), + ), + .alpha => asm ( + \\ rduniq + : [ret] "={$0}" (-> usize), + ), + .arc, .arceb => asm ( + \\ mov %[ret], r25 + : [ret] "=r" (-> usize), + ), + .arm, .armeb, .thumb, .thumbeb => asm ( + \\ mrc p15, 0, %[ret], c13, c0, 3 + : [ret] "=r" (-> usize), + ), + .csky => asm ( + \\ mov %[ret], r31 + : [ret] "=r" (-> usize), + ), + .hexagon => asm ( + \\ %[ret] = ugp + : [ret] "=r" (-> usize), + ), + .hppa => asm ( + \\ mfctl %%cr27, %[ret] + : [ret] "=r" (-> usize), + ), + .loongarch32, .loongarch64 => asm ( + \\ move %[ret], $tp + : [ret] "=r" (-> usize), + ), + .m68k => linux.syscall1(.get_thread_area), + .mips, .mipsel, .mips64, .mips64el => asm ( + \\ rdhwr %[ret], $29 + : [ret] "=r" (-> usize), + ), + .microblaze, .microblazeel => asm ( + \\ ori %[ret], r21, 0 + : [ret] "=r" (-> usize), + ), + .or1k => asm ( + \\ l.ori %[ret], r10, 0 + : [ret] "=r" (-> usize), + ), + .riscv32, .riscv64 => asm ( + \\ mv %[ret], tp + : [ret] "=r" (-> usize), + ), + .powerpc, .powerpcle => asm ( + \\ mr %[ret], 2 + : [ret] "=r" (-> usize), + ), + .powerpc64, .powerpc64le => asm ( + \\ mr %[ret], 13 + : [ret] "=r" (-> usize), + ), + .s390x => asm ( + \\ ear %[ret], %%a0 + \\ sllg %[ret], %[ret], 32 + \\ ear %[ret], %%a1 + : [ret] "=r" (-> usize), + ), + .sh, .sheb => asm ( + \\ stc %[ret], gbr + : [ret] "=r" (-> usize), + ), + .sparc, .sparc64 => asm ( + \\ mov %%g7, %[ret] + : [ret] "=r" (-> usize), + ), + .x86 => asm ( + \\ movl %%gs:0, %[ret] + : [ret] "=r" (-> usize), + ), + .x86_64 => switch (@sizeOf(usize)) { + 8 => asm ( + \\ movq %%fs:0, %[ret] + : [ret] "=r" (-> usize), + ), + // On x32, usize is 32 bits. + 4 => asm ( + \\ movl %%fs:0, %[ret] + : [ret] "=r" (-> usize), + ), + else => comptime unreachable, + }, + .xtensa, .xtensaeb => asm ( + \\ rur %[ret], threadptr + : [ret] "=r" (-> usize), + ), + else => @compileError("Unsupported architecture"), + }; +} + fn computeAreaDesc(phdrs: []elf.Phdr) void { @setRuntimeSafety(false); @disableInstrumentation(); @@ -616,3 +717,32 @@ inline fn mmap_tls(length: usize) usize { }); } } + +comptime { + assert(!builtin.link_libc); // otherwise libc should control TLS + + if (builtin.output_mode == .Exe and builtin.link_mode == .static) { + // This is a static executable without libc, so it is our job to provide the TLS accessor + // function for the GD and LD models. This function is unlikely to actually be used, since + // the linker should be able to relax every TLS access to the LE model and therefore + // eliminate all calls to this function, but that isn't guaranteed. + _ = struct { + const TlsIndex = switch (native_arch) { + .x86_64 => extern struct { module: u64, offset: u64 }, // Even for x32... + else => extern struct { module: usize, offset: usize }, // ...but not MIPS N32! + }; + export fn __tls_get_addr(ti: *const TlsIndex) *anyopaque { + assert(ti.module == 1); // The executable's module ID is always 1 + const tp = getThreadPointer(); + const block: [*]u8 = switch (current_variant) { + .I_original => @ptrFromInt(tp -% area_desc.abi_tcb.offset +% area_desc.block.offset), + .I_modified => @ptrFromInt(tp -% current_tp_offset), + // The `.I_original` approach would also work for `.II`, but there is an + // alternative strategy which is one less operation: + .II => @ptrFromInt(tp -% area_desc.block.size), + }; + return block[@intCast(ti.offset)..]; + } + }; + } +}