Commit f34247c4bc

Jakub Konka <kubkon@jakubkonka.com>
2023-11-10 19:14:42
elf: lower TLS data into appropriate TLS section
1 parent 70d8baa
Changed files (4)
src/link/Elf/Object.zig
@@ -251,6 +251,7 @@ fn initOutputSection(self: Object, elf_file: *Elf, shdr: ElfShdr) error{OutOfMem
         .type = @"type",
         .flags = flags,
         .name = name,
+        .offset = std.math.maxInt(u32),
     });
     return out_shndx;
 }
src/link/Elf/ZigObject.zig
@@ -29,6 +29,9 @@ lazy_syms: LazySymbolTable = .{},
 /// Table of tracked Decls.
 decls: DeclTable = .{},
 
+/// TLS variables indexed by Atom.Index.
+tls_variables: TlsTable = .{},
+
 /// Table of unnamed constants associated with a parent `Decl`.
 /// We store them here so that we can free the constants whenever the `Decl`
 /// needs updating or is freed.
@@ -137,6 +140,11 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void {
         self.anon_decls.deinit(allocator);
     }
 
+    for (self.tls_variables.values()) |*tlv| {
+        tlv.deinit(allocator);
+    }
+    self.tls_variables.deinit(allocator);
+
     if (self.dwarf) |*dw| {
         dw.deinit();
     }
@@ -212,8 +220,6 @@ pub fn flushModule(self: *ZigObject, elf_file: *Elf) !void {
         self.saveDebugSectionsSizes(elf_file);
     }
 
-    try self.sortSymbols(elf_file);
-
     // The point of flushModule() is to commit changes, so in theory, nothing should
     // be dirty after this. However, it is possible for some things to remain
     // dirty because they fail to be written in the event of compile errors,
@@ -388,6 +394,19 @@ pub fn claimUnresolvedObject(self: ZigObject, elf_file: *Elf) void {
     }
 }
 
