Commit 00f3d84f38

Andrew Kelley <andrew@ziglang.org>
2022-05-17 05:26:20
LLVM: support mixing extern and export with the same symbol name
1 parent e252f92
Changed files (2)
src
codegen
test
behavior
src/codegen/llvm.zig
@@ -395,11 +395,11 @@ pub const Object = struct {
         return slice.ptr;
     }
 
-    fn genErrorNameTable(self: *Object, comp: *Compilation) !void {
+    fn genErrorNameTable(self: *Object) !void {
         // If self.error_name_table is null, there was no instruction that actually referenced the error table.
         const error_name_table_ptr_global = self.error_name_table orelse return;
 
-        const mod = comp.bin_file.options.module.?;
+        const mod = self.module;
         const target = mod.getTarget();
 
         const llvm_ptr_ty = self.context.intType(8).pointerType(0); // TODO: Address space
@@ -413,8 +413,8 @@ pub const Object = struct {
         const slice_alignment = slice_ty.abiAlignment(target);
 
         const error_name_list = mod.error_name_list.items;
-        const llvm_errors = try comp.gpa.alloc(*const llvm.Value, error_name_list.len);
-        defer comp.gpa.free(llvm_errors);
+        const llvm_errors = try mod.gpa.alloc(*const llvm.Value, error_name_list.len);
+        defer mod.gpa.free(llvm_errors);
 
         llvm_errors[0] = llvm_slice_ty.getUndef();
         for (llvm_errors[1..]) |*llvm_error, i| {
@@ -447,10 +447,10 @@ pub const Object = struct {
         error_name_table_ptr_global.setInitializer(error_name_table_ptr);
     }
 
-    fn genCmpLtErrorsLenFunction(object: *Object, comp: *Compilation) !void {
+    fn genCmpLtErrorsLenFunction(object: *Object) !void {
         // If there is no such function in the module, it means the source code does not need it.
         const llvm_fn = object.llvm_module.getNamedFunction(lt_errors_fn_name) orelse return;
-        const mod = comp.bin_file.options.module.?;
+        const mod = object.module;
         const errors_len = mod.global_error_set.count();
 
         // Delete previous implementation. We replace it with every flush() because the
@@ -476,10 +476,10 @@ pub const Object = struct {
         _ = builder.buildRet(is_lt);
     }
 
-    fn genModuleLevelAssembly(object: *Object, comp: *Compilation) !void {
-        const mod = comp.bin_file.options.module.?;
+    fn genModuleLevelAssembly(object: *Object) !void {
+        const mod = object.module;
         if (mod.global_assembly.count() == 0) return;
-        var buffer = std.ArrayList(u8).init(comp.gpa);
+        var buffer = std.ArrayList(u8).init(mod.gpa);
         defer buffer.deinit();
         var it = mod.global_assembly.iterator();
         while (it.next()) |kv| {
@@ -489,15 +489,53 @@ pub const Object = struct {
         object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1);
     }
 
+    fn resolveExportExternCollisions(object: *Object) !void {
+        const mod = object.module;
+
+        const export_keys = mod.decl_exports.keys();
+        for (mod.decl_exports.values()) |export_list, i| {
+            const decl_index = export_keys[i];
+            const llvm_global = object.decl_map.get(decl_index) orelse continue;
+            for (export_list) |exp| {
+                // Detect if the LLVM global has already been created as an extern. In such
+                // case, we need to replace all uses of it with this exported global.
+                // TODO update std.builtin.ExportOptions to have the name be a
+                // null-terminated slice.
+                const exp_name_z = try mod.gpa.dupeZ(u8, exp.options.name);
+                defer mod.gpa.free(exp_name_z);
+
+                const other_global = object.getLlvmGlobal(exp_name_z.ptr) orelse continue;
+                if (other_global == llvm_global) continue;
+
+                // replaceAllUsesWith requires the type to be unchanged. So we bitcast
+                // the new global to the old type and use that as the thing to replace
+                // old uses.
+                const new_global_ptr = llvm_global.constBitCast(other_global.typeOf());
+                other_global.replaceAllUsesWith(new_global_ptr);
+                llvm_global.takeName(other_global);
+                other_global.deleteGlobal();
+                // Problem: now we need to replace in the decl_map that
+                // the extern decl index points to this new global. However we don't
+                // know the decl index.
+                // Even if we did, a future incremental update to the extern would then
+                // treat the LLVM global as an extern rather than an export, so it would
+                // need a way to check that.
+                // This is a TODO that needs to be solved when making
+                // the LLVM backend support incremental compilation.
+            }
+        }
+    }
+
     pub fn flushModule(self: *Object, comp: *Compilation, prog_node: *std.Progress.Node) !void {
         var sub_prog_node = prog_node.start("LLVM Emit Object", 0);
         sub_prog_node.activate();
         sub_prog_node.context.refresh();
         defer sub_prog_node.end();
 
-        try self.genErrorNameTable(comp);
-        try self.genCmpLtErrorsLenFunction(comp);
-        try self.genModuleLevelAssembly(comp);
+        try self.resolveExportExternCollisions();
+        try self.genErrorNameTable();
+        try self.genCmpLtErrorsLenFunction();
+        try self.genModuleLevelAssembly();
 
         if (self.di_builder) |dib| {
             // When lowering debug info for pointers, we emitted the element types as
@@ -761,6 +799,14 @@ pub const Object = struct {
         try self.updateDeclExports(module, decl_index, decl_exports);
     }
 
+    /// TODO replace this with a call to `Module::getNamedValue`. This will require adding
+    /// a new wrapper in zig_llvm.h/zig_llvm.cpp.
+    fn getLlvmGlobal(o: Object, name: [*:0]const u8) ?*const llvm.Value {
+        if (o.llvm_module.getNamedFunction(name)) |x| return x;
+        if (o.llvm_module.getNamedGlobal(name)) |x| return x;
+        return null;
+    }
+
     pub fn updateDeclExports(
         self: *Object,
         module: *Module,
@@ -827,6 +873,7 @@ pub const Object = struct {
                     llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel);
                 }
             }
+
             // If a Decl is exported more than one time (which is rare),
             // we add aliases for all but the first export.
             // TODO LLVM C API does not support deleting aliases. We need to
test/behavior/basic.zig
@@ -797,7 +797,11 @@ test "auto created variables have correct alignment" {
 }
 
 test "extern variable with non-pointer opaque type" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     @export(var_to_export, .{ .name = "opaque_extern_var" });
     try expect(@ptrCast(*align(1) u32, &opaque_extern_var).* == 42);