zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit a87fc47bc17ae28a8a0a90da4c64751a071bac44 (tree)
parent c8328cb57b9b23c474ab0c55ca5caa0e90bc5f80
Author: kcbanner <kcbanner@gmail.com>
Date:   Fri,  5 Jun 2026 01:55:33 -0400

- Move the export table logic into updateExportsInner
- Set up relocs for exported symbols in the export address table
- Move the sort to `idle`, and set it up to only occur when necessary
- Handle the name table being moved

Diffstat:
Msrc/link/Coff.zig | 336+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 191 insertions(+), 145 deletions(-)

diff --git a/src/link/Coff.zig b/src/link/Coff.zig @@ -279,15 +279,17 @@ pub const Node = union(enum) { pub const ExportTable = struct { ni: MappedFile.Node.Index, - export_address_table_ni: MappedFile.Node.Index, + export_address_table_si: Symbol.Index, name_pointer_table_ni: MappedFile.Node.Index, ordinal_table_ni: MappedFile.Node.Index, name_table_ni: MappedFile.Node.Index, entries: std.AutoArrayHashMapUnmanaged(void, Entry), + pending_sort: bool = false, pub const Entry = struct { name_index: u32, name_len: u32, + export_address_table_ri: Reloc.Index, }; const Adapter = struct { @@ -306,10 +308,10 @@ pub const ExportTable = struct { } }; - pub const Index = enum(u32) { + pub const Ordinal = enum(u16) { _, - pub fn get(export_index: ExportTable.Index, coff: *Coff) *Entry { + pub fn get(export_index: ExportTable.Ordinal, coff: *Coff) *Entry { return &coff.export_table.entries.values()[@intFromEnum(export_index)]; } }; @@ -744,7 +746,7 @@ fn create( }, .export_table = .{ .ni = .none, - .export_address_table_ni = .none, + .export_address_table_si = .null, .name_pointer_table_ni = .none, .ordinal_table_ni = .none, .name_table_ni = .none, @@ -1083,10 +1085,21 @@ fn initHeaders( ); @memcpy(coff.export_table.ni.slice(&coff.mf)[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); - coff.export_table.export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ + const export_address_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ .alignment = .of(u32), .moved = true, }); + + try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity(); + + const export_address_table_sym = coff.export_table.export_address_table_si.get(coff); + export_address_table_sym.ni = export_address_table_ni; + assert(export_address_table_sym.loc_relocs == .none); + export_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len); + export_address_table_sym.section_number = + coff.getNode(edata_section_ni).pseudo_section.symbol(coff).get(coff).section_number; + coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, edata_section_ni, .{ .alignment = .of(u32), .moved = true, @@ -1120,6 +1133,8 @@ fn initHeaders( .name_pointer_table_rva = 0, .ordinal_table_rva = 0, }; + if (target_endian != native_endian) + std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table); } // While tls variables allocated at runtime are writable, the template itself is not @@ -1315,10 +1330,6 @@ pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable { return @ptrCast(@alignCast(coff.export_table.ni.slice(&coff.mf))); } -pub fn exportAddressTableSlice(coff: *Coff) []std.coff.ExportAddressTableEntry { - return @ptrCast(@alignCast(coff.export_table.export_address_table_ni.slice(&coff.mf))); -} - pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry { return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf))); } @@ -1875,82 +1886,6 @@ pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void { }; } -fn flushExports(coff: *Coff, tid: Zcu.PerThread.Id) !void { - const export_count = coff.export_table.entries.count(); - if (export_count == 0) return; - - const gpa = coff.base.comp.zcu.?.gpa; - const edt = coff.exportDirectoryTable(); - edt.number_of_names = @intCast(export_count); - edt.number_of_entries = @intCast(export_count); - - try coff.export_table.name_pointer_table_ni.resize( - &coff.mf, - gpa, - export_count * @sizeOf(std.coff.ExportNamePointerTableEntry), - ); - - try coff.export_table.ordinal_table_ni.resize( - &coff.mf, - gpa, - export_count * @sizeOf(std.coff.ExportOrdinalTableEntry), - ); - - while (try coff.idle(tid)) {} - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, edt); - - const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni); - for ( - coff.exportNamePointerTableSlice(), - coff.exportOrdinalTableSlice(), - coff.export_table.entries.values(), - 0.., - ) |*np, *ord, entry, entry_i| { - np.name_rva = name_table_rva + entry.name_index; - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportNamePointerTableEntry, np); - - ord.unbiased_ordinal = @intCast(entry_i); - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportOrdinalTableEntry, &ord); - } - - const Context = struct { - np: []std.coff.ExportNamePointerTableEntry, - ord: []std.coff.ExportOrdinalTableEntry, - entries: []ExportTable.Entry, - names: []const u8, - - pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { - const lhs_entry = &ctx.entries[lhs]; - const rhs_entry = &ctx.entries[rhs]; - return std.mem.lessThan( - u8, - ctx.names[lhs_entry.name_index..][0..lhs_entry.name_len], - ctx.names[rhs_entry.name_index..][0..rhs_entry.name_len], - ); - } - - pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { - std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]); - std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]); - std.mem.swap(ExportTable.Entry, &ctx.entries[lhs], &ctx.entries[rhs]); - } - }; - - std.sort.pdqContext(0, export_count, Context{ - .np = coff.exportNamePointerTableSlice(), - .ord = coff.exportOrdinalTableSlice(), - .entries = coff.export_table.entries.values(), - .names = coff.export_table.name_table_ni.slice(&coff.mf), - }); - - // TODO: Is there a way to know if this is the last flush? We could skip doing this if so. - // TODO: Need to reindex with adaptor? - //try coff.export_table.entries.reIndexContext(gpa, ExportTable.Adapter{ .coff = coff }); -} - pub fn flush( coff: *Coff, arena: std.mem.Allocator, @@ -1961,9 +1896,6 @@ pub fn flush( _ = prog_node; while (try coff.idle(tid)) {} - coff.flushExports(tid) catch |err| - return coff.base.comp.link_diags.fail("linker failed to flush exports: {t}", .{err}); - // hack for stage2_x86_64 + coff const comp = coff.base.comp; if (comp.compiler_rt_dyn_lib) |crt_file| { @@ -2061,11 +1993,17 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool { break :task; } else coff.mf.update_prog_node.completeOne(); } + if (coff.export_table.pending_sort) { + coff.export_table.pending_sort = false; + coff.flushExportsSort(); + break :task; + } } if (coff.pending_uavs.count() > 0) return true; if (coff.globals.count() > coff.global_pending_index) return true; for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true; if (coff.mf.updates.items.len > 0) return true; + if (coff.export_table.pending_sort) return true; return false; } @@ -2158,19 +2096,19 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { const gpa = zcu.gpa; const gn = gmi.globalName(coff); - const target_endian = coff.targetEndian(); - const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); - const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { - _ => unreachable, - .PE32 => .{ 4, .@"4" }, - .@"PE32+" => .{ 8, .@"8" }, - }; - - const name = gn.name.toSlice(coff); if (gn.lib_name.toSlice(coff)) |lib_name| { + const name = gn.name.toSlice(coff); try coff.nodes.ensureUnusedCapacity(gpa, 4); try coff.symbol_table.ensureUnusedCapacity(gpa, 1); + const target_endian = coff.targetEndian(); + const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic); + const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) { + _ => unreachable, + .PE32 => .{ 4, .@"4" }, + .@"PE32+" => .{ 8, .@"8" }, + }; + const gop = try coff.import_table.entries.getOrPutAdapted( gpa, lib_name, @@ -2315,47 +2253,6 @@ fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void { coff.nodes.appendAssumeCapacity(.{ .global = gmi }); sym.rva = coff.computeNodeRva(sym.ni); si.applyLocationRelocs(coff); - } else { - const entries_ctx = ExportTable.Adapter{ .coff = coff }; - const gop = try coff.export_table.entries.getOrPutAdapted( - gpa, - name, - entries_ctx, - ); - - if (!gop.found_existing) { - errdefer _ = coff.export_table.entries.pop(); - if (coff.export_table.entries.count() > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) - return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); - - const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; - const new_name_table_size = name_index + name.len + 1; - if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) - return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); - - try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size); - - const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); - @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); - - gop.value_ptr.* = .{ - .name_index = @intCast(name_index), - .name_len = @intCast(name.len), - }; - - const si = gmi.symbol(coff); - const sym = si.get(coff); - - try coff.export_table.export_address_table_ni.resize( - &coff.mf, - gpa, - coff.export_table.entries.count() * @sizeOf(std.coff.ExportAddressTableEntry), - ); - const ea = &coff.exportAddressTableSlice()[gop.index]; - ea.export_or_forwarder_rva = sym.rva; - if (coff.targetEndian() != native_endian) - std.mem.byteSwapAllFields(std.coff.ExportAddressTableEntry, &ea); - } } } @@ -2490,10 +2387,19 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva); coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable)); }, - .export_address_table => coff.targetStore( - &coff.exportDirectoryTable().export_address_table_rva, - coff.computeNodeRva(ni), - ), + .export_address_table => { + coff.export_table.export_address_table_si.flushMoved(coff); + + // These relocs are applied directly here instead of via the above flushMoved call as + // they are non-contiguous, and not tracked under export_address_table_si. + for (coff.export_table.entries.values()) |entry| + entry.export_address_table_ri.get(coff).apply(coff); + + coff.targetStore( + &coff.exportDirectoryTable().export_address_table_rva, + coff.computeNodeRva(ni), + ); + }, .export_name_pointer_table => coff.targetStore( &coff.exportDirectoryTable().name_pointer_table_rva, coff.computeNodeRva(ni), @@ -2503,8 +2409,18 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void { coff.computeNodeRva(ni), ), .export_name_table => { - // .export_name_pointer_table entries are updated in flush - log.warn("flushMoved export_name_table unhandled", .{}); + const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni); + for ( + coff.exportNamePointerTableSlice(), + coff.exportOrdinalTableSlice(), + ) |*np, target_ord| { + const ord: ExportTable.Ordinal = @enumFromInt(coff.targetLoad(&target_ord.unbiased_ordinal)); + const entry = ord.get(coff); + coff.targetStore( + &np.name_rva, + @intCast(name_table_rva + entry.name_index), + ); + } }, inline .pseudo_section, .object_section, @@ -2571,6 +2487,40 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void { .global, .nav, .uav, .lazy_code, .lazy_const_data => {}, } } + +fn flushExportsSort(coff: *Coff) void { + const Context = struct { + coff: *Coff, + np: []std.coff.ExportNamePointerTableEntry, + ord: []std.coff.ExportOrdinalTableEntry, + entries: []ExportTable.Entry, + nt: []const u8, + + pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { + const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)]; + const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)]; + return std.mem.lessThan( + u8, + ctx.nt[lhs_entry.name_index..][0..lhs_entry.name_len], + ctx.nt[rhs_entry.name_index..][0..rhs_entry.name_len], + ); + } + + pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { + std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]); + std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]); + } + }; + + std.sort.pdqContext(0, coff.export_table.entries.count(), Context{ + .coff = coff, + .np = coff.exportNamePointerTableSlice(), + .ord = coff.exportOrdinalTableSlice(), + .entries = coff.export_table.entries.values(), + .nt = coff.export_table.name_table_ni.slice(&coff.mf), + }); +} + fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void { var rva = start_rva; for ( @@ -2597,6 +2547,17 @@ pub fn updateExports( exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, ) !void { + return coff.updateExportsInner(pt, exported, export_indices) catch |err| switch (err) { + error.OutOfMemory => error.OutOfMemory, + else => |e| coff.base.comp.link_diags.fail("updateExports failed {t}", .{e}) catch error.AnalysisFail, + }; +} +fn updateExportsInner( + coff: *Coff, + pt: Zcu.PerThread, + exported: Zcu.Exported, + export_indices: []const Zcu.Export.Index, +) !void { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -2622,7 +2583,8 @@ pub fn updateExports( const exported_sym = exported_si.get(coff); for (export_indices) |export_index| { const @"export" = export_index.ptr(zcu); - const export_si = try coff.globalSymbol(@"export".opts.name.toSlice(ip), null); + const name = @"export".opts.name.toSlice(ip); + const export_si = try coff.globalSymbol(name, null); const export_sym = export_si.get(coff); export_sym.ni = exported_ni; export_sym.rva = exported_sym.rva; @@ -2637,6 +2599,90 @@ pub fn updateExports( if (coff.targetEndian() != native_endian) std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory); } + + const entries_ctx = ExportTable.Adapter{ .coff = coff }; + const gop = try coff.export_table.entries.getOrPutAdapted( + gpa, + name, + entries_ctx, + ); + + if (!gop.found_existing) { + errdefer _ = coff.export_table.entries.pop(); + + const export_count = coff.export_table.entries.count(); + if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries"))) + return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{}); + + const name_index = coff.export_table.name_table_ni.fileLocation(&coff.mf, true).size; + const new_name_table_size = name_index + name.len + 1; + if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index"))) + return coff.base.comp.link_diags.fail("exports name table limit reached", .{}); + + try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size); + + const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf); + @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]); + + // If the new name sorts after the current tail of the sorted list, we don't need to re-sort + const ordinal_table_slice = coff.exportOrdinalTableSlice(); + if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) { + const tail_index: ExportTable.Ordinal = + @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal); + const tail_entry = tail_index.get(coff); + const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len]; + coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name); + } + + const edt = coff.exportDirectoryTable(); + coff.targetStore(&edt.number_of_names, @intCast(export_count)); + edt.number_of_entries = edt.number_of_names; + + try coff.export_table.export_address_table_si.node(coff).resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportAddressTableEntry), + ); + + try coff.export_table.name_pointer_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportNamePointerTableEntry), + ); + + try coff.export_table.ordinal_table_ni.resize( + &coff.mf, + gpa, + export_count * @sizeOf(std.coff.ExportOrdinalTableEntry), + ); + + coff.targetStore( + &coff.exportNamePointerTableSlice()[gop.index].name_rva, + @intCast(coff.computeNodeRva(coff.export_table.name_table_ni) + name_index), + ); + coff.targetStore( + &coff.exportOrdinalTableSlice()[gop.index].unbiased_ordinal, + @intCast(gop.index), + ); + + gop.value_ptr.* = .{ + .name_index = @intCast(name_index), + .name_len = @intCast(name.len), + .export_address_table_ri = @enumFromInt(coff.relocs.items.len), + }; + + try coff.addReloc( + coff.export_table.export_address_table_si, + @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index), + export_si, + 0, + .{ .AMD64 = .ADDR32NB }, + ); + } else { + const reloc = gop.value_ptr.*.export_address_table_ri.get(coff); + reloc.target = export_si; + export_si.applyTargetRelocs(coff); + } } }