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.
This commit is contained in:
Andrew Kelley 2021-05-11 14:17:52 -07:00
parent dae22a0a1f
commit bcf15e39e2
6 changed files with 128 additions and 59 deletions

View File

@ -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

View File

@ -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;

View File

@ -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", .{

View File

@ -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);

View File

@ -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

View File

@ -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 };