Merge pull request #21758 from kcbanner/dll_storage_class

Add `is_dll_import` to @extern, to support `__declspec(dllimport)` with the MSVC ABI
This commit is contained in:
Andrew Kelley
2024-10-23 15:35:54 -07:00
committed by GitHub
12 changed files with 99 additions and 19 deletions

View File

@@ -969,6 +969,7 @@ pub const ExternOptions = struct {
library_name: ?[]const u8 = null,
linkage: GlobalLinkage = .strong,
is_thread_local: bool = false,
is_dll_import: bool = false,
};
/// This data structure is used by the Zig language code generation and

View File

@@ -2054,6 +2054,7 @@ pub const Key = union(enum) {
is_const: bool,
is_threadlocal: bool,
is_weak_linkage: bool,
is_dll_import: bool,
alignment: Alignment,
@"addrspace": std.builtin.AddressSpace,
/// The ZIR instruction which created this extern; used only for source locations.
@@ -2675,7 +2676,8 @@ pub const Key = union(enum) {
asBytes(&e.ty) ++ asBytes(&e.lib_name) ++
asBytes(&e.is_const) ++ asBytes(&e.is_threadlocal) ++
asBytes(&e.is_weak_linkage) ++ asBytes(&e.alignment) ++
asBytes(&e.@"addrspace") ++ asBytes(&e.zir_index)),
asBytes(&e.is_dll_import) ++ asBytes(&e.@"addrspace") ++
asBytes(&e.zir_index)),
};
}
@@ -2771,6 +2773,7 @@ pub const Key = union(enum) {
a_info.is_const == b_info.is_const and
a_info.is_threadlocal == b_info.is_threadlocal and
a_info.is_weak_linkage == b_info.is_weak_linkage and
a_info.is_dll_import == b_info.is_dll_import and
a_info.alignment == b_info.alignment and
a_info.@"addrspace" == b_info.@"addrspace" and
a_info.zir_index == b_info.zir_index;
@@ -5370,7 +5373,8 @@ pub const Tag = enum(u8) {
is_const: bool,
is_threadlocal: bool,
is_weak_linkage: bool,
_: u29 = 0,
is_dll_import: bool,
_: u28 = 0,
};
};
@@ -6714,6 +6718,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
.is_const = extra.flags.is_const,
.is_threadlocal = extra.flags.is_threadlocal,
.is_weak_linkage = extra.flags.is_weak_linkage,
.is_dll_import = extra.flags.is_dll_import,
.alignment = nav.status.resolved.alignment,
.@"addrspace" = nav.status.resolved.@"addrspace",
.zir_index = extra.zir_index,
@@ -7380,6 +7385,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
.is_const = false,
.is_threadlocal = variable.is_threadlocal,
.is_weak_linkage = variable.is_weak_linkage,
.is_dll_import = false,
},
}),
});
@@ -8643,6 +8649,7 @@ pub fn getExtern(
.is_const = key.is_const,
.is_threadlocal = key.is_threadlocal,
.is_weak_linkage = key.is_weak_linkage,
.is_dll_import = key.is_dll_import,
},
.zir_index = key.zir_index,
.owner_nav = owner_nav,

View File