+pub fn allocateTlvAtoms(self: ZigObject, elf_file: *Elf) void {
+    for (self.tls_variables.keys(), self.tls_variables.values()) |atom_index, tlv| {
+        const atom = elf_file.atom(atom_index) orelse continue;
+        if (!atom.flags.alive) continue;
+        const local = elf_file.symbol(tlv.symbol_index);
+        const shdr = elf_file.shdrs.items[atom.output_section_index];
+        atom.value += shdr.sh_addr;
+        local.value += shdr.sh_addr;
+
+        // TODO exported TLS vars
+    }
+}
+
 pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void {
     for (self.atoms.items) |atom_index| {
         const atom = elf_file.atom(atom_index) orelse continue;
@@ -421,72 +440,6 @@ pub fn markLive(self: *ZigObject, elf_file: *Elf) void {
     }
 }
 
-fn sortSymbols(self: *ZigObject, elf_file: *Elf) error{OutOfMemory}!void {
-    _ = self;
-    _ = elf_file;
-    // const Entry = struct {
-    //     index: Symbol.Index,
-
-    //     const Ctx = struct {
-    //         zobj: ZigObject,
-    //         efile: *Elf,
-    //     };
-
-    //     pub fn lessThan(ctx: Ctx, lhs: @This(), rhs: @This()) bool {
-    //         const lhs_sym = ctx.efile.symbol(zobj.symbol(lhs.index));
-    //         const rhs_sym = ctx.efile.symbol(zobj.symbol(rhs.index));
-    //         if (lhs_sym.outputShndx() != null and rhs_sym.outputShndx() != null) {
-    //             if (lhs_sym.output_section_index == rhs_sym.output_section_index) {
-    //                 if (lhs_sym.value == rhs_sym.value) {
-    //                     return lhs_sym.name_offset < rhs_sym.name_offset;
-    //                 }
-    //                 return lhs_sym.value < rhs_sym.value;
-    //             }
-    //             return lhs_sym.output_section_index < rhs_sym.output_section_index;
-    //         }
-    //         if (lhs_sym.outputShndx() != null) {
-    //             if (rhs_sym.isAbs(ctx.efile)) return false;
-    //             return true;
-    //         }
-    //         return false;
-    //     }
-    // };
-
-    // const gpa = elf_file.base.allocator;
-
-    // {
-    //     const sorted = try gpa.alloc(Entry, self.local_symbols.items.len);
-    //     defer gpa.free(sorted);
-    //     for (0..self.local_symbols.items.len) |index| {
-    //         sorted[i] = .{ .index = @as(Symbol.Index, @intCast(index)) };
-    //     }
-    //     mem.sort(Entry, sorted, .{ .zobj = self, .efile = elf_file }, Entry.lessThan);
-
-    //     const backlinks = try gpa.alloc(Symbol.Index, sorted.len);
-    //     defer gpa.free(backlinks);
-    //     for (sorted, 0..) |entry, i| {
-    //         backlinks[entry.index] = @as(Symbol.Index, @intCast(i));
-    //     }
-
-    //     const local_symbols = try self.local_symbols.toOwnedSlice(gpa);
-    //     defer gpa.free(local_symbols);
-
-    //     try self.local_symbols.ensureTotalCapacityPrecise(gpa, local_symbols.len);
-    //     for (sorted) |entry| {
-    //         self.local_symbols.appendAssumeCapacity(local_symbols[entry.index]);
-    //     }
-
-    //     for (self.)
-    // }
-
-    // const sorted_globals = try gpa.alloc(Entry, self.global_symbols.items.len);
-    // defer gpa.free(sorted_globals);
-    // for (self.global_symbols.items, 0..) |index, i| {
-    //     sorted_globals[i] = .{ .index = index };
-    // }
-    // mem.sort(Entry, sorted_globals, elf_file, Entry.lessThan);
-}
-
 pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) error{OutOfMemory}!void {
     const gpa = elf_file.base.allocator;
 
@@ -583,6 +536,13 @@ pub fn codeAlloc(self: ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8
     const atom = elf_file.atom(atom_index).?;
     assert(atom.file_index == self.index);
     const shdr = &elf_file.shdrs.items[atom.outputShndx().?];
+
+    if (shdr.sh_flags & elf.SHF_TLS != 0) {
+        const tlv = self.tls_variables.get(atom_index).?;
+        const code = try gpa.dupe(u8, tlv.code);
+        return code;
+    }
+
     const file_offset = shdr.sh_offset + atom.value - shdr.sh_addr;
     const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
     const code = try gpa.alloc(u8, size);
@@ -765,15 +725,37 @@ pub fn getOrCreateMetadataForDecl(
     return gop.value_ptr.symbol_index;
 }
 
-fn getDeclShdrIndex(self: *ZigObject, elf_file: *Elf, decl_index: Module.Decl.Index, code: []const u8) u16 {
+fn getDeclShdrIndex(
+    self: *ZigObject,
+    elf_file: *Elf,
+    decl: *const Module.Decl,
+    code: []const u8,
+) error{OutOfMemory}!u16 {
     _ = self;
     const mod = elf_file.base.options.module.?;
-    const decl = mod.declPtr(decl_index);
+    const single_threaded = elf_file.base.options.single_threaded;
     const shdr_index = switch (decl.ty.zigTypeTag(mod)) {
-        // TODO: what if this is a function pointer?
         .Fn => elf_file.zig_text_section_index.?,
         else => blk: {
             if (decl.getOwnedVariable(mod)) |variable| {
+                if (variable.is_threadlocal and !single_threaded) {
+                    const is_all_zeroes = for (code) |byte| {
+                        if (byte != 0) break false;
+                    } else true;
+                    if (is_all_zeroes) break :blk elf_file.sectionByName(".tbss") orelse try elf_file.addSection(.{
+                        .type = elf.SHT_NOBITS,
+                        .flags = elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_TLS,
+                        .name = ".tbss",
+                        .offset = std.math.maxInt(u32),
+                    });
+
+                    break :blk elf_file.sectionByName(".tdata") orelse try elf_file.addSection(.{
+                        .type = elf.SHT_PROGBITS,
+                        .flags = elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_TLS,
+                        .name = ".tdata",
+                        .offset = std.math.maxInt(u32),
+                    });
+                }
                 if (variable.is_const) break :blk elf_file.zig_data_rel_ro_section_index.?;
                 if (variable.init.toValue().isUndefDeep(mod)) {
                     const mode = elf_file.base.options.optimize_mode;
@@ -799,6 +781,7 @@ fn updateDeclCode(
     elf_file: *Elf,
     decl_index: Module.Decl.Index,
     sym_index: Symbol.Index,
+    shdr_index: u16,
     code: []const u8,
     stt_bits: u8,
 ) !void {
@@ -815,7 +798,6 @@ fn updateDeclCode(
     const esym = &self.local_esyms.items(.elf_sym)[sym.esym_index];
     const atom_ptr = sym.atom(elf_file).?;
 
-    const shdr_index = self.getDeclShdrIndex(elf_file, decl_index, code);
     sym.output_section_index = shdr_index;
     atom_ptr.output_section_index = shdr_index;
 
@@ -893,6 +875,53 @@ fn updateDeclCode(
     }
 }
 
+fn updateTlv(
+    self: *ZigObject,
+    elf_file: *Elf,
+    decl_index: Module.Decl.Index,
+    sym_index: Symbol.Index,
+    shndx: u16,
+    code: []const u8,
+) !void {
+    const gpa = elf_file.base.allocator;
+    const mod = elf_file.base.options.module.?;
+    const decl = mod.declPtr(decl_index);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+
+    log.debug("updateTlv {s}{*}", .{ decl_name, decl });
+
+    const required_alignment = decl.getAlignment(mod);
+
+    const sym = elf_file.symbol(sym_index);
+    const esym = &self.local_esyms.items(.elf_sym)[sym.esym_index];
+    const atom_ptr = sym.atom(elf_file).?;
+
+    sym.output_section_index = shndx;
+    atom_ptr.output_section_index = shndx;
+
+    sym.name_offset = try self.strtab.insert(gpa, decl_name);
+    atom_ptr.flags.alive = true;
+    atom_ptr.name_offset = sym.name_offset;
+    esym.st_name = sym.name_offset;
+    esym.st_info |= elf.STT_TLS;
+    esym.st_size = code.len;
+
+    atom_ptr.alignment = required_alignment;
+    atom_ptr.size = code.len;
+
+    {
+        const gop = try self.tls_variables.getOrPut(gpa, atom_ptr.atom_index);
+        assert(!gop.found_existing); // TODO incremental updates
+        gop.value_ptr.* = .{ .symbol_index = sym_index, .code = try gpa.dupe(u8, code) };
+    }
+
+    {
+        const gop = try elf_file.output_sections.getOrPut(gpa, atom_ptr.output_section_index);
+        if (!gop.found_existing) gop.value_ptr.* = .{};
+        try gop.value_ptr.append(gpa, atom_ptr.atom_index);
+    }
+}
+
 pub fn updateFunc(
     self: *ZigObject,
     elf_file: *Elf,
@@ -947,7 +976,10 @@ pub fn updateFunc(
             return;
         },
     };
-    try self.updateDeclCode(elf_file, decl_index, sym_index, code, elf.STT_FUNC);
+
+    const shndx = try self.getDeclShdrIndex(elf_file, decl, code);
+    try self.updateDeclCode(elf_file, decl_index, sym_index, shndx, code, elf.STT_FUNC);
+
     if (decl_state) |*ds| {
         const sym = elf_file.symbol(sym_index);
         try self.dwarf.?.commitDeclState(
@@ -1026,7 +1058,12 @@ pub fn updateDecl(
         },
     };
 
-    try self.updateDeclCode(elf_file, decl_index, sym_index, code, elf.STT_OBJECT);
+    const shndx = try self.getDeclShdrIndex(elf_file, decl, code);
+    if (elf_file.shdrs.items[shndx].sh_flags & elf.SHF_TLS != 0)
+        try self.updateTlv(elf_file, decl_index, sym_index, shndx, code)
+    else
+        try self.updateDeclCode(elf_file, decl_index, sym_index, shndx, code, elf.STT_OBJECT);
+
     if (decl_state) |*ds| {
         const sym = elf_file.symbol(sym_index);
         try self.dwarf.?.commitDeclState(
@@ -1454,11 +1491,21 @@ const DeclMetadata = struct {
     }
 };
 
+const TlsVariable = struct {
+    symbol_index: Symbol.Index,
+    code: []const u8,
+
+    fn deinit(tlv: *TlsVariable, allocator: Allocator) void {
+        allocator.free(tlv.code);
+    }
+};
+
 const AtomList = std.ArrayListUnmanaged(Atom.Index);
 const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(Symbol.Index));
 const DeclTable = std.AutoHashMapUnmanaged(Module.Decl.Index, DeclMetadata);
 const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
 const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata);
+const TlsTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlsVariable);
 
 const assert = std.debug.assert;
 const builtin = @import("builtin");
src/link/Elf.zig
@@ -4512,7 +4512,10 @@ fn allocateAllocSections(self: *Elf) error{OutOfMemory}!void {
 
         for (cover.items) |shndx| {
             const shdr = &self.shdrs.items[shndx];
-            if (shdr.sh_type == elf.SHT_NOBITS) continue;
+            if (shdr.sh_type == elf.SHT_NOBITS) {
+                shdr.sh_offset = 0;
+                continue;
+            }
             off = alignment.@"align"(shndx, shdr.sh_addralign, off);
             shdr.sh_offset = off;
             off += shdr.sh_size;
@@ -4640,6 +4643,9 @@ fn allocateSpecialPhdrs(self: *Elf) void {
 }
 
 fn allocateAtoms(self: *Elf) void {
+    if (self.zigObjectPtr()) |zig_object| {
+        zig_object.allocateTlvAtoms(self);
+    }
     for (self.objects.items) |index| {
         self.file(index).?.object.allocateAtoms(self);
     }
@@ -4698,7 +4704,6 @@ fn writeAtoms(self: *Elf) !void {
             const atom_ptr = self.atom(atom_index).?;
             assert(atom_ptr.flags.alive);
 
-            const object = atom_ptr.file(self).?.object;
             const offset = math.cast(usize, atom_ptr.value - shdr.sh_addr - base_offset) orelse
                 return error.Overflow;
             const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow;
@@ -4707,7 +4712,11 @@ fn writeAtoms(self: *Elf) !void {
 
             // TODO decompress directly into provided buffer
             const out_code = buffer[offset..][0..size];
-            const in_code = try object.codeDecompressAlloc(self, atom_index);
+            const in_code = switch (atom_ptr.file(self).?) {
+                .object => |x| try x.codeDecompressAlloc(self, atom_index),
+                .zig_object => |x| try x.codeAlloc(self, atom_index),
+                else => unreachable,
+            };
             defer gpa.free(in_code);
             @memcpy(out_code, in_code);
 
src/target.zig
@@ -654,7 +654,7 @@ pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend
 
 pub fn supportsThreads(target: std.Target, backend: std.builtin.CompilerBackend) bool {
     return switch (backend) {
-        .stage2_x86_64 => target.ofmt == .macho,
+        .stage2_x86_64 => target.ofmt == .macho or target.ofmt == .elf,
         else => true,
     };
 }