Commit bcf15e39e2

Andrew Kelley <andrew@ziglang.org>
2021-05-11 23:17:52
stage2: add `owns_tv` flag to `Module.Decl`
Decl objects need to know whether they are the owner of the Type/Value associated with them, in order to decide whether to destroy the associated Namespace, Fn, or Var when cleaning up.
1 parent dae22a0
Changed files (6)
src
test
stage2
src/codegen/c.zig
@@ -30,6 +30,7 @@ pub const CValue = union(enum) {
     arg: usize,
     /// By-value
     decl: *Decl,
+    decl_ref: *Decl,
 };
 
 pub const CValueMap = std.AutoHashMap(*Inst, CValue);
@@ -117,6 +118,7 @@ pub const Object = struct {
             .constant => |inst| return o.dg.renderValue(w, inst.ty, inst.value().?),
             .arg => |i| return w.print("a{d}", .{i}),
             .decl => |decl| return w.writeAll(mem.span(decl.name)),
+            .decl_ref => |decl| return w.print("&{s}", .{decl.name}),
         }
     }
 
@@ -528,13 +530,17 @@ pub const DeclGen = struct {
         }
     }
 
-    fn functionIsGlobal(dg: *DeclGen, tv: TypedValue) bool {
+    fn declIsGlobal(dg: *DeclGen, tv: TypedValue) bool {
         switch (tv.val.tag()) {
             .extern_fn => return true,
             .function => {
                 const func = tv.val.castTag(.function).?.data;
                 return dg.module.decl_exports.contains(func.owner_decl);
             },
+            .variable => {
+                const variable = tv.val.castTag(.variable).?.data;
+                return dg.module.decl_exports.contains(variable.owner_decl);
+            },
             else => unreachable,
         }
     }
@@ -549,7 +555,7 @@ pub fn genDecl(o: *Object) !void {
         .val = o.dg.decl.val,
     };
     if (tv.val.castTag(.function)) |func_payload| {
-        const is_global = o.dg.functionIsGlobal(tv);
+        const is_global = o.dg.declIsGlobal(tv);
         const fwd_decl_writer = o.dg.fwd_decl.writer();
         if (is_global) {
             try fwd_decl_writer.writeAll("ZIG_EXTERN_C ");
@@ -570,6 +576,30 @@ pub fn genDecl(o: *Object) !void {
         try writer.writeAll("ZIG_EXTERN_C ");
         try o.dg.renderFunctionSignature(writer, true);
         try writer.writeAll(";\n");
+    } else if (tv.val.castTag(.variable)) |var_payload| {
+        const variable: *Module.Var = var_payload.data;
+        const is_global = o.dg.declIsGlobal(tv);
+        const fwd_decl_writer = o.dg.fwd_decl.writer();
+        if (is_global or variable.is_extern) {
+            try fwd_decl_writer.writeAll("ZIG_EXTERN_C ");
+        }
+        if (variable.is_threadlocal) {
+            try fwd_decl_writer.writeAll("zig_threadlocal ");
+        }
+        try o.dg.renderType(fwd_decl_writer, o.dg.decl.ty);
+        const decl_name = mem.span(o.dg.decl.name);
+        try fwd_decl_writer.print(" {s};\n", .{decl_name});
+
+        try o.indent_writer.insertNewline();
+        const w = o.writer();
+        try o.dg.renderType(w, o.dg.decl.ty);
+        try w.print(" {s} = ", .{decl_name});
+        if (variable.init.tag() != .unreachable_value) {
+            try o.dg.renderValue(w, tv.ty, variable.init);
+        }
+        try w.writeAll(";");
+        try o.indent_writer.insertNewline();
+
     } else {
         const writer = o.writer();
         try writer.writeAll("static ");
@@ -598,7 +628,7 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
 
     switch (tv.ty.zigTypeTag()) {
         .Fn => {
-            const is_global = dg.functionIsGlobal(tv);
+            const is_global = dg.declIsGlobal(tv);
             if (is_global) {
                 try writer.writeAll("ZIG_EXTERN_C ");
             }
@@ -696,7 +726,7 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
             .wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
             .br_block_flat => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for br_block_flat", .{}),
             .ptrtoint => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for ptrtoint", .{}),
-            .varptr => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for varptr", .{}),
+            .varptr => try genVarPtr(o, inst.castTag(.varptr).?),
             .floatcast => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for floatcast", .{}),
         };
         switch (result_value) {
@@ -709,6 +739,10 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
     try writer.writeAll("}");
 }
 
+fn genVarPtr(o: *Object, inst: *Inst.VarPtr) !CValue {
+    return CValue{ .decl_ref = inst.variable.owner_decl };
+}
+
 fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
     const writer = o.writer();
 
@@ -743,6 +777,12 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
             try o.writeCValue(writer, wrapped);
             try writer.writeAll(";\n");
         },
+        .decl_ref => |decl| {
+            const wrapped: CValue = .{ .decl = decl };
+            try writer.writeAll(" = ");
+            try o.writeCValue(writer, wrapped);
+            try writer.writeAll(";\n");
+        },
         else => {
             try writer.writeAll(" = *");
             try o.writeCValue(writer, operand);
@@ -791,6 +831,13 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
             try o.writeCValue(writer, src_val);
             try writer.writeAll(";\n");
         },
+        .decl_ref => |decl| {
+            const dest: CValue = .{ .decl = decl };
+            try o.writeCValue(writer, dest);
+            try writer.writeAll(" = ");
+            try o.writeCValue(writer, src_val);
+            try writer.writeAll(";\n");
+        },
         else => {
             try writer.writeAll("*");
             try o.writeCValue(writer, dest_ptr);
src/link/C/zig.h
@@ -1,25 +1,15 @@
-#if __STDC_VERSION__ >= 199901L
-#include <stdbool.h>
-#else
-#define bool unsigned char
-#define true 1
-#define false 0
-#endif
-
 #if __STDC_VERSION__ >= 201112L
 #define zig_noreturn _Noreturn
+#define zig_threadlocal thread_local
 #elif __GNUC__
 #define zig_noreturn __attribute__ ((noreturn))
+#define zig_threadlocal __thread
 #elif _MSC_VER
 #define zig_noreturn __declspec(noreturn)
+#define zig_threadlocal __declspec(thread)
 #else
 #define zig_noreturn
-#endif
-
-#if defined(__GNUC__)
-#define zig_unreachable() __builtin_unreachable()
-#else
-#define zig_unreachable()
+#define zig_threadlocal zig_threadlocal_unavailable
 #endif
 
 #if __STDC_VERSION__ >= 199901L
@@ -30,6 +20,20 @@
 #define ZIG_RESTRICT
 #endif
 
+#if __STDC_VERSION__ >= 199901L
+#include <stdbool.h>
+#else
+#define bool unsigned char
+#define true 1
+#define false 0
+#endif
+
+#if defined(__GNUC__)
+#define zig_unreachable() __builtin_unreachable()
+#else
+#define zig_unreachable()
+#endif
+
 #ifdef __cplusplus
 #define ZIG_EXTERN_C extern "C"
 #else
src/Module.zig
@@ -227,6 +227,10 @@ pub const Decl = struct {
     },
     /// Whether `typed_value`, `align_val`, and `linksection_val` are populated.
     has_tv: bool,
+    /// If `true` it means the `Decl` is the resource owner of the type/value associated
+    /// with it. That means when `Decl` is destroyed, the cleanup code should additionally
+    /// check if the value owns a `Namespace`, and destroy that too.
+    owns_tv: bool,
     /// This flag is set when this Decl is added to `Module.deletion_set`, and cleared
     /// when removed.
     deletion_flag: bool,
@@ -278,9 +282,7 @@ pub const Decl = struct {
         decl.clearName(gpa);
         if (decl.has_tv) {
             if (decl.getInnerNamespace()) |namespace| {
-                if (namespace.getDecl() == decl) {
-                    namespace.clearDecls(module);
-                }
+                namespace.clearDecls(module);
             }
             decl.clearValues(gpa);
         }
@@ -307,6 +309,7 @@ pub const Decl = struct {
             arena_state.promote(gpa).deinit();
             decl.value_arena = null;
             decl.has_tv = false;
+            decl.owns_tv = false;
         }
     }
 
@@ -441,36 +444,36 @@ pub const Decl = struct {
     /// If the Decl has a value and it is a struct, return it,
     /// otherwise null.
     pub fn getStruct(decl: *Decl) ?*Struct {
-        if (!decl.has_tv) return null;
+        if (!decl.owns_tv) return null;
         const ty = (decl.val.castTag(.ty) orelse return null).data;
         const struct_obj = (ty.castTag(.@"struct") orelse return null).data;
-        if (struct_obj.owner_decl != decl) return null;
+        assert(struct_obj.owner_decl == decl);
         return struct_obj;
     }
 
     /// If the Decl has a value and it is a union, return it,
     /// otherwise null.
     pub fn getUnion(decl: *Decl) ?*Union {
-        if (!decl.has_tv) return null;
+        if (!decl.owns_tv) return null;
         const ty = (decl.val.castTag(.ty) orelse return null).data;
         const union_obj = (ty.cast(Type.Payload.Union) orelse return null).data;
-        if (union_obj.owner_decl != decl) return null;
+        assert(union_obj.owner_decl == decl);
         return union_obj;
     }
 
     /// If the Decl has a value and it is a function, return it,
     /// otherwise null.
     pub fn getFunction(decl: *Decl) ?*Fn {
-        if (!decl.has_tv) return null;
+        if (!decl.owns_tv) return null;
         const func = (decl.val.castTag(.function) orelse return null).data;
-        if (func.owner_decl != decl) return null;
+        assert(func.owner_decl == decl);
         return func;
     }
 
     pub fn getVariable(decl: *Decl) ?*Var {
-        if (!decl.has_tv) return null;
+        if (!decl.owns_tv) return null;
         const variable = (decl.val.castTag(.variable) orelse return null).data;
-        if (variable.owner_decl != decl) return null;
+        assert(variable.owner_decl == decl);
         return variable;
     }
 
@@ -478,29 +481,28 @@ pub const Decl = struct {
     /// enum, or opaque.
     /// Only returns it if the Decl is the owner.
     pub fn getInnerNamespace(decl: *Decl) ?*Scope.Namespace {
-        if (!decl.has_tv) return null;
+        if (!decl.owns_tv) return null;
         const ty = (decl.val.castTag(.ty) orelse return null).data;
         switch (ty.tag()) {
             .@"struct" => {
                 const struct_obj = ty.castTag(.@"struct").?.data;
-                if (struct_obj.owner_decl != decl) return null;
+                assert(struct_obj.owner_decl == decl);
                 return &struct_obj.namespace;
             },
             .enum_full => {
                 const enum_obj = ty.castTag(.enum_full).?.data;
-                if (enum_obj.owner_decl != decl) return null;
+                assert(enum_obj.owner_decl == decl);
                 return &enum_obj.namespace;
             },
             .empty_struct => {
-                // design flaw, can't verify the owner is this decl
-                @panic("TODO can't implement getInnerNamespace for this type");
+                return ty.castTag(.empty_struct).?.data;
             },
             .@"opaque" => {
                 @panic("TODO opaque types");
             },
             .@"union", .union_tagged => {
                 const union_obj = ty.cast(Type.Payload.Union).?.data;
-                if (union_obj.owner_decl != decl) return null;
+                assert(union_obj.owner_decl == decl);
                 return &union_obj.namespace;
             },
 
@@ -554,7 +556,11 @@ pub const Decl = struct {
 
             .complete,
             .outdated,
-            => true,
+            => {
+                if (!decl.owns_tv)
+                    return false;
+                return decl.ty.hasCodeGenBits();
+            },
         };
     }
 };
@@ -2838,6 +2844,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) InnerError!void {
     new_decl.ty = struct_ty;
     new_decl.val = struct_val;
     new_decl.has_tv = true;
+    new_decl.owns_tv = true;
     new_decl.analysis = .complete;
     new_decl.generation = mod.generation;
 
@@ -2948,7 +2955,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
     errdefer decl_arena.deinit();
     const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
 
-    if (decl_tv.val.tag() == .function) {
+    if (decl_tv.val.castTag(.function)) |fn_payload| {
         var prev_type_has_bits = false;
         var prev_is_inline = false;
         var type_changed = true;
@@ -2956,10 +2963,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         if (decl.has_tv) {
             prev_type_has_bits = decl.ty.hasCodeGenBits();
             type_changed = !decl.ty.eql(decl_tv.ty);
-            if (decl.val.castTag(.function)) |payload| {
-                const prev_func = payload.data;
+            if (decl.getFunction()) |prev_func| {
                 prev_is_inline = prev_func.state == .inline_only;
-                prev_func.deinit(gpa);
             }
             decl.clearValues(gpa);
         }
@@ -2969,6 +2974,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         decl.align_val = try align_val.copy(&decl_arena.allocator);
         decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
         decl.has_tv = true;
+        decl.owns_tv = fn_payload.data.owner_decl == decl;
         decl_arena_state.* = decl_arena.state;
         decl.value_arena = decl_arena_state;
         decl.analysis = .complete;
@@ -3004,6 +3010,25 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
             decl.clearValues(gpa);
         }
 
+        decl.owns_tv = false;
+        var queue_linker_work = false;
+        if (decl_tv.val.castTag(.variable)) |payload| {
+            const variable = payload.data;
+            if (variable.owner_decl == decl) {
+                decl.owns_tv = true;
+                queue_linker_work = true;
+
+                const copied_init = try variable.init.copy(&decl_arena.allocator);
+                variable.init = copied_init;
+            }
+        } else if (decl_tv.val.castTag(.extern_fn)) |payload| {
+            const owner_decl = payload.data;
+            if (decl == owner_decl) {
+                decl.owns_tv = true;
+                queue_linker_work = true;
+            }
+        }
+
         decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
         decl.val = try decl_tv.val.copy(&decl_arena.allocator);
         decl.align_val = try align_val.copy(&decl_arena.allocator);
@@ -3014,13 +3039,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         decl.analysis = .complete;
         decl.generation = mod.generation;
 
-        if (decl.is_exported) {
-            const export_src = src; // TODO point to the export token
-            // The scope needs to have the decl in it.
-            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
-        }
-
-        if (decl.val.tag() == .extern_fn) {
+        if (queue_linker_work and decl.ty.hasCodeGenBits()) {
             try mod.comp.bin_file.allocateDeclIndexes(decl);
             try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
 
@@ -3029,6 +3048,13 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
             }
         }
 
+        if (decl.is_exported) {
+            const export_src = src; // TODO point to the export token
+            // The scope needs to have the decl in it.
+            try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
+        }
+
+
         return type_changed;
     }
 }
@@ -3531,6 +3557,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node
         .src_node = src_node,
         .src_line = undefined,
         .has_tv = false,
+        .owns_tv = false,
         .ty = undefined,
         .val = undefined,
         .align_val = undefined,
@@ -3779,6 +3806,7 @@ pub fn createAnonymousDeclNamed(
     new_decl.ty = typed_value.ty;
     new_decl.val = typed_value.val;
     new_decl.has_tv = true;
+    new_decl.owns_tv = true;
     new_decl.analysis = .complete;
     new_decl.generation = mod.generation;
 
src/Sema.zig
@@ -5867,7 +5867,7 @@ fn zirVarExtended(
         extra_index += 1;
         const init_tv = try sema.resolveInstConst(block, init_src, init_ref);
         break :blk init_tv.val;
-    } else Value.initTag(.null_value);
+    } else Value.initTag(.unreachable_value);
 
     if (!var_ty.isValidVarType(small.is_extern)) {
         return sema.mod.fail(&block.base, mut_src, "variable of type '{}' must be const", .{
test/stage2/cbe.zig
@@ -517,7 +517,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\}
         , &.{
             ":3:21: error: missing struct field: x",
-            ":1:15: note: struct 'Point' declared here",
+            ":1:15: note: struct 'test_case.Point' declared here",
         });
         case.addError(
             \\const Point = struct { x: i32, y: i32 };
BRANCH_TODO
@@ -1,13 +1,3 @@
- * The next problem is that when trying to deinitialize everything, when we
-   deinit a Decl that is the owner of a Namespace, there may still be other Decl
-   objects that reference that Namespace. They want to check if they are the owner
-   in order to find out if they should destroy it. But they can't check if they are
-   the owner because the owner_decl field is destroyed.
-   So there's a memory management problem to solve. We could easily solve this with
-   ref counting or whatever but the goal is to not introduce extra overhead / unnecessary
-   fields just to help figure out how to free stuff. So come up with some way to make 
-   this sound, and easily debuggable when something goes wrong.
-
  * get stage2 tests passing
  * modify stage2 tests so that only 1 uses _start and the rest use
    pub fn main