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
logbd50917c0d86cc45c46cb10868e5e4da6d87c58f
treeb8c878fe0c18af7cdfda239f7ea00a90f0fc1357
parent1b95427715b58cf4cd6df63fc9a7a8d200ca5d47

Coff: emit weak externals


5 files changed, 149 insertions(+), 69 deletions(-)

src/link/Coff.zig+105-22
......@@ -847,7 +847,8 @@ pub const Section = struct {
847847
848848pub const GlobalName = struct { name: String, lib_name: String.Optional };
849849
850pub const WeakExternalStrat = enum(u2) {
850pub const WeakExternalStrat = enum(u3) {
851 none,
851852 no_library,
852853 library,
853854 alias,
......@@ -880,9 +881,8 @@ pub const Symbol = struct {
880881 extra_tag: ExtraTag,
881882 type: Symbol.Type,
882883 dll_storage_class: DllStorageClass,
883 // Only defined for .alias_si and .alias_name
884884 weak_external_strat: WeakExternalStrat,
885 _: u6 = 0,
885 _: u5 = 0,
886886 },
887887 /// Relocations contained within this symbol
888888 loc_relocs: Reloc.Index,
......@@ -915,12 +915,13 @@ pub const Symbol = struct {
915915 /// don't create their own nodes: .input_section, .import_address_table
916916 /// Images only.
917917 node_offset: u32,
918 /// This is a weak alias that can replace this symbol
919 /// Globals only, images only.
918 /// Images: the weak alias that should replace this symbol if it is not resolved.
919 /// Objects: he target of a weak external that hasn't been assigned an sti yet.
920 /// Globals only.
920921 weak_alias_si: Symbol.Index,
921922 /// For weak externals that have an alias that is also an undef
922923 /// external, this is the name of the alias global that should
923 /// be generated if this symbol is not resolved.
924 /// be generated and resolved if this symbol is not resolved.
924925 /// Globals only, images only.
925926 weak_alias_name: String,
926927 /// Index of this symbol in the symbol table
......@@ -2096,6 +2097,7 @@ fn initHeaders(
20962097 });
20972098 }
20982099
2100 // TODO: Lazily initialize this instead?
20992101 coff.import_table.ni = try coff.mf.addLastChildNode(
21002102 gpa,
21012103 (try coff.objectSectionMapIndex(
......@@ -2558,6 +2560,16 @@ pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) s
25582560 }
25592561}
25602562
2563pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, si: Symbol.Index) ?*align(2) std.coff.WeakExternalDefinition {
2564 const sti = si.get(coff).value.sti;
2565 if (symbolTableEntryPtr(coff, sti)) |entry| {
2566 assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1);
2567 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
2568 } else {
2569 return null;
2570 }
2571}
2572
25612573pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 {
25622574 return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)]));
25632575}
......@@ -2599,7 +2611,7 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
25992611 .extra_tag = .size,
26002612 .type = .unknown,
26012613 .dll_storage_class = .default,
2602 .weak_external_strat = undefined,
2614 .weak_external_strat = .none,
26032615 },
26042616 .loc_relocs = .none,
26052617 .target_relocs = .none,
......@@ -2686,8 +2698,8 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
26862698
26872699const GlobalOptions = struct {
26882700 name: []const u8,
2689 type: Symbol.Type = .unknown,
26902701 lib_name: ?[]const u8 = null,
2702 type: Symbol.Type = .unknown,
26912703 dll_storage_class: Symbol.DllStorageClass = .default,
26922704};
26932705
......@@ -2749,13 +2761,14 @@ pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
27492761pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
27502762 assert(!coff.isImage());
27512763 const sym = si.get(coff);
2752 assert(sym.ni != .none or sym.gmi != .none);
2764 if (sym.flags.value_tag == .sti and sym.value.sti != .none)
2765 return;
27532766
2767 assert(sym.ni != .none or sym.gmi != .none);
27542768 const gpa = coff.base.comp.gpa;
27552769 const pending_gop = try coff.symbol_table.pending.getOrPut(gpa, si);
2756 if (!pending_gop.found_existing) {
2770 if (!pending_gop.found_existing)
27572771 coff.symbol_prog_node.increaseEstimatedTotalItems(1);
2758 }
27592772}
27602773
27612774fn navSection(
......@@ -3040,15 +3053,20 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
30403053
30413054 const sym = si.get(coff);
30423055 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 };
30433061
3044 const entry = coff.symbolTableEntryPtr(sym.value.sti) orelse entry: {
3062 const entry = coff.symbolTableEntryPtr(existing_sti) orelse entry: {
30453063 var buf: [15]u8 = undefined;
30463064 const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType =
30473065 if (sym.gmi != .none) blk: {
30483066 const gn = sym.gmi.globalName(coff);
30493067 break :blk .{
30503068 try coff.getOrPutSymbolName(gn.name.toSlice(coff), gn.name),
3051 0,
3069 @intFromBool(sym.flags.weak_external_strat != .none),
30523070 if (Symbol.Index.text.get(coff).section_number == sym.section_number)
30533071 .FUNCTION
30543072 else
......@@ -3102,12 +3120,12 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31023120
31033121 const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
31043122 const new_num_symbols = old_num_symbols + 1 + num_aux_symbols;
3123 coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
31053124
31063125 try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
31073126
3108 coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
3109
3110 sym.value.sti = .wrap(old_num_symbols);
3127 const old_value = sym.value;
3128 sym.setValue(.{ .sti = .wrap(old_num_symbols) });
31113129 si.flushSymbolTableIndex(coff);
31123130
31133131 const entry = coff.symbolTableEntryPtr(sym.value.sti).?;
......@@ -3118,13 +3136,70 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31183136 .complex_type = complex_type,
31193137 .base_type = .NULL,
31203138 };
3121 entry.storage_class = if (sym.gmi == .none) .STATIC else .EXTERNAL;
3139
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;
3145 var alias_sym = sym;
3146 while (alias_sym.flags.extra_tag == .next_alias_si) {
3147 const alias_si = alias_sym.extra.next_alias_si;
3148 alias_sym = alias_si.get(coff);
3149 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;
3168
31223169 entry.number_of_aux_symbols = num_aux_symbols;
31233170 if (coff.targetEndian() != native_endian)
31243171 std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry);
31253172
31263173 if (num_aux_symbols > 0) aux_init: {
3127 if (sym.gmi == .none) switch (coff.getNode(sym.ni)) {
3174 if (sym.gmi != .none) {
3175 entry.section_number = .UNDEFINED;
3176 entry.storage_class = .WEAK_EXTERNAL;
3177
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).?;
3187 aux_ptr.* = .{
3188 .tag_index = tag_index,
3189 .flag = switch (sym.flags.weak_external_strat) {
3190 .none => unreachable,
3191 .no_library => .SEARCH_NOLIBRARY,
3192 .library => .SEARCH_LIBRARY,
3193 .alias => .SEARCH_ALIAS,
3194 .anti_dependency => .ANTI_DEPENDENCY,
3195 },
3196 .unused = @splat(0),
3197 };
3198 if (coff.targetEndian() != native_endian)
3199 std.mem.byteSwapAllFields(std.coff.SectionDefinition, .@"2", aux_ptr);
3200
3201 break :aux_init;
3202 } else switch (coff.getNode(sym.ni)) {
31283203 .image_section => |sec_si| {
31293204 assert(si == sec_si);
31303205 const header = sym.section_number.header(coff);
......@@ -3144,7 +3219,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31443219 break :aux_init;
31453220 },
31463221 else => {},
3147 };
3222 }
31483223
31493224 unreachable;
31503225 }
......@@ -3153,7 +3228,7 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
31533228 };
31543229
31553230 coff.targetStore(&entry.value, switch (sym.section_number) {
3156 .UNDEFINED => sym.size(),
3231 .UNDEFINED => if (entry.storage_class == .WEAK_EXTERNAL) 0 else sym.size(),
31573232 .ABSOLUTE,
31583233 .DEBUG,
31593234 => unreachable,
......@@ -6128,6 +6203,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
61286203 const opt_alt_search_name = coff.alternate_names.get(search_name);
61296204 const search_libs = if (is_late) switch (sym.flags.value_tag) {
61306205 .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) {
6206 .none => unreachable,
61316207 .no_library => false,
61326208 .library,
61336209 .alias,
......@@ -7199,11 +7275,12 @@ fn updateExportsInner(
71997275 const exported_ni = exported_si.node(coff);
72007276 const exported_sym = exported_si.get(coff);
72017277 var prev_alias_si = exported_si;
7278
72027279 for (export_indices) |export_index| {
72037280 const @"export" = export_index.ptr(zcu);
72047281 const name = @"export".opts.name.toSlice(ip);
7205 // TODO: Add an errMsg if this conflicts with an existing global from an input
7206 // first_export_si relies on this being a new symbol.
7282
7283 // TODO: add an errMsg if this conflicts with an existing global
72077284 const export_si = try coff.globalSymbol(.{
72087285 .name = name,
72097286 .lib_name = null,
......@@ -7212,8 +7289,14 @@ fn updateExportsInner(
72127289 export_sym.ni = exported_ni;
72137290 export_sym.rva = exported_sym.rva;
72147291 export_sym.section_number = exported_sym.section_number;
7292 if (@"export".opts.linkage == .weak and !coff.isImage()) {
7293 try coff.pendingSymbolTableEntry(exported_si);
7294 export_sym.flags.weak_external_strat = .alias;
7295 export_sym.setValue(.{ .weak_alias_si = exported_si });
7296 }
72157297 defer export_si.applyTargetRelocs(coff, .none) catch unreachable;
72167298
7299 // The last symbol in the alias list holds the size
72177300 const prev_alias_sym = prev_alias_si.get(coff);
72187301 switch (prev_alias_sym.flags.extra_tag) {
72197302 .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }),
test/link.zig+17-15
......@@ -44,7 +44,7 @@ pub fn addCases(ctx: *LinkContext) void {
4444 "--elements=file-type",
4545 "--symbols",
4646 "--only-symbol=foo",
47 }, .{});
47 }, .{ .use_llvm = true });
4848
4949 const exe = case.addExecutable(.{
5050 .name = "test",
......@@ -192,20 +192,6 @@ pub fn addCases(ctx: *LinkContext) void {
192192 "--relocs",
193193 }, .{ .arch = true });
194194
195 const exe_reloc_err = case.addExecutable(.{
196 .name = "test-reloc-err",
197 .zig_source_bytes =
198 \\extern const foo: usize;
199 \\pub fn main() !u8 {
200 \\ return @intFromBool(foo != 0xcafecafe);
201 \\}
202 ,
203 });
204 exe_reloc_err.root_module.addObject(abs);
205 case.expectLinkErrors(exe_reloc_err, .{
206 .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/",
207 });
208
209195 const exe = case.addExecutable(.{
210196 .name = "test",
211197 .zig_source_bytes =
......@@ -220,6 +206,22 @@ pub fn addCases(ctx: *LinkContext) void {
220206
221207 const run = case.addRunArtifact(exe);
222208 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
209
210 if (!ctx.use_llvm) {
211 const exe_reloc_err = case.addExecutable(.{
212 .name = "test-reloc-err",
213 .zig_source_bytes =
214 \\extern const foo: u32;
215 \\pub fn main() !u8 {
216 \\ return @intFromBool(foo != 0xcafecafe);
217 \\}
218 ,
219 });
220 exe_reloc_err.root_module.addObject(abs);
221 case.expectLinkErrors(exe_reloc_err, .{
222 .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/",
223 });
224 }
223225 }
224226}
225227
test/link/snapshots/dynamic-lib-code.implib-windows.dmp deleted-32
......@@ -1,32 +0,0 @@
1 0 date
2 0 user_id
3 0 group_id
4 0 file_mode
5xxxxxxxxxxxxxxxx size
6 second_linker type
7 | 7 symbols
8 | 5 members
9xxxxxxxx __imp_foo1
10xxxxxxxx __imp_foo2
11xxxxxxxx foo1
12xxxxxxxx foo2
13 0 version
14 8664 machine (AMD64)
15 0 time_date_stamp
16xxxxxxxxxxxxxxxx size_of_data
17 0 hint
18 CODE import_type
19 NAME name_type
20 symbol name | foo1
21 import name | foo1
22 dll | dynamic-lib-code-lib.dll
23 0 version
24 8664 machine (AMD64)
25 0 time_date_stamp
26xxxxxxxxxxxxxxxx size_of_data
27 0 hint
28 CODE import_type
29 NAME name_type
30 symbol name | foo2
31 import name | foo2
32 dll | dynamic-lib-code-lib.dll
test/link/snapshots/static-lib.llvm.dmp created+15
......@@ -0,0 +1,15 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 1 NULL() STATIC | this_is_a_long_name.fooWeak
8xxxx 00000000 2 NULL STATIC | this_is_a_long_name.foo_strong
9xxxx 00000008 2 NULL STATIC | this_is_a_long_name.foo_array
10xxxx 00000000 2 NULL EXTERNAL | foo_strong
11xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
12xxxx 00000008 2 NULL EXTERNAL | foo_array
13xxxx 00000000 UNDEF NULL WEAK_EXTERNAL | fooWeak
14 | Weak External [falls back to 00000025 via SEARCH_ALIAS]
15xxxx 00000000 1 NULL() EXTERNAL | .weak.fooWeak.default.foo_strong
test/link/snapshots/static-lib.no-llvm.dmp created+12
......@@ -0,0 +1,12 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.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
12xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak