diff --git a/src/codegen.cpp b/src/codegen.cpp index 3f3d80d51d..246ecb4369 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -7754,6 +7754,13 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n } static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name) { + if (const_val->special == ConstValSpecialRuntime) { + // `const_val` refers to an extern variable. Don't generate an `LLVMValueRef` for + // the variable. We shouldn't call `LLVMSetInitializer` on it either. + assert(const_val->llvm_global); + return; + } + if (!const_val->llvm_value) const_val->llvm_value = gen_const_val(g, const_val, name); @@ -7762,6 +7769,13 @@ static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name) } static void render_const_val_global(CodeGen *g, ZigValue *const_val, const char *name) { + if (const_val->special == ConstValSpecialRuntime) { + // `const_val` refers to an extern variable. `llvm_global` should already + // have been created by an earlier codegen pass. + assert(const_val->llvm_global); + return; + } + if (!const_val->llvm_global) { LLVMTypeRef type_ref = const_val->llvm_value ? LLVMTypeOf(const_val->llvm_value) : get_llvm_type(g, const_val->type); @@ -7891,6 +7905,39 @@ static void do_code_gen(CodeGen *g) { generate_error_name_table(g); + // Create extern variables + for (size_t i = 0; i < g->global_vars.length; i += 1) { + TldVar *tld_var = g->global_vars.at(i); + ZigVar *var = tld_var->var; + + bool externally_initialized = var->decl_node->data.variable_declaration.expr == nullptr; + if (!externally_initialized) { + continue; + } + + assert(var->decl_node->data.variable_declaration.is_extern); + const char *symbol_name = var->name; + + LLVMValueRef global_value; + LLVMValueRef existing_llvm_var = LLVMGetNamedGlobal(g->module, symbol_name); + if (existing_llvm_var) { + global_value = LLVMConstBitCast(existing_llvm_var, + LLVMPointerType(get_llvm_type(g, var->var_type), 0)); + } else { + global_value = LLVMAddGlobal(g->module, get_llvm_type(g, var->var_type), symbol_name); + // TODO debug info for the extern variable + + LLVMSetLinkage(global_value, LLVMExternalLinkage); + maybe_import_dll(g, global_value, GlobalLinkageIdStrong); + LLVMSetAlignment(global_value, var->align_bytes); + LLVMSetGlobalConstant(global_value, var->gen_is_const); + set_global_tls(g, var, global_value); + } + + var->value_ref = global_value; + var->const_value->llvm_global = global_value; + } + // Generate module level variables for (size_t i = 0; i < g->global_vars.length; i += 1) { TldVar *tld_var = g->global_vars.at(i); @@ -7956,28 +8003,12 @@ static void do_code_gen(CodeGen *g) { linkage = global_export->linkage; } - LLVMValueRef global_value; bool externally_initialized = var->decl_node->data.variable_declaration.expr == nullptr; - if (externally_initialized) { - LLVMValueRef existing_llvm_var = LLVMGetNamedGlobal(g->module, symbol_name); - if (existing_llvm_var) { - global_value = LLVMConstBitCast(existing_llvm_var, - LLVMPointerType(get_llvm_type(g, var->var_type), 0)); - } else { - global_value = LLVMAddGlobal(g->module, get_llvm_type(g, var->var_type), symbol_name); - // TODO debug info for the extern variable - - LLVMSetLinkage(global_value, to_llvm_linkage(linkage)); - maybe_import_dll(g, global_value, GlobalLinkageIdStrong); - LLVMSetAlignment(global_value, var->align_bytes); - LLVMSetGlobalConstant(global_value, var->gen_is_const); - set_global_tls(g, var, global_value); - } - } else { + if (!externally_initialized) { bool exported = (linkage != GlobalLinkageIdInternal); render_const_val(g, var->const_value, symbol_name); render_const_val_global(g, var->const_value, symbol_name); - global_value = var->const_value->llvm_global; + LLVMValueRef global_value = var->const_value->llvm_global; if (exported) { LLVMSetLinkage(global_value, to_llvm_linkage(linkage)); @@ -7997,10 +8028,9 @@ static void do_code_gen(CodeGen *g) { LLVMSetGlobalConstant(global_value, var->gen_is_const); set_global_tls(g, var, global_value); + var->value_ref = global_value; } - var->value_ref = global_value; - for (size_t export_i = 1; export_i < var->export_list.length; export_i += 1) { GlobalExport *global_export = &var->export_list.items[export_i]; LLVMAddAlias(g->module, LLVMTypeOf(var->value_ref), var->value_ref, buf_ptr(&global_export->name)); diff --git a/src/ir.cpp b/src/ir.cpp index cf8558761f..df5a0a47df 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -19894,30 +19894,34 @@ static IrInstGen *ir_get_var_ptr(IrAnalyze *ira, IrInst *source_instr, ZigVar *v IrInstGen *result = ir_build_var_ptr_gen(ira, source_instr, var); result->value->type = var_ptr_type; - if (!linkage_makes_it_runtime && !var->is_thread_local && value_is_comptime(var->const_value)) { + bool is_local_var = !var->decl_node->data.variable_declaration.is_extern && + var->const_value->special == ConstValSpecialRuntime; + + // The address of a thread-local variable can't be resolved even by a linker because + // it's dependent on the current thread. The concept of current thread doesn't exist + // at compile time, so even if we had a symbolic (i.e., relocatable) representation + // of a pointer to a thread-local variable, there would be no ways to make use of it + // in a meaningful way. + // + // The same goes for local variables - They are stored in a stack frame, whose + // instance doesn't even exist at compile/link time. + if (!var->is_thread_local && !is_local_var) { ZigValue *val = var->const_value; - switch (val->special) { - case ConstValSpecialRuntime: - break; - case ConstValSpecialStatic: // fallthrough - case ConstValSpecialLazy: // fallthrough - case ConstValSpecialUndef: { - ConstPtrMut ptr_mut; - if (comptime_var_mem) { - ptr_mut = ConstPtrMutComptimeVar; - } else if (var->gen_is_const) { - ptr_mut = ConstPtrMutComptimeConst; - } else { - assert(!comptime_var_mem); - ptr_mut = ConstPtrMutRuntimeVar; - } - result->value->special = ConstValSpecialStatic; - result->value->data.x_ptr.mut = ptr_mut; - result->value->data.x_ptr.special = ConstPtrSpecialRef; - result->value->data.x_ptr.data.ref.pointee = val; - return result; - } + + ConstPtrMut ptr_mut; + if (comptime_var_mem) { + ptr_mut = ConstPtrMutComptimeVar; + } else if (var->gen_is_const && !linkage_makes_it_runtime) { + ptr_mut = ConstPtrMutComptimeConst; + } else { + assert(!comptime_var_mem); + ptr_mut = ConstPtrMutRuntimeVar; } + result->value->special = ConstValSpecialStatic; + result->value->data.x_ptr.mut = ptr_mut; + result->value->data.x_ptr.special = ConstPtrSpecialRef; + result->value->data.x_ptr.data.ref.pointee = val; + return result; } bool in_fn_scope = (scope_fn_entry(var->parent_scope) != nullptr); @@ -22288,12 +22292,15 @@ static IrInstGen *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInst* source_ins if (type_is_invalid(struct_val->type)) return ira->codegen->invalid_inst_gen; - // This to allow lazy values to be resolved. - if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, - source_instr->source_node, struct_val, UndefOk))) - { - return ira->codegen->invalid_inst_gen; + if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar) { + // This to allow lazy values to be resolved. + if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, + source_instr->source_node, struct_val, UndefOk))) + { + return ira->codegen->invalid_inst_gen; + } } + if (initializing && struct_val->special == ConstValSpecialUndef) { struct_val->data.x_struct.fields = alloc_const_vals_ptrs(ira->codegen, struct_type->data.structure.src_field_count); struct_val->special = ConstValSpecialStatic; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index a1a261f887..1ccc95c9cc 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -7637,4 +7637,26 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { , &[_][]const u8{ "tmp.zig:4:9: error: expected type '*c_void', found '?*c_void'", }); + + cases.add("pointer to a local runtime `var` is not constant", + \\export fn get_ptr() *const u32 { + \\ var local_var: u32 = 42; + \\ return struct { + \\ const ptr = &local_var; + \\ }.ptr; + \\} + , &[_][]const u8{ + ":4:21: error: cannot store runtime value in compile time variable", + }); + + cases.add("pointer to a local runtime `const` is not constant", + \\export fn get_ptr(x: u32) *const u32 { + \\ const local_var: u32 = x; + \\ return struct { + \\ const ptr = &local_var; + \\ }.ptr; + \\} + , &[_][]const u8{ + ":4:21: error: cannot store runtime value in compile time variable", + }); } diff --git a/test/standalone.zig b/test/standalone.zig index d73587ccd6..ad41ba6762 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -19,6 +19,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/use_alias/build.zig"); cases.addBuildFile("test/standalone/brace_expansion/build.zig"); cases.addBuildFile("test/standalone/empty_env/build.zig"); + cases.addBuildFile("test/standalone/extern_ref/build.zig"); if (std.Target.current.os.tag != .wasi) { cases.addBuildFile("test/standalone/load_dynamic_library/build.zig"); } diff --git a/test/standalone/extern_ref/build.zig b/test/standalone/extern_ref/build.zig new file mode 100644 index 0000000000..839898345a --- /dev/null +++ b/test/standalone/extern_ref/build.zig @@ -0,0 +1,15 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const obj = b.addStaticLibrary("obj", "obj.zig"); + obj.setBuildMode(mode); + + const main = b.addTest("main.zig"); + main.setBuildMode(mode); + main.linkLibrary(obj); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&main.step); +} diff --git a/test/standalone/extern_ref/main.zig b/test/standalone/extern_ref/main.zig new file mode 100644 index 0000000000..52fe2d9f21 --- /dev/null +++ b/test/standalone/extern_ref/main.zig @@ -0,0 +1,120 @@ +const std = @import("std"); +const eql = std.mem.eql; + +// These are defined in `obj.zig` +extern var global_var: usize; +extern const global_const: usize; + +const TheStruct = @import("./types.zig").TheStruct; +extern var global_var_struct: TheStruct; +extern const global_const_struct: TheStruct; + +const TheUnion = @import("./types.zig").TheUnion; +extern var global_var_union: TheUnion; +extern const global_const_union: TheUnion; + +extern var global_var_array: [4]u32; +extern const global_const_array: [4]u32; + +// Take the pointers to external entities as constant values +const p_global_var = &global_var; +const p_global_const = &global_const; + +test "access the external integers" { + std.testing.expect(p_global_var.* == 2); + std.testing.expect(p_global_const.* == 422); +} + +const p_global_var_struct = &global_var_struct; +const p_global_const_struct = &global_const_struct; + +const p_global_var_struct_val = &global_var_struct.value; +const p_global_const_struct_val = &global_const_struct.value; + +const p_global_var_struct_array = &global_var_struct.array; +const p_global_const_struct_array = &global_const_struct.array; + +const p_global_var_struct_array2 = global_var_struct.array[1..3]; +const p_global_const_struct_array2 = global_const_struct.array[1..3]; + +const p_global_var_struct_array3 = &global_var_struct.array[1]; +const p_global_const_struct_array3 = &global_const_struct.array[1]; + +test "access the external integers in a struct through comptime ptrs" { + std.testing.expect(p_global_var_struct.value == 2); + std.testing.expect(p_global_const_struct.value == 422); + + std.testing.expect(p_global_var_struct_val.* == 2); + std.testing.expect(p_global_const_struct_val.* == 422); +} + +test "access the external arrays in a struct through comptime ptrs" { + // TODO + // std.testing.expect(eql(u32, &p_global_var_struct.array, &[_]u32{1, 2, 3, 4})); + // std.testing.expect(eql(u32, &p_global_const_struct.array, &[_]u32{5, 6, 7, 8})); + + // TODO + // std.testing.expect(eql(u32, p_global_var_struct_array, &[_]u32{1, 2, 3, 4})); + // std.testing.expect(eql(u32, p_global_const_struct_array, &[_]u32{5, 6, 7, 8})); + + // TODO + // std.testing.expect(eql(u32, p_global_var_struct_array2, &[_]u32{2, 3})); + // std.testing.expect(eql(u32, p_global_const_struct_array2, &[_]u32{6, 7})); + + // TODO + // std.testing.expect(p_global_var_struct_array3.* == 2); + // std.testing.expect(p_global_const_struct_array3.* == 6); +} + +test "access the external integers with indirection through comptime ptrs" { + std.testing.expect(p_global_var_struct.p_value.* == 3); + std.testing.expect(p_global_const_struct.p_value.* == 423); +} + +const p_global_var_struct_inner_val = &global_var_struct.inner.value; +const p_global_const_struct_inner_val = &global_const_struct.inner.value; + +test "access the external integers in a nested struct through comptime ptrs" { + // TODO + // std.testing.expect(p_global_var_struct_inner_val.* == 4); + // std.testing.expect(p_global_const_struct_inner_val.* == 424); +} + +const p_global_var_union = &global_var_union; +const p_global_const_union = &global_const_union; + +const p_global_var_union_val = &global_var_union.U32; +const p_global_const_union_val = &global_const_union.U32; + +test "access the external integers in a union through comptime ptrs" { + std.testing.expect(p_global_var_union.U32 == 10); + std.testing.expect(p_global_const_union.U32 == 20); + + // TODO + // std.testing.expect(p_global_var_union_val.* == 10); + // std.testing.expect(p_global_const_union_val.* == 20); +} + +const p_global_var_array = &global_var_array; +const p_global_const_array = &global_const_array; + +const p_global_var_array2 = global_var_array[1..3]; +const p_global_const_array2 = global_const_array[1..3]; + +const p_global_var_array3 = &global_var_array[1]; +const p_global_const_array3 = &global_const_array[1]; + +test "access the external arrays through comptime ptrs" { + std.testing.expect(eql(u32, &global_var_array, &[_]u32{1, 2, 3, 4})); + std.testing.expect(eql(u32, &global_const_array, &[_]u32{5, 6, 7, 8})); + + std.testing.expect(eql(u32, p_global_var_array, &[_]u32{1, 2, 3, 4})); + std.testing.expect(eql(u32, p_global_const_array, &[_]u32{5, 6, 7, 8})); + + std.testing.expect(eql(u32, p_global_var_array2, &[_]u32{2, 3})); + std.testing.expect(eql(u32, p_global_const_array2, &[_]u32{6, 7})); + + // TODO + // std.testing.expect(p_global_var_array3.* == 2); + // std.testing.expect(p_global_const_array3.* == 6); +} diff --git a/test/standalone/extern_ref/obj.zig b/test/standalone/extern_ref/obj.zig new file mode 100644 index 0000000000..8a44fd3a60 --- /dev/null +++ b/test/standalone/extern_ref/obj.zig @@ -0,0 +1,27 @@ +export var global_var: usize = 2; +export const global_const: usize = 422; + +const TheStruct = @import("./types.zig").TheStruct; +export var global_var_struct = TheStruct{ + .value = 2, + .array = [_]u32{ 1, 2, 3, 4 }, + .p_value = &@as(u32, 3), + .inner = .{ .value = 4 }, +}; +export const global_const_struct = TheStruct{ + .value = 422, + .array = [_]u32{ 5, 6, 7, 8 }, + .p_value = &@as(u32, 423), + .inner = .{ .value = 424 }, +}; + +const TheUnion = @import("./types.zig").TheUnion; +export var global_var_union = TheUnion{ + .U32 = 10, +}; +export const global_const_union = TheUnion{ + .U32 = 20, +}; + +export var global_var_array = [4]u32{ 1, 2, 3, 4 }; +export const global_const_array = [4]u32{ 5, 6, 7, 8 }; diff --git a/test/standalone/extern_ref/types.zig b/test/standalone/extern_ref/types.zig new file mode 100644 index 0000000000..df8a3807b0 --- /dev/null +++ b/test/standalone/extern_ref/types.zig @@ -0,0 +1,15 @@ +pub const TheStruct = extern struct { + value: u32, + array: [4]u32, + p_value: *const u32, + inner: InnerStruct, +}; + +pub const InnerStruct = extern struct { + value: u32, +}; + +pub const TheUnion = extern union { + U32: u32, + Bool: bool, +};