authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-16 11:25:08+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-17 04:25:01+02:00
log5aef08aa09ffbb7c1ea4dff7d5f15dc147ec2ecb
tree2584faae3b5187ff3c95909b09f98d711caf6524
parent58baf6dcdfe5fa4a496e8048e9ba6750719c4e55
signature Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

llvm,Elf: fix strong exports overriding weak exports

Partially resolves https://codeberg.org/ziglang/zig/issues/36986, but does not folly resolve it---the bug still exists in the C backend and the self-hosted WASM and Mach-O linkers.

5 files changed, 46 insertions(+), 30 deletions(-)

lib/std/zig/llvm/Builder.zig+1-1
...@@ -3467,7 +3467,7 @@ pub const Global = struct {...@@ -3467,7 +3467,7 @@ pub const Global = struct {
3467 try builder.aliases.ensureUnusedCapacity(builder.gpa, 1);3467 try builder.aliases.ensureUnusedCapacity(builder.gpa, 1);
3468 errdefer comptime unreachable;3468 errdefer comptime unreachable;
3469 const alias: Alias.Index = @fromBackingInt(@intCast(builder.aliases.items.len));3469 const alias: Alias.Index = @fromBackingInt(@intCast(builder.aliases.items.len));
3470 builder.aliass.appendAssumeCapacity(.{ .global = global, .aliasee = .none });3470 builder.aliases.appendAssumeCapacity(.{ .global = global, .aliasee = .none });
3471 global.ptr(builder).kind = .{ .alias = alias };3471 global.ptr(builder).kind = .{ .alias = alias };
3472 return alias;3472 return alias;
3473 }3473 }
src/codegen/llvm.zig+15-25
...@@ -1234,7 +1234,6 @@ pub const Object = struct {...@@ -1234,7 +1234,6 @@ pub const Object = struct {
1234 // The name, aliasee, and type will be set within this block. Other properties of the1234 // The name, aliasee, and type will be set within this block. Other properties of the
1235 // alias will be set below.1235 // alias will be set below.
1236 const alias_global: Builder.Global.Index = global: {1236 const alias_global: Builder.Global.Index = global: {
1237
1238 // WORKAROUND (see https://github.com/llvm/llvm-project/issues/213504, https://github.com/llvm/llvm-project/issues/214835)1237 // WORKAROUND (see https://github.com/llvm/llvm-project/issues/213504, https://github.com/llvm/llvm-project/issues/214835)
1239 // For NVPTX, LLVM throws "NVPTX aliasee must be a non-kernel function definition" if we try to alias a kernel1238 // For NVPTX, LLVM throws "NVPTX aliasee must be a non-kernel function definition" if we try to alias a kernel
1240 // On AMDGCN, LLVM does not generate an alias for the kernel descriptor symbol on associated functions1239 // On AMDGCN, LLVM does not generate an alias for the kernel descriptor symbol on associated functions
...@@ -1256,33 +1255,24 @@ pub const Object = struct {...@@ -1256,33 +1255,24 @@ pub const Object = struct {
1256 };1255 };
1257 // There is an existing global with this name, so we can't just create an alias. We1256 // There is an existing global with this name, so we can't just create an alias. We
1258 // need to figure out what to do with the existing global instead.1257 // need to figure out what to do with the existing global instead.
1259 switch (existing_global.ptrConst(&o.builder).kind) {1258 const alias: Builder.Alias.Index = switch (existing_global.ptrConst(&o.builder).kind) {
1260 .alias => |alias| {1259 .alias => |alias| alias: {
1261 // We can just repurpose the existing alias.1260 // The existing alias is from an earlier export, so if `exp` is weak, ignore it.
1262 alias.setAliasee(llvm_global.toConst(), &o.builder);1261 if (exp.opts.linkage == .weak) return;
1263 alias.ptrConst(&o.builder).global.ptr(&o.builder).type = llvm_global.typeOf(&o.builder);1262 // But otherwise, we can just repurpose the existing alias.
1264 alias.ptrConst(&o.builder).global.ptr(&o.builder).addr_space = llvm_global.ptrConst(&o.builder).addr_space;1263 break :alias alias;
1265 break :global existing_global;
1266 },1264 },
1267 .variable, .function => {1265 .variable, .function => alias: {
1268 // This must be an extern, which is no good to us---we need an alias. The1266 // This is an extern which our export resolves, so we can simply convert this
1269 // extern should refer to the value we're exporting, so replace it with the1267 // global into the alias we want.
1270 // exported value. That will free up the name for us to create a new alias.1268 break :alias try existing_global.toNewAlias(&o.builder);
1271 // We need to make a new global which is an alias. Replace this existing one
1272 // with the target global, making the name available and fixing references
1273 // to this global to point to the target.
1274 try existing_global.replace(llvm_global, &o.builder);
1275 // The name is now free, so create an alias.
1276 const alias = try o.builder.addAlias(
1277 exp_name,
1278 llvm_global_ty,
1279 llvm_global.ptrConst(&o.builder).addr_space,
1280 llvm_global.toConst(),
1281 );
1282 break :global alias.ptrConst(&o.builder).global;
1283 },1269 },
1284 .replaced => unreachable, // a replaced global would have lost the name `exp_name`1270 .replaced => unreachable, // a replaced global would have lost the name `exp_name`
1285 }1271 };
1272 alias.setAliasee(llvm_global.toConst(), &o.builder);
1273 alias.ptrConst(&o.builder).global.ptr(&o.builder).type = llvm_global_ty;
1274 alias.ptrConst(&o.builder).global.ptr(&o.builder).addr_space = llvm_global.ptrConst(&o.builder).addr_space;
1275 break :global existing_global;
1286 };1276 };
12871277
1288 // We need the alias to *not* be `unnamed_addr` to ensure that the alias address equals1278 // We need the alias to *not* be `unnamed_addr` to ensure that the alias address equals
src/link/Elf/ZigObject.zig+11-4
...@@ -1871,6 +1871,17 @@ pub fn updateExports(...@@ -1871,6 +1871,17 @@ pub fn updateExports(
18711871
1872 for (export_indices) |export_index| {1872 for (export_indices) |export_index| {
1873 const exp = export_index.ptr(zcu);1873 const exp = export_index.ptr(zcu);
1874 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
1875
1876 const global_sym_index = try self.getGlobalSymbol(elf_file, exp_name, null);
1877 const global_sym = self.symbol(global_sym_index);
1878 const global_esym = &self.symtab.items(.elf_sym)[global_sym.esym_index];
1879
1880 if (global_esym.st_shndx != std.elf.SHN_UNDEF and exp.opts.linkage == .weak) {
1881 // The symbol is already defined by a previous export, so ignore this weak export.
1882 continue;
1883 }
1884
1874 const metadata = switch (exp.exported) {1885 const metadata = switch (exp.exported) {
1875 .nav => |nav| blk: {1886 .nav => |nav| blk: {
1876 _ = try self.getOrCreateMetadataForNav(zcu, nav);1887 _ = try self.getOrCreateMetadataForNav(zcu, nav);
...@@ -1902,18 +1913,14 @@ pub fn updateExports(...@@ -1902,18 +1913,14 @@ pub fn updateExports(
1902 .weak => elf.STB_WEAK,1913 .weak => elf.STB_WEAK,
1903 };1914 };
1904 const stt_bits: u8 = @as(u4, @truncate(esym.st_info));1915 const stt_bits: u8 = @as(u4, @truncate(esym.st_info));
1905 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
1906 const name_off = try self.strtab.insert(gpa, exp_name);1916 const name_off = try self.strtab.insert(gpa, exp_name);
1907 const global_sym_index = try self.getGlobalSymbol(elf_file, exp_name, null);
1908 try metadata.exports.append(gpa, global_sym_index);1917 try metadata.exports.append(gpa, global_sym_index);
19091918
1910 const value = self.symbol(sym_index).value;1919 const value = self.symbol(sym_index).value;
1911 const global_sym = self.symbol(global_sym_index);
1912 global_sym.value = value;1920 global_sym.value = value;
1913 global_sym.flags.weak = exp.opts.linkage == .weak;1921 global_sym.flags.weak = exp.opts.linkage == .weak;
1914 global_sym.version_index = elf_file.default_sym_version;1922 global_sym.version_index = elf_file.default_sym_version;
1915 global_sym.ref = .{ .index = esym_shndx, .file = self.index };1923 global_sym.ref = .{ .index = esym_shndx, .file = self.index };
1916 const global_esym = &self.symtab.items(.elf_sym)[global_sym.esym_index];
1917 global_esym.st_value = @intCast(value);1924 global_esym.st_value = @intCast(value);
1918 global_esym.st_shndx = esym.st_shndx;1925 global_esym.st_shndx = esym.st_shndx;
1919 global_esym.st_info = (stb_bits << 4) | stt_bits;1926 global_esym.st_info = (stb_bits << 4) | stt_bits;
test/behavior/extern.zig+17
...@@ -66,3 +66,20 @@ test "pass extern function to function" {...@@ -66,3 +66,20 @@ test "pass extern function to function" {
66}66}
6767
68export fn an_extern_function() void {}68export fn an_extern_function() void {}
69
70test "@extern binds to strong @export over weak @export" {
71 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/36986
72
73 const sym_name = "extern_binds_to_strong_export_over_weak_export";
74
75 const weak_0: u8 = 42;
76 const strong: u8 = 123;
77 const weak_1: u8 = 255;
78
79 @export(&weak_0, .{ .name = sym_name, .linkage = .weak });
80 @export(&strong, .{ .name = sym_name, .linkage = .strong });
81 @export(&weak_1, .{ .name = sym_name, .linkage = .weak });
82
83 const extern_ptr = @extern(*const u8, .{ .name = sym_name });
84 try expect(extern_ptr.* == strong);
85}
test/cases/export_override_libc.zig+2
...@@ -13,4 +13,6 @@ pub fn main() void {...@@ -13,4 +13,6 @@ pub fn main() void {
13}13}
1414
15// run15// run
16// target=x86_64-linux-gnu,x86_64-linux-musl
17// backend=selfhosted,llvm
16// link_libc=true18// link_libc=true