stage2: fix memory management of ZIR code

* free Module.Fn ZIR code when destroying the owner Decl
 * unreachable_safe and unreachable_unsafe are collapsed into one ZIR
   instruction with a safety flag.
 * astgen: emit an unreachable instruction for unreachable literals
 * don't forget to call deinit on ZIR code
 * astgen: implement some builtin functions
This commit is contained in:
Andrew Kelley 2021-03-20 22:40:08 -07:00
parent d8692b8bdb
commit 7598a00f34
5 changed files with 114 additions and 84 deletions

View File

@ -224,6 +224,10 @@ pub const Decl = struct {
const gpa = module.gpa;
gpa.free(mem.spanZ(decl.name));
if (decl.typedValueManaged()) |tvm| {
if (tvm.typed_value.val.castTag(.function)) |payload| {
const func = payload.data;
func.deinit(gpa);
}
tvm.deinit(gpa);
}
decl.dependants.deinit(gpa);
@ -334,7 +338,7 @@ pub const EmitH = struct {
fwd_decl: std.ArrayListUnmanaged(u8) = .{},
};
/// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
/// Extern functions do not have this data structure; they are represented by
/// the `Decl` only, with a `Value` tag of `extern_fn`.
pub const Fn = struct {
@ -347,6 +351,7 @@ pub const Fn = struct {
/// The number of parameters is determined by referring to the type.
/// The first N elements of `extra` are indexes into `string_bytes` to
/// a null-terminated string.
/// This memory is managed with gpa, must be freed when the function is freed.
zir: zir.Code,
/// undefined unless analysis state is `success`.
body: ir.Body,
@ -370,6 +375,10 @@ pub const Fn = struct {
pub fn dump(func: *Fn, mod: Module) void {
ir.dumpFn(mod, func);
}
pub fn deinit(func: *Fn, gpa: *Allocator) void {
func.zir.deinit(gpa);
}
};
pub const Var = struct {
@ -1502,8 +1511,7 @@ pub const WipZirCode = struct {
.ret_node,
.ret_tok,
.ret_coerce,
.unreachable_unsafe,
.unreachable_safe,
.@"unreachable",
.loop,
.suspend_block,
.suspend_block_one,
@ -1521,6 +1529,7 @@ pub const WipZirCode = struct {
pub fn deinit(wzc: *WipZirCode) void {
wzc.instructions.deinit(wzc.gpa);
wzc.extra.deinit(wzc.gpa);
wzc.string_bytes.deinit(wzc.gpa);
}
};
@ -2078,7 +2087,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
var analysis_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer analysis_arena.deinit();
const code: zir.Code = blk: {
var code: zir.Code = blk: {
var wip_zir_code: WipZirCode = .{
.decl = decl,
.arena = &analysis_arena.allocator,
@ -2102,6 +2111,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
}
break :blk code;
};
defer code.deinit(mod.gpa);
var sema: Sema = .{
.mod = mod,
@ -2154,17 +2164,17 @@ fn astgenAndSemaFn(
var fn_type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer fn_type_scope_arena.deinit();
var fn_type_wip_zir_exec: WipZirCode = .{
var fn_type_wip_zir_code: WipZirCode = .{
.decl = decl,
.arena = &fn_type_scope_arena.allocator,
.gpa = mod.gpa,
};
defer fn_type_wip_zir_exec.deinit();
defer fn_type_wip_zir_code.deinit();
var fn_type_scope: Scope.GenZir = .{
.force_comptime = true,
.parent = &decl.container.base,
.zir_code = &fn_type_wip_zir_exec,
.zir_code = &fn_type_wip_zir_code,
};
defer fn_type_scope.instructions.deinit(mod.gpa);
@ -2317,7 +2327,8 @@ fn astgenAndSemaFn(
errdefer decl_arena.deinit();
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
const fn_type_code = try fn_type_scope.finish();
var fn_type_code = try fn_type_scope.finish();
defer fn_type_code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
fn_type_code.dump(mod.gpa, "fn_type", &fn_type_scope.base, 0) catch {};
}
@ -2621,7 +2632,8 @@ fn astgenAndSemaVarDecl(
init_result_loc,
var_decl.ast.init_node,
);
const code = try gen_scope.finish();
var code = try gen_scope.finish();
defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
code.dump(mod.gpa, "var_init", &gen_scope.base, 0) catch {};
}
@ -2683,7 +2695,8 @@ fn astgenAndSemaVarDecl(
defer type_scope.instructions.deinit(mod.gpa);
const var_type = try astgen.typeExpr(mod, &type_scope.base, var_decl.ast.type_node);
const code = try type_scope.finish();
var code = try type_scope.finish();
defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
code.dump(mod.gpa, "var_type", &type_scope.base, 0) catch {};
}

View File

@ -137,8 +137,7 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde
.as_node => try sema.zirAsNode(block, zir_inst),
.@"asm" => try sema.zirAsm(block, zir_inst, false),
.asm_volatile => try sema.zirAsm(block, zir_inst, true),
.unreachable_safe => try sema.zirUnreachable(block, zir_inst, true),
.unreachable_unsafe => try sema.zirUnreachable(block, zir_inst, false),
.@"unreachable" => try sema.zirUnreachable(block, zir_inst),
.ret_coerce => try sema.zirRetTok(block, zir_inst, true),
.ret_tok => try sema.zirRetTok(block, zir_inst, false),
.ret_node => try sema.zirRetNode(block, zir_inst),
@ -2852,17 +2851,13 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
return parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
}
fn zirUnreachable(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
safety_check: bool,
) InnerError!*Inst {
fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const src_node = sema.code.instructions.items(.data)[inst].node;
const src: LazySrcLoc = .{ .node_offset = src_node };
const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable";
const src = inst_data.src();
const safety_check = inst_data.safety;
try sema.requireRuntimeBlock(block, src);
// TODO Add compile error for @optimizeFor occurring too late in a scope.
if (safety_check and block.wantSafety()) {

View File

@ -415,10 +415,13 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
return callExpr(mod, scope, rl, node, tree.callFull(node));
},
.unreachable_literal => {
const result = @enumToInt(zir.Const.unreachable_value);
return rvalue(mod, scope, rl, result, node);
},
.unreachable_literal => return gz.add(.{
.tag = .@"unreachable",
.data = .{ .@"unreachable" = .{
.safety = true,
.src_node = gz.zir_code.decl.nodeIndexToRelative(node),
} },
}),
.@"return" => return ret(mod, scope, node),
.field_access => return fieldAccess(mod, scope, rl, node),
.float_literal => return floatLiteral(mod, scope, rl, node),
@ -3012,10 +3015,11 @@ fn as(
scope: *Scope,
rl: ResultLoc,
builtin_token: ast.TokenIndex,
src: usize,
node: ast.Node.Index,
lhs: ast.Node.Index,
rhs: ast.Node.Index,
) InnerError!zir.Inst.Ref {
if (true) @panic("TODO update for zir-memory-layout");
const dest_type = try typeExpr(mod, scope, lhs);
switch (rl) {
.none, .discard, .ref, .ty => {
@ -3090,10 +3094,11 @@ fn bitCast(
scope: *Scope,
rl: ResultLoc,
builtin_token: ast.TokenIndex,
src: usize,
node: ast.Node.Index,
lhs: ast.Node.Index,
rhs: ast.Node.Index,
) InnerError!zir.Inst.Ref {
if (true) @panic("TODO update for zir-memory-layout");
const dest_type = try typeExpr(mod, scope, lhs);
switch (rl) {
.none => {
@ -3138,9 +3143,10 @@ fn typeOf(
scope: *Scope,
rl: ResultLoc,
builtin_token: ast.TokenIndex,
src: usize,
node: ast.Node.Index,
params: []const ast.Node.Index,
) InnerError!zir.Inst.Ref {
if (true) @panic("TODO update for zir-memory-layout");
if (params.len < 1) {
return mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{});
}
@ -3158,14 +3164,13 @@ fn builtinCall(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
call: ast.Node.Index,
node: ast.Node.Index,
params: []const ast.Node.Index,
) InnerError!zir.Inst.Ref {
if (true) @panic("TODO update for zir-memory-layout");
const tree = scope.tree();
const main_tokens = tree.nodes.items(.main_token);
const builtin_token = main_tokens[call];
const builtin_token = main_tokens[node];
const builtin_name = tree.tokenSlice(builtin_token);
// We handle the different builtins manually because they have different semantics depending
@ -3187,56 +3192,60 @@ fn builtinCall(
}
}
const gz = scope.getGenZir();
switch (info.tag) {
.ptr_to_int => {
const operand = try expr(mod, scope, .none, params[0]);
const result = try addZIRUnOp(mod, scope, src, .ptrtoint, operand);
return rvalue(mod, scope, rl, result);
const result = try gz.addUnNode(.ptrtoint, operand, node);
return rvalue(mod, scope, rl, result, node);
},
.float_cast => {
if (true) @panic("TODO update for zir-memory-layout");
const dest_type = try typeExpr(mod, scope, params[0]);
const rhs = try expr(mod, scope, .none, params[1]);
const result = try addZIRBinOp(mod, scope, src, .floatcast, dest_type, rhs);
return rvalue(mod, scope, rl, result);
return rvalue(mod, scope, rl, result, node);
},
.int_cast => {
if (true) @panic("TODO update for zir-memory-layout");
const dest_type = try typeExpr(mod, scope, params[0]);
const rhs = try expr(mod, scope, .none, params[1]);
const result = try addZIRBinOp(mod, scope, src, .intcast, dest_type, rhs);
return rvalue(mod, scope, rl, result);
return rvalue(mod, scope, rl, result, node);
},
.breakpoint => {
if (true) @panic("TODO update for zir-memory-layout");
const result = try addZIRNoOp(mod, scope, src, .breakpoint);
return rvalue(mod, scope, rl, result);
return rvalue(mod, scope, rl, result, node);
},
.import => {
const target = try expr(mod, scope, .none, params[0]);
const result = try addZIRUnOp(mod, scope, src, .import, target);
return rvalue(mod, scope, rl, result);
const result = try gz.addUnNode(.import, target, node);
return rvalue(mod, scope, rl, result, node);
},
.compile_error => {
const target = try expr(mod, scope, .none, params[0]);
const result = try addZIRUnOp(mod, scope, src, .compile_error, target);
return rvalue(mod, scope, rl, result);
const result = try gz.addUnNode(.compile_error, target, node);
return rvalue(mod, scope, rl, result, node);
},
.set_eval_branch_quota => {
const u32_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.u32_type),
});
const quota = try expr(mod, scope, .{ .ty = u32_type }, params[0]);
const result = try addZIRUnOp(mod, scope, src, .set_eval_branch_quota, quota);
return rvalue(mod, scope, rl, result);
const u32_rl: ResultLoc = .{ .ty = @enumToInt(zir.Const.u32_type) };
const quota = try expr(mod, scope, u32_rl, params[0]);
const result = try gz.addUnNode(.set_eval_branch_quota, quota, node);
return rvalue(mod, scope, rl, result, node);
},
.compile_log => {
if (true) @panic("TODO update for zir-memory-layout");
const arena = scope.arena();
var targets = try arena.alloc(zir.Inst.Ref, params.len);
for (params) |param, param_i|
targets[param_i] = try expr(mod, scope, .none, param);
const result = try addZIRInst(mod, scope, src, zir.Inst.CompileLog, .{ .to_log = targets }, .{});
return rvalue(mod, scope, rl, result);
return rvalue(mod, scope, rl, result, node);
},
.field => {
if (true) @panic("TODO update for zir-memory-layout");
const string_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.const_slice_u8_type),
@ -3252,11 +3261,11 @@ fn builtinCall(
return rvalue(mod, scope, rl, try addZirInstTag(mod, scope, src, .field_val_named, .{
.object = try expr(mod, scope, .none, params[0]),
.field_name = try comptimeExpr(mod, scope, string_rl, params[1]),
}));
}), node);
},
.as => return as(mod, scope, rl, builtin_token, src, params[0], params[1]),
.bit_cast => return bitCast(mod, scope, rl, builtin_token, src, params[0], params[1]),
.TypeOf => return typeOf(mod, scope, rl, builtin_token, src, params),
.as => return as(mod, scope, rl, builtin_token, node, params[0], params[1]),
.bit_cast => return bitCast(mod, scope, rl, builtin_token, node, params[0], params[1]),
.TypeOf => return typeOf(mod, scope, rl, builtin_token, node, params),
.add_with_overflow,
.align_cast,

View File

@ -1750,15 +1750,12 @@ fn buildOutputType(
}
const self_exe_path = try fs.selfExePathAlloc(arena);
var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir|
.{
.path = lib_dir,
.handle = try fs.cwd().openDir(lib_dir, .{}),
}
else
introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
};
var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{
.path = lib_dir,
.handle = try fs.cwd().openDir(lib_dir, .{}),
} else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
};
defer zig_lib_directory.handle.close();
var thread_pool: ThreadPool = undefined;
@ -2461,15 +2458,12 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
}
}
var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir|
.{
.path = lib_dir,
.handle = try fs.cwd().openDir(lib_dir, .{}),
}
else
introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
};
var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{
.path = lib_dir,
.handle = try fs.cwd().openDir(lib_dir, .{}),
} else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
};
defer zig_lib_directory.handle.close();
const std_special = "std" ++ fs.path.sep_str ++ "special";
@ -3281,8 +3275,7 @@ pub const ClangArgIterator = struct {
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
},
}
else {
} else {
fatal("Unknown Clang option: '{s}'", .{arg});
}
}

View File

@ -67,6 +67,13 @@ pub const Code = struct {
return code.string_bytes[index..end :0];
}
pub fn deinit(code: *Code, gpa: *Allocator) void {
code.instructions.deinit(gpa);
gpa.free(code.string_bytes);
gpa.free(code.extra);
code.* = undefined;
}
/// For debugging purposes, like dumpFn but for unanalyzed zir blocks
pub fn dump(
code: Code,
@ -737,15 +744,9 @@ pub const Inst = struct {
/// of one or more params.
/// Uses the `pl_node` field. AST node is the `@TypeOf` call. Payload is `MultiOp`.
typeof_peer,
/// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
/// will assume the correctness of this instruction.
/// Uses the `node` union field.
unreachable_unsafe,
/// Asserts control-flow will not reach this instruction. In safety-checked modes,
/// this will generate a call to the panic function unless it can be proven unreachable
/// by the compiler.
/// Uses the `node` union field.
unreachable_safe,
/// Asserts control-flow will not reach this instruction (`unreachable`).
/// Uses the `unreachable` union field.
@"unreachable",
/// Bitwise XOR. `^`
xor,
/// Create an optional type '?T'
@ -989,8 +990,7 @@ pub const Inst = struct {
.ret_node,
.ret_tok,
.ret_coerce,
.unreachable_unsafe,
.unreachable_safe,
.@"unreachable",
.loop,
.suspend_block,
.suspend_block_one,
@ -1131,6 +1131,20 @@ pub const Inst = struct {
callee: Ref,
param_index: u32,
},
@"unreachable": struct {
/// Offset from Decl AST node index.
/// `Tag` determines which kind of AST node this points to.
src_node: i32,
/// `false`: Not safety checked - the compiler will assume the
/// correctness of this instruction.
/// `true`: In safety-checked modes, this will generate a call
/// to the panic function unless it can be proven unreachable by the compiler.
safety: bool,
pub fn src(self: @This()) LazySrcLoc {
return .{ .node_offset = self.src_node };
}
},
// Make sure we don't accidentally add a field to make this union
// bigger than expected. Note that in Debug builds, Zig is allowed
@ -1408,8 +1422,6 @@ const Writer = struct {
.dbg_stmt_node,
.ret_ptr,
.ret_type,
.unreachable_unsafe,
.unreachable_safe,
=> try self.writeNode(stream, inst),
.decl_ref,
@ -1424,6 +1436,7 @@ const Writer = struct {
.fn_type_cc => try self.writeFnTypeCc(stream, inst, false),
.fn_type_var_args => try self.writeFnType(stream, inst, true),
.fn_type_cc_var_args => try self.writeFnTypeCc(stream, inst, true),
.@"unreachable" => try self.writeUnreachable(stream, inst),
.enum_literal_small => try self.writeSmallStr(stream, inst),
@ -1612,6 +1625,13 @@ const Writer = struct {
return self.writeFnTypeCommon(stream, param_types, inst_data.return_type, var_args, cc);
}
fn writeUnreachable(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].@"unreachable";
const safety_str = if (inst_data.safety) "safe" else "unsafe";
try stream.print("{s}) ", .{safety_str});
try self.writeSrc(stream, inst_data.src());
}
fn writeFnTypeCommon(
self: *Writer,
stream: anytype,