diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index ccf948372e..114a4922a2 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -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 diff --git a/src/InternPool.zig b/src/InternPool.zig index 37173377be..22defd5a96 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -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, diff --git a/src/Sema.zig b/src/Sema.zig index 00448c47ca..cd32c989ee 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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. diff --git a/src/Value.zig b/src/Value.zig index 6c5c01e2cc..be2f7dc07f 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -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, }; diff --git a/src/Zcu.zig b/src/Zcu.zig index c860e31d9f..d86fee6365 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -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, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 55b19762d6..4ec9306792 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -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, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d83e5b4e0a..f19577cac8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -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); } diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index bcc93d79b9..ce4040c127 100644 --- a/src/codegen/llvm/Builder.zig +++ b/src/codegen/llvm/Builder.zig @@ -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); } diff --git a/test/cases/compile_errors/builtin_extern_in_comptime_scope.zig b/test/cases/compile_errors/builtin_extern_in_comptime_scope.zig new file mode 100644 index 0000000000..4a85ee8aab --- /dev/null +++ b/test/cases/compile_errors/builtin_extern_in_comptime_scope.zig @@ -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 diff --git a/test/standalone/extern/build.zig b/test/standalone/extern/build.zig index a378735b66..ff8701bf40 100644 --- a/test/standalone/extern/build.zig +++ b/test/standalone/extern/build.zig @@ -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); } diff --git a/test/standalone/extern/main.zig b/test/standalone/extern/main.zig index 3f5168f841..d27c40bd80 100644 --- a/test/standalone/extern/main.zig +++ b/test/standalone/extern/main.zig @@ -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); } diff --git a/test/standalone/extern/shared.c b/test/standalone/extern/shared.c new file mode 100644 index 0000000000..845963c848 --- /dev/null +++ b/test/standalone/extern/shared.c @@ -0,0 +1,5 @@ +#ifndef API +#define API +#endif + +API int shared_val = 1234;