From 5aef08aa09ffbb7c1ea4dff7d5f15dc147ec2ecb Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 16 Oct 2026 11:25:08 +0100 Subject: [PATCH] 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. --- lib/std/zig/llvm/Builder.zig | 2 +- src/codegen/llvm.zig | 40 +++++++++++------------------ src/link/Elf/ZigObject.zig | 15 ++++++++--- test/behavior/extern.zig | 17 ++++++++++++ test/cases/export_override_libc.zig | 2 ++ 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 8677b40b95f6df2972819665005bf53c78182d25..9b6016a0295df7232be402d9a8e01a1884f52ece 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -3467,7 +3467,7 @@ pub const Global = struct { try builder.aliases.ensureUnusedCapacity(builder.gpa, 1); errdefer comptime unreachable; const alias: Alias.Index = @fromBackingInt(@intCast(builder.aliases.items.len)); - builder.aliass.appendAssumeCapacity(.{ .global = global, .aliasee = .none }); + builder.aliases.appendAssumeCapacity(.{ .global = global, .aliasee = .none }); global.ptr(builder).kind = .{ .alias = alias }; return alias; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d924136dc17c98aa65c207c3c37deb577253f6a8..9b7dc14533385fcf29d7d0367ea468c1e3e52efe 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1234,7 +1234,6 @@ pub const Object = struct { // The name, aliasee, and type will be set within this block. Other properties of the // alias will be set below. const alias_global: Builder.Global.Index = global: { - // WORKAROUND (see https://github.com/llvm/llvm-project/issues/213504, https://github.com/llvm/llvm-project/issues/214835) // For NVPTX, LLVM throws "NVPTX aliasee must be a non-kernel function definition" if we try to alias a kernel // On AMDGCN, LLVM does not generate an alias for the kernel descriptor symbol on associated functions @@ -1256,33 +1255,24 @@ pub const Object = struct { }; // There is an existing global with this name, so we can't just create an alias. We // need to figure out what to do with the existing global instead. - switch (existing_global.ptrConst(&o.builder).kind) { - .alias => |alias| { - // We can just repurpose the existing alias. - alias.setAliasee(llvm_global.toConst(), &o.builder); - alias.ptrConst(&o.builder).global.ptr(&o.builder).type = llvm_global.typeOf(&o.builder); - alias.ptrConst(&o.builder).global.ptr(&o.builder).addr_space = llvm_global.ptrConst(&o.builder).addr_space; - break :global existing_global; + const alias: Builder.Alias.Index = switch (existing_global.ptrConst(&o.builder).kind) { + .alias => |alias| alias: { + // The existing alias is from an earlier export, so if `exp` is weak, ignore it. + if (exp.opts.linkage == .weak) return; + // But otherwise, we can just repurpose the existing alias. + break :alias alias; }, - .variable, .function => { - // This must be an extern, which is no good to us---we need an alias. The - // extern should refer to the value we're exporting, so replace it with the - // exported value. That will free up the name for us to create a new alias. - // We need to make a new global which is an alias. Replace this existing one - // with the target global, making the name available and fixing references - // to this global to point to the target. - try existing_global.replace(llvm_global, &o.builder); - // The name is now free, so create an alias. - const alias = try o.builder.addAlias( - exp_name, - llvm_global_ty, - llvm_global.ptrConst(&o.builder).addr_space, - llvm_global.toConst(), - ); - break :global alias.ptrConst(&o.builder).global; + .variable, .function => alias: { + // This is an extern which our export resolves, so we can simply convert this + // global into the alias we want. + break :alias try existing_global.toNewAlias(&o.builder); }, .replaced => unreachable, // a replaced global would have lost the name `exp_name` - } + }; + alias.setAliasee(llvm_global.toConst(), &o.builder); + alias.ptrConst(&o.builder).global.ptr(&o.builder).type = llvm_global_ty; + alias.ptrConst(&o.builder).global.ptr(&o.builder).addr_space = llvm_global.ptrConst(&o.builder).addr_space; + break :global existing_global; }; // We need the alias to *not* be `unnamed_addr` to ensure that the alias address equals diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 8bb2212f4af421de651ac0f7f7933aa99d5b61d4..1f90d961a7e9f963aceed75e90e5c878f84f1c89 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -1871,6 +1871,17 @@ pub fn updateExports( for (export_indices) |export_index| { const exp = export_index.ptr(zcu); + const exp_name = exp.opts.name.toSlice(&zcu.intern_pool); + + const global_sym_index = try self.getGlobalSymbol(elf_file, exp_name, null); + const global_sym = self.symbol(global_sym_index); + const global_esym = &self.symtab.items(.elf_sym)[global_sym.esym_index]; + + if (global_esym.st_shndx != std.elf.SHN_UNDEF and exp.opts.linkage == .weak) { + // The symbol is already defined by a previous export, so ignore this weak export. + continue; + } + const metadata = switch (exp.exported) { .nav => |nav| blk: { _ = try self.getOrCreateMetadataForNav(zcu, nav); @@ -1902,18 +1913,14 @@ pub fn updateExports( .weak => elf.STB_WEAK, }; const stt_bits: u8 = @as(u4, @truncate(esym.st_info)); - const exp_name = exp.opts.name.toSlice(&zcu.intern_pool); const name_off = try self.strtab.insert(gpa, exp_name); - const global_sym_index = try self.getGlobalSymbol(elf_file, exp_name, null); try metadata.exports.append(gpa, global_sym_index); const value = self.symbol(sym_index).value; - const global_sym = self.symbol(global_sym_index); global_sym.value = value; global_sym.flags.weak = exp.opts.linkage == .weak; global_sym.version_index = elf_file.default_sym_version; global_sym.ref = .{ .index = esym_shndx, .file = self.index }; - const global_esym = &self.symtab.items(.elf_sym)[global_sym.esym_index]; global_esym.st_value = @intCast(value); global_esym.st_shndx = esym.st_shndx; global_esym.st_info = (stb_bits << 4) | stt_bits; diff --git a/test/behavior/extern.zig b/test/behavior/extern.zig index ed95406aa889d361abbf87a79764eb23d62021ca..f9e2f853ac3107a7918ffacd439c98a51de270b4 100644 --- a/test/behavior/extern.zig +++ b/test/behavior/extern.zig @@ -66,3 +66,20 @@ test "pass extern function to function" { } export fn an_extern_function() void {} + +test "@extern binds to strong @export over weak @export" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/36986 + + const sym_name = "extern_binds_to_strong_export_over_weak_export"; + + const weak_0: u8 = 42; + const strong: u8 = 123; + const weak_1: u8 = 255; + + @export(&weak_0, .{ .name = sym_name, .linkage = .weak }); + @export(&strong, .{ .name = sym_name, .linkage = .strong }); + @export(&weak_1, .{ .name = sym_name, .linkage = .weak }); + + const extern_ptr = @extern(*const u8, .{ .name = sym_name }); + try expect(extern_ptr.* == strong); +} diff --git a/test/cases/export_override_libc.zig b/test/cases/export_override_libc.zig index bc16cb8dd2cdfcb0f09da2da83715b2ec9715321..fdd5aa2ce8eaf4a00bfcf8f6ab2fd95e349326b4 100644 --- a/test/cases/export_override_libc.zig +++ b/test/cases/export_override_libc.zig @@ -13,4 +13,6 @@ pub fn main() void { } // run +// target=x86_64-linux-gnu,x86_64-linux-musl +// backend=selfhosted,llvm // link_libc=true -- 2.54.0