From 00f3d84f38c54e70716cf8e2908c899b49de1d88 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 16 May 2022 20:26:20 -0700 Subject: LLVM: support mixing extern and export with the same symbol name --- src/codegen/llvm.zig | 71 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 12 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index dfb0f8f03b..fb7a2c39bc 100644 --- a/src/codegen/llvm.zig +++ b/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 -- cgit v1.2.3