authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:37-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:27:17-04:00
log537d7b74274c31ea4240870724ffd41e0e5de8dd
treef0b18a14be815b44df3c69752efdf0fd4c326b16
parentbd50917c0d86cc45c46cb10868e5e4da6d87c58f

Coff: rework symbol table generation

Instead of popping from the end of a map of pending symbols, use the map as the storage and flush symbols in order. This simplifies the weak external flow, and keeps the symbol table in a more intuitive order. Also, .sti is no longer in Value, which means that it won't collide with .node_offset if an imported symbol is later exported.

2 files changed, 71 insertions(+), 85 deletions(-)

src/link/Coff.zig+66-80
......@@ -615,7 +615,8 @@ pub const SymbolTable = struct {
615615 ni: MappedFile.Node.Index,
616616 strings_ni: MappedFile.Node.Index,
617617 strings: std.AutoArrayHashMapUnmanaged(String, StringIndex),
618 pending: std.AutoArrayHashMapUnmanaged(Symbol.Index, void),
618 symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, SymbolTable.Index),
619 pending_symbol_index: u32,
619620
620621 // Resizing the symbol table node has the result of accumulating padding
621622 // between the last symbol in the symbol table node and the start of the
......@@ -653,8 +654,8 @@ pub const SymbolTable = struct {
653654 none,
654655 _,
655656
656 pub fn wrap(i: ?u32) Index {
657 return @enumFromInt((i orelse return .none) + 1);
657 pub fn wrap(i: u32) Index {
658 return @enumFromInt(i + 1);
658659 }
659660
660661 pub fn unwrap(sti: Index) ?u32 {
......@@ -904,13 +905,14 @@ pub const Symbol = struct {
904905 };
905906
906907 const ValueTag = enum(u2) {
908 none,
907909 node_offset,
908910 weak_alias_si,
909911 weak_alias_name,
910 sti,
911912 };
912913
913914 pub const Value = union(ValueTag) {
915 none,
914916 /// The offset of the symbol within its node. Used with symbols that
915917 /// don't create their own nodes: .input_section, .import_address_table
916918 /// Images only.
......@@ -924,9 +926,6 @@ pub const Symbol = struct {
924926 /// be generated and resolved if this symbol is not resolved.
925927 /// Globals only, images only.
926928 weak_alias_name: String,
927 /// Index of this symbol in the symbol table
928 /// Only used when outputting objects
929 sti: SymbolTable.Index,
930929 };
931930
932931 const ExtraTag = enum(u2) {
......@@ -1041,6 +1040,11 @@ pub const Symbol = struct {
10411040 return ni;
10421041 }
10431042
1043 pub fn sti(si: Symbol.Index, coff: *Coff) SymbolTable.Index {
1044 assert(!coff.isImage());
1045 return coff.symbol_table.symbols.get(si) orelse .none;
1046 }
1047
10441048 pub fn next(si: Symbol.Index) Symbol.Index {
10451049 return @enumFromInt(@intFromEnum(si) + 1);
10461050 }
......@@ -1070,7 +1074,7 @@ pub const Symbol = struct {
10701074
10711075 pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void {
10721076 const sym = si.get(coff);
1073 const index = sym.value.sti.unwrap() orelse return;
1077 const index = si.sti(coff).unwrap().?;
10741078 var ri = sym.target_relocs;
10751079 while (ri != .none) {
10761080 const reloc = ri.get(coff);
......@@ -1583,7 +1587,8 @@ fn create(
15831587 .ni = .none,
15841588 .strings_ni = .none,
15851589 .strings = .empty,
1586 .pending = .empty,
1590 .symbols = .empty,
1591 .pending_symbol_index = 0,
15871592 .pending_shrink = false,
15881593 },
15891594 .inputs = .empty,
......@@ -1667,7 +1672,7 @@ pub fn deinit(coff: *Coff) void {
16671672 coff.import_table.iat_symbol_indices.deinit(gpa);
16681673 coff.export_table.entries.deinit(gpa);
16691674 coff.symbol_table.strings.deinit(gpa);
1670 coff.symbol_table.pending.deinit(gpa);
1675 coff.symbol_table.symbols.deinit(gpa);
16711676 coff.inputs.deinit(gpa);
16721677 coff.input_archives.deinit(gpa);
16731678 coff.input_archive_members.deinit(gpa);
......@@ -2280,7 +2285,10 @@ pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void {
22802285 });
22812286 if (!isImage(coff)) {
22822287 prog_node.increaseEstimatedTotalItems(2);
2283 coff.symbol_prog_node = prog_node.start("Symbols", coff.symbol_table.pending.count());
2288 coff.symbol_prog_node = prog_node.start(
2289 "Symbols",
2290 coff.symbol_table.symbols.count() - coff.symbol_table.pending_symbol_index,
2291 );
22842292 coff.member_prog_node = prog_node.start("Members", coff.pending_members.count());
22852293 }
22862294 coff.input_prog_node = prog_node.start(
......@@ -2550,8 +2558,7 @@ pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.c
25502558 return null;
25512559}
25522560
2553pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.SectionDefinition {
2554 const sti = si.get(coff).value.sti;
2561pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.SectionDefinition {
25552562 if (symbolTableEntryPtr(coff, sti)) |entry| {
25562563 assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1);
25572564 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
......@@ -2560,8 +2567,7 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) s
25602567 }
25612568}
25622569
2563pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.WeakExternalDefinition {
2564 const sti = si.get(coff).value.sti;
2570pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.WeakExternalDefinition {
25652571 if (symbolTableEntryPtr(coff, sti)) |entry| {
25662572 assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1);
25672573 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
......@@ -2604,10 +2610,10 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
26042610 defer coff.symbols.addOneAssumeCapacity().* = .{
26052611 .ni = .none,
26062612 .rva = 0,
2607 .value = .{ .sti = .none },
2613 .value = .{ .none = {} },
26082614 .extra = .{ .size = 0 },
26092615 .flags = .{
2610 .value_tag = .sti,
2616 .value_tag = .none,
26112617 .extra_tag = .size,
26122618 .type = .unknown,
26132619 .dll_storage_class = .default,
......@@ -2761,14 +2767,14 @@ pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
27612767pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
27622768 assert(!coff.isImage());
27632769 const sym = si.get(coff);
2764 if (sym.flags.value_tag == .sti and sym.value.sti != .none)
2765 return;
27662770
27672771 assert(sym.ni != .none or sym.gmi != .none);
27682772 const gpa = coff.base.comp.gpa;
2769 const pending_gop = try coff.symbol_table.pending.getOrPut(gpa, si);
2770 if (!pending_gop.found_existing)
2773 const gop = try coff.symbol_table.symbols.getOrPut(gpa, si);
2774 if (!gop.found_existing) {
27712775 coff.symbol_prog_node.increaseEstimatedTotalItems(1);
2776 gop.value_ptr.* = .none;
2777 }
27722778}
27732779
27742780fn navSection(
......@@ -3047,19 +3053,17 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
30473053 coff.member_prog_node.increaseEstimatedTotalItems(1);
30483054}
30493055
3050fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void {
3056fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void {
30513057 assert(!coff.isImage());
30523058 const gpa = coff.base.comp.gpa;
30533059
3060 const si = coff.symbol_table.symbols.keys()[index];
3061 const sti = &coff.symbol_table.symbols.values()[index];
3062
30543063 const sym = si.get(coff);
30553064 assert(sym.ni != .none or sym.gmi != .none);
3056 const existing_sti = switch (sym.flags.value_tag) {
3057 .sti => sym.value.sti,
3058 .weak_alias_si => .none,
3059 else => unreachable,
3060 };
30613065
3062 const entry = coff.symbolTableEntryPtr(existing_sti) orelse entry: {
3066 const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: {
30633067 var buf: [15]u8 = undefined;
30643068 const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType =
30653069 if (sym.gmi != .none) blk: {
......@@ -3124,11 +3128,10 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31243128
31253129 try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
31263130
3127 const old_value = sym.value;
3128 sym.setValue(.{ .sti = .wrap(old_num_symbols) });
3131 sti.* = .wrap(old_num_symbols);
31293132 si.flushSymbolTableIndex(coff);
31303133
3131 const entry = coff.symbolTableEntryPtr(sym.value.sti).?;
3134 const entry = coff.symbolTableEntryPtr(sti.*).?;
31323135 symbol_name.store(coff, &entry.name);
31333136
31343137 entry.section_number = @enumFromInt(@intFromEnum(sym.section_number));
......@@ -3137,34 +3140,19 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31373140 .base_type = .NULL,
31383141 };
31393142
3140 entry.storage_class = if (sym.flags.extra_tag == .next_alias_si) storage: {
3141 // TODO: Could avoid this ordering issue by flushing these in fifo order, instead of lifo
3142 // TODO: Instead keep a map of si -> sti (remove it from sym.value) and walk in forwards order
3143 // Update any existing aux symbols for weak externals that reference this symbol
3144 var any_weak_external = false;
3143 entry.storage_class = if (sym.gmi != .none)
3144 .EXTERNAL
3145 else if (sym.flags.extra_tag == .next_alias_si) storage: {
31453146 var alias_sym = sym;
3146 while (alias_sym.flags.extra_tag == .next_alias_si) {
3147 const weak_external = while (alias_sym.flags.extra_tag == .next_alias_si) {
31473148 const alias_si = alias_sym.extra.next_alias_si;
31483149 alias_sym = alias_si.get(coff);
31493150 assert(alias_sym.ni == sym.ni);
3150
3151 if (alias_sym.flags.weak_external_strat != .none) {
3152 switch (alias_sym.flags.value_tag) {
3153 .sti => if (coff.symbolTableWeakExternalAuxEntryPtr(alias_si)) |aux_ptr|
3154 coff.targetStore(&aux_ptr.tag_index, sym.value.sti.unwrap().?),
3155 .weak_alias_si => {},
3156 else => unreachable,
3157 }
3158
3159 any_weak_external = true;
3160 }
3161 }
3162
3163 break :storage if (any_weak_external or sym.gmi != .none) .EXTERNAL else .STATIC;
3164 } else if (sym.gmi == .none)
3165 .STATIC
3166 else
3167 .EXTERNAL;
3151 if (alias_sym.flags.weak_external_strat != .none)
3152 break true;
3153 } else false;
3154 break :storage if (weak_external) .EXTERNAL else .STATIC;
3155 } else .STATIC;
31683156
31693157 entry.number_of_aux_symbols = num_aux_symbols;
31703158 if (coff.targetEndian() != native_endian)
......@@ -3175,15 +3163,8 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31753163 entry.section_number = .UNDEFINED;
31763164 entry.storage_class = .WEAK_EXTERNAL;
31773165
3178 const alias_si = old_value.weak_alias_si;
3179 const alias_sym = alias_si.get(coff);
3180 const tag_index = alias_sym.value.sti.unwrap() orelse tag_index: {
3181 // The alias will update `tag_index` when it is flushed
3182 assert(coff.symbol_table.pending.contains(alias_si));
3183 break :tag_index 0;
3184 };
3185
3186 const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(si).?;
3166 const tag_index = sym.value.weak_alias_si.sti(coff).unwrap().?;
3167 const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(sti.*).?;
31873168 aux_ptr.* = .{
31883169 .tag_index = tag_index,
31893170 .flag = switch (sym.flags.weak_external_strat) {
......@@ -3203,7 +3184,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
32033184 .image_section => |sec_si| {
32043185 assert(si == sec_si);
32053186 const header = sym.section_number.header(coff);
3206 const aux_ptr = coff.symbolTableSectionAuxEntryPtr(si).?;
3187 const aux_ptr = coff.symbolTableSectionAuxEntryPtr(sti.*).?;
32073188 aux_ptr.* = .{
32083189 .length = @intCast(sym.ni.location(&coff.mf).resolve(&coff.mf)[1]),
32093190 .number_of_relocations = header.number_of_relocations,
......@@ -3238,7 +3219,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
32383219 },
32393220 });
32403221
3241 log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sym.value.sti });
3222 log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sti.* });
32423223}
32433224
32443225fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {
......@@ -3300,7 +3281,7 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S
33003281 try coff.nodes.ensureUnusedCapacity(gpa, 1);
33013282 try coff.section_table.ensureUnusedCapacity(gpa, 1);
33023283 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3303 if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1);
3284 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
33043285
33053286 const coff_header = coff.headerPtr();
33063287 const section_index = coff.targetLoad(&coff_header.number_of_sections);
......@@ -3643,8 +3624,9 @@ pub fn addReloc(
36433624 else => |loc_sn| sri: {
36443625 // The target may not have a node yet, or it could be an extern that will never
36453626 // have a node. In that case, flushGlobal will create the symbol table entry.
3646 const sti: SymbolTable.Index = if (target.value.sti != .none)
3647 target.value.sti
3627 const existing_sti = target_si.sti(coff);
3628 const sti: SymbolTable.Index = if (existing_sti != .none)
3629 existing_sti
36483630 else if (target.ni != .none) sti: {
36493631 try coff.pendingSymbolTableEntry(target_si);
36503632 break :sti .none;
......@@ -3669,7 +3651,7 @@ pub fn addReloc(
36693651 }
36703652
36713653 coff.targetStore(&header.number_of_relocations, new_num_relocations);
3672 if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff))) |aux_ptr|
3654 if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr|
36733655 coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations);
36743656
36753657 // TODO: These need to allocate from a free list (once deleting relocs is supported) (or can we just remove swap?)
......@@ -5306,7 +5288,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
53065288 .none => {
53075289 const sec_si = try coff.navSection(zcu, nav.resolved.?);
53085290 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5309 if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1);
5291 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
53105292 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
53115293 .alignment = zcu.navAlignment(nav_index).toStdMem(),
53125294 .moved = true,
......@@ -5428,7 +5410,7 @@ fn updateFuncInner(
54285410 .none => {
54295411 const sec_si = try coff.navSection(zcu, nav.resolved.?);
54305412 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5431 if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1);
5413 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
54325414 const mod = zcu.navFileScope(func.owner_nav).mod.?;
54335415 const target = &mod.resolved_target.result;
54345416 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
......@@ -5902,19 +5884,21 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
59025884 };
59035885 break :task;
59045886 };
5905 while (coff.symbol_table.pending.pop()) |pending_si| {
5906 const sym = pending_si.key.get(coff);
5887 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) {
5888 defer coff.symbol_table.pending_symbol_index += 1;
5889 const si = coff.symbol_table.symbols.keys()[coff.symbol_table.pending_symbol_index];
5890 const sym = si.get(coff);
59075891 const sub_prog_node = coff.idleProgNode(
59085892 tid,
59095893 coff.symbol_prog_node,
59105894 if (sym.ni != .none)
59115895 coff.getNode(sym.ni)
59125896 else
5913 .{ .import_thunk = pending_si.key.get(coff).gmi },
5897 .{ .import_thunk = sym.gmi },
59145898 );
59155899 defer sub_prog_node.end();
59165900 coff.flushSymbolTableEntry(
5917 pending_si.key,
5901 coff.symbol_table.pending_symbol_index,
59185902 .{ .zcu = comp.zcu.?, .tid = tid },
59195903 ) catch |err| switch (err) {
59205904 error.OutOfMemory => return error.OutOfMemory,
......@@ -5935,7 +5919,7 @@ fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
59355919 if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) return true;
59365920 if (coff.exports_complete and coff.pending_special_symbol != .none) return true;
59375921 for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
5938 if (coff.symbol_table.pending.count() > 0) return true;
5922 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) return true;
59395923 return false;
59405924}
59415925
......@@ -6066,7 +6050,7 @@ fn flushUav(
60666050 .{ .read = true, .initialized = true },
60676051 )).symbol(coff);
60686052 try coff.nodes.ensureUnusedCapacity(gpa, 1);
6069 if (!isImage(coff)) try coff.symbol_table.pending.ensureUnusedCapacity(gpa, 1);
6053 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
60706054 const sym = si.get(coff);
60716055 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
60726056 .alignment = uav_align.toStdMem(),
......@@ -7012,7 +6996,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
70126996 }
70136997
70146998 if (!coff.isImage()) {
7015 if (coff.symbolTableSectionAuxEntryPtr(si)) |aux_ptr|
6999 if (coff.symbolTableSectionAuxEntryPtr(si.sti(coff))) |aux_ptr|
70167000 coff.targetStore(&aux_ptr.length, @intCast(size));
70177001 }
70187002 },
......@@ -7290,6 +7274,8 @@ fn updateExportsInner(
72907274 export_sym.rva = exported_sym.rva;
72917275 export_sym.section_number = exported_sym.section_number;
72927276 if (@"export".opts.linkage == .weak and !coff.isImage()) {
7277 // exported_si needs to be ahead of export_si in the symbol table,
7278 // so that its sti is known when creating the aux entry
72937279 try coff.pendingSymbolTableEntry(exported_si);
72947280 export_sym.flags.weak_external_strat = .alias;
72957281 export_sym.setValue(.{ .weak_alias_si = exported_si });
......@@ -7466,10 +7452,10 @@ fn printSymbol(
74667452 else
74677453 0,
74687454 switch (sym.flags.value_tag) {
7455 .none => "xx",
74697456 .weak_alias_name => "an",
74707457 .weak_alias_si => "as",
74717458 .node_offset => "no",
7472 .sti => "st",
74737459 },
74747460 switch (sym.flags.extra_tag) {
74757461 .size => "sz",
test/link/snapshots/static-lib.no-llvm.dmp+5-5
......@@ -4,9 +4,9 @@ xxxx 00000000 1 NULL() EXTERNAL | fooBar
44xxxx 00000000 2 NULL EXTERNAL | foo1
55xxxx 00000004 2 NULL EXTERNAL | foo2
66lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak
8 | Weak External [falls back to 00000012 via SEARCH_ALIAS]
9xxxx 00000010 2 NULL EXTERNAL | foo_array
10xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
11xxxx 00000000 2 NULL EXTERNAL | foo_strong
127xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak
8xxxx 00000000 2 NULL EXTERNAL | foo_strong
9xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
10xxxx 00000010 2 NULL EXTERNAL | foo_array
11xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak
12 | Weak External [falls back to 0000000d via SEARCH_ALIAS]