Commit d3ebd42865

yvt <i@yvt.jp>
2020-05-15 11:28:58
Support taking extern pointers at comptime
This commit makes it possible to obtain pointers to `extern` variables at comptime. - `ir_get_var_ptr` employs several checks to determine if the given variable is eligible for obtaining its pointer at comptime. This commit alters these checks to consider `extern` variables, which have runtime values, as eligible. - After this change, it's now possible for `render_const_val` to be called for `extern` variables. This commit modifies `render_const_val` to suppress the value generation for `extern` variables. - `do_code_gen` now creates `ZigValue::llvm_global` of `extern` variables before iterating through module-level variables so that other module-level variables can refer to them. This solution is incomplete since there are several cases still failing: - `global_var.array[n..m]` - `&global_var.array[i]` - `&global_var.inner_struct.value` - `&global_array[i]` Closes #5349
1 parent 3b26e50
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));
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;
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);
+}
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);
+}
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 };
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,
+};
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",
+    });
 }
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");
     }