Commit 7598a00f34

Andrew Kelley <andrew@ziglang.org>
2021-03-21 06:40:08
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
1 parent d8692b8
src/astgen.zig
@@ -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,
src/main.zig
@@ -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});
         }
     }
src/Module.zig
@@ -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 {};
         }
src/Sema.zig
@@ -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()) {
src/zir.zig
@@ -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,