From 7cc50a4fb3f8184742abe7a6939ae920c648ebab Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 20 Oct 2026 23:39:09 +0100 Subject: [PATCH 1/6] Elf2: absorb `GotReloc` into `SymbolReloc` --- src/link/Elf2.zig | 966 ++++++++++++++++++++++------------------------ 1 file changed, 468 insertions(+), 498 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 9405cba58dac79010e8c10b37c5c83fac4866378..9f4fd73e49547a0084a8b47be1a721e06ba2716a 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -208,29 +208,24 @@ navs: std.array_hash_map.Auto(InternPool.Nav.Index, struct { lsi: Symbol.LocalIndex, /// The start index of the contiguous sequence of symbol relocations in this NAV. first_symbol_reloc: SymbolReloc.Index, - /// The start index of the contiguous sequence of GOT relocations in this NAV. - first_got_reloc: GotReloc.Index, }), uavs: std.array_hash_map.Auto(InternPool.Index, struct { lsi: Symbol.LocalIndex, /// The start index of the contiguous sequence of symbol relocations in this UAV. first_symbol_reloc: SymbolReloc.Index, - // No `first_got_reloc` field because a UAV never contains GOT relocations. }), lazy: std.EnumArray(link.File.LazySymbol.Kind, struct { map: std.array_hash_map.Auto(InternPool.Index, struct { lsi: Symbol.LocalIndex, /// The start index of the contiguous sequence of symbol relocations in this lazy code/data. first_symbol_reloc: SymbolReloc.Index, - /// The start index of the contiguous sequence of GOT relocations in this lazy code/data. - first_got_reloc: GotReloc.Index, }), pending_index: u32, }), pending_uavs: std.ArrayList(Node.UavMapIndex), symbol_relocs: std.ArrayList(SymbolReloc), node_relocs: std.ArrayList(NodeReloc), -got_relocs: std.ArrayList(GotReloc), +got_relocs: std.array_hash_map.Auto(SymbolReloc.Index, void), /// Set of relocations which must be re-applied if the size of the TLS segment changes. tls_size_symbol_relocs: std.array_hash_map.Auto(SymbolReloc.Index, void), /// Index matches the index into `shdrs`. Like `shdrs`, this map excludes `SHN_UNDEF`. @@ -388,9 +383,6 @@ const Node = union(enum) { fn firstSymbolReloc(nmi: NavMapIndex, elf: *const Elf) SymbolReloc.Index { return elf.navs.values()[@backingInt(nmi)].first_symbol_reloc; } - fn firstGotReloc(nmi: NavMapIndex, elf: *const Elf) GotReloc.Index { - return elf.navs.values()[@backingInt(nmi)].first_got_reloc; - } }; pub const UavMapIndex = enum(u32) { @@ -407,11 +399,6 @@ const Node = union(enum) { fn firstSymbolReloc(umi: UavMapIndex, elf: *const Elf) SymbolReloc.Index { return elf.uavs.values()[@backingInt(umi)].first_symbol_reloc; } - fn firstGotReloc(umi: UavMapIndex, elf: *const Elf) GotReloc.Index { - _ = umi; - _ = elf; - return .none; - } }; pub const LazyMapRef = struct { @@ -437,9 +424,6 @@ const Node = union(enum) { fn firstSymbolReloc(lmi: @This(), elf: *const Elf) SymbolReloc.Index { return elf.lazy.getPtrConst(kind).map.values()[@backingInt(lmi)].first_symbol_reloc; } - fn firstGotReloc(lmi: @This(), elf: *const Elf) GotReloc.Index { - return elf.lazy.getPtrConst(kind).map.values()[@backingInt(lmi)].first_got_reloc; - } }; } @@ -603,8 +587,6 @@ const InputSection = struct { node: MappedFile.Node.Index, /// The start index of the contiguous sequence of symbol relocations in this input section. first_symbol_reloc: SymbolReloc.Index, - /// The start index of the contiguous sequence of GOT relocations in this input section. - first_got_reloc: GotReloc.Index, const Index = enum(u32) { _, @@ -1387,14 +1369,29 @@ const SymbolReloc = struct { node: MappedFile.Node.Index.Optional, /// The offset of the relocation inside of `node`. offset: u64, - /// A symbol used to compute the relocated value. Precise meaning depends on `@"type"`. - target: Symbol.Id, - /// A signed constant used to compute the relocated value. Precise meaning depends on `@"type"`. + /// A symbol used to compute the relocated value. Precise meaning depends on `bits.@"type"`. + target_symbol: Symbol.Id, + /// A signed constant used to compute the relocated value. Precise meaning depends on `bits.@"type"`. addend: SymbolReloc.Addend, - /// Specifies how to apply the relocation. - /// - /// When emitting a relocatable, this field is `undefined`. - type: SymbolReloc.Type, + bits: packed struct(u32) { + /// Specifies how to apply the relocation. + /// + /// When emitting a relocatable, this field is `undefined`. + type: SymbolReloc.Type, + + result: enum(u3) { + ok, + overflowed, + misaligned, + illegal_dynamic_type, + illegal_dynamic_section, + }, + + /// ~~mystery bits~~ + /// what will MLUGG use them for? + /// nobody knows + _: u11 = 0, + }, /// Forms a linked list of all symbol relocations with the same `target`. This list exists so /// that all relocations targeting a particular symbol can be re-applied if that symbol moves. /// Doubly-linked so that relocations can be removed. @@ -1412,13 +1409,6 @@ const SymbolReloc = struct { /// relocation entry. The entry will be removed if we discover a definition which allows us to /// statically resolve the relocation. rela_index: Section.RelaIndex.Optional, - result: enum(u8) { - ok, - overflowed, - misaligned, - illegal_dynamic_type, - illegal_dynamic_section, - }, /// Determines the section in which this relocation will be placed if it is outstanding. /// @@ -1464,7 +1454,7 @@ const SymbolReloc = struct { /// relocations which write multiple sequences of bits or which need to do unusual arithmetic on /// a symbol value. The representation is simply a big enum containing all of these exceptional /// cases---see `Special`. This representation is in use when `Type.target == .special`. - const Type = packed struct(u16) { + const Type = packed struct(u18) { /// Helper function for constructing a "simple" relocation type. This mainly exists to /// improve readability in the relocation lowering logic in `addRelocAssumeCapacity`. fn simple(target: Target, action: Simple) SymbolReloc.Type { @@ -1493,7 +1483,7 @@ const SymbolReloc = struct { /// Otherwise (for "simple" relocations), `Target` indicates the first step in computing the /// relocation---whether we care about the target symbol's absolute address, its PC-relative /// address, its PLT entry, etc. - const Target = enum(u3) { + const Target = enum(u5) { /// This is a "special" relocation whose specific type is in the `action.special` field. special, @@ -1501,6 +1491,7 @@ const SymbolReloc = struct { abs, /// Offset from the relocation itself to the target symbol ("PC-relative"). rel, + /// Address of the target symbol's PLT entry. /// /// If the target symbol does not have a PLT entry, equivalent to `.abs`. @@ -1509,12 +1500,35 @@ const SymbolReloc = struct { /// /// If the target symbol does not have a PLT entry, equivalent to `.rel`. pltrel, + /// Offset of the target TLS symbol from the base of this DSO's own TLS region. dtpoff, /// Offset of the target TLS symbol from the raw thread pointer. tpoff, + /// Size of the target symbol. size, + + /// Address of the GOT entry with key `.{ .symbol = target_symbol }`. + gotabs_symbol, + /// Offset from the relocation to the GOT entry with key `.{ .symbol = target_symbol }`. + gotrel_symbol, + /// Offset from the GOT base to the GOT entry with key `.{ .symbol = target_symbol }`. + gotoff_symbol, + + /// Address of the GOT entry with key `.{ .tpoff = target_symbol }`. + gotabs_tpoff, + /// Offset from the relocation to the GOT entry with key `.{ .tpoff = target_symbol }`. + gotrel_tpoff, + /// Offset from the GOT base to the GOT entry with key `.{ .tpoff = target_symbol }`. + gotoff_tpoff, + + /// Address of the GOT entry with key `.{ .tlsgd0 = target_symbol }`. + gotabs_tlsgd, + /// Offset from the relocation to the GOT entry with key `.{ .tlsgd0 = target_symbol }`. + gotrel_tlsgd, + /// Offset from the GOT base to the GOT entry with key `.{ .tlsgd0 = target_symbol }`. + gotoff_tlsgd, }; /// For a "simple" relocation, after the initial value is computed according to `Target`, a @@ -1701,17 +1715,22 @@ const SymbolReloc = struct { larch_b21, larch_b26, larch_call36, + larch_got_pcala_hi20, + larch_got_pcala64_lo20, + larch_got_pcala64_hi12, sparc_olo10, sparc_hix22, sparc_le_hix22, sparc_wdisp10, sparc_wdisp16, + sparc_got_op_lox10, + sparc_got_op_hix22, fn applyInner( s: Special, elf: *Elf, - target: Symbol.Id, + target_sym: Symbol.Id, addend: SymbolReloc.Addend, dest_vaddr: u64, dest_slice: []u8, @@ -1719,7 +1738,7 @@ const SymbolReloc = struct { const normal_addend: u64 = @bitCast(addend.normal); switch (s) { .larch_pcala_hi20 => { - const val = target.value(elf) +% normal_addend; + const val = target_sym.value(elf) +% normal_addend; const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); elf.targetStore(inst, .{ .b0_4 = elf.targetLoad(inst).b0_4, @@ -1728,7 +1747,7 @@ const SymbolReloc = struct { }); }, .larch_pcala64_lo20 => { - const val = target.value(elf) +% normal_addend; + const val = target_sym.value(elf) +% normal_addend; const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); elf.targetStore(inst, .{ .b0_4 = elf.targetLoad(inst).b0_4, @@ -1737,7 +1756,7 @@ const SymbolReloc = struct { }); }, .larch_pcala64_hi12 => { - const val = target.value(elf) +% normal_addend; + const val = target_sym.value(elf) +% normal_addend; const inst: *align(1) link.loongarch.K12 = @ptrCast(dest_slice[0..4]); elf.targetStore(inst, .{ .b0_9 = elf.targetLoad(inst).b0_9, @@ -1746,7 +1765,7 @@ const SymbolReloc = struct { }); }, .larch_b21, .larch_b26, .larch_call36 => { - const target_vaddr: u64 = elf.pltEntryTargetAddr(target) orelse target.value(elf); + const target_vaddr: u64 = elf.pltEntryTargetAddr(target_sym) orelse target_sym.value(elf); const jump_offset: i64 = @bitCast(target_vaddr +% normal_addend -% dest_vaddr); if ((jump_offset >> 2) << 2 != jump_offset) { return error.RelocationMisaligned; @@ -1806,9 +1825,43 @@ const SymbolReloc = struct { } }, + .larch_got_pcala_hi20 => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .symbol = target_sym }).? * elf.targetPtrSize(); + const val = got_vaddr +% got_offset +% normal_addend; + const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); + elf.targetStore(inst, .{ + .b0_4 = elf.targetLoad(inst).b0_4, + .j20 = link.loongarch.pcalaHi20(val, dest_vaddr), + .b25_31 = elf.targetLoad(inst).b25_31, + }); + }, + .larch_got_pcala64_lo20 => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .symbol = target_sym }).? * elf.targetPtrSize(); + const val = got_vaddr +% got_offset +% normal_addend; + const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); + elf.targetStore(inst, .{ + .b0_4 = elf.targetLoad(inst).b0_4, + .j20 = link.loongarch.pcala64Lo20(val, dest_vaddr), + .b25_31 = elf.targetLoad(inst).b25_31, + }); + }, + .larch_got_pcala64_hi12 => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .symbol = target_sym }).? * elf.targetPtrSize(); + const val = got_vaddr +% got_offset +% normal_addend; + const inst: *align(1) link.loongarch.K12 = @ptrCast(dest_slice[0..4]); + elf.targetStore(inst, .{ + .b0_9 = elf.targetLoad(inst).b0_9, + .k12 = link.loongarch.pcala64Hi12(val, dest_vaddr), + .b22_31 = elf.targetLoad(inst).b22_31, + }); + }, + .sparc_olo10 => { const addends = addend.sparc_olo10; - const lo10: i64 = @as(u10, @truncate(target.value(elf) +% addends.a)); + const lo10: i64 = @as(u10, @truncate(target_sym.value(elf) +% addends.a)); const simm13 = std.math.cast(i13, lo10 + addends.o) orelse return error.RelocationOverflow; const dest_ptr: *align(1) packed struct(u32) { simm13: u13, @@ -1825,7 +1878,7 @@ const SymbolReloc = struct { b22_31: u10, } = @ptrCast(dest_slice); elf.targetStore(dest_ptr, .{ - .imm22 = @truncate(~(target.value(elf) +% normal_addend) >> 10), + .imm22 = @truncate(~(target_sym.value(elf) +% normal_addend) >> 10), .b22_31 = elf.targetLoad(dest_ptr).b22_31, }); }, @@ -1842,12 +1895,12 @@ const SymbolReloc = struct { b22_31: u10, } = @ptrCast(dest_slice); elf.targetStore(dest_ptr, .{ - .imm22 = @truncate(~(target.value(elf) +% normal_addend -% tls_size) >> 10), + .imm22 = @truncate(~(target_sym.value(elf) +% normal_addend -% tls_size) >> 10), .b22_31 = elf.targetLoad(dest_ptr).b22_31, }); }, .sparc_wdisp16, .sparc_wdisp10 => { - const target_vaddr: u64 = elf.pltEntryTargetAddr(target) orelse target.value(elf); + const target_vaddr: u64 = elf.pltEntryTargetAddr(target_sym) orelse target_sym.value(elf); const jump_offset: i64 = @bitCast(target_vaddr +% normal_addend -% dest_vaddr); if ((jump_offset >> 2) << 2 != jump_offset) { return error.RelocationMisaligned; @@ -1897,10 +1950,120 @@ const SymbolReloc = struct { else => unreachable, } }, + + .sparc_got_op_lox10 => { + const got_offset = elf.got.getIndex(.{ .symbol = target_sym }).? * elf.targetPtrSize(); + const dest_ptr: *align(1) packed struct(u32) { + imm13: u13, + b13_31: u19, + } = @ptrCast(dest_slice); + elf.targetStore(dest_ptr, .{ + .imm13 = @as(u10, @truncate(got_offset)), + .b13_31 = elf.targetLoad(dest_ptr).b13_31, + }); + }, + .sparc_got_op_hix22 => { + const got_offset = elf.got.getIndex(.{ .symbol = target_sym }).? * elf.targetPtrSize(); + const dest_ptr: *align(1) packed struct(u32) { + imm22: u22, + b22_31: u10, + } = @ptrCast(dest_slice); + elf.targetStore(dest_ptr, .{ + .imm22 = @truncate(got_offset >> 10), + .b22_31 = elf.targetLoad(dest_ptr).b22_31, + }); + }, } } }; + fn needsGotEntry(t: SymbolReloc.Type) ?enum { symbol, tpoff, tlsgd } { + return switch (t.target) { + .abs, + .rel, + .pltabs, + .pltrel, + .dtpoff, + .tpoff, + .size, + => null, + + .gotabs_symbol, .gotrel_symbol, .gotoff_symbol => .symbol, + .gotabs_tpoff, .gotrel_tpoff, .gotoff_tpoff => .tpoff, + .gotabs_tlsgd, .gotrel_tlsgd, .gotoff_tlsgd => .tlsgd, + + .special => switch (t.action.special) { + .larch_pcala_hi20, + .larch_pcala64_lo20, + .larch_pcala64_hi12, + .larch_b21, + .larch_b26, + .larch_call36, + .sparc_olo10, + .sparc_hix22, + .sparc_le_hix22, + .sparc_wdisp10, + .sparc_wdisp16, + => null, + + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, + .sparc_got_op_lox10, + .sparc_got_op_hix22, + => .symbol, + }, + }; + } + + fn dependsOnGotAddress(t: SymbolReloc.Type) bool { + return switch (t.target) { + .abs, + .rel, + .pltabs, + .pltrel, + .dtpoff, + .tpoff, + .size, + => false, + + .gotabs_symbol, + .gotrel_symbol, + .gotabs_tpoff, + .gotrel_tpoff, + .gotabs_tlsgd, + .gotrel_tlsgd, + => true, + + .gotoff_symbol, + .gotoff_tpoff, + .gotoff_tlsgd, + => false, + + .special => switch (t.action.special) { + .larch_pcala_hi20, + .larch_pcala64_lo20, + .larch_pcala64_hi12, + .larch_b21, + .larch_b26, + .larch_call36, + .sparc_olo10, + .sparc_hix22, + .sparc_le_hix22, + .sparc_wdisp10, + .sparc_wdisp16, + => false, + + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, + .sparc_got_op_lox10, + .sparc_got_op_hix22, + => true, + }, + }; + } + fn dependsOnTlsSize(t: SymbolReloc.Type, elf: *const Elf) bool { return switch (elf.targetTlsVariant()) { // In TLS variant I, the executable's TLS block starts at a fixed offset from the @@ -1917,6 +2080,15 @@ const SymbolReloc = struct { .pltrel, .dtpoff, .size, + .gotabs_symbol, + .gotrel_symbol, + .gotoff_symbol, + .gotabs_tpoff, + .gotrel_tpoff, + .gotoff_tpoff, + .gotabs_tlsgd, + .gotrel_tlsgd, + .gotoff_tlsgd, => false, .tpoff => true, @@ -1931,11 +2103,16 @@ const SymbolReloc = struct { .larch_b21, .larch_b26, .larch_call36, + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, .sparc_olo10, .sparc_hix22, .sparc_wdisp10, .sparc_wdisp16, + .sparc_got_op_lox10, + .sparc_got_op_hix22, => false, }, }, @@ -1954,7 +2131,7 @@ const SymbolReloc = struct { fn apply(reloc: *SymbolReloc, elf: *Elf) void { const node = reloc.node.unwrap() orelse return; // deleted - if (node.hasMoved(&elf.mf) or reloc.target.hasMoved(elf)) { + if (node.hasMoved(&elf.mf) or reloc.target_symbol.hasMoved(elf)) { // There's no point applying the relocation now, because it will be re-applied by // `flushMoved` at some point anyway. return; @@ -1966,7 +2143,7 @@ const SymbolReloc = struct { reloc.relaSection(elf).relaSetOffset(elf, rela_index, reloc_addr); return; } - switch (reloc.result) { + switch (reloc.bits.result) { .ok => {}, .overflowed => elf.overflowed_reloc_count -= 1, .misaligned => elf.misaligned_reloc_count -= 1, @@ -1977,14 +2154,14 @@ const SymbolReloc = struct { } if (reloc.applyInner(elf)) { @branchHint(.likely); - reloc.result = .ok; + reloc.bits.result = .ok; } else |err| switch (err) { error.RelocationOverflow => { - reloc.result = .overflowed; + reloc.bits.result = .overflowed; elf.overflowed_reloc_count += 1; }, error.RelocationMisaligned => { - reloc.result = .misaligned; + reloc.bits.result = .misaligned; elf.misaligned_reloc_count += 1; }, } @@ -1995,21 +2172,21 @@ const SymbolReloc = struct { const dest_slice = node.slice(&elf.mf)[@intCast(reloc.offset)..]; const addend: u64 = @bitCast(reloc.addend); - const target_val: u64 = type: switch (reloc.type.target) { - .abs => reloc.target.value(elf) +% addend, - .rel => reloc.target.value(elf) +% addend -% dest_vaddr, + const target_val: u64 = type: switch (reloc.bits.type.target) { + .abs => reloc.target_symbol.value(elf) +% addend, + .rel => reloc.target_symbol.value(elf) +% addend -% dest_vaddr, .pltabs => { - const plt_entry_addr = elf.pltEntryTargetAddr(reloc.target) orelse continue :type .abs; + const plt_entry_addr = elf.pltEntryTargetAddr(reloc.target_symbol) orelse continue :type .abs; break :type plt_entry_addr +% addend; }, .pltrel => { - const plt_entry_addr = elf.pltEntryTargetAddr(reloc.target) orelse continue :type .rel; + const plt_entry_addr = elf.pltEntryTargetAddr(reloc.target_symbol) orelse continue :type .rel; break :type plt_entry_addr +% addend -% dest_vaddr; }, - .dtpoff => reloc.target.value(elf) +% addend, + .dtpoff => reloc.target_symbol.value(elf) +% addend, .tpoff => switch (elf.targetTlsVariant()) { - .I_original => |tls| tls.tcb_size +% reloc.target.value(elf) +% addend, - .I_modified => |tls| 0 -% tls.tp_off +% reloc.target.value(elf) +% addend, + .I_original => |tls| tls.tcb_size +% reloc.target_symbol.value(elf) +% addend, + .I_modified => |tls| 0 -% tls.tp_off +% reloc.target_symbol.value(elf) +% addend, .II => { const tls_phndx = elf.getNode(elf.ni.tls.unwrap().?).segment; const tls_size: u64 = switch (elf.phdrSlice()) { @@ -2018,13 +2195,57 @@ const SymbolReloc = struct { break :tls_size elf.targetLoad(&phdr[tls_phndx].memsz); }, }; - break :type reloc.target.value(elf) +% addend -% tls_size; + break :type reloc.target_symbol.value(elf) +% addend -% tls_size; }, }, - .size => reloc.target.size(elf), - .special => return reloc.type.action.special.applyInner( + .size => reloc.target_symbol.size(elf), + + .gotabs_symbol => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .symbol = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend; + }, + .gotrel_symbol => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .symbol = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend -% dest_vaddr; + }, + .gotoff_symbol => { + const got_offset = elf.got.getIndex(.{ .symbol = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type got_offset +% addend; + }, + .gotabs_tpoff => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .tpoff = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend; + }, + .gotrel_tpoff => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .tpoff = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend -% dest_vaddr; + }, + .gotoff_tpoff => { + const got_offset = elf.got.getIndex(.{ .tpoff = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type got_offset +% addend; + }, + .gotabs_tlsgd => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .tlsgd0 = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend; + }, + .gotrel_tlsgd => { + const got_vaddr = elf.shndx.got.vaddr(elf); + const got_offset = elf.got.getIndex(.{ .tlsgd0 = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type (got_vaddr + got_offset) +% addend -% dest_vaddr; + }, + .gotoff_tlsgd => { + const got_offset = elf.got.getIndex(.{ .tlsgd0 = reloc.target_symbol }).? * elf.targetPtrSize(); + break :type got_offset +% addend; + }, + + .special => return reloc.bits.type.action.special.applyInner( elf, - reloc.target, + reloc.target_symbol, reloc.addend, dest_vaddr, dest_slice, @@ -2033,7 +2254,7 @@ const SymbolReloc = struct { // Check for the `R_*_RELATIVE` case now, because it is possible only when no shift or cast // is required, meaning we can handle it now and return early. - if (reloc.rela_index.unwrap()) |rela_index| switch (elf.classifySymbolValue(reloc.target)) { + if (reloc.rela_index.unwrap()) |rela_index| switch (elf.classifySymbolValue(reloc.target_symbol)) { .static => unreachable, .dynamic => { // The relocation happens at runtime; we just need to update its offset. @@ -2046,31 +2267,34 @@ const SymbolReloc = struct { // destination slice, we actually want to write it to the runtime relocation entry. switch (elf.identClass()) { .NONE, _ => unreachable, - .@"32" => assert(reloc.type.action.simple.dest == .@"32"), - .@"64" => assert(reloc.type.action.simple.dest == .@"64"), + .@"32" => assert(reloc.bits.type.action.simple.dest == .@"32"), + .@"64" => assert(reloc.bits.type.action.simple.dest == .@"64"), } - assert(reloc.type.action.simple.cast == .unsigned); - assert(reloc.type.action.simple.shift == .@"0"); + assert(reloc.bits.type.action.simple.cast == .unsigned); + assert(reloc.bits.type.action.simple.shift == .@"0"); elf.shndx.rela_dyn.relaSetOffset(elf, rela_index, dest_vaddr); elf.shndx.rela_dyn.relaSetAddend(elf, rela_index, target_val); return; }, }; - try reloc.type.action.simple.write(target_val, dest_slice, elf.targetEndian()); + try reloc.bits.type.action.simple.write(target_val, dest_slice, elf.targetEndian()); } fn delete(reloc: *SymbolReloc, elf: *Elf, index: SymbolReloc.Index) void { assert(index.get(elf) == reloc); reloc.deleteOutputRel(elf); - if (reloc.type.dependsOnTlsSize(elf)) { + if (reloc.bits.type.dependsOnTlsSize(elf)) { assert(elf.tls_size_symbol_relocs.swapRemove(index)); } + if (reloc.bits.type.dependsOnGotAddress()) { + assert(elf.got_relocs.swapRemove(index)); + } switch (reloc.prev) { .none => { - const first_target_reloc = &reloc.target.index(elf).ptr(elf).first_target_reloc; + const first_target_reloc = &reloc.target_symbol.index(elf).ptr(elf).first_target_reloc; assert(first_target_reloc.* == index); first_target_reloc.* = reloc.next; }, @@ -2080,7 +2304,7 @@ const SymbolReloc = struct { .none => {}, else => |next| next.get(elf).prev = reloc.prev, } - switch (reloc.result) { + switch (reloc.bits.result) { .ok => {}, .overflowed => elf.overflowed_reloc_count -= 1, .misaligned => elf.misaligned_reloc_count -= 1, @@ -2113,25 +2337,25 @@ const SymbolReloc = struct { const reloc_node = reloc.node.unwrap().?; // deleted // Delete any current output relocation or error. - switch (reloc.result) { + switch (reloc.bits.result) { .ok, .overflowed, .misaligned => { reloc.deleteOutputRel(elf); }, .illegal_dynamic_type => { elf.illegal_dynamic_type_reloc_count -= 1; - reloc.result = .ok; + reloc.bits.result = .ok; }, .illegal_dynamic_section => { elf.illegal_dynamic_section_reloc_count -= 1; - reloc.result = .ok; + reloc.bits.result = .ok; }, } assert(reloc.rela_index == .none); - const symbol_class = elf.classifySymbolValue(reloc.target); + const symbol_class = elf.classifySymbolValue(reloc.target_symbol); - switch (reloc.target.unwrap()) { + switch (reloc.target_symbol.unwrap()) { .local => {}, .global => |gsi| switch (symbol_class) { .static => gsi.ptr(elf).status.any_static_target_relocs = true, @@ -2152,25 +2376,36 @@ const SymbolReloc = struct { } = switch (symbol_class) { .static => return, - .static_relative => switch (reloc.type.target) { + .static_relative => switch (reloc.bits.type.target) { // We need a runtime relocation if the relocation depends on our load address, which // is the case iff the relocation resolves to the absolute value of a symbol. Relocs // which are PC-relative, or which resolve to something other than a symbol address // (e.g. a symbol's size), are fine. .pltrel, + .gotrel_symbol, + .gotoff_symbol, + .gotrel_tpoff, + .gotoff_tpoff, + .gotrel_tlsgd, + .gotoff_tlsgd, .rel, .size, .dtpoff, .tpoff, => return, - .abs, .pltabs => switch (reloc.type.action.simple.dest.isAddr(elf)) { + .abs, + .pltabs, + .gotabs_symbol, + .gotabs_tpoff, + .gotabs_tlsgd, + => switch (reloc.bits.type.action.simple.dest.isAddr(elf)) { true => .relative, false => .illegal, }, - .special => switch (reloc.type.action.special) { + .special => switch (reloc.bits.type.action.special) { .larch_pcala_hi20, .larch_pcala64_lo20, .larch_pcala64_hi12, @@ -2180,6 +2415,11 @@ const SymbolReloc = struct { .sparc_wdisp10, .sparc_wdisp16, .sparc_le_hix22, + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, + .sparc_got_op_lox10, + .sparc_got_op_hix22, => return, .sparc_olo10, @@ -2188,44 +2428,60 @@ const SymbolReloc = struct { }, }, - .dynamic => switch (reloc.type.target) { - // PLT relocations targeting dynamic symbols actually target that symbol's PLT - // entry, so we should emit an `R_*_RELATIVE` relocation instead. - .pltabs => switch (reloc.type.action.simple.dest.isAddr(elf)) { + .dynamic => switch (reloc.bits.type.target) { + // PLT/GOT relocations targeting dynamic symbols actually target that symbol's PLT + // or GOT entry, so we should emit an `R_*_RELATIVE` relocation instead. + .pltabs, + .gotabs_symbol, + .gotabs_tpoff, + .gotabs_tlsgd, + => switch (reloc.bits.type.action.simple.dest.isAddr(elf)) { true => .relative, false => .illegal, }, - // ...although PC-relative PLT relocations don't even need that that! - .pltrel => return, + // ...although PC-relative PLT/GOT relocations don't even need that that! + .pltrel, + .gotrel_symbol, + .gotoff_symbol, + .gotrel_tpoff, + .gotoff_tpoff, + .gotrel_tlsgd, + .gotoff_tlsgd, + => return, // PC-relative relocations are not valid dynamic relocations. .rel => .illegal, // On the few targets supporting size relocations, they are valid at runtime. - .size => switch (reloc.type.action.simple.dest) { + .size => switch (reloc.bits.type.action.simple.dest) { .@"32" => .size32, .@"64" => .size64, else => .illegal, }, // Absolute addresses and TLS offsets can be lowered at runtime provided they // are address-sized. - .dtpoff => switch (reloc.type.action.simple.dest.isAddr(elf)) { + .dtpoff => switch (reloc.bits.type.action.simple.dest.isAddr(elf)) { true => .dtp_off, false => .illegal, }, - .tpoff => switch (reloc.type.action.simple.dest.isAddr(elf)) { + .tpoff => switch (reloc.bits.type.action.simple.dest.isAddr(elf)) { true => .tp_off, false => .illegal, }, - .abs => switch (reloc.type.action.simple.dest.isAddr(elf)) { + .abs => switch (reloc.bits.type.action.simple.dest.isAddr(elf)) { true => .abs_addr, false => .illegal, }, - .special => switch (reloc.type.action.special) { + .special => switch (reloc.bits.type.action.special) { .larch_b21, .larch_b26, .larch_call36, .sparc_wdisp10, .sparc_wdisp16, + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, + .sparc_got_op_lox10, + .sparc_got_op_hix22, => return, .larch_pcala_hi20, @@ -2255,7 +2511,7 @@ const SymbolReloc = struct { .abs_addr, .dtp_off, .tp_off, - => switch (reloc.target.unwrap()) { + => switch (reloc.target_symbol.unwrap()) { .local => true, .global => |gsi| gsi.resolveAlias(elf).defined(elf), }, @@ -2267,7 +2523,7 @@ const SymbolReloc = struct { }; if (!allow) { // ...but in this case we actually do need to trigger an error. - reloc.result = .illegal_dynamic_section; + reloc.bits.result = .illegal_dynamic_section; elf.illegal_dynamic_section_reloc_count += 1; } return; @@ -2278,7 +2534,7 @@ const SymbolReloc = struct { const rel_type: MachineRelocType = rel: switch (want_dynamic_reloc) { .illegal => { - reloc.result = .illegal_dynamic_type; + reloc.bits.result = .illegal_dynamic_type; elf.illegal_dynamic_type_reloc_count += 1; return; }, @@ -2292,7 +2548,7 @@ const SymbolReloc = struct { const target_dynsym: u32 = switch (want_dynamic_reloc) { .illegal => unreachable, // handled above .relative => 0, - else => reloc.target.unwrap().global.dynsymIndex(elf).?, + else => reloc.target_symbol.unwrap().global.dynsymIndex(elf).?, }; try elf.shndx.rela_dyn.relaEnsureAdditionalCapacity(elf, 1); @@ -2435,227 +2691,20 @@ const GotKey = union(enum) { /// Value is the signed offset of the given symbol from the TLS pointer. tpoff: Symbol.Id, - /// Value is the TLS module ID of the DSO we are creating. + /// Value is the TLS module ID for the given STT_TLS symbol. Alternatively, it may be set to + /// `Symbol.Id.null` to resolve to the TLS module ID of this module (for TLSLD relocations). /// - /// Used for the first of the two GOT entries generated by a TLSLD relocation. - tlsld0, - /// Value is always 0. - /// - /// Used for the second of the two GOT entries generated by a TLSLD relocation. - tlsld1, - - /// Value is the TLS module ID for the given STT_TLS symbol. + /// Alternative /// /// Used for the first of the two GOT entries generated by a TLSGD relocation. tlsgd0: Symbol.Id, /// Value is the offset of the given STT_TLS symbol from the base of the per-module TLS area. + /// Alternatively, it may be set to `Symbol.Id.null` to resolve to 0 (for TLSLD relocations). /// /// Used for the second of the two GOT entries generated by a TLSGD relocation. tlsgd1: Symbol.Id, }; -/// A relocation targeting a particular GOT entry. -const GotReloc = struct { - /// The node containing this relocation. Possible values are: - /// * An input section - /// * A section - /// * A NAV, UAV, or lazy code/data - /// * `.none`, if this relocation was deleted (in which case it should be ignored) - node: MappedFile.Node.Index.Optional, - /// The offset of the relocation inside of `node`. - offset: u64, - target: GotKey, - addend: i64, - type: GotReloc.Type, - result: enum(u8) { ok, overflowed, misaligned }, - - /// `GotReloc.Type` has the same structure as `SymbolReloc.Type`, just with different `Target` - /// and `Special` enums---consult doc comments on `SymbolReloc.Type` for an overview. - const Type = packed struct(u16) { - fn simple(target: Target, action: Simple) GotReloc.Type { - assert(target != .special); - return .{ .target = target, .action = .{ .simple = action } }; - } - - fn special(s: Special) GotReloc.Type { - return .{ .target = .special, .action = .{ .special = s } }; - } - - target: Target, - action: packed union { - simple: Simple, - special: Special, - }, - - /// Like `SymbolReloc.Target`, but for GOT relocations. There are fewer tags because there - /// are fewer different kinds of GOT relocation. - const Target = enum(u3) { - /// This is a "special" relocation whose specific type is in the `action.special` field. - special, - - /// Absolute address of the GOT entry. - abs, - /// Offset from the relocation itself to the GOT entry ("PC-relative"). - rel, - /// Offset from the base of the GOT to the GOT entry. - offset, - }; - - const Simple = SymbolReloc.Type.Simple; - - /// Like `SymbolReloc.Special`, but for GOT relocations. - const Special = enum(u13) { - larch_pcala_hi20, - larch_pcala64_lo20, - larch_pcala64_hi12, - - sparc_op_lox10, - sparc_op_hix22, - - fn applyInner( - s: Special, - elf: *Elf, - got_vaddr: u64, - got_offset: u64, - addend: u64, - dest_vaddr: u64, - dest_slice: []u8, - ) error{ RelocationMisaligned, RelocationOverflow }!void { - switch (s) { - .larch_pcala_hi20 => { - const val = got_vaddr +% got_offset +% addend; - const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); - elf.targetStore(inst, .{ - .b0_4 = elf.targetLoad(inst).b0_4, - .j20 = link.loongarch.pcalaHi20(val, dest_vaddr), - .b25_31 = elf.targetLoad(inst).b25_31, - }); - }, - .larch_pcala64_lo20 => { - const val = got_vaddr +% got_offset +% addend; - const inst: *align(1) link.loongarch.J20 = @ptrCast(dest_slice[0..4]); - elf.targetStore(inst, .{ - .b0_4 = elf.targetLoad(inst).b0_4, - .j20 = link.loongarch.pcala64Lo20(val, dest_vaddr), - .b25_31 = elf.targetLoad(inst).b25_31, - }); - }, - .larch_pcala64_hi12 => { - const val = got_vaddr +% got_offset +% addend; - const inst: *align(1) link.loongarch.K12 = @ptrCast(dest_slice[0..4]); - elf.targetStore(inst, .{ - .b0_9 = elf.targetLoad(inst).b0_9, - .k12 = link.loongarch.pcala64Hi12(val, dest_vaddr), - .b22_31 = elf.targetLoad(inst).b22_31, - }); - }, - .sparc_op_lox10 => { - const dest_ptr: *align(1) packed struct(u32) { - imm13: u13, - b13_31: u19, - } = @ptrCast(dest_slice); - elf.targetStore(dest_ptr, .{ - .imm13 = @as(u10, @truncate(got_offset)), - .b13_31 = elf.targetLoad(dest_ptr).b13_31, - }); - }, - .sparc_op_hix22 => { - const dest_ptr: *align(1) packed struct(u32) { - imm22: u22, - b22_31: u10, - } = @ptrCast(dest_slice); - elf.targetStore(dest_ptr, .{ - .imm22 = @truncate(got_offset >> 10), - .b22_31 = elf.targetLoad(dest_ptr).b22_31, - }); - }, - } - } - }; - }; - - const Index = enum(u32) { - none = std.math.maxInt(u32), - _, - - fn get(index: GotReloc.Index, elf: *Elf) *GotReloc { - return &elf.got_relocs.items[@backingInt(index)]; - } - }; - - fn apply(reloc: *GotReloc, elf: *Elf) void { - assert(elf.ehdrType() != .REL); - const node = reloc.node.unwrap() orelse return; // deleted - if (node.hasMoved(&elf.mf) or elf.shndx.got.node(elf).hasMoved(&elf.mf)) { - // There's no point applying the relocation now, because it will be re-applied by - // `flushMoved` at some point anyway. - return; - } - switch (reloc.result) { - .ok => {}, - .overflowed => elf.overflowed_reloc_count -= 1, - .misaligned => elf.misaligned_reloc_count -= 1, - } - if (reloc.applyInner(elf)) { - @branchHint(.likely); - reloc.result = .ok; - } else |err| switch (err) { - error.RelocationOverflow => { - reloc.result = .overflowed; - elf.overflowed_reloc_count += 1; - }, - error.RelocationMisaligned => { - reloc.result = .misaligned; - elf.misaligned_reloc_count += 1; - }, - } - } - fn applyInner(reloc: *const GotReloc, elf: *Elf) error{ RelocationOverflow, RelocationMisaligned }!void { - const node = reloc.node.unwrap().?; - const dest_vaddr = elf.getNodeVAddr(node) + reloc.offset; - const dest_slice = node.slice(&elf.mf)[@intCast(reloc.offset)..]; - - const got_vaddr = elf.shndx.got.vaddr(elf); - const got_index: u64 = elf.got.getIndex(reloc.target).?; - const got_offset: u64 = switch (elf.identClass()) { - .NONE, _ => unreachable, - inline else => |class| @sizeOf(class.ElfN().Addr) * got_index, - }; - const addend: u64 = @bitCast(reloc.addend); - - const target_val: u64 = switch (reloc.type.target) { - .abs => got_vaddr +% got_offset +% addend, - .rel => got_vaddr +% got_offset +% addend -% dest_vaddr, - .offset => got_offset +% addend, - .special => return reloc.type.action.special.applyInner( - elf, - got_vaddr, - got_offset, - addend, - dest_vaddr, - dest_slice, - ), - }; - try reloc.type.action.simple.write(target_val, dest_slice, elf.targetEndian()); - } - - fn delete(reloc: *GotReloc, elf: *Elf) void { - switch (reloc.result) { - .ok => {}, - .overflowed => elf.overflowed_reloc_count -= 1, - .misaligned => elf.misaligned_reloc_count -= 1, - } - reloc.* = .{ - .node = .none, - .offset = undefined, - .target = undefined, - .addend = undefined, - .type = undefined, - .result = undefined, - }; - } -}; - /// Records all global symbols defined in any DSO we depend on. For each symbol, its name, version, /// type, size, and alignment are all stored, as well as the soname of the DSO defining it. If one /// of these symbols ends up being referenced by the ELF module we are producing, we need this @@ -3000,6 +3049,8 @@ const AddLocalSymbolOptions = struct { fn addLocalSymbol(elf: *Elf, opts: AddLocalSymbolOptions) Error!Symbol.LocalIndex { const gpa = elf.base.comp.gpa; + assert(opts.shndx != .UNDEF); + try elf.symtab.ensureUnusedCapacity(gpa, 1); try elf.changed_symtab_index.ensureUnusedCapacity(gpa, 1); try Section.Index.symtab.node(elf).ensureMinimumSize(gpa, &elf.mf, switch (elf.shdrPtr(.symtab)) { @@ -4293,7 +4344,7 @@ const Symbol = struct { var ri = sym_id.index(elf).ptr(elf).first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); - assert(reloc.target == sym_id); + assert(reloc.target_symbol == sym_id); try reloc.updateDynamicReloc(elf); ri = reloc.next; } @@ -4470,7 +4521,7 @@ const Symbol = struct { var ri = sym_id.index(elf).ptr(elf).first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); - assert(reloc.target == sym_id); + assert(reloc.target_symbol == sym_id); reloc.apply(elf); ri = reloc.next; } @@ -4563,7 +4614,7 @@ fn classifySymbolValue(elf: *Elf, sym: Symbol.Id) enum { const shndx: Section.Index = switch (resolved_sym.unwrap()) { .local => shndx: { - assert(symtab_shndx != .UNDEF); + assert(sym == Symbol.Id.null or symtab_shndx != .UNDEF); assert(visibility == .DEFAULT); break :shndx symtab_shndx; }, @@ -4683,7 +4734,6 @@ fn lazySymbolInner(elf: *Elf, lazy: link.File.LazySymbol) Error!link.File.Symbol .shndx = shndx, }), .first_symbol_reloc = .none, - .first_got_reloc = .none, }; elf.base.comp.link_prog_node.increaseEstimatedTotalItems(1); } @@ -6787,7 +6837,6 @@ pub fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void { skip_symbol_relocs: MappedFile.Node.Index.Optional = .none, first_node_reloc: ?*NodeReloc.Index = null, skip_node_relocs: MappedFile.Node.Index.Optional = .none, - first_got_reloc: ?*GotReloc.Index = null, } = switch (elf.getNode(ni)) { .deleted, .archive, @@ -6814,18 +6863,15 @@ pub fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void { => unreachable, // cannot contain relocs (.plt, .dynamic, and .eh_frame_hdr unsupported) .input_section => |isi| .{ .first_symbol_reloc = &elf.input_sections.items[@backingInt(isi)].first_symbol_reloc, - .first_got_reloc = &elf.input_sections.items[@backingInt(isi)].first_got_reloc, }, .nav => |nmi| .{ .first_symbol_reloc = &elf.navs.values()[@backingInt(nmi)].first_symbol_reloc, - .first_got_reloc = &elf.navs.values()[@backingInt(nmi)].first_got_reloc, }, .uav => |umi| .{ .first_symbol_reloc = &elf.uavs.values()[@backingInt(umi)].first_symbol_reloc, }, inline .lazy_code, .lazy_const_data => |lmi| .{ .first_symbol_reloc = &elf.lazy.getPtr(lmi.ref().kind).map.values()[lmi.ref().index].first_symbol_reloc, - .first_got_reloc = &elf.lazy.getPtr(lmi.ref().kind).map.values()[lmi.ref().index].first_got_reloc, }, .unit_debug_info_header => |ui| .{ .first_node_reloc = &elf.dwarf_units[@backingInt(ui)].debug_info_header_first_node_reloc, @@ -6893,19 +6939,6 @@ pub fn resetNodeRelocs(elf: *Elf, ni: MappedFile.Node.Index) void { } ptr.* = @fromBackingInt(@intCast(elf.node_relocs.items.len)); } - - if (opts.first_got_reloc) |ptr| { - if (ptr.* != .none) { - for (elf.got_relocs.items[@backingInt(ptr.*)..]) |*reloc| { - if (reloc.node != ni.toOptional()) { - if (reloc.node == .none) continue; - break; - } - reloc.delete(elf); - } - } - ptr.* = @fromBackingInt(@intCast(elf.got_relocs.items.len)); - } } /// Given that `node` has moved, updates all relocations in `node` as needed. In relocatables, this @@ -6918,7 +6951,6 @@ fn flushMovedNodeRelocs( skip_symbol_relocs: MappedFile.Node.Index.Optional = .none, first_node_reloc: NodeReloc.Index = .none, skip_node_relocs: MappedFile.Node.Index.Optional = .none, - first_got_reloc: GotReloc.Index = .none, }, ) void { if (opts.first_symbol_reloc != .none) { @@ -6942,16 +6974,6 @@ fn flushMovedNodeRelocs( reloc.apply(elf); } } - - if (opts.first_got_reloc != .none) { - for (elf.got_relocs.items[@backingInt(opts.first_got_reloc)..]) |*reloc| { - if (reloc.node != node.toOptional()) { - if (reloc.node == .none) continue; - break; - } - reloc.apply(elf); - } - } } fn identClass(elf: *const Elf) std.elf.CLASS { @@ -7496,7 +7518,6 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) Error!Node .shndx = shndx, }), .first_symbol_reloc = .none, - .first_got_reloc = .none, }; } return nmi; @@ -8064,7 +8085,6 @@ fn loadObject( .vaddr = 0, .node = ni, .first_symbol_reloc = .none, - .first_got_reloc = .none, }; elf.input_prog_node.increaseEstimatedTotalItems(1); } @@ -9422,7 +9442,6 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize) const gpa = elf.base.comp.gpa; try elf.symbol_relocs.ensureUnusedCapacity(gpa, len); try elf.node_relocs.ensureUnusedCapacity(gpa, len); - try elf.got_relocs.ensureUnusedCapacity(gpa, len); const class = elf.identClass(); switch (elf.ehdrType()) { .REL => { @@ -9460,7 +9479,6 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize) try shndx.get(elf).rela.shndx.relaEnsureAdditionalCapacity(elf, len); }, .EXEC, .DYN => { - try elf.tls_size_symbol_relocs.ensureUnusedCapacity(gpa, len); const new_got_entries = len * 2; // at worst, every reloc is a new TLSGD try elf.got.ensureUnusedCapacity(gpa, new_got_entries); const need_got_size = switch (class) { @@ -9506,13 +9524,12 @@ fn addRelocAssumeCapacity( elf.symbol_relocs.appendAssumeCapacity(.{ .node = node.toOptional(), .offset = offset, - .type = undefined, - .target = target, + .bits = undefined, + .target_symbol = target, .addend = .{ .normal = addend }, .next = next, .prev = .none, .rela_index = rela_index.toOptional(), - .result = .ok, }); ri.get(elf).apply(elf); }, @@ -9567,14 +9584,14 @@ fn addRelocAssumeCapacity( .TLS_LE64_LO20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.tpoff, .{ .dest = .@"32[24:5]", .cast = .trunc, .shift = .@"32" })), .TLS_LE64_HI12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.tpoff, .{ .dest = .@"32[21:10]", .cast = .unsigned, .shift = .@"52" })), - .GOT_PC_LO12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.abs, .{ .dest = .@"32[21:10]", .cast = .trunc, .shift = .@"0" })), - .GOT_PC_HI20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .special(.larch_pcala_hi20)), - .GOT64_PC_LO20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .special(.larch_pcala64_lo20)), - .GOT64_PC_HI12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .special(.larch_pcala64_hi12)), - .GOT_LO12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.abs, .{ .dest = .@"32[21:10]", .cast = .trunc, .shift = .@"0" })), - .GOT_HI20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.abs, .{ .dest = .@"32[24:5]", .cast = .trunc, .shift = .@"12" })), - .GOT64_LO20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.abs, .{ .dest = .@"32[24:5]", .cast = .trunc, .shift = .@"32" })), - .GOT64_HI12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.abs, .{ .dest = .@"32[21:10]", .cast = .unsigned, .shift = .@"52" })), + .GOT_PC_LO12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotabs_symbol, .{ .dest = .@"32[21:10]", .cast = .trunc, .shift = .@"0" })), + .GOT_PC_HI20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .special(.larch_got_pcala_hi20)), + .GOT64_PC_LO20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .special(.larch_got_pcala64_lo20)), + .GOT64_PC_HI12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .special(.larch_got_pcala64_hi12)), + .GOT_LO12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotabs_symbol, .{ .dest = .@"32[21:10]", .cast = .trunc, .shift = .@"0" })), + .GOT_HI20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotabs_symbol, .{ .dest = .@"32[24:5]", .cast = .trunc, .shift = .@"12" })), + .GOT64_LO20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotabs_symbol, .{ .dest = .@"32[24:5]", .cast = .trunc, .shift = .@"32" })), + .GOT64_HI12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotabs_symbol, .{ .dest = .@"32[21:10]", .cast = .unsigned, .shift = .@"52" })), // zig fmt: on }, .PPC64 => switch (@"type".PPC64) { @@ -9678,13 +9695,13 @@ fn addRelocAssumeCapacity( .TLS_DTPOFF32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.dtpoff, .{ .dest = .@"32", .cast = .unsigned, .shift = .@"0" })), .TLS_DTPOFF64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.dtpoff, .{ .dest = .@"64", .cast = .unsigned, .shift = .@"0" })), - .GOT13 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.offset, .{ .dest = .@"32[12:0]", .cast = .unsigned, .shift = .@"0" })), - .GOT22 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.offset, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), - .GOTDATA_OP_LOX10 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .special(.sparc_op_lox10)), - .GOTDATA_OP_HIX22 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .special(.sparc_op_hix22)), - .TLS_GD_HI22 => elf.addGotRelocAssumeCapacity(node, offset, .{ .tlsgd0 = target }, addend, .simple(.offset, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), - .TLS_LDM_HI22 => elf.addGotRelocAssumeCapacity(node, offset, .tlsld0, addend, .simple(.offset, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), - .TLS_IE_HI22 => elf.addGotRelocAssumeCapacity(node, offset, .{ .tpoff = target }, addend, .simple(.offset, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), + .GOT13 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_symbol, .{ .dest = .@"32[12:0]", .cast = .unsigned, .shift = .@"0" })), + .GOT22 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_symbol, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), + .GOTDATA_OP_LOX10 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .special(.sparc_got_op_lox10)), + .GOTDATA_OP_HIX22 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .special(.sparc_got_op_hix22)), + .TLS_GD_HI22 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_tlsgd, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), + .TLS_LDM_HI22 => try elf.addSymbolRelocAssumeCapacity(node, offset, .null, addend, .simple(.gotoff_tlsgd, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), + .TLS_IE_HI22 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_tpoff, .{ .dest = .@"32[21:0]", .cast = .trunc, .shift = .@"10" })), // zig fmt: on .TLS_GD_CALL, .TLS_LDM_CALL => { @@ -9750,19 +9767,19 @@ fn addRelocAssumeCapacity( }, .GOT10 => { try elf.one_shot_fixups.append(elf.base.comp.gpa, .{ .node = node, .offset = offset, .action = .@"32[12:10] = 0b000" }); - elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.offset, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); + try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_symbol, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); }, .TLS_GD_LO10 => { try elf.one_shot_fixups.append(elf.base.comp.gpa, .{ .node = node, .offset = offset, .action = .@"32[12:10] = 0b000" }); - elf.addGotRelocAssumeCapacity(node, offset, .{ .tlsgd0 = target }, addend, .simple(.offset, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); + try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_tlsgd, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); }, .TLS_LDM_LO10 => { try elf.one_shot_fixups.append(elf.base.comp.gpa, .{ .node = node, .offset = offset, .action = .@"32[12:10] = 0b000" }); - elf.addGotRelocAssumeCapacity(node, offset, .tlsld0, addend, .simple(.offset, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); + try elf.addSymbolRelocAssumeCapacity(node, offset, .null, addend, .simple(.gotoff_tlsgd, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); }, .TLS_IE_LO10 => { try elf.one_shot_fixups.append(elf.base.comp.gpa, .{ .node = node, .offset = offset, .action = .@"32[12:10] = 0b000" }); - elf.addGotRelocAssumeCapacity(node, offset, .{ .tpoff = target }, addend, .simple(.offset, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); + try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_tpoff, .{ .dest = .@"32[9:0]", .cast = .trunc, .shift = .@"0" })); }, }, .X86_64 => rel_type: switch (@"type".X86_64) { @@ -9790,12 +9807,7 @@ fn addRelocAssumeCapacity( .GOTOFF64 => return error.UnimplementedRelocation, // offset of symbol from GOT base .PLTOFF64 => return error.UnimplementedRelocation, // offset of PLT entry from GOT base (yes, I know, the name is stupid) - // TODO: figure out how to do relaxations. Perhaps we want to remove a `GotReloc` - // and replace it with a `SymbolReloc` when a relaxation becomes possible, but we'd - // need to bear in mind whether incremental updates might make a relaxation - // impossible again or something like that. Relaxations seem kind of hostile to - // incremental compilation, so perhaps we just only support them in non-incremental - // compilations and just apply them in flush or something. + // TODO: figure out how to do relaxations // Relaxable versions of other relocations. Since we don't yet implement relaxation, // just use the handling for the non-relaxable versions. @@ -9831,13 +9843,13 @@ fn addRelocAssumeCapacity( .TPOFF32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.tpoff, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), .TPOFF64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.tpoff, .{ .dest = .@"64", .cast = .signed, .shift = .@"0" })), - .GOT32 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.offset, .{ .dest = .@"32", .cast = .unsigned, .shift = .@"0" })), - .GOT64 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.offset, .{ .dest = .@"64", .cast = .unsigned, .shift = .@"0" })), - .GOTPCREL => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.rel, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), - .GOTPCREL64 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .simple(.rel, .{ .dest = .@"64", .cast = .signed, .shift = .@"0" })), - .TLSGD => elf.addGotRelocAssumeCapacity(node, offset, .{ .tlsgd0 = target }, addend, .simple(.rel, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), - .TLSLD => elf.addGotRelocAssumeCapacity(node, offset, .tlsld0, addend, .simple(.rel, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), - .GOTTPOFF => elf.addGotRelocAssumeCapacity(node, offset, .{ .tpoff = target }, addend, .simple(.rel, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), + .GOT32 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_symbol, .{ .dest = .@"32", .cast = .unsigned, .shift = .@"0" })), + .GOT64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotoff_symbol, .{ .dest = .@"64", .cast = .unsigned, .shift = .@"0" })), + .GOTPCREL => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotrel_symbol, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), + .GOTPCREL64 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotrel_symbol, .{ .dest = .@"64", .cast = .signed, .shift = .@"0" })), + .TLSGD => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotrel_tlsgd, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), + .TLSLD => try elf.addSymbolRelocAssumeCapacity(node, offset, .null, addend, .simple(.gotrel_tlsgd, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), + .GOTTPOFF => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .simple(.gotrel_tpoff, .{ .dest = .@"32", .cast = .signed, .shift = .@"0" })), // zig fmt: on .GOTPC64 => { @@ -9872,6 +9884,8 @@ fn addSymbolRelocAssumeCapacityInner( ) Error!void { assert(elf.ehdrType() != .REL); + const gpa = elf.base.comp.gpa; + request_static_target: { if (elf.shndx.dynamic == .UNDEF) break :request_static_target; const gsi: Symbol.Global.Index = switch (target.unwrap()) { @@ -9894,6 +9908,15 @@ fn addSymbolRelocAssumeCapacityInner( .dtpoff, .tpoff, .size, + .gotabs_symbol, + .gotrel_symbol, + .gotoff_symbol, + .gotabs_tpoff, + .gotrel_tpoff, + .gotoff_tpoff, + .gotabs_tlsgd, + .gotrel_tlsgd, + .gotoff_tlsgd, => false, .special => switch (@"type".action.special) { @@ -9908,10 +9931,15 @@ fn addSymbolRelocAssumeCapacityInner( .larch_b21, .larch_b26, .larch_call36, + .larch_got_pcala_hi20, + .larch_got_pcala64_lo20, + .larch_got_pcala64_hi12, .sparc_le_hix22, .sparc_wdisp10, .sparc_wdisp16, + .sparc_got_op_lox10, + .sparc_got_op_hix22, => false, }, }; @@ -9923,6 +9951,42 @@ fn addSymbolRelocAssumeCapacityInner( } } + if (@"type".needsGotEntry()) |got_kind| { + const new_index: usize, const new_len: u32 = switch (got_kind) { + .symbol => new: { + const gop = try elf.got.getOrPut(gpa, .{ .symbol = target }); + if (gop.found_existing) break :new .{ 0, 0 }; + gop.value_ptr.* = .none; + break :new .{ gop.index, 1 }; + }, + .tpoff => new: { + const gop = try elf.got.getOrPut(gpa, .{ .tpoff = target }); + if (gop.found_existing) break :new .{ 0, 0 }; + gop.value_ptr.* = .none; + break :new .{ gop.index, 1 }; + }, + .tlsgd => new: { + try elf.got.ensureUnusedCapacity(gpa, 2); + const gop = elf.got.getOrPutAssumeCapacity(.{ .tlsgd0 = target }); + if (gop.found_existing) break :new .{ 0, 0 }; + gop.value_ptr.* = .none; + elf.got.putAssumeCapacityNoClobber(.{ .tlsgd1 = target }, .none); + break :new .{ gop.index, 1 }; + }, + }; + + switch (elf.shdrPtr(elf.shndx.got)) { + inline else => |got_shdr, class| { + const new_size = elf.targetLoad(&got_shdr.size) + new_len * @sizeOf(class.ElfN().Addr); + elf.targetStore(&got_shdr.size, new_size); + try elf.shndx.got.node(elf).ensureMinimumSize(gpa, &elf.mf, new_size); + }, + } + for (new_index..new_index + new_len) |got_index| { + elf.updateGotEntry(got_index); + } + } + const ri: SymbolReloc.Index = @fromBackingInt(@intCast(elf.symbol_relocs.items.len)); const first_target_reloc = &target.index(elf).ptr(elf).first_target_reloc; const next = first_target_reloc.*; @@ -9931,16 +9995,21 @@ fn addSymbolRelocAssumeCapacityInner( elf.symbol_relocs.appendAssumeCapacity(.{ .node = node.toOptional(), .offset = offset, - .target = target, + .target_symbol = target, .addend = addend, - .type = @"type", + .bits = .{ + .type = @"type", + .result = .ok, + }, .next = next, .prev = .none, .rela_index = .none, - .result = .ok, }); if (@"type".dependsOnTlsSize(elf)) { - elf.tls_size_symbol_relocs.putAssumeCapacityNoClobber(ri, {}); + try elf.tls_size_symbol_relocs.putNoClobber(gpa, ri, {}); + } + if (@"type".dependsOnGotAddress()) { + try elf.got_relocs.put(gpa, ri, {}); } try ri.get(elf).updateDynamicReloc(elf); @@ -10046,95 +10115,6 @@ fn addNodeRelocAssumeCapacity( }, } } -fn addGotRelocAssumeCapacity( - elf: *Elf, - node: MappedFile.Node.Index, - offset: u64, - target: GotKey, - addend: i64, - @"type": GotReloc.Type, -) void { - assert(elf.ehdrType() != .REL); - switch (elf.getNode(node)) { - .deleted, - .archive, - .archive_header, - .archive_input_member, - .archive_elf_member_header, - .elf, - .ehdr, - .shdr, - .segment, - .copied_global, - .debug_shared, - .debug_addr, - .eh_frame_footer, - .debug_str_offsets, - .unit_padding, - .unit_frame, - .unit_frame_cie, - .unit_debug_info, - .unit_debug_info_header, - .unit_debug_info_footer, - .unit_debug_line, - .unit_debug_line_header, - .unit_debug_rnglists, - .const_debug_info, - .global_debug_info, - .func_frame_fde, - .func_debug_info, - .func_debug_line, - .decl_debug_info, - => unreachable, // cannot contain relocs, - .section, - .section_manual_size, - .uav, - => unreachable, // cannot contain GOT relocs - .input_section, - .nav, - .lazy_code, - .lazy_const_data, - => {}, - } - - const gop = elf.got.getOrPutAssumeCapacity(target); - if (!gop.found_existing) { - gop.value_ptr.* = .none; - const maybe_next_key: ?GotKey = switch (target) { - .reserved => null, - .tpoff => null, - .symbol => null, - .tlsld0 => .tlsld1, - .tlsgd0 => |sym| .{ .tlsgd1 = sym }, - .tlsld1 => unreachable, - .tlsgd1 => unreachable, - }; - switch (elf.shdrPtr(elf.shndx.got)) { - inline else => |got_shdr, class| { - const Addr = class.ElfN().Addr; - const old_size = elf.targetLoad(&got_shdr.size); - const new_entry_count = @as(u32, 1) + @intFromBool(maybe_next_key != null); - elf.targetStore(&got_shdr.size, @intCast(old_size + @sizeOf(Addr) * new_entry_count)); - }, - } - if (maybe_next_key) |next_key| { - elf.got.putAssumeCapacityNoClobber(next_key, .none); - elf.updateGotEntry(gop.index); - elf.updateGotEntry(gop.index + 1); - } else { - elf.updateGotEntry(gop.index); - } - } - - elf.got_relocs.appendAssumeCapacity(.{ - .node = .wrap(node), - .offset = offset, - .target = target, - .addend = addend, - .type = @"type", - .result = .ok, - }); -} fn updateGotEntry(elf: *Elf, got_index: usize) void { assert(elf.ehdrType() != .REL); const entry_value: union(enum) { @@ -10193,15 +10173,6 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void { .addend = 0, } }, }, - .tlsgd1 => |sym| switch (elf.classifySymbolValue(sym)) { - .static => .{ .unsigned = sym.value(elf) }, - .static_relative => unreachable, // TLS variables should be in TLS sections, which do not return `.static_relative` - .dynamic => .{ .reloc = .{ - .type = .dtpOff(elf), - .dynsym_index = sym.unwrap().global.dynsymIndex(elf).?, - .addend = 0, - } }, - }, .tlsgd0 => |sym| switch (elf.base.comp.config.link_mode) { .static => val: { assert(elf.base.comp.config.output_mode == .Exe); // static libraries don't have GOTs @@ -10209,25 +10180,28 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void { }, .dynamic => .{ .reloc = .{ .type = .dtpMod(elf), - .dynsym_index = switch (elf.classifySymbolValue(sym)) { - .static, .static_relative => 0, - .dynamic => sym.unwrap().global.dynsymIndex(elf).?, + .dynsym_index = switch (sym) { + .null => 0, + else => switch (elf.classifySymbolValue(sym)) { + .static, .static_relative => 0, + .dynamic => sym.unwrap().global.dynsymIndex(elf).?, + }, }, .addend = 0, } }, }, - .tlsld0 => switch (elf.base.comp.config.link_mode) { - .static => val: { - assert(elf.base.comp.config.output_mode == .Exe); // static libraries don't have GOTs - break :val .{ .unsigned = 1 }; // TLS module ID for executable + .tlsgd1 => |sym| switch (sym) { + .null => .{ .unsigned = 0 }, + else => switch (elf.classifySymbolValue(sym)) { + .static => .{ .unsigned = sym.value(elf) }, + .static_relative => unreachable, // TLS variables should be in TLS sections, which do not return `.static_relative` + .dynamic => .{ .reloc = .{ + .type = .dtpOff(elf), + .dynsym_index = sym.unwrap().global.dynsymIndex(elf).?, + .addend = 0, + } }, }, - .dynamic => .{ .reloc = .{ - .type = .dtpMod(elf), - .dynsym_index = 0, - .addend = 0, - } }, }, - .tlsld1 => .{ .unsigned = 0 }, }; // First, write to the GOT itself. If we're planning to use a relocation, we'll just write zeroes. @@ -10994,7 +10968,7 @@ pub fn idle(elf: *Elf) link.Error!bool { var ri = symtab_index.ptr(elf).first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); - assert(reloc.target == sym_id); + assert(reloc.target_symbol == sym_id); // In relocatables, every symbol relocation has an output relocation. const rela_index = reloc.rela_index.unwrap().?; reloc.relaSection(elf).relaUpdateSym(elf, rela_index, @backingInt(symtab_index)); @@ -11024,7 +10998,7 @@ pub fn idle(elf: *Elf) link.Error!bool { var ri = symtab_index.ptr(elf).first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); - assert(reloc.target == sym_id); + assert(reloc.target_symbol == sym_id); // There may or may not be a runtime relocation for this symbol reloc. if (reloc.rela_index.unwrap()) |rela_index| { elf.shndx.rela_dyn.relaUpdateSym(elf, rela_index, dynsym_index); @@ -11615,8 +11589,8 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void const rela_index = opt_rela_index.unwrap() orelse continue; rela_dyn_shndx.relaAdjustOffset(elf, rela_index, old_addr, addr); } - for (elf.got_relocs.items) |*reloc| { - reloc.apply(elf); + for (elf.got_relocs.keys()) |ri| { + ri.get(elf).apply(elf); } } else if (shndx == elf.shndx.plt) { elf.flushMovedNodeRelocs(ni, .{ @@ -11683,7 +11657,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void elf.flushMovedNodeRelocs(ni, .{ .first_symbol_reloc = isi.ptrConst(elf).first_symbol_reloc, - .first_got_reloc = isi.ptrConst(elf).first_got_reloc, }); }, .copied_global => |global_name| { @@ -11714,7 +11687,6 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!void .none, .uav, .lazy_code, .lazy_const_data => .none, }, - .first_got_reloc = mi.firstGotReloc(elf), }); }, .debug_shared => |ss| { @@ -12200,8 +12172,6 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) std.mem.Allocator.Error!vo switch (got_key) { .reserved, .symbol, - .tlsld0, - .tlsld1, .tlsgd0, .tlsgd1, => { -- 2.54.0 From d74e9a786f96af052065c0d3a6c22ab38e9b697f Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 21 Oct 2026 13:07:43 +0100 Subject: [PATCH 2/6] Elf2: prepare to support simple relaxations --- src/link/Elf2.zig | 52 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 9f4fd73e49547a0084a8b47be1a721e06ba2716a..65e30f5771f3f25b3933b7614caaeeceaf9bc4c6 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1387,10 +1387,9 @@ const SymbolReloc = struct { illegal_dynamic_section, }, - /// ~~mystery bits~~ - /// what will MLUGG use them for? - /// nobody knows - _: u11 = 0, + relaxation: Relaxation, + + _: u10 = 0, }, /// Forms a linked list of all symbol relocations with the same `target`. This list exists so /// that all relocations targeting a particular symbol can be re-applied if that symbol moves. @@ -1439,6 +1438,19 @@ const SymbolReloc = struct { }, }; + const Relaxation = enum(u1) { + none, + }; + /// Updates `reloc.type` and `reloc.offset` as needed based on `reloc.bits.relaxation`. It is + /// possible that `reloc` is already relaxed, and may need to be un-relaxed due to a change in + /// the symbol definition. + fn processRelaxation(reloc: *SymbolReloc, elf: *Elf) void { + _ = elf; + switch (reloc.bits.relaxation) { + .none => return, + } + } + /// Instead of using the ELF relocation enums, we have our own internal representation for /// relocation types. This representation is more compact (requiring only 16 bits), and allows /// sharing a lot of relocation handling between multiple relocs and target architectures. @@ -2332,7 +2344,8 @@ const SymbolReloc = struct { reloc.rela_index = .none; } - fn updateDynamicReloc(reloc: *SymbolReloc, elf: *Elf) Error!void { + /// Called when the classification of the target symbol changes. + fn updateDynamicReloc(reloc: *SymbolReloc, elf: *Elf, ri: SymbolReloc.Index) Error!void { assert(elf.ehdrType() != .REL); const reloc_node = reloc.node.unwrap().?; // deleted @@ -2351,6 +2364,30 @@ const SymbolReloc = struct { }, } + { + const gpa = elf.base.comp.gpa; + + const old_depends_on_got_address = reloc.bits.type.dependsOnGotAddress(); + const old_depends_on_tls_size = reloc.bits.type.dependsOnTlsSize(elf); + + reloc.processRelaxation(elf); + + const new_depends_on_got_address = reloc.bits.type.dependsOnGotAddress(); + const new_depends_on_tls_size = reloc.bits.type.dependsOnTlsSize(elf); + + if (old_depends_on_got_address and !new_depends_on_got_address) { + assert(elf.got_relocs.swapRemove(ri)); + } else if (!old_depends_on_got_address and new_depends_on_got_address) { + try elf.got_relocs.putNoClobber(gpa, ri, {}); + } + + if (old_depends_on_tls_size and !new_depends_on_tls_size) { + assert(elf.tls_size_symbol_relocs.swapRemove(ri)); + } else if (!old_depends_on_tls_size and new_depends_on_tls_size) { + try elf.tls_size_symbol_relocs.putNoClobber(gpa, ri, {}); + } + } + assert(reloc.rela_index == .none); const symbol_class = elf.classifySymbolValue(reloc.target_symbol); @@ -4345,7 +4382,7 @@ const Symbol = struct { while (ri != .none) { const reloc = ri.get(elf); assert(reloc.target_symbol == sym_id); - try reloc.updateDynamicReloc(elf); + try reloc.updateDynamicReloc(elf, ri); ri = reloc.next; } @@ -10000,6 +10037,7 @@ fn addSymbolRelocAssumeCapacityInner( .bits = .{ .type = @"type", .result = .ok, + .relaxation = .none, }, .next = next, .prev = .none, @@ -10011,7 +10049,7 @@ fn addSymbolRelocAssumeCapacityInner( if (@"type".dependsOnGotAddress()) { try elf.got_relocs.put(gpa, ri, {}); } - try ri.get(elf).updateDynamicReloc(elf); + try ri.get(elf).updateDynamicReloc(elf, ri); // Actually apply the new relocation! ri.get(elf).apply(elf); -- 2.54.0 From 1cdcac604f5408f06689c6c9866aef355d1b2f72 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 23 Oct 2026 02:11:17 +0100 Subject: [PATCH 3/6] Elf2: fix a performance issue --- src/link/Elf2.zig | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 65e30f5771f3f25b3933b7614caaeeceaf9bc4c6..034a885793833c6ba0592a3953e4596fe73a5a32 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -8100,11 +8100,15 @@ fn loadObject( if (opts.node_fixed) ni: { const shndx_ni = opts.shndx.node(elf); const after_oni: MappedFile.Node.Index.Optional = after: { - const last_ni = shndx_ni.last(&elf.mf).unwrap() orelse break :after .none; - break :after switch (last_ni.position(&elf.mf)) { - .header => .wrap(last_ni), - .footer, .floating => .none, - }; + var node_it = shndx_ni.last(&elf.mf); + while (node_it.unwrap()) |cur_ni| { + switch (cur_ni.position(&elf.mf)) { + .header => break :after .wrap(cur_ni), // last header + .footer, .floating => node_it = cur_ni.prev(&elf.mf), + } + } else { + break :after .none; + } }; break :ni try shndx_ni.addHeaderChildAfter(gpa, &elf.mf, after_oni, add_node_opts); } else try opts.shndx.node(elf).addFloatingChild(gpa, &elf.mf, add_node_opts), -- 2.54.0 From 50752ce0f53961b300dee8dcd00ce7a96865add2 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 24 Oct 2026 13:34:12 +0100 Subject: [PATCH 4/6] Elf2: slightly refactor object processing, and add more tracy calls --- src/link/Elf2.zig | 922 ++++++++++++++++++++++------------------ src/link/MappedFile.zig | 34 ++ 2 files changed, 534 insertions(+), 422 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 034a885793833c6ba0592a3953e4596fe73a5a32..5feed708a15c714dfbbd5bfbb6818761348ed5e6 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -2349,6 +2349,9 @@ const SymbolReloc = struct { assert(elf.ehdrType() != .REL); const reloc_node = reloc.node.unwrap().?; // deleted + const trace = tracy.trace(@src()); + defer trace.end(); + // Delete any current output relocation or error. switch (reloc.bits.result) { .ok, .overflowed, .misaligned => { @@ -3086,6 +3089,10 @@ const AddLocalSymbolOptions = struct { fn addLocalSymbol(elf: *Elf, opts: AddLocalSymbolOptions) Error!Symbol.LocalIndex { const gpa = elf.base.comp.gpa; + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addText(opts.name); + assert(opts.shndx != .UNDEF); try elf.symtab.ensureUnusedCapacity(gpa, 1); @@ -3162,6 +3169,10 @@ fn addGlobalSymbol(elf: *Elf, opts: Symbol.Global.AddOptions) (error{ } || Error)!Symbol.Global.Index { _ = opts.lib_name; // TODO + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addText(opts.name); + log.debug("addGlobalSymbol({q})", .{opts.name}); const gpa = elf.base.comp.gpa; @@ -4347,6 +4358,9 @@ const Symbol = struct { /// Returns `true` if any relocations were updated, in which case relocations targeting /// `gsi` must be re-applied by the caller. fn updateDynamicTargetRelocs(gsi: Symbol.Global.Index, elf: *Elf) Error!bool { + const trace = tracy.trace(@src()); + defer trace.end(); + if (elf.shndx.dynamic == .UNDEF) return false; const sym_id: Symbol.Id = .global(gsi); @@ -4517,20 +4531,8 @@ const Symbol = struct { } fn flushMoved(sym_id: Symbol.Id, elf: *Elf, new_value: u64) void { - const trace = tracy.trace(@src()); - defer trace.end(); - const sym_index = sym_id.index(elf); - trace.addText(name: { - // There isn't a general `name` method on `Symbol.Id` because it would be incorrect - // to ever actually care about the name of a local symbol. - const name: String(.strtab) = switch (elf.symPtr(sym_index)) { - inline else => |sym| @fromBackingInt(elf.targetLoad(&sym.name)), - }; - break :name name.slice(elf); - }); - switch (elf.symPtr(sym_index)) { inline else => |sym| elf.targetStore(&sym.value, @intCast(new_value)), } @@ -4555,6 +4557,9 @@ const Symbol = struct { } fn applyTargetRelocs(sym_id: Symbol.Id, elf: *Elf) void { + const trace = tracy.trace(@src()); + defer trace.end(); + var ri = sym_id.index(elf).ptr(elf).first_target_reloc; while (ri != .none) { const reloc = ri.get(elf); @@ -7709,6 +7714,10 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void } } fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addTextFmt("{f}", .{path.fmtEscapeString()}); + const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -7788,6 +7797,10 @@ fn loadObject( fr: *Io.File.Reader, fl: MappedFile.Node.FileLocation, ) LoadParseInputError!void { + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addTextFmt("{f}{f}", .{ path.fmtEscapeString(), fmtMemberString(member) }); + const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -7939,443 +7952,492 @@ fn loadObject( defer gpa.free(shstrtab); try elf.nodes.ensureUnusedCapacity(gpa, ehdr.shnum - 1); try elf.input_sections.ensureUnusedCapacity(gpa, ehdr.shnum - 1); - for (sections[1..]) |*section| { - if (section.shdr.name >= shstrtab.len) continue; - const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); - if (!comp.config.any_unwind_tables and std.mem.eql(u8, name, ".eh_frame")) continue; - const sect_type = section.shdr.type; - const opts: struct { - shndx: Section.Index, - node_fixed: bool, - } = switch (sect_type) { - else => continue, - .PROGBITS, .NOBITS, .X86_64_UNWIND => opts: { - const shndx = elf.mapInputSection(.{ - .name = name, - .flags = section.shdr.flags.shf, - .entsize = section.shdr.entsize, - .type = section.shdr.type, - }) catch |err| switch (err) { - else => |e| return e, - error.StripSection => continue, - error.TlsSectionUnavailable => { - if (section.shdr.size == 0) continue; - return diags.failParse( + { + const sections_trace = tracy.traceNamed(@src(), "scan input sections"); + defer sections_trace.end(); + for (sections[1..]) |*section| { + const section_trace = tracy.traceNamed(@src(), "input section"); + defer section_trace.end(); + if (section.shdr.name >= shstrtab.len) continue; + const name = std.mem.sliceTo(shstrtab[section.shdr.name..], 0); + section_trace.addText(name); + if (!comp.config.any_unwind_tables and std.mem.eql(u8, name, ".eh_frame")) continue; + const sect_type = section.shdr.type; + const opts: struct { + shndx: Section.Index, + node_fixed: bool, + } = switch (sect_type) { + else => continue, + .PROGBITS, .NOBITS, .X86_64_UNWIND => opts: { + const shndx = elf.mapInputSection(.{ + .name = name, + .flags = section.shdr.flags.shf, + .entsize = section.shdr.entsize, + .type = section.shdr.type, + }) catch |err| switch (err) { + else => |e| return e, + error.StripSection => continue, + error.TlsSectionUnavailable => { + if (section.shdr.size == 0) continue; + + return diags.failParse( + path, + "thread-local storage section '{s}' is incompatible with '-fsingle-threaded'", + .{name}, + ); + }, + error.UnsupportedSectionFlags => if (!section.shdr.flags.shf.ALLOC) { + // It probably doesn't matter, just skip this section. + continue; + } else return diags.failParse( + path, + "unsupported flags for section '{s}'", + .{name}, + ), + error.SectionTypeConflict => if (!section.shdr.flags.shf.ALLOC) { + // It probably doesn't matter, just skip this section. + continue; + } else return diags.failParse( + path, + "type of section '{s}' conflicts with other inputs", + .{name}, + ), + error.SectionFlagsConflict => if (!section.shdr.flags.shf.ALLOC) { + // It probably doesn't matter, just skip this section. + continue; + } else return diags.failParse( + path, + "flags of section '{s}' conflict with other inputs", + .{name}, + ), + error.UnsupportedNobitsSection => return diags.failParse( + path, + "unsupported section type SHT_NOBITS for non-bss section '{s}'", + .{name}, + ), + }; + if (section.shdr.flags.shf.COMPRESSED) { + // SHF_COMPRESSED is only allowed on non-alloc sections. + if (section.shdr.flags.shf.ALLOC) return diags.failParse( path, - "thread-local storage section '{s}' is incompatible with '-fsingle-threaded'", + "section '{s}' has conflicting flags SHF_ALLOC and SHF_COMPRESSED", .{name}, ); - }, - error.UnsupportedSectionFlags => if (!section.shdr.flags.shf.ALLOC) { - // It probably doesn't matter, just skip this section. - continue; - } else return diags.failParse( - path, - "unsupported flags for section '{s}'", - .{name}, - ), - error.SectionTypeConflict => if (!section.shdr.flags.shf.ALLOC) { - // It probably doesn't matter, just skip this section. - continue; - } else return diags.failParse( - path, - "type of section '{s}' conflicts with other inputs", - .{name}, - ), - error.SectionFlagsConflict => if (!section.shdr.flags.shf.ALLOC) { - // It probably doesn't matter, just skip this section. + // TODO: handle compressed input sections. We'll need to set a flag to + // indicate that `flushInputSection` needs to decompress the section. + // But because this section isn't SHF_ALLOC, it's probably okay to just + // skip it for now. continue; - } else return diags.failParse( - path, - "flags of section '{s}' conflict with other inputs", - .{name}, - ), - error.UnsupportedNobitsSection => return diags.failParse( - path, - "unsupported section type SHT_NOBITS for non-bss section '{s}'", - .{name}, - ), + } + break :opts .{ + .shndx = shndx, + // For well-known sections, we know that it's fine to have e.g. random + // padding, so there's no need to make the sections fixed. For custom + // sections, however, we do want fixed nodes to avoid padding. + .node_fixed = shndx != .text and + shndx != .rodata and + shndx != .data and + shndx != .data_rel_ro and + shndx != elf.shndx.tdata, + }; + }, + inline .INIT_ARRAY, .FINI_ARRAY, .PREINIT_ARRAY => |@"type"| .{ + .shndx = shndx: { + // TODO: the input section name may include a "priority" value between 1 + // and 65535 which should affect the order we assemble input sections in + const init_fini_section_name: []const u8 = switch (@"type") { + .INIT_ARRAY => "init_array", + .FINI_ARRAY => "fini_array", + .PREINIT_ARRAY => "preinit_array", + else => comptime unreachable, + }; + const shndx: *Section.Index = &@field(elf.shndx, init_fini_section_name); + const need_addralign: u8 = switch (class) { + .NONE, _ => unreachable, + .@"32" => 4, + .@"64" => 8, + }; + if (section.shdr.addralign != need_addralign) { + return diags.failParse(path, "bad addralign on {t} shdr", .{@"type"}); + } + if (shndx.* == .UNDEF) { + try elf.createInitFiniArraySection(shndx, init_fini_section_name, @"type"); + } + switch (elf.shdrPtr(shndx.*)) { + inline else => |shdr| { + const old_size = elf.targetLoad(&shdr.size); + const new_size = old_size + section.shdr.size; + elf.targetStore(&shdr.size, @intCast(new_size)); + elf.updateInitFiniArraySectionSize(shndx.*, init_fini_section_name); + }, + } + break :shndx shndx.*; + }, + // This node must be fixed to prevent padding from being added between different + // INIT_ARRAY/FINI_ARRAY/PREINIT_ARRAY input sections. + .node_fixed = true, + }, + }; + const need_align: Alignment = .fromByteUnits( + std.math.ceilPowerOfTwoAssert(usize, @intCast(@max(section.shdr.addralign, 1))), + ); + + try opts.shndx.ensureAligned(elf, need_align); + + if (sect_type == .NOBITS) { + const offset: u64 = switch (elf.shdrPtr(opts.shndx)) { + inline else => |shdr| offset: { + // Bump our BSS output section size + const old_size: u64 = elf.targetLoad(&shdr.size); + const offset = need_align.forward(old_size); + elf.targetStore(&shdr.size, @intCast(offset + section.shdr.size)); + break :offset offset; + }, }; - if (section.shdr.flags.shf.COMPRESSED) { - // SHF_COMPRESSED is only allowed on non-alloc sections. - if (section.shdr.flags.shf.ALLOC) return diags.failParse( - path, - "section '{s}' has conflicting flags SHF_ALLOC and SHF_COMPRESSED", - .{name}, - ); - // TODO: handle compressed input sections. We'll need to set a flag to - // indicate that `flushInputSection` needs to decompress the section. - // But because this section isn't SHF_ALLOC, it's probably okay to just - // skip it for now. - continue; + + section.placement = .{ .nobits = .{ + .shndx = opts.shndx, + .offset = offset, + } }; + if (opts.shndx == elf.shndx.bss) { + input_has_bss = true; + } else if (opts.shndx == elf.shndx.tbss) { + input_has_tbss = true; + } else { + assert(elf.ehdrType() == .REL); } - break :opts .{ - .shndx = shndx, - // For well-known sections, we know that it's fine to have e.g. random - // padding, so there's no need to make the sections fixed. For custom - // sections, however, we do want fixed nodes to avoid padding. - .node_fixed = shndx != .text and - shndx != .rodata and - shndx != .data and - shndx != .data_rel_ro and - shndx != elf.shndx.tdata, - }; - }, - inline .INIT_ARRAY, .FINI_ARRAY, .PREINIT_ARRAY => |@"type"| .{ - .shndx = shndx: { - // TODO: the input section name may include a "priority" value between 1 - // and 65535 which should affect the order we assemble input sections in - const init_fini_section_name: []const u8 = switch (@"type") { - .INIT_ARRAY => "init_array", - .FINI_ARRAY => "fini_array", - .PREINIT_ARRAY => "preinit_array", - else => comptime unreachable, - }; - const shndx: *Section.Index = &@field(elf.shndx, init_fini_section_name); - const need_addralign: u8 = switch (class) { - .NONE, _ => unreachable, - .@"32" => 4, - .@"64" => 8, - }; - if (section.shdr.addralign != need_addralign) { - return diags.failParse(path, "bad addralign on {t} shdr", .{@"type"}); - } - if (shndx.* == .UNDEF) { - try elf.createInitFiniArraySection(shndx, init_fini_section_name, @"type"); - } - switch (elf.shdrPtr(shndx.*)) { - inline else => |shdr| { - const old_size = elf.targetLoad(&shdr.size); - const new_size = old_size + section.shdr.size; - elf.targetStore(&shdr.size, @intCast(new_size)); - elf.updateInitFiniArraySectionSize(shndx.*, init_fini_section_name); - }, - } - break :shndx shndx.*; - }, - // This node must be fixed to prevent padding from being added between different - // INIT_ARRAY/FINI_ARRAY/PREINIT_ARRAY input sections. - .node_fixed = true, - }, - }; - const need_align: Alignment = .fromByteUnits( - std.math.ceilPowerOfTwoAssert(usize, @intCast(@max(section.shdr.addralign, 1))), - ); - try opts.shndx.ensureAligned(elf, need_align); - - if (sect_type == .NOBITS) { - const offset: u64 = switch (elf.shdrPtr(opts.shndx)) { - inline else => |shdr| offset: { - // Bump our BSS output section size - const old_size: u64 = elf.targetLoad(&shdr.size); - const offset = need_align.forward(old_size); - elf.targetStore(&shdr.size, @intCast(offset + section.shdr.size)); - break :offset offset; - }, - }; - - section.placement = .{ .nobits = .{ - .shndx = opts.shndx, - .offset = offset, - } }; - if (opts.shndx == elf.shndx.bss) { - input_has_bss = true; - } else if (opts.shndx == elf.shndx.tbss) { - input_has_tbss = true; - } else { - assert(elf.ehdrType() == .REL); + continue; } - continue; + const add_node_opts: MappedFile.Node.AddOptions = .{ + .size = need_align.forward(section.shdr.size), + .alignment = need_align, + .moved = true, // see assert at end of `flushInputSection` + }; + const ni = elf.addNodeAssumeCapacity( + if (opts.node_fixed) ni: { + const shndx_ni = opts.shndx.node(elf); + const after_oni: MappedFile.Node.Index.Optional = after: { + var node_it = shndx_ni.last(&elf.mf); + while (node_it.unwrap()) |cur_ni| { + switch (cur_ni.position(&elf.mf)) { + .header => break :after .wrap(cur_ni), // last header + .footer, .floating => node_it = cur_ni.prev(&elf.mf), + } + } else { + break :after .none; + } + }; + break :ni try shndx_ni.addHeaderChildAfter(gpa, &elf.mf, after_oni, add_node_opts); + } else try opts.shndx.node(elf).addFloatingChild(gpa, &elf.mf, add_node_opts), + .{ .input_section = @fromBackingInt(@intCast(elf.input_sections.items.len)) }, + ); + section.placement = .{ .progbits = @fromBackingInt(@intCast(elf.input_sections.items.len)) }; + elf.input_sections.addOneAssumeCapacity().* = .{ + .input = input_index, + .file_location = .{ + .offset = fl.offset + section.shdr.offset, + .size = if (section.shdr.type == .NOBITS) 0 else section.shdr.size, + }, + // The section vaddr is initially 0, because the symbol addresses are + // zero-based. This will eventually be updated by `flushMoved`. + .vaddr = 0, + .node = ni, + .first_symbol_reloc = .none, + }; + elf.input_prog_node.increaseEstimatedTotalItems(1); } - - const add_node_opts: MappedFile.Node.AddOptions = .{ - .size = need_align.forward(section.shdr.size), - .alignment = need_align, - .moved = true, // see assert at end of `flushInputSection` - }; - const ni = elf.addNodeAssumeCapacity( - if (opts.node_fixed) ni: { - const shndx_ni = opts.shndx.node(elf); - const after_oni: MappedFile.Node.Index.Optional = after: { - var node_it = shndx_ni.last(&elf.mf); - while (node_it.unwrap()) |cur_ni| { - switch (cur_ni.position(&elf.mf)) { - .header => break :after .wrap(cur_ni), // last header - .footer, .floating => node_it = cur_ni.prev(&elf.mf), - } - } else { - break :after .none; - } - }; - break :ni try shndx_ni.addHeaderChildAfter(gpa, &elf.mf, after_oni, add_node_opts); - } else try opts.shndx.node(elf).addFloatingChild(gpa, &elf.mf, add_node_opts), - .{ .input_section = @fromBackingInt(@intCast(elf.input_sections.items.len)) }, - ); - section.placement = .{ .progbits = @fromBackingInt(@intCast(elf.input_sections.items.len)) }; - elf.input_sections.addOneAssumeCapacity().* = .{ - .input = input_index, - .file_location = .{ - .offset = fl.offset + section.shdr.offset, - .size = if (section.shdr.type == .NOBITS) 0 else section.shdr.size, - }, - // The section vaddr is initially 0, because the symbol addresses are - // zero-based. This will eventually be updated by `flushMoved`. - .vaddr = 0, - .node = ni, - .first_symbol_reloc = .none, - }; - elf.input_prog_node.increaseEstimatedTotalItems(1); } + + // This array will exclude the "null" symbol (symtab index 0). var symmap: std.ArrayList(Symbol.Id) = .empty; defer symmap.deinit(gpa); - for (sections[1..], 1..) |*symtab, symtab_shndx| switch (symtab.shdr.type) { - else => {}, - .SYMTAB => { - if (symtab.shdr.entsize < @sizeOf(ElfN.Sym)) - return diags.failParse(path, "unsupported symtab entsize", .{}); - const strtab = strtab: { - if (symtab.shdr.link == std.elf.SHN_UNDEF or symtab.shdr.link >= ehdr.shnum) - return diags.failParse(path, "missing symbol names", .{}); - const shdr = §ions[symtab.shdr.link].shdr; - if (shdr.type != .STRTAB) - return diags.failParse(path, "invalid strtab type", .{}); - const strtab = try gpa.alloc(u8, @intCast(shdr.size)); - errdefer gpa.free(strtab); - try fr.seekTo(fl.offset + shdr.offset); - try r.readSliceAll(strtab); - break :strtab strtab; + + const maybe_symtab_shndx: ?usize = for (sections[1..], 1..) |*symtab, symtab_shndx| { + if (symtab.shdr.type == .SYMTAB) break symtab_shndx; + } else null; + + if (maybe_symtab_shndx) |symtab_shndx| { + const symtab = §ions[symtab_shndx]; + + const symbols_trace = tracy.traceNamed(@src(), "scan input symbols"); + defer symbols_trace.end(); + + for (sections[symtab_shndx + 1 ..]) |*other_section| { + if (other_section.shdr.type == .SYMTAB) { + return diags.failParse(path, "multiple SHT_SYMTAB sections", .{}); + } + } + + if (symtab.shdr.entsize < @sizeOf(ElfN.Sym)) + return diags.failParse(path, "unsupported symtab entsize", .{}); + + const strtab = strtab: { + if (symtab.shdr.link == std.elf.SHN_UNDEF or symtab.shdr.link >= ehdr.shnum) + return diags.failParse(path, "missing symbol names", .{}); + const shdr = §ions[symtab.shdr.link].shdr; + if (shdr.type != .STRTAB) + return diags.failParse(path, "invalid strtab type", .{}); + const strtab = try gpa.alloc(u8, @intCast(shdr.size)); + errdefer gpa.free(strtab); + try fr.seekTo(fl.offset + shdr.offset); + try r.readSliceAll(strtab); + break :strtab strtab; + }; + defer gpa.free(strtab); + + const symnum = std.math.divExact( + u32, + @intCast(symtab.shdr.size), + @intCast(symtab.shdr.entsize), + ) catch return diags.failParse( + path, + "symtab section size (0x{x}) is not a multiple of entsize (0x{x})", + .{ symtab.shdr.size, symtab.shdr.entsize }, + ); + + try symmap.resize(gpa, symnum -| 1); // -1 to exclude "null" symbol + try fr.seekTo(fl.offset + symtab.shdr.offset + symtab.shdr.entsize); // +entsize to skip "null" symbol + for (symmap.items) |*si| { + si.* = .null; + const input_sym = try r.peekStruct(ElfN.Sym, target_endian); + try r.discardAll64(symtab.shdr.entsize); + if (input_sym.name >= strtab.len or input_sym.shndx >= ehdr.shnum) continue; + + const name = std.mem.sliceTo(strtab[input_sym.name..], 0); + + const symbol_trace = tracy.traceNamed(@src(), "input symbol"); + defer symbol_trace.end(); + symbol_trace.addText(name); + + const sym_type: std.elf.STT = switch (input_sym.info.type) { + .NOTYPE, .OBJECT, .FUNC, .TLS => |t| t, + .SECTION => .NOTYPE, + .FILE, .COMMON, _ => continue, }; - defer gpa.free(strtab); - const symnum = std.math.sub(u32, std.math.divExact( - u32, - @intCast(symtab.shdr.size), - @intCast(symtab.shdr.entsize), - ) catch return diags.failParse( - path, - "symtab section size (0x{x}) is not a multiple of entsize (0x{x})", - .{ symtab.shdr.size, symtab.shdr.entsize }, - ), 1) catch continue; - symmap.clearRetainingCapacity(); - try symmap.resize(gpa, symnum); - try fr.seekTo(fl.offset + symtab.shdr.offset + symtab.shdr.entsize); - for (symmap.items) |*si| { - si.* = .null; - const input_sym = try r.peekStruct(ElfN.Sym, target_endian); - try r.discardAll64(symtab.shdr.entsize); - if (input_sym.name >= strtab.len or input_sym.shndx >= ehdr.shnum) continue; - - const name = std.mem.sliceTo(strtab[input_sym.name..], 0); - - const sym_type: std.elf.STT = switch (input_sym.info.type) { - .NOTYPE, .OBJECT, .FUNC, .TLS => |t| t, - .SECTION => .NOTYPE, - .FILE, .COMMON, _ => continue, - }; - - if (input_sym.shndx == std.elf.SHN_UNDEF) switch (input_sym.info.bind) { - else => |bind| return diags.failParse( + + if (input_sym.shndx == std.elf.SHN_UNDEF) switch (input_sym.info.bind) { + else => |bind| return diags.failParse( + path, + "symbol '{s}' has unsupported binding (0x{x})", + .{ name, bind }, + ), + .LOCAL => continue, + .GLOBAL, .WEAK, .GNU_UNIQUE => |bind| { + si.* = .global(elf.addGlobalSymbol(.{ + .node = .none, + .name = name, + .value = input_sym.value, + .size = input_sym.size, + .type = sym_type, + .bind = switch (bind) { + .WEAK, .GNU_UNIQUE => .weak, + .GLOBAL => .strong, + else => unreachable, + }, + .visibility = input_sym.other.visibility, + .shndx = .UNDEF, + }) catch |err| switch (err) { + error.MultipleDefinitions => unreachable, // shndx is .UNDEF + error.MultipleDefaultVersions => unreachable, // shndx is .UNDEF + error.UndefinedDefaultVersion => return diags.failParse( + path, + "symbol '{s}' specifies default version without defining it", + .{name}, + ), + else => |e| return e, + }); + continue; + }, + }; + + const symbol_oni: MappedFile.Node.Index.Optional, const output_shndx: Section.Index, const symbol_value: u64 = switch (sections[input_sym.shndx].placement) { + .none => continue, + .progbits => |isi| .{ + .wrap(isi.node(elf)), + elf.getNodeShndx(isi.node(elf)), + input_sym.value, + }, + .nobits => |nobits| sym: { + const section_base: u64 = switch (elf.ehdrType()) { + .REL => 0, + .EXEC, .DYN => base: { + if (elf.ni.tls != .none and + nobits.shndx == elf.shndx.tbss) + { + break :base elf.tbssOffset(); + } + assert(nobits.shndx == elf.shndx.bss); + break :base nobits.shndx.vaddr(elf); + }, + }; + break :sym .{ + .none, + nobits.shndx, + section_base + nobits.offset + input_sym.value, + }; + }, + }; + + switch (input_sym.info.bind) { + else => |bind| return diags.failParse( + path, + "symbol '{s}' has unsupported binding (0x{x})", + .{ name, bind }, + ), + .LOCAL => { + const lsi = try elf.addLocalSymbol(.{ + .node = symbol_oni, + .name = name, + .value = symbol_value, + .size = input_sym.size, + .type = sym_type, + .shndx = output_shndx, + }); + si.* = .local(lsi); + }, + .GLOBAL, .WEAK, .GNU_UNIQUE => |bind| { + si.* = .global(elf.addGlobalSymbol(.{ + .node = symbol_oni, + .name = name, + .value = symbol_value, + .size = input_sym.size, + .type = sym_type, + .bind = switch (bind) { + .WEAK, .GNU_UNIQUE => .weak, + .GLOBAL => .strong, + else => unreachable, + }, + .visibility = input_sym.other.visibility, + .shndx = output_shndx, + }) catch |err| switch (err) { + error.MultipleDefinitions => return diags.failParse( + path, + "multiple definitions of '{s}'", + .{name}, + ), + error.MultipleDefaultVersions => return diags.failParse( + path, + "multiple default versions of '{s}'", + .{parseVersionedSymbolName(name).name}, + ), + error.UndefinedDefaultVersion => unreachable, // shndx is not `.UNDEF` (i.e. the symbol is defined) + else => |e| return e, + }); + }, + } + } + } + + { + const all_relocs_trace = tracy.traceNamed(@src(), "scan input relocations"); + defer all_relocs_trace.end(); + for (sections[1..]) |*rel_sec| switch (rel_sec.shdr.type) { + else => {}, + inline .REL, .RELA => |sht| { + const relocs_trace = tracy.traceNamed(@src(), "input relocation section"); + defer relocs_trace.end(); + + const link_is_symtab: bool = if (maybe_symtab_shndx) |symtab_shndx| s: { + break :s rel_sec.shdr.link == symtab_shndx; + } else false; + if (!link_is_symtab) { + return diags.failParse(path, "relocation section does not link to SHT_SYMTAB section", .{}); + } + if (rel_sec.shdr.info == std.elf.SHN_UNDEF or rel_sec.shdr.info >= ehdr.shnum) { + return diags.failParse( path, - "symbol '{s}' has unsupported binding (0x{x})", - .{ name, bind }, - ), - .LOCAL => continue, - .GLOBAL, .WEAK, .GNU_UNIQUE => |bind| { - si.* = .global(elf.addGlobalSymbol(.{ - .node = .none, - .name = name, - .value = input_sym.value, - .size = input_sym.size, - .type = sym_type, - .bind = switch (bind) { - .WEAK, .GNU_UNIQUE => .weak, - .GLOBAL => .strong, - else => unreachable, - }, - .visibility = input_sym.other.visibility, - .shndx = .UNDEF, - }) catch |err| switch (err) { - error.MultipleDefinitions => unreachable, // shndx is .UNDEF - error.MultipleDefaultVersions => unreachable, // shndx is .UNDEF - error.UndefinedDefaultVersion => return diags.failParse( - path, - "symbol '{s}' specifies default version without defining it", - .{name}, - ), - else => |e| return e, - }); - continue; - }, + "relocation section 'sh_info' refers to invalid section index '{d}'", + .{rel_sec.shdr.info}, + ); + } + const Rel = switch (sht) { + else => comptime unreachable, + .REL => ElfN.Rel, + .RELA => ElfN.Rela, }; + if (rel_sec.shdr.entsize < @sizeOf(Rel)) + return diags.failParse(path, "unsupported rel entsize", .{}); - const symbol_oni: MappedFile.Node.Index.Optional, const output_shndx: Section.Index, const symbol_value: u64 = switch (sections[input_sym.shndx].placement) { + const loc_sec = §ions[rel_sec.shdr.info]; + relocs_trace.addText(std.mem.sliceTo(shstrtab[loc_sec.shdr.name..], 0)); + const loc_node = switch (loc_sec.placement) { .none => continue, - .progbits => |isi| .{ - .wrap(isi.node(elf)), - elf.getNodeShndx(isi.node(elf)), - input_sym.value, - }, - .nobits => |nobits| sym: { - const section_base: u64 = switch (elf.ehdrType()) { - .REL => 0, - .EXEC, .DYN => base: { - if (elf.ni.tls != .none and - nobits.shndx == elf.shndx.tbss) - { - break :base elf.tbssOffset(); - } - assert(nobits.shndx == elf.shndx.bss); - break :base nobits.shndx.vaddr(elf); - }, - }; - break :sym .{ - .none, - nobits.shndx, - section_base + nobits.offset + input_sym.value, - }; - }, - }; - - switch (input_sym.info.bind) { - else => |bind| return diags.failParse( + .progbits => |isi| isi.node(elf), + .nobits => return diags.failParse( path, - "symbol '{s}' has unsupported binding (0x{x})", - .{ name, bind }, + "cannot apply relocation to SHT_NOBITS section '{s}'", + .{std.mem.sliceTo(shstrtab[loc_sec.shdr.name..], 0)}, ), - .LOCAL => { - const lsi = try elf.addLocalSymbol(.{ - .node = symbol_oni, - .name = name, - .value = symbol_value, - .size = input_sym.size, - .type = sym_type, - .shndx = output_shndx, - }); - si.* = .local(lsi); - }, - .GLOBAL, .WEAK, .GNU_UNIQUE => |bind| { - si.* = .global(elf.addGlobalSymbol(.{ - .node = symbol_oni, - .name = name, - .value = symbol_value, - .size = input_sym.size, - .type = sym_type, - .bind = switch (bind) { - .WEAK, .GNU_UNIQUE => .weak, - .GLOBAL => .strong, - else => unreachable, - }, - .visibility = input_sym.other.visibility, - .shndx = output_shndx, - }) catch |err| switch (err) { - error.MultipleDefinitions => return diags.failParse( - path, - "multiple definitions of '{s}'", - .{name}, - ), - error.MultipleDefaultVersions => return diags.failParse( - path, - "multiple default versions of '{s}'", - .{parseVersionedSymbolName(name).name}, - ), - error.UndefinedDefaultVersion => unreachable, // shndx is not `.UNDEF` (i.e. the symbol is defined) - else => |e| return e, - }); - }, - } - } - for (sections[1..]) |*rel_sec| switch (rel_sec.shdr.type) { - else => {}, - inline .REL, .RELA => |sht| { - if (rel_sec.shdr.link != symtab_shndx or rel_sec.shdr.info == std.elf.SHN_UNDEF or - rel_sec.shdr.info >= ehdr.shnum) continue; - const Rel = switch (sht) { - else => comptime unreachable, - .REL => ElfN.Rel, - .RELA => ElfN.Rela, - }; - if (rel_sec.shdr.entsize < @sizeOf(Rel)) - return diags.failParse(path, "unsupported rel entsize", .{}); + }; - const loc_sec = §ions[rel_sec.shdr.info]; - const loc_node = switch (loc_sec.placement) { - .none => continue, - .progbits => |isi| isi.node(elf), - .nobits => return diags.failParse( - path, - "cannot apply relocation to SHT_NOBITS section '{s}'", - .{std.mem.sliceTo(shstrtab[loc_sec.shdr.name..], 0)}, - ), - }; + elf.resetNodeRelocs(loc_node); - elf.resetNodeRelocs(loc_node); - - const relnum = std.math.divExact( - u32, - @intCast(rel_sec.shdr.size), - @intCast(rel_sec.shdr.entsize), - ) catch return diags.failParse( + const relnum = std.math.divExact( + u32, + @intCast(rel_sec.shdr.size), + @intCast(rel_sec.shdr.entsize), + ) catch return diags.failParse( + path, + "relocation section size (0x{x}) is not a multiple of entsize (0x{x})", + .{ rel_sec.shdr.size, rel_sec.shdr.entsize }, + ); + try elf.ensureUnusedRelocCapacity(loc_node, relnum); + try fr.seekTo(fl.offset + rel_sec.shdr.offset); + for (0..relnum) |_| { + const rel = try r.peekStruct(Rel, target_endian); + try r.discardAll64(rel_sec.shdr.entsize); + if (rel.info.sym == 0) continue; + if (rel.info.sym - 1 > symmap.items.len) return diags.failParse( path, - "relocation section size (0x{x}) is not a multiple of entsize (0x{x})", - .{ rel_sec.shdr.size, rel_sec.shdr.entsize }, + "relocation target symbol index {d} exceeds symtab size", + .{rel.info.sym}, ); - try elf.ensureUnusedRelocCapacity(loc_node, relnum); - try fr.seekTo(fl.offset + rel_sec.shdr.offset); - for (0..relnum) |_| { - const rel = try r.peekStruct(Rel, target_endian); - try r.discardAll64(rel_sec.shdr.entsize); - if (rel.info.sym == 0) continue; - if (rel.info.sym > symnum) return diags.failParse( - path, - "relocation target symbol index {d} exceeds symtab size", - .{rel.info.sym}, - ); - const target = symmap.items[rel.info.sym - 1]; - if (target == Symbol.Id.null) { - // If this is not an SHF_ALLOC section, then let's not report - // this for now, because it probably doesn't affect the final - // binary's functionality for this section to be a bit broken. - if (loc_sec.shdr.flags.shf.ALLOC) { - diags.addParseError( - path, - "unsupported symbol at index {d} required for relocation", - .{rel.info.sym}, - ); - } - continue; + const target = symmap.items[rel.info.sym - 1]; + if (target == Symbol.Id.null) { + // If this is not an SHF_ALLOC section, then let's not report + // this for now, because it probably doesn't affect the final + // binary's functionality for this section to be a bit broken. + if (loc_sec.shdr.flags.shf.ALLOC) { + diags.addParseError( + path, + "unsupported symbol at index {d} required for relocation", + .{rel.info.sym}, + ); } - const rt: MachineRelocType = .wrap(rel.info.type, elf); - elf.addRelocAssumeCapacity( - loc_node, - rel.offset - loc_sec.shdr.addr, - target, - rel.addend, - rt, - ) catch |err| switch (err) { - else => |e| return e, - error.UnknownRelocation => diags.addParseError( - path, - "unknown relocation type '{f}'", - .{rt.fmt(elf)}, - ), - error.NonStaticRelocation => diags.addParseError( - path, - "non-static relocation type '{f}'", - .{rt.fmt(elf)}, - ), - error.UnimplementedRelocation => diags.addParseError( - path, - "TODO(Elf2): unimplemented relocation type '{f}'", - .{rt.fmt(elf)}, - ), - }; + continue; } - }, - }; - }, - }; + const rt: MachineRelocType = .wrap(rel.info.type, elf); + elf.addRelocAssumeCapacity( + loc_node, + rel.offset - loc_sec.shdr.addr, + target, + rel.addend, + rt, + ) catch |err| switch (err) { + else => |e| return e, + error.UnknownRelocation => diags.addParseError( + path, + "unknown relocation type '{f}'", + .{rt.fmt(elf)}, + ), + error.NonStaticRelocation => diags.addParseError( + path, + "non-static relocation type '{f}'", + .{rt.fmt(elf)}, + ), + error.UnimplementedRelocation => diags.addParseError( + path, + "TODO(Elf2): unimplemented relocation type '{f}'", + .{rt.fmt(elf)}, + ), + }; + } + }, + }; + } if (input_has_bss) { try elf.bss_inputs.append(gpa, input_index); @@ -8435,6 +8497,10 @@ fn loadDso( fallback_soname: link.Input.Dso.FallbackSoname, fr: *Io.File.Reader, ) (LoadParseInputError || error{BadMagic})!void { + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addTextFmt("{f}", .{path.fmtEscapeString()}); + const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -8580,6 +8646,9 @@ fn loadDso( const verdef: []DsoGlobals.String.Optional = verdef: { const verdef_sh = shdr.verdef orelse break :verdef &.{}; + const versions_trace = tracy.traceNamed(@src(), "dso versions"); + defer versions_trace.end(); + // This parsing is a bit of a mess because it appears that at this moment in history // nobody at GNU was yet aware of the concept of an "array". @@ -8663,6 +8732,9 @@ fn loadDso( const soname = try elf.string(.dynstr, soname_slice); try elf.needed.put(gpa, soname, {}); + const symbols_trace = tracy.traceNamed(@src(), "dso symbols"); + defer symbols_trace.end(); + // Scan the symbol table and populate `elf.dso_globals`. const first_global = @min(shdr.dynsym.info, symnum); try elf.dso_globals.symbols.ensureUnusedCapacity(gpa, symnum - first_global); @@ -9544,6 +9616,9 @@ fn addRelocAssumeCapacity( addend: i64, @"type": MachineRelocType, ) (Error || error{ UnknownRelocation, NonStaticRelocation, UnimplementedRelocation })!void { + const trace = tracy.trace(@src()); + defer trace.end(); + switch (elf.ehdrType()) { .REL => { const rela_shndx = elf.getNodeShndx(node).get(elf).rela.shndx; @@ -9925,6 +10000,9 @@ fn addSymbolRelocAssumeCapacityInner( ) Error!void { assert(elf.ehdrType() != .REL); + const trace = tracy.trace(@src()); + defer trace.end(); + const gpa = elf.base.comp.gpa; request_static_target: { diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index e455ba232dc46dac3c3d54e8e57209486ccaa13e..17b7872b17eec2238181d5f4d6ba35fd7ffaed9e 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -11,6 +11,8 @@ const assert = std.debug.assert; const linux = std.os.linux; const windows = std.os.windows; +const tracy = @import("../tracy.zig"); + io: Io, flags: packed struct { block_size: Alignment, @@ -892,6 +894,9 @@ fn addNode(mf: *MappedFile, gpa: Allocator, opts: struct { }) Error!Node.Index { mf.nodes_lock.assertUnlocked(); + const trace = tracy.trace(@src()); + defer trace.end(); + try mf.nodes.ensureUnusedCapacity(gpa, 1); try mf.large.ensureUnusedCapacity(gpa, 2); @@ -1014,6 +1019,9 @@ fn shrinkLeafNode( ) Error!void { mf.nodes_lock.assertUnlocked(); + const trace = tracy.trace(@src()); + defer trace.end(); + const old_offset, const old_size = ni.location(mf).resolve(mf); assert(new_size < old_size); @@ -1152,6 +1160,9 @@ fn growNode( ) Error!void { mf.nodes_lock.assertUnlocked(); + const trace = tracy.trace(@src()); + defer trace.end(); + const node = ni.get(mf); const old_offset, const old_size = node.location().resolve(mf); @@ -1569,6 +1580,9 @@ fn growFloatingNodeWithAlignment( ) Error!void { mf.nodes_lock.assertUnlocked(); + const trace = tracy.trace(@src()); + defer trace.end(); + const parent_ni = ni.parent(mf).unwrap().?; // `ni` cannot be `.root` const old_offset, const old_size = ni.location(mf).resolve(mf); @@ -1791,6 +1805,9 @@ fn growNodeViaInsertRange( new_size: u64, grow_options: GrowOptions, ) Error!bool { + const trace = tracy.trace(@src()); + defer trace.end(); + if (!is_linux or mf.flags.fallocate_insert_range_unsupported) { return false; } @@ -1999,6 +2016,9 @@ fn ensureAdditionalHeaderCapacity( parent_ni: Node.Index, extra_capacity: u64, ) Error!void { + const trace = tracy.trace(@src()); + defer trace.end(); + _, const parent_size = parent_ni.location(mf).resolve(mf); const last_header_oni = parent_ni.lastHeader(mf); @@ -2274,6 +2294,9 @@ fn realignNode( ) Error!void { mf.nodes_lock.assertUnlocked(); + const trace = tracy.trace(@src()); + defer trace.end(); + const old_offset, const old_size = ni.location(mf).resolve(mf); if (ni == .root or ni.position(mf) != .floating) { @@ -2310,6 +2333,10 @@ fn updateWriters(mf: *MappedFile) void { fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) Error!void { if (old_file_offset == new_file_offset) return; + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addTextFmt("{Bi} (0x{X} -> 0x{X})", .{ size, old_file_offset, new_file_offset }); + if (old_file_offset >= new_file_offset + size or new_file_offset >= old_file_offset + size) { @@ -2348,6 +2375,10 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: } } fn zeroRange(mf: *MappedFile, file_offset: u64, size: u64) Error!void { + const trace = tracy.trace(@src()); + defer trace.end(); + trace.addTextFmt("{Bi} (at 0x{X})", .{ size, file_offset }); + if (is_linux and !mf.flags.fallocate_punch_hole_unsupported and size >= mf.flags.block_size.toByteUnits() * 2 - 1) @@ -2395,6 +2426,9 @@ fn copyFileRange( return 0; } + const trace = tracy.trace(@src()); + defer trace.end(); + const min_size = mf.flags.block_size.toByteUnits() * 2 - 1; if (size < min_size) return 0; -- 2.54.0 From 8d471cd9018d99a3a39ee94212b2d42efe755ea1 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 23 Oct 2026 17:01:27 +0100 Subject: [PATCH 5/6] Elf2: switch string table to ArrayHashMap This seems to be a performance win, presumably thanks to the entire hash being stored which avoids redundant string comparisons. --- src/link/Elf2.zig | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 5feed708a15c714dfbbd5bfbb6818761348ed5e6..c7a2c2b9ecc52153dd3990ad1d34cf7a696acc82 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -4963,31 +4963,20 @@ fn stringExisting(elf: *Elf, comptime section: StringSection, key: []const u8) S } const StringTable = struct { - map: std.HashMapUnmanaged(u32, void, StringTable.Context, std.hash_map.default_max_load_percentage), - - const Context = struct { - slice: []const u8, - - pub fn eql(_: Context, lhs_key: u32, rhs_key: u32) bool { - return lhs_key == rhs_key; - } - - pub fn hash(ctx: Context, key: u32) u64 { - return std.hash_map.hashString(std.mem.sliceTo(ctx.slice[key..], 0)); - } - }; + map: std.array_hash_map.Custom(u32, void, void, true), const Adapter = struct { slice: []const u8, - pub fn eql(adapter: Adapter, lhs_key: []const u8, rhs_key: u32) bool { + pub fn eql(adapter: Adapter, lhs_key: []const u8, rhs_key: u32, rhs_index: usize) bool { + _ = rhs_index; return std.mem.startsWith(u8, adapter.slice[rhs_key..], lhs_key) and adapter.slice[rhs_key + lhs_key.len] == 0; } - pub fn hash(_: Adapter, key: []const u8) u64 { + pub fn hash(_: Adapter, key: []const u8) u32 { assert(std.mem.findScalar(u8, key, 0) == null); - return std.hash_map.hashString(key); + return std.array_hash_map.hashString(key); } }; @@ -5001,8 +4990,7 @@ const StringTable = struct { fn ensureAdditionalCapacity(st: *StringTable, elf: *Elf, shndx: Section.Index, string_len: usize) Error!void { const gpa = elf.base.comp.gpa; const ni = shndx.node(elf); - const slice_const = ni.sliceConst(&elf.mf); - try st.map.ensureUnusedCapacityContext(gpa, 1, .{ .slice = slice_const }); + try st.map.ensureUnusedCapacity(gpa, 1); const size: u64 = switch (elf.shdrPtr(shndx)) { inline else => |shdr| elf.targetLoad(&shdr.size), }; -- 2.54.0 From fac41b3ec9237583f4d6bcb73c339464eb9a1edc Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 24 Oct 2026 23:39:49 +0100 Subject: [PATCH 6/6] Elf2: minor performance improvements --- src/link/Elf2.zig | 54 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index c7a2c2b9ecc52153dd3990ad1d34cf7a696acc82..4be20575d8049d1addc66280946d7656a92901c8 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -21,6 +21,15 @@ const Alignment = MappedFile.Alignment; base: link.File, options: link.File.OpenOptions, mf: MappedFile, + +/// These fields could simply be read from the ehdr node when needed, but it turns out that they are +/// accessed frequently enough that we see measurable performance gains from caching them here. +ehdr_fields: struct { + class: std.elf.CLASS, + type: EhdrType, + machine: EhdrMachine, +}, + ni: struct { elf: MappedFile.Node.Index, ehdr: MappedFile.Node.Index, @@ -5115,6 +5124,11 @@ fn create( }, .options = options, .mf = try .init(file, comp.gpa, io), + .ehdr_fields = .{ + .class = class, + .type = @"type", + .machine = machine, + }, .ni = .{ .elf = undefined, .ehdr = undefined, @@ -7007,7 +7021,9 @@ fn flushMovedNodeRelocs( } fn identClass(elf: *const Elf) std.elf.CLASS { - return @fromBackingInt(elf.ni.elf.sliceConst(&elf.mf)[std.elf.EI.CLASS]); + // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often + // enough to see measurable performance gains from memoizing its result on `Elf`. + return elf.ehdr_fields.class; } /// Like `std.elf.ET`, but only includes the ELF machine architectures we support, so that we can @@ -7039,24 +7055,14 @@ const EhdrType = enum(u16) { } }; fn ehdrMachine(elf: *const Elf) EhdrMachine { - const ehdr_slice = elf.ni.ehdr.sliceConst(&elf.mf); - switch (elf.identClass()) { - .NONE, _ => unreachable, - inline else => |class| { - const ehdr: *const class.ElfN().Ehdr = @ptrCast(@alignCast(ehdr_slice)); - return @bitCast(elf.targetLoad(&ehdr.machine)); - }, - } + // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often + // enough to see measurable performance gains from memoizing its result on `Elf`. + return elf.ehdr_fields.machine; } fn ehdrType(elf: *const Elf) EhdrType { - const ehdr_slice = elf.ni.ehdr.sliceConst(&elf.mf); - switch (elf.identClass()) { - .NONE, _ => unreachable, - inline else => |class| { - const ehdr: *const class.ElfN().Ehdr = @ptrCast(@alignCast(ehdr_slice)); - return @bitCast(elf.targetLoad(&ehdr.type)); - }, - } + // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often + // enough to see measurable performance gains from memoizing its result on `Elf`. + return elf.ehdr_fields.type; } fn baseLoadableSegmentVAddr(elf: *const Elf) u64 { @@ -7279,11 +7285,17 @@ const SymPtr = union(std.elf.CLASS) { @"32": *std.elf.Elf32.Sym, @"64": *std.elf.Elf64.Sym, }; -fn symPtr(elf: *Elf, index: Symbol.Index) SymPtr { +/// This function is marked as `inline` because performance measurements (specifically, using this +/// linker to link the Zig compiler against a static build of LLVM 23) show a measurable performance +/// improvement from doing so---it seems that this is one of the hottest functions in this linker! +inline fn symPtr(elf: *Elf, index: Symbol.Index) SymPtr { const raw_slice = Section.Index.symtab.node(elf).slice(&elf.mf); - switch (elf.shdrPtr(.symtab)) { - inline else => |shdr, class| { - const size = elf.targetLoad(&shdr.size); + // Avoid calling `shdrPtr` here: this function is extremely hot, so having to locate the shdr + // actually noticably affects performance! + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const size = elf.symtab.items.len * @sizeOf(class.ElfN().Sym); const slice: []class.ElfN().Sym = @ptrCast(@alignCast(raw_slice[0..@intCast(size)])); return @unionInit(SymPtr, @tagName(class), &slice[@backingInt(index)]); }, -- 2.54.0