LLVM: support mixing extern and export with the same symbol name

This commit is contained in:
Andrew Kelley 2022-05-16 20:26:20 -07:00
parent e252f92b99
commit 00f3d84f38
2 changed files with 64 additions and 13 deletions

View File

@ -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

View File

@ -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);