@@ -876,6 +876,7 @@ const InferredAlloc = struct {
const NeededComptimeReason = struct {
needed_comptime_reason: []const u8,
value_comptime_reason: ?[]const u8 = null,
block_comptime_reason: ?*const Block.ComptimeReason = null,
};
@@ -2246,7 +2247,7 @@ fn resolveValueAllowVariables(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Val
}
};
const val = Value.fromInterned(ip_index);
if (val.isPtrToThreadLocal(pt.zcu)) return null;
if (val.isPtrRuntimeValue(pt.zcu)) return null;
return val;
}
@@ -2272,8 +2273,14 @@ pub fn resolveFinalDeclValue(
const zcu = sema.pt.zcu;
const val = try sema.resolveValueAllowVariables(air_ref) orelse {
const value_comptime_reason: ?[]const u8 = if (air_ref.toInterned()) |_|
"thread local and dll imported variables have runtime-known addresses"
else
null;
return sema.failWithNeededComptime(block, src, .{
.needed_comptime_reason = "global variable initializer must be comptime-known",
.value_comptime_reason = value_comptime_reason,
});
};
if (val.isGenericPoison()) return error.GenericPoison;
@@ -2291,6 +2298,9 @@ fn failWithNeededComptime(sema: *Sema, block: *Block, src: LazySrcLoc, reason: N
const msg = try sema.errMsg(src, "unable to resolve comptime value", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "{s}", .{reason.needed_comptime_reason});
if (reason.value_comptime_reason) |value_comptime_reason| {
try sema.errNote(src, msg, "{s}", .{value_comptime_reason});
}
if (reason.block_comptime_reason) |block_comptime_reason| {
try block_comptime_reason.explain(sema, msg);
@@ -10023,6 +10033,7 @@ fn funcCommon(
.is_const = true,
.is_threadlocal = false,
.is_weak_linkage = false,
.is_dll_import = false,
.alignment = alignment orelse .none,
.@"addrspace" = address_space orelse .generic,
.zir_index = sema.getOwnerCauDeclInst(), // `declaration` instruction
@@ -26577,6 +26588,7 @@ fn zirVarExtended(
.is_const = small.is_const,
.is_threadlocal = small.is_threadlocal,
.is_weak_linkage = false,
.is_dll_import = false,
.alignment = alignment,
.@"addrspace" = @"addrspace",
.zir_index = sema.getOwnerCauDeclInst(), // `declaration` instruction
@@ -27030,6 +27042,7 @@ fn resolveExternOptions(
library_name: InternPool.OptionalNullTerminatedString = .none,
linkage: std.builtin.GlobalLinkage = .strong,
is_thread_local: bool = false,
is_dll_import: bool = false,
} {
const pt = sema.pt;
const zcu = pt.zcu;
@@ -27043,6 +27056,7 @@ fn resolveExternOptions(
const library_src = block.src(.{ .init_field_library = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const linkage_src = block.src(.{ .init_field_linkage = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const thread_local_src = block.src(.{ .init_field_thread_local = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const dll_import_src = block.src(.{ .init_field_dll_import = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls), name_src);
const name = try sema.toConstString(block, name_src, name_ref, .{
@@ -27076,6 +27090,11 @@ fn resolveExternOptions(
break :library_name library_name;
} else null;
const is_dll_import_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "is_dll_import", .no_embedded_nulls), dll_import_src);
const is_dll_import_val = try sema.resolveConstDefinedValue(block, dll_import_src, is_dll_import_ref, .{
.needed_comptime_reason = "it must be comptime-known if the symbol is imported from a dll",
});
if (name.len == 0) {
return sema.fail(block, name_src, "extern symbol name cannot be empty", .{});
}
@@ -27089,6 +27108,7 @@ fn resolveExternOptions(
.library_name = try ip.getOrPutStringOpt(gpa, pt.tid, library_name, .no_embedded_nulls),
.linkage = linkage,
.is_thread_local = is_thread_local_val.toBool(),
.is_dll_import = is_dll_import_val.toBool(),
};
}
@@ -27134,6 +27154,7 @@ fn zirBuiltinExtern(
.is_const = ptr_info.flags.is_const,
.is_threadlocal = options.is_thread_local,
.is_weak_linkage = options.linkage == .weak,
.is_dll_import = options.is_dll_import,
.alignment = ptr_info.flags.alignment,
.@"addrspace" = ptr_info.flags.address_space,
// This instruction is just for source locations.

View File

@@ -1340,11 +1340,11 @@ pub fn isLazySize(val: Value, zcu: *Zcu) bool {
};
}
pub fn isPtrToThreadLocal(val: Value, zcu: *Zcu) bool {
pub fn isPtrRuntimeValue(val: Value, zcu: *Zcu) bool {
const ip = &zcu.intern_pool;
const nav = ip.getBackingNav(val.toIntern()).unwrap() orelse return false;
return switch (ip.indexToKey(ip.getNav(nav).status.resolved.val)) {
.@"extern" => |e| e.is_threadlocal,
.@"extern" => |e| e.is_threadlocal or e.is_dll_import,
.variable => |v| v.is_threadlocal,
else => false,
};

View File

@@ -1522,6 +1522,7 @@ pub const SrcLoc = struct {
.init_field_cache,
.init_field_library,
.init_field_thread_local,
.init_field_dll_import,
=> |builtin_call_node| {
const wanted = switch (src_loc.lazy) {
.init_field_name => "name",
@@ -1533,6 +1534,7 @@ pub const SrcLoc = struct {
.init_field_cache => "cache",
.init_field_library => "library",
.init_field_thread_local => "thread_local",
.init_field_dll_import => "dll_import",
else => unreachable,
};
const tree = try src_loc.file_scope.getTree(gpa);
@@ -1959,6 +1961,7 @@ pub const LazySrcLoc = struct {
init_field_cache: i32,
init_field_library: i32,
init_field_thread_local: i32,
init_field_dll_import: i32,
/// The source location points to the value of an item in a specific
/// case of a `switch`.
switch_case_item: SwitchItem,

View File

@@ -2763,6 +2763,7 @@ pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!V
.is_const = e.is_const,
.is_threadlocal = e.is_threadlocal,
.is_weak_linkage = e.is_weak_linkage,
.is_dll_import = e.is_dll_import,
.alignment = e.alignment,
.@"addrspace" = e.@"addrspace",
.zir_index = e.zir_index,

View File

@@ -3260,10 +3260,10 @@ pub const Object = struct {
const ip = &zcu.intern_pool;
const nav = ip.getNav(nav_index);
const resolved = nav.status.resolved;
const is_extern, const is_threadlocal, const is_weak_linkage = switch (ip.indexToKey(resolved.val)) {
.variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage },
.@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage },
else => .{ false, false, false },
const is_extern, const is_threadlocal, const is_weak_linkage, const is_dll_import = switch (ip.indexToKey(resolved.val)) {
.variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage, false },
.@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import },
else => .{ false, false, false, false },
};
const variable_index = try o.builder.addVariable(
@@ -3280,6 +3280,7 @@ pub const Object = struct {
if (is_threadlocal and !zcu.navFileScope(nav_index).mod.single_threaded)
variable_index.setThreadLocal(.generaldynamic, &o.builder);
if (is_weak_linkage) variable_index.setLinkage(.extern_weak, &o.builder);
if (is_dll_import) variable_index.setDllStorageClass(.dllimport, &o.builder);
} else {
variable_index.setLinkage(.internal, &o.builder);
variable_index.setUnnamedAddr(.unnamed_addr, &o.builder);
@@ -4810,10 +4811,10 @@ pub const NavGen = struct {
const nav = ip.getNav(nav_index);
const resolved = nav.status.resolved;
const is_extern, const lib_name, const is_threadlocal, const is_weak_linkage, const is_const, const init_val, const owner_nav = switch (ip.indexToKey(resolved.val)) {
.variable => |variable| .{ false, variable.lib_name, variable.is_threadlocal, variable.is_weak_linkage, false, variable.init, variable.owner_nav },
.@"extern" => |@"extern"| .{ true, @"extern".lib_name, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_const, .none, @"extern".owner_nav },
else => .{ false, .none, false, false, true, resolved.val, nav_index },
const is_extern, const lib_name, const is_threadlocal, const is_weak_linkage, const is_dll_import, const is_const, const init_val, const owner_nav = switch (ip.indexToKey(resolved.val)) {
.variable => |variable| .{ false, variable.lib_name, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav },
.@"extern" => |@"extern"| .{ true, @"extern".lib_name, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import, @"extern".is_const, .none, @"extern".owner_nav },
else => .{ false, .none, false, false, false, true, resolved.val, nav_index },
};
const ty = Type.fromInterned(nav.typeOf(ip));
@@ -4888,8 +4889,11 @@ pub const NavGen = struct {
try global_index.rename(decl_name, &o.builder);
global_index.setLinkage(.external, &o.builder);
global_index.setUnnamedAddr(.default, &o.builder);
if (zcu.comp.config.dll_export_fns)
if (is_dll_import) {
global_index.setDllStorageClass(.dllimport, &o.builder);
} else if (zcu.comp.config.dll_export_fns) {
global_index.setDllStorageClass(.default, &o.builder);
}
if (is_weak_linkage) global_index.setLinkage(.extern_weak, &o.builder);
}

View File

@@ -2541,6 +2541,10 @@ pub const Variable = struct {
return self.ptrConst(builder).global.setLinkage(linkage, builder);
}
pub fn setDllStorageClass(self: Index, class: DllStorageClass, builder: *Builder) void {
return self.ptrConst(builder).global.setDllStorageClass(class, builder);
}
pub fn setUnnamedAddr(self: Index, unnamed_addr: UnnamedAddr, builder: *Builder) void {
return self.ptrConst(builder).global.setUnnamedAddr(unnamed_addr, builder);
}

View File

@@ -0,0 +1,18 @@
const foo_tl = @extern(*i32, .{ .name = "foo", .is_thread_local = true });
const foo_dll = @extern(*i32, .{ .name = "foo", .is_dll_import = true });
pub export fn entry() void {
_ = foo_tl;
}
pub export fn entry2() void {
_ = foo_dll;
}
// error
// backend=stage2
// target=native
//
// :1:16: error: unable to resolve comptime value
// :1:16: note: global variable initializer must be comptime-known
// :1:16: note: thread local and dll imported variables have runtime-known addresses
// :2:17: error: unable to resolve comptime value
// :2:17: note: global variable initializer must be comptime-known
// :2:17: note: thread local and dll imported variables have runtime-known addresses

View File

@@ -1,6 +1,9 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const optimize: std.builtin.OptimizeMode = .Debug;
const obj = b.addObject(.{
@@ -9,12 +12,20 @@ pub fn build(b: *std.Build) void {
.target = b.graph.host,
.optimize = optimize,
});
const main = b.addTest(.{
const shared = b.addSharedLibrary(.{
.name = "shared",
.target = b.graph.host,
.optimize = optimize,
.link_libc = true,
});
if (b.graph.host.result.abi == .msvc) shared.defineCMacro("API", "__declspec(dllexport)");
shared.addCSourceFile(.{ .file = b.path("shared.c"), .flags = &.{} });
const test_exe = b.addTest(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
});
main.addObject(obj);
test_exe.addObject(obj);
test_exe.linkLibrary(shared);
const test_step = b.step("test", "Test it");
test_step.dependOn(&main.step);
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}

View File

@@ -1,4 +1,5 @@
const assert = @import("std").debug.assert;
const testing = @import("std").testing;
const updateHidden = @extern(*const fn (u32) callconv(.C) void, .{ .name = "updateHidden" });
const getHidden = @extern(*const fn () callconv(.C) u32, .{ .name = "getHidden" });
@@ -8,14 +9,18 @@ const T = extern struct { x: u32 };
test {
const mut_val_ptr = @extern(*f64, .{ .name = "mut_val" });
const const_val_ptr = @extern(*const T, .{ .name = "const_val" });
const shared_val_ptr = @extern(*c_int, .{ .name = "shared_val", .is_dll_import = true });
assert(getHidden() == 0);
updateHidden(123);
assert(getHidden() == 123);
assert(mut_val_ptr.* == 1.23);
mut_val_ptr.* = 10.0;
assert(mut_val_ptr.* == 10.0);
assert(const_val_ptr.x == 42);
assert(shared_val_ptr.* == 1234);
shared_val_ptr.* = 1235;
assert(shared_val_ptr.* == 1235);
}

5
test/standalone/extern/shared.c vendored Normal file
View File

@@ -0,0 +1,5 @@
#ifndef API
#define API
#endif
API int shared_val = 1234;