Commit 2f391df2a7

Andrew Kelley <andrew@ziglang.org>
2021-03-23 01:12:52
stage2: Sema improvements and boolean logic astgen
* add `Module.setBlockBody` and related functions * redo astgen for `and` and `or` to use fewer ZIR instructions and require less processing for comptime known values * Sema: rework `analyzeBody` function. See the new doc comments in this commit. Divides ZIR instructions up into 3 categories: - always noreturn - never noreturn - sometimes noreturn
1 parent 9f0b9b8
src/astgen.zig
@@ -370,8 +370,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
         .array_cat => return simpleBinOp(mod, scope, rl, node, .array_cat),
         .array_mult => return simpleBinOp(mod, scope, rl, node, .array_mul),
 
-        .bool_and => return boolBinOp(mod, scope, rl, node, .bool_and),
-        .bool_or => return boolBinOp(mod, scope, rl, node, .bool_or),
+        .bool_and => return boolBinOp(mod, scope, rl, node, .bool_br_and),
+        .bool_or => return boolBinOp(mod, scope, rl, node, .bool_br_or),
 
         .bool_not => return boolNot(mod, scope, rl, node),
         .bit_not => return bitNot(mod, scope, rl, node),
@@ -425,8 +425,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
         .field_access => return fieldAccess(mod, scope, rl, node),
         .float_literal => return floatLiteral(mod, scope, rl, node),
 
-        .if_simple => return ifExpr(mod, scope, rl, tree.ifSimple(node)),
-        .@"if" => return ifExpr(mod, scope, rl, tree.ifFull(node)),
+        .if_simple => return ifExpr(mod, scope, rl, node, tree.ifSimple(node)),
+        .@"if" => return ifExpr(mod, scope, rl, node, tree.ifFull(node)),
 
         .while_simple => return whileExpr(mod, scope, rl, tree.whileSimple(node)),
         .while_cont => return whileExpr(mod, scope, rl, tree.whileCont(node)),
@@ -923,6 +923,7 @@ fn labeledBlockExpr(
     // so that break statements can reference it.
     const gz = parent_scope.getGenZir();
     const block_inst = try gz.addBlock(zir_tag, block_node);
+    try gz.instructions.append(mod.gpa, block_inst);
 
     var block_scope: Scope.GenZir = .{
         .parent = parent_scope,
@@ -946,8 +947,6 @@ fn labeledBlockExpr(
         return mod.failTok(parent_scope, label_token, "unused block label", .{});
     }
 
-    try gz.instructions.append(mod.gpa, block_inst);
-
     const zir_tags = gz.zir_code.instructions.items(.tag);
     const zir_datas = gz.zir_code.instructions.items(.data);
 
@@ -961,7 +960,7 @@ fn labeledBlockExpr(
             }
             // TODO technically not needed since we changed the tag to break_void but
             // would be better still to elide the ones that are in this list.
-            try copyBodyNoEliding(block_inst, block_scope);
+            try block_scope.setBlockBody(block_inst);
 
             return gz.zir_code.ref_start_index + block_inst;
         },
@@ -975,7 +974,7 @@ fn labeledBlockExpr(
                 // TODO technically not needed since we changed the tag to elided but
                 // would be better still to elide the ones that are in this list.
             }
-            try copyBodyNoEliding(block_inst, block_scope);
+            try block_scope.setBlockBody(block_inst);
             const block_ref = gz.zir_code.ref_start_index + block_inst;
             switch (rl) {
                 .ref => return block_ref,
@@ -1635,8 +1634,8 @@ fn finishThenElseBlock(
                 });
             }
             assert(!strat.elide_store_to_block_ptr_instructions);
-            try copyBodyNoEliding(then_body, then_scope.*);
-            try copyBodyNoEliding(else_body, else_scope.*);
+            try then_scope.setBlockBody(then_body);
+            try else_scope.setBlockBody(else_body);
             return &main_block.base;
         },
         .break_operand => {
@@ -1662,8 +1661,8 @@ fn finishThenElseBlock(
                 try copyBodyWithElidedStoreBlockPtr(then_body, then_scope.*);
                 try copyBodyWithElidedStoreBlockPtr(else_body, else_scope.*);
             } else {
-                try copyBodyNoEliding(then_body, then_scope.*);
-                try copyBodyNoEliding(else_body, else_scope.*);
+                try then_scope.setBlockBody(then_body);
+                try else_scope.setBlockBody(else_body);
             }
             switch (rl) {
                 .ref => return &main_block.base,
@@ -1801,95 +1800,49 @@ fn boolBinOp(
     mod: *Module,
     scope: *Scope,
     rl: ResultLoc,
-    infix_node: ast.Node.Index,
-    kind: enum { bool_and, bool_or },
+    node: ast.Node.Index,
+    zir_tag: zir.Inst.Tag,
 ) InnerError!zir.Inst.Ref {
-    const tree = scope.tree();
-    const node_datas = tree.nodes.items(.data);
-    const bool_type = @enumToInt(zir.Const.bool_type);
     const gz = scope.getGenZir();
+    const node_datas = gz.tree().nodes.items(.data);
+    const bool_type = @enumToInt(zir.Const.bool_type);
 
-    const lhs = try expr(mod, scope, .{ .ty = bool_type }, node_datas[infix_node].lhs);
-
-    const block_inst = try gz.addBlock(.block, infix_node);
-    const block_ref = gz.zir_code.ref_start_index + block_inst;
-    var block_scope: Scope.GenZir = .{
-        .parent = scope,
-        .zir_code = gz.zir_code,
-        .force_comptime = gz.force_comptime,
-    };
-    defer block_scope.instructions.deinit(mod.gpa);
+    const lhs = try expr(mod, scope, .{ .ty = bool_type }, node_datas[node].lhs);
+    const bool_br = try gz.addBoolBr(zir_tag, lhs);
 
     var rhs_scope: Scope.GenZir = .{
-        .parent = &block_scope.base,
+        .parent = scope,
         .zir_code = gz.zir_code,
         .force_comptime = gz.force_comptime,
     };
     defer rhs_scope.instructions.deinit(mod.gpa);
-    const rhs = try expr(mod, &rhs_scope.base, .{ .ty = bool_type }, node_datas[infix_node].rhs);
-    _ = try rhs_scope.addBin(.@"break", block_inst, rhs);
-
-    // TODO: should we have zir.Const instructions for `break true` and `break false`?
-    const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
-    const break_true_false_ref = new_index + gz.zir_code.ref_start_index;
-    try gz.zir_code.instructions.append(gz.zir_code.gpa, .{ .tag = .@"break", .data = .{ .bin = .{
-        .lhs = block_inst,
-        .rhs = switch (kind) {
-            .bool_and => @enumToInt(zir.Const.bool_false),
-            .bool_or => @enumToInt(zir.Const.bool_true),
-        },
-    } } });
-
-    switch (kind) {
-        // if lhs // AND
-        //     break rhs
-        // else
-        //     break false
-        .bool_and => _ = try block_scope.addCondBr(
-            lhs,
-            rhs_scope.instructions.items,
-            &[_]zir.Inst.Ref{break_true_false_ref},
-            infix_node,
-        ),
-        // if lhs // OR
-        //     break true
-        // else
-        //     break rhs
-        .bool_or => _ = try block_scope.addCondBr(
-            lhs,
-            &[_]zir.Inst.Ref{break_true_false_ref},
-            rhs_scope.instructions.items,
-            infix_node,
-        ),
-    }
+    const rhs = try expr(mod, &rhs_scope.base, .{ .ty = bool_type }, node_datas[node].rhs);
+    _ = try rhs_scope.addUnNode(.break_flat, rhs, node);
+    try rhs_scope.setBoolBrBody(bool_br);
 
-    try gz.instructions.append(mod.gpa, block_inst);
-    try copyBodyNoEliding(block_inst, block_scope);
-
-    return rvalue(mod, scope, rl, block_ref, infix_node);
+    const block_ref = gz.zir_code.ref_start_index + bool_br;
+    return rvalue(mod, scope, rl, block_ref, node);
 }
 
 fn ifExpr(
     mod: *Module,
     scope: *Scope,
     rl: ResultLoc,
+    node: ast.Node.Index,
     if_full: ast.full.If,
 ) InnerError!zir.Inst.Ref {
     if (true) @panic("TODO update for zir-memory-layout");
+    const parent_gz = scope.getGenZir();
     var block_scope: Scope.GenZir = .{
         .parent = scope,
-        .decl = scope.ownerDecl().?,
-        .arena = scope.arena(),
+        .zir_code = parent_gz.zir_code,
         .force_comptime = scope.isComptime(),
         .instructions = .{},
     };
     setBlockResultLoc(&block_scope, rl);
     defer block_scope.instructions.deinit(mod.gpa);
 
-    const tree = scope.tree();
-    const main_tokens = tree.nodes.items(.main_token);
-
-    const if_src = token_starts[if_full.ast.if_token];
+    const tree = parent_gz.tree();
 
     const cond = c: {
         // TODO https://github.com/ziglang/zig/issues/7929
@@ -1898,23 +1851,16 @@ fn ifExpr(
         } else if (if_full.payload_token) |payload_token| {
             return mod.failTok(scope, payload_token, "TODO implement if optional", .{});
         } else {
-            const bool_type = try addZIRInstConst(mod, &block_scope.base, if_src, .{
-                .ty = Type.initTag(.type),
-                .val = Value.initTag(.bool_type),
-            });
-            break :c try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_full.ast.cond_expr);
+            const bool_rl: ResultLoc = .{ .ty = @enumToInt(zir.Const.bool_type) };
+            break :c try expr(mod, &block_scope.base, bool_rl, if_full.ast.cond_expr);
         }
     };
 
-    const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{
-        .condition = cond,
-        .then_body = undefined, // populated below
-        .else_body = undefined, // populated below
-    }, .{});
+    const condbr = try block_scope.addCondBr(node);
 
-    const block = try addZIRInstBlock(mod, scope, if_src, .block, .{
-        .instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items),
-    });
+    const block = try parent_gz.addBlock(.block, node);
+    try parent_gz.instructions.append(mod.gpa, block);
+    try block_scope.setBlockBody(block);
 
     const then_src = token_starts[tree.lastToken(if_full.ast.then_expr)];
     var then_scope: Scope.GenZir = .{
@@ -1990,12 +1936,6 @@ fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZir)
     assert(dst_index == body.instructions.len);
 }
 
-fn copyBodyNoEliding(block_inst: zir.Inst.Index, gz: Module.Scope.GenZir) !void {
-    const zir_datas = gz.zir_code.instructions.items(.data);
-    zir_datas[block_inst].pl_node.payload_index = @intCast(u32, gz.zir_code.extra.items.len);
-    try gz.zir_code.extra.appendSlice(gz.zir_code.gpa, gz.instructions.items);
-}
-
 fn whileExpr(
     mod: *Module,
     scope: *Scope,
src/Module.zig
@@ -477,7 +477,7 @@ pub const Scope = struct {
         switch (scope.tag) {
             .file => return &scope.cast(File).?.tree,
             .block => return &scope.cast(Block).?.src_decl.container.file_scope.tree,
-            .gen_zir => return &scope.cast(GenZir).?.zir_code.decl.container.file_scope.tree,
+            .gen_zir => return scope.cast(GenZir).?.tree(),
             .local_val => return &scope.cast(LocalVal).?.gen_zir.zir_code.decl.container.file_scope.tree,
             .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.zir_code.decl.container.file_scope.tree,
             .container => return &scope.cast(Container).?.file_scope.tree,
@@ -983,6 +983,30 @@ pub const Scope = struct {
             return gz.zir_code.decl.nodeSrcLoc(node_index);
         }
 
+        pub fn tree(gz: *const GenZir) *const ast.Tree {
+            return &gz.zir_code.decl.container.file_scope.tree;
+        }
+
+        pub fn setBoolBrBody(gz: GenZir, inst: zir.Inst.Index) !void {
+            try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len +
+                @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+            const zir_datas = gz.zir_code.instructions.items(.data);
+            zir_datas[inst].bool_br.payload_index = gz.zir_code.addExtraAssumeCapacity(
+                zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
+            );
+            gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items);
+        }
+
+        pub fn setBlockBody(gz: GenZir, inst: zir.Inst.Index) !void {
+            try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len +
+                @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+            const zir_datas = gz.zir_code.instructions.items(.data);
+            zir_datas[inst].pl_node.payload_index = gz.zir_code.addExtraAssumeCapacity(
+                zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
+            );
+            gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items);
+        }
+
         pub fn addFnTypeCc(gz: *GenZir, tag: zir.Inst.Tag, args: struct {
             param_types: []const zir.Inst.Ref,
             ret_ty: zir.Inst.Ref,
@@ -1044,73 +1068,62 @@ pub const Scope = struct {
             return new_index + gz.zir_code.ref_start_index;
         }
 
-        pub fn addCondBr(
+        pub fn addCall(
             gz: *GenZir,
-            condition: zir.Inst.Ref,
-            then_body: []const zir.Inst.Ref,
-            else_body: []const zir.Inst.Ref,
+            tag: zir.Inst.Tag,
+            callee: zir.Inst.Ref,
+            args: []const zir.Inst.Ref,
             /// Absolute node index. This function does the conversion to offset from Decl.
             abs_node_index: ast.Node.Index,
-        ) !zir.Inst.Ref {
+        ) !zir.Inst.Index {
+            assert(callee != 0);
+            assert(abs_node_index != 0);
             const gpa = gz.zir_code.gpa;
             try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
             try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1);
             try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len +
-                @typeInfo(zir.Inst.CondBr).Struct.fields.len + then_body.len + else_body.len);
+                @typeInfo(zir.Inst.Call).Struct.fields.len + args.len);
 
-            const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.CondBr{
-                .condition = condition,
-                .then_body_len = @intCast(u32, then_body.len),
-                .else_body_len = @intCast(u32, else_body.len),
+            const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.Call{
+                .callee = callee,
+                .args_len = @intCast(u32, args.len),
             });
-            gz.zir_code.extra.appendSliceAssumeCapacity(then_body);
-            gz.zir_code.extra.appendSliceAssumeCapacity(else_body);
+            gz.zir_code.extra.appendSliceAssumeCapacity(args);
 
             const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
             gz.zir_code.instructions.appendAssumeCapacity(.{
-                .tag = .condbr,
+                .tag = tag,
                 .data = .{ .pl_node = .{
                     .src_node = gz.zir_code.decl.nodeIndexToRelative(abs_node_index),
                     .payload_index = payload_index,
                 } },
             });
             gz.instructions.appendAssumeCapacity(new_index);
-
             return new_index + gz.zir_code.ref_start_index;
         }
 
-        pub fn addCall(
+        /// Note that this returns a `zir.Inst.Index` not a ref.
+        /// Leaves the `payload_index` field undefined.
+        pub fn addBoolBr(
             gz: *GenZir,
             tag: zir.Inst.Tag,
-            callee: zir.Inst.Ref,
-            args: []const zir.Inst.Ref,
-            /// Absolute node index. This function does the conversion to offset from Decl.
-            abs_node_index: ast.Node.Index,
+            lhs: zir.Inst.Ref,
         ) !zir.Inst.Index {
-            assert(callee != 0);
-            assert(abs_node_index != 0);
+            assert(lhs != 0);
             const gpa = gz.zir_code.gpa;
             try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
             try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1);
-            try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len +
-                @typeInfo(zir.Inst.Call).Struct.fields.len + args.len);
-
-            const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.Call{
-                .callee = callee,
-                .args_len = @intCast(u32, args.len),
-            });
-            gz.zir_code.extra.appendSliceAssumeCapacity(args);
 
             const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
             gz.zir_code.instructions.appendAssumeCapacity(.{
                 .tag = tag,
-                .data = .{ .pl_node = .{
-                    .src_node = gz.zir_code.decl.nodeIndexToRelative(abs_node_index),
-                    .payload_index = payload_index,
+                .data = .{ .bool_br = .{
+                    .lhs = lhs,
+                    .payload_index = undefined,
                 } },
             });
             gz.instructions.appendAssumeCapacity(new_index);
-            return new_index + gz.zir_code.ref_start_index;
+            return new_index;
         }
 
         pub fn addInt(gz: *GenZir, integer: u64) !zir.Inst.Ref {
@@ -1291,6 +1304,20 @@ pub const Scope = struct {
             return new_index;
         }
 
+        /// Note that this returns a `zir.Inst.Index` not a ref.
+        /// Leaves the `payload_index` field undefined.
+        pub fn addCondBr(gz: *GenZir, node: ast.Node.Index) !zir.Inst.Index {
+            const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len);
+            try gz.zir_code.instructions.append(gz.zir_code.gpa, .{
+                .tag = .condbr,
+                .data = .{ .pl_node = .{
+                    .src_node = gz.zir_code.decl.nodeIndexToRelative(node),
+                    .payload_index = undefined,
+                } },
+            });
+            return new_index;
+        }
+
         pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref {
             const gpa = gz.zir_code.gpa;
             try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
@@ -1409,9 +1436,9 @@ pub const WipZirCode = struct {
                 .bitcast_result_ptr,
                 .bit_or,
                 .block,
-                .block_flat,
                 .block_comptime,
-                .block_comptime_flat,
+                .bool_br_and,
+                .bool_br_or,
                 .bool_not,
                 .bool_and,
                 .bool_or,
@@ -1461,9 +1488,6 @@ pub const WipZirCode = struct {
                 .ret_type,
                 .shl,
                 .shr,
-                .store,
-                .store_to_block_ptr,
-                .store_to_inferred_ptr,
                 .str,
                 .sub,
                 .subwrap,
@@ -1497,7 +1521,6 @@ pub const WipZirCode = struct {
                 .slice_sentinel,
                 .import,
                 .typeof_peer,
-                .resolve_inferred_alloc,
                 => return false,
 
                 .breakpoint,
@@ -1509,6 +1532,7 @@ pub const WipZirCode = struct {
                 .ensure_err_payload_void,
                 .@"break",
                 .break_void_tok,
+                .break_flat,
                 .condbr,
                 .compile_error,
                 .ret_node,
@@ -1517,6 +1541,10 @@ pub const WipZirCode = struct {
                 .@"unreachable",
                 .loop,
                 .elided,
+                .store,
+                .store_to_block_ptr,
+                .store_to_inferred_ptr,
+                .resolve_inferred_alloc,
                 => return true,
             }
         }
@@ -2150,7 +2178,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
             };
             defer block_scope.instructions.deinit(mod.gpa);
 
-            try sema.root(&block_scope);
+            _ = try sema.root(&block_scope);
 
             decl.analysis = .complete;
             decl.generation = mod.generation;
@@ -2338,6 +2366,7 @@ fn astgenAndSemaFn(
         const tag: zir.Inst.Tag = if (is_var_args) .fn_type_var_args else .fn_type;
         break :fn_type try fn_type_scope.addFnType(tag, return_type_inst, param_types);
     };
+    _ = try fn_type_scope.addUnNode(.break_flat, fn_type_inst, 0);
 
     // We need the memory for the Type to go into the arena for the Decl
     var decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
@@ -2370,7 +2399,7 @@ fn astgenAndSemaFn(
     };
     defer block_scope.instructions.deinit(mod.gpa);
 
-    const fn_type = try fn_type_sema.rootAsType(&block_scope, fn_type_inst);
+    const fn_type = try fn_type_sema.rootAsType(&block_scope);
     if (body_node == 0) {
         if (!is_extern) {
             return mod.failNode(&block_scope.base, fn_proto.ast.fn_token, "non-extern function has no body", .{});
@@ -2650,6 +2679,7 @@ fn astgenAndSemaVarDecl(
             init_result_loc,
             var_decl.ast.init_node,
         );
+        _ = try gen_scope.addUnNode(.break_flat, init_inst, var_decl.ast.init_node);
         var code = try gen_scope.finish();
         defer code.deinit(mod.gpa);
         if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@@ -2676,10 +2706,9 @@ fn astgenAndSemaVarDecl(
         };
         defer block_scope.instructions.deinit(mod.gpa);
 
-        try sema.root(&block_scope);
-
+        const init_inst_zir_ref = try sema.root(&block_scope);
         // The result location guarantees the type coercion.
-        const analyzed_init_inst = try sema.resolveInst(init_inst);
+        const analyzed_init_inst = try sema.resolveInst(init_inst_zir_ref);
         // The is_comptime in the Scope.Block guarantees the result is comptime-known.
         const val = analyzed_init_inst.value().?;
 
@@ -2713,6 +2742,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);
+        _ = try type_scope.addUnNode(.break_flat, var_type, 0);
+
         var code = try type_scope.finish();
         defer code.deinit(mod.gpa);
         if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@@ -2739,7 +2770,7 @@ fn astgenAndSemaVarDecl(
         };
         defer block_scope.instructions.deinit(mod.gpa);
 
-        const ty = try sema.rootAsType(&block_scope, var_type);
+        const ty = try sema.rootAsType(&block_scope);
 
         break :vi .{
             .ty = try ty.copy(&decl_arena.allocator),
@@ -3328,7 +3359,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
     func.state = .in_progress;
     log.debug("set {s} to in_progress", .{decl.name});
 
-    try sema.root(&inner_block);
+    _ = try sema.root(&inner_block);
 
     const instructions = try arena.allocator.dupe(*ir.Inst, inner_block.instructions.items);
     func.state = .success;
src/Sema.zig
@@ -53,172 +53,230 @@ const InnerError = Module.InnerError;
 const Decl = Module.Decl;
 const LazySrcLoc = Module.LazySrcLoc;
 
-pub fn root(sema: *Sema, root_block: *Scope.Block) !void {
+pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Ref {
     const root_body = sema.code.extra[sema.code.root_start..][0..sema.code.root_len];
     return sema.analyzeBody(root_block, root_body);
 }
 
-pub fn rootAsType(sema: *Sema, root_block: *Scope.Block, result_inst: zir.Inst.Ref) !Type {
-    const root_body = sema.code.extra[sema.code.root_start..][0..sema.code.root_len];
-    try sema.analyzeBody(root_block, root_body);
-
+/// Assumes that `root_block` ends with `break_flat`.
+pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
+    const zir_inst_ref = try sema.root(root_block);
     // Source location is unneeded because resolveConstValue must have already
     // been successfully called when coercing the value to a type, from the
     // result location.
-    return sema.resolveType(root_block, .unneeded, result_inst);
-}
-
-pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
+    return sema.resolveType(root_block, .unneeded, zir_inst_ref);
+}
+
+/// ZIR instructions which are always `noreturn` return this. This matches the
+/// return type of `analyzeBody` so that we can tail call them.
+/// Only appropriate to return when the instruction is known to be NoReturn
+/// solely based on the ZIR tag.
+const always_noreturn: InnerError!zir.Inst.Ref = @as(zir.Inst.Index, 0);
+
+/// This function is the main loop of `Sema` and it can be used in two different ways:
+/// * The traditional way where there are N breaks out of the block and peer type
+///   resolution is done on the break operands. In this case, the `zir.Inst.Index`
+///   part of the return value will be `undefined`, and callsites should ignore it,
+///   finding the block result value via the block scope.
+/// * The "flat" way. There is only 1 break out of the block, and it is with a `break_flat`
+///   instruction. In this case, the `zir.Inst.Index` part of the return value will be
+///   the block result value. No block scope needs to be created for this strategy.
+pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) !zir.Inst.Index {
+    // No tracy calls here, to avoid interfering with the tail call mechanism.
 
     const map = block.sema.inst_map;
     const tags = block.sema.code.instructions.items(.tag);
 
-    // TODO: As an optimization, look into making these switch prongs directly jump
-    // to the next one, rather than detouring through the loop condition.
-    // Also, look into leaving only the "noreturn" loop break condition, and removing
-    // the iteration based one. Better yet, have an extra entry in the tags array as a
-    // sentinel, so that exiting the loop is just another jump table prong.
-    // Related: https://github.com/ziglang/zig/issues/8220
-    for (body) |zir_inst| {
-        map[zir_inst] = switch (tags[zir_inst]) {
-            .alloc => try sema.zirAlloc(block, zir_inst),
-            .alloc_mut => try sema.zirAllocMut(block, zir_inst),
-            .alloc_inferred => try sema.zirAllocInferred(block, zir_inst, Type.initTag(.inferred_alloc_const)),
-            .alloc_inferred_mut => try sema.zirAllocInferred(block, zir_inst, Type.initTag(.inferred_alloc_mut)),
-            .bitcast_ref => try sema.zirBitcastRef(block, zir_inst),
-            .bitcast_result_ptr => try sema.zirBitcastResultPtr(block, zir_inst),
-            .block => try sema.zirBlock(block, zir_inst, false),
-            .block_comptime => try sema.zirBlock(block, zir_inst, true),
-            .block_flat => try sema.zirBlockFlat(block, zir_inst, false),
-            .block_comptime_flat => try sema.zirBlockFlat(block, zir_inst, true),
-            .@"break" => try sema.zirBreak(block, zir_inst),
-            .break_void_tok => try sema.zirBreakVoidTok(block, zir_inst),
-            .breakpoint => try sema.zirBreakpoint(block, zir_inst),
-            .call => try sema.zirCall(block, zir_inst, .auto),
-            .call_compile_time => try sema.zirCall(block, zir_inst, .compile_time),
-            .call_none => try sema.zirCallNone(block, zir_inst),
-            .coerce_result_ptr => try sema.zirCoerceResultPtr(block, zir_inst),
-            .compile_error => try sema.zirCompileError(block, zir_inst),
-            .compile_log => try sema.zirCompileLog(block, zir_inst),
-            .@"const" => try sema.zirConst(block, zir_inst),
-            .dbg_stmt_node => try sema.zirDbgStmtNode(block, zir_inst),
-            .decl_ref => try sema.zirDeclRef(block, zir_inst),
-            .decl_val => try sema.zirDeclVal(block, zir_inst),
+    // We use a while(true) loop here to avoid a redundant way of breaking out of
+    // the loop. The only way to break out of the loop is with a `noreturn`
+    // instruction.
+    // TODO: As an optimization, make sure the codegen for these switch prongs
+    // directly jump to the next one, rather than detouring through the loop
+    // continue expression. Related: https://github.com/ziglang/zig/issues/8220
+    var i: usize = 0;
+    while (true) : (i += 1) {
+        const inst = body[i];
+        map[inst] = switch (tags[inst]) {
             .elided => continue,
-            .ensure_result_used => try sema.zirEnsureResultUsed(block, zir_inst),
-            .ensure_result_non_error => try sema.zirEnsureResultNonError(block, zir_inst),
-            .indexable_ptr_len => try sema.zirIndexablePtrLen(block, zir_inst),
-            .ref => try sema.zirRef(block, zir_inst),
-            .resolve_inferred_alloc => try sema.zirResolveInferredAlloc(block, zir_inst),
-            .ret_ptr => try sema.zirRetPtr(block, zir_inst),
-            .ret_type => try sema.zirRetType(block, zir_inst),
-            .store_to_block_ptr => try sema.zirStoreToBlockPtr(block, zir_inst),
-            .store_to_inferred_ptr => try sema.zirStoreToInferredPtr(block, zir_inst),
-            .ptr_type_simple => try sema.zirPtrTypeSimple(block, zir_inst),
-            .ptr_type => try sema.zirPtrType(block, zir_inst),
-            .store => try sema.zirStore(block, zir_inst),
-            .set_eval_branch_quota => try sema.zirSetEvalBranchQuota(block, zir_inst),
-            .str => try sema.zirStr(block, zir_inst),
-            .int => try sema.zirInt(block, zir_inst),
-            .int_type => try sema.zirIntType(block, zir_inst),
-            .loop => try sema.zirLoop(block, zir_inst),
-            .param_type => try sema.zirParamType(block, zir_inst),
-            .ptrtoint => try sema.zirPtrtoint(block, zir_inst),
-            .field_ptr => try sema.zirFieldPtr(block, zir_inst),
-            .field_val => try sema.zirFieldVal(block, zir_inst),
-            .field_ptr_named => try sema.zirFieldPtrNamed(block, zir_inst),
-            .field_val_named => try sema.zirFieldValNamed(block, zir_inst),
-            .deref_node => try sema.zirDerefNode(block, zir_inst),
-            .as => try sema.zirAs(block, zir_inst),
-            .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" => 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),
-            .fn_type => try sema.zirFnType(block, zir_inst, false),
-            .fn_type_cc => try sema.zirFnTypeCc(block, zir_inst, false),
-            .fn_type_var_args => try sema.zirFnType(block, zir_inst, true),
-            .fn_type_cc_var_args => try sema.zirFnTypeCc(block, zir_inst, true),
-            .intcast => try sema.zirIntcast(block, zir_inst),
-            .bitcast => try sema.zirBitcast(block, zir_inst),
-            .floatcast => try sema.zirFloatcast(block, zir_inst),
-            .elem_ptr => try sema.zirElemPtr(block, zir_inst),
-            .elem_ptr_node => try sema.zirElemPtrNode(block, zir_inst),
-            .elem_val => try sema.zirElemVal(block, zir_inst),
-            .elem_val_node => try sema.zirElemValNode(block, zir_inst),
-            .add => try sema.zirArithmetic(block, zir_inst),
-            .addwrap => try sema.zirArithmetic(block, zir_inst),
-            .sub => try sema.zirArithmetic(block, zir_inst),
-            .subwrap => try sema.zirArithmetic(block, zir_inst),
+
+            .add => try sema.zirArithmetic(block, inst),
+            .addwrap => try sema.zirArithmetic(block, inst),
+            .alloc => try sema.zirAlloc(block, inst),
+            .alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)),
+            .alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)),
+            .alloc_mut => try sema.zirAllocMut(block, inst),
+            .array_cat => try sema.zirArrayCat(block, inst),
+            .array_mul => try sema.zirArrayMul(block, inst),
+            .array_type => try sema.zirArrayType(block, inst),
+            .array_type_sentinel => try sema.zirArrayTypeSentinel(block, inst),
+            .as => try sema.zirAs(block, inst),
+            .as_node => try sema.zirAsNode(block, inst),
+            .@"asm" => try sema.zirAsm(block, inst, false),
+            .asm_volatile => try sema.zirAsm(block, inst, true),
+            .bit_and => try sema.zirBitwise(block, inst),
+            .bit_not => try sema.zirBitNot(block, inst),
+            .bit_or => try sema.zirBitwise(block, inst),
+            .bitcast => try sema.zirBitcast(block, inst),
+            .bitcast_ref => try sema.zirBitcastRef(block, inst),
+            .bitcast_result_ptr => try sema.zirBitcastResultPtr(block, inst),
+            .block => try sema.zirBlock(block, inst, false),
+            .block_comptime => try sema.zirBlock(block, inst, true),
+            .bool_not => try sema.zirBoolNot(block, inst),
+            .bool_and => try sema.zirBoolOp(block, inst, false),
+            .bool_or => try sema.zirBoolOp(block, inst, true),
+            .bool_br_and => try sema.zirBoolBr(block, inst, false),
+            .bool_br_or => try sema.zirBoolBr(block, inst, true),
+            .call => try sema.zirCall(block, inst, .auto),
+            .call_compile_time => try sema.zirCall(block, inst, .compile_time),
+            .call_none => try sema.zirCallNone(block, inst),
+            .cmp_eq => try sema.zirCmp(block, inst, .eq),
+            .cmp_gt => try sema.zirCmp(block, inst, .gt),
+            .cmp_gte => try sema.zirCmp(block, inst, .gte),
+            .cmp_lt => try sema.zirCmp(block, inst, .lt),
+            .cmp_lte => try sema.zirCmp(block, inst, .lte),
+            .cmp_neq => try sema.zirCmp(block, inst, .neq),
+            .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),
+            .@"const" => try sema.zirConst(block, inst),
+            .decl_ref => try sema.zirDeclRef(block, inst),
+            .decl_val => try sema.zirDeclVal(block, inst),
+            .deref_node => try sema.zirDerefNode(block, inst),
+            .div => try sema.zirArithmetic(block, inst),
+            .elem_ptr => try sema.zirElemPtr(block, inst),
+            .elem_ptr_node => try sema.zirElemPtrNode(block, inst),
+            .elem_val => try sema.zirElemVal(block, inst),
+            .elem_val_node => try sema.zirElemValNode(block, inst),
+            .enum_literal => try sema.zirEnumLiteral(block, inst),
+            .enum_literal_small => try sema.zirEnumLiteralSmall(block, inst),
+            .err_union_code => try sema.zirErrUnionCode(block, inst),
+            .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
+            .err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true),
+            .err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, true),
+            .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst, false),
+            .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false),
+            .error_set => try sema.zirErrorSet(block, inst),
+            .error_union_type => try sema.zirErrorUnionType(block, inst),
+            .error_value => try sema.zirErrorValue(block, inst),
+            .field_ptr => try sema.zirFieldPtr(block, inst),
+            .field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
+            .field_val => try sema.zirFieldVal(block, inst),
+            .field_val_named => try sema.zirFieldValNamed(block, inst),
+            .floatcast => try sema.zirFloatcast(block, inst),
+            .fn_type => try sema.zirFnType(block, inst, false),
+            .fn_type_cc => try sema.zirFnTypeCc(block, inst, false),
+            .fn_type_cc_var_args => try sema.zirFnTypeCc(block, inst, true),
+            .fn_type_var_args => try sema.zirFnType(block, inst, true),
+            .import => try sema.zirImport(block, inst),
+            .indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
+            .int => try sema.zirInt(block, inst),
+            .int_type => try sema.zirIntType(block, inst),
+            .intcast => try sema.zirIntcast(block, inst),
+            .is_err => try sema.zirIsErr(block, inst),
+            .is_err_ptr => try sema.zirIsErrPtr(block, inst),
+            .is_non_null => try sema.zirIsNull(block, inst, true),
+            .is_non_null_ptr => try sema.zirIsNullPtr(block, inst, true),
+            .is_null => try sema.zirIsNull(block, inst, false),
+            .is_null_ptr => try sema.zirIsNullPtr(block, inst, false),
+            .merge_error_sets => try sema.zirMergeErrorSets(block, inst),
+            .mod_rem => try sema.zirArithmetic(block, inst),
+            .mul => try sema.zirArithmetic(block, inst),
+            .mulwrap => try sema.zirArithmetic(block, inst),
             .negate => @panic("TODO"),
             .negate_wrap => @panic("TODO"),
-            .mul => try sema.zirArithmetic(block, zir_inst),
-            .mulwrap => try sema.zirArithmetic(block, zir_inst),
-            .div => try sema.zirArithmetic(block, zir_inst),
-            .mod_rem => try sema.zirArithmetic(block, zir_inst),
-            .array_cat => try sema.zirArrayCat(block, zir_inst),
-            .array_mul => try sema.zirArrayMul(block, zir_inst),
-            .bit_and => try sema.zirBitwise(block, zir_inst),
-            .bit_not => try sema.zirBitNot(block, zir_inst),
-            .bit_or => try sema.zirBitwise(block, zir_inst),
-            .xor => try sema.zirBitwise(block, zir_inst),
-            .shl => try sema.zirShl(block, zir_inst),
-            .shr => try sema.zirShr(block, zir_inst),
-            .cmp_lt => try sema.zirCmp(block, zir_inst, .lt),
-            .cmp_lte => try sema.zirCmp(block, zir_inst, .lte),
-            .cmp_eq => try sema.zirCmp(block, zir_inst, .eq),
-            .cmp_gte => try sema.zirCmp(block, zir_inst, .gte),
-            .cmp_gt => try sema.zirCmp(block, zir_inst, .gt),
-            .cmp_neq => try sema.zirCmp(block, zir_inst, .neq),
-            .condbr => try sema.zirCondbr(block, zir_inst),
-            .is_null => try sema.zirIsNull(block, zir_inst, false),
-            .is_non_null => try sema.zirIsNull(block, zir_inst, true),
-            .is_null_ptr => try sema.zirIsNullPtr(block, zir_inst, false),
-            .is_non_null_ptr => try sema.zirIsNullPtr(block, zir_inst, true),
-            .is_err => try sema.zirIsErr(block, zir_inst),
-            .is_err_ptr => try sema.zirIsErrPtr(block, zir_inst),
-            .bool_not => try sema.zirBoolNot(block, zir_inst),
-            .typeof => try sema.zirTypeof(block, zir_inst),
-            .typeof_peer => try sema.zirTypeofPeer(block, zir_inst),
-            .optional_type => try sema.zirOptionalType(block, zir_inst),
-            .optional_type_from_ptr_elem => try sema.zirOptionalTypeFromPtrElem(block, zir_inst),
-            .optional_payload_safe => try sema.zirOptionalPayload(block, zir_inst, true),
-            .optional_payload_unsafe => try sema.zirOptionalPayload(block, zir_inst, false),
-            .optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, zir_inst, true),
-            .optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, zir_inst, false),
-            .err_union_payload_safe => try sema.zirErrUnionPayload(block, zir_inst, true),
-            .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, zir_inst, false),
-            .err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, zir_inst, true),
-            .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, zir_inst, false),
-            .err_union_code => try sema.zirErrUnionCode(block, zir_inst),
-            .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, zir_inst),
-            .ensure_err_payload_void => try sema.zirEnsureErrPayloadVoid(block, zir_inst),
-            .array_type => try sema.zirArrayType(block, zir_inst),
-            .array_type_sentinel => try sema.zirArrayTypeSentinel(block, zir_inst),
-            .enum_literal => try sema.zirEnumLiteral(block, zir_inst),
-            .enum_literal_small => try sema.zirEnumLiteralSmall(block, zir_inst),
-            .merge_error_sets => try sema.zirMergeErrorSets(block, zir_inst),
-            .error_union_type => try sema.zirErrorUnionType(block, zir_inst),
-            .error_set => try sema.zirErrorSet(block, zir_inst),
-            .error_value => try sema.zirErrorValue(block, zir_inst),
-            .slice_start => try sema.zirSliceStart(block, zir_inst),
-            .slice_end => try sema.zirSliceEnd(block, zir_inst),
-            .slice_sentinel => try sema.zirSliceSentinel(block, zir_inst),
-            .import => try sema.zirImport(block, zir_inst),
-            .bool_and => try sema.zirBoolOp(block, zir_inst, false),
-            .bool_or => try sema.zirBoolOp(block, zir_inst, true),
+            .optional_payload_safe => try sema.zirOptionalPayload(block, inst, true),
+            .optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, inst, true),
+            .optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false),
+            .optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false),
+            .optional_type => try sema.zirOptionalType(block, inst),
+            .optional_type_from_ptr_elem => try sema.zirOptionalTypeFromPtrElem(block, inst),
+            .param_type => try sema.zirParamType(block, inst),
+            .ptr_type => try sema.zirPtrType(block, inst),
+            .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
+            .ptrtoint => try sema.zirPtrtoint(block, inst),
+            .ref => try sema.zirRef(block, inst),
+            .ret_ptr => try sema.zirRetPtr(block, inst),
+            .ret_type => try sema.zirRetType(block, inst),
+            .shl => try sema.zirShl(block, inst),
+            .shr => try sema.zirShr(block, inst),
+            .slice_end => try sema.zirSliceEnd(block, inst),
+            .slice_sentinel => try sema.zirSliceSentinel(block, inst),
+            .slice_start => try sema.zirSliceStart(block, inst),
+            .str => try sema.zirStr(block, inst),
+            .sub => try sema.zirArithmetic(block, inst),
+            .subwrap => try sema.zirArithmetic(block, inst),
+            .typeof => try sema.zirTypeof(block, inst),
+            .typeof_peer => try sema.zirTypeofPeer(block, inst),
+            .xor => try sema.zirBitwise(block, inst),
             // TODO
-            //.switchbr => try sema.zirSwitchBr(block, zir_inst, false),
-            //.switchbr_ref => try sema.zirSwitchBr(block, zir_inst, true),
-            //.switch_range => try sema.zirSwitchRange(block, zir_inst),
+            //.switchbr => try sema.zirSwitchBr(block, inst, false),
+            //.switchbr_ref => try sema.zirSwitchBr(block, inst, true),
+            //.switch_range => try sema.zirSwitchRange(block, inst),
+
+            // Instructions that we know to *always* be noreturn based solely on their tag.
+            // These functions match the return type of analyzeBody so that we can
+            // tail call them here.
+            .condbr => return sema.zirCondbr(block, inst),
+            .@"break" => return sema.zirBreak(block, inst),
+            .break_void_tok => return sema.zirBreakVoidTok(block, inst),
+            .break_flat => return sema.code.instructions.items(.data)[inst].un_node.operand,
+            .compile_error => return sema.zirCompileError(block, inst),
+            .ret_coerce => return sema.zirRetTok(block, inst, true),
+            .ret_node => return sema.zirRetNode(block, inst),
+            .ret_tok => return sema.zirRetTok(block, inst, false),
+            .@"unreachable" => return sema.zirUnreachable(block, inst),
+            .loop => return sema.zirLoop(block, inst),
+
+            // Instructions that we know can *never* be noreturn based solely on
+            // their tag. We avoid needlessly checking if they are noreturn and
+            // continue the loop.
+            // We also know that they cannot be referenced later, so we avoid
+            // putting them into the map.
+            .breakpoint => {
+                try sema.zirBreakpoint(block, inst);
+                continue;
+            },
+            .dbg_stmt_node => {
+                try sema.zirDbgStmtNode(block, inst);
+                continue;
+            },
+            .ensure_err_payload_void => {
+                try sema.zirEnsureErrPayloadVoid(block, inst);
+                continue;
+            },
+            .ensure_result_non_error => {
+                try sema.zirEnsureResultNonError(block, inst);
+                continue;
+            },
+            .ensure_result_used => {
+                try sema.zirEnsureResultUsed(block, inst);
+                continue;
+            },
+            .compile_log => {
+                try sema.zirCompileLog(block, inst);
+                continue;
+            },
+            .set_eval_branch_quota => {
+                try sema.zirSetEvalBranchQuota(block, inst);
+                continue;
+            },
+            .store => {
+                try sema.zirStore(block, inst);
+                continue;
+            },
+            .store_to_block_ptr => {
+                try sema.zirStoreToBlockPtr(block, inst);
+                continue;
+            },
+            .store_to_inferred_ptr => {
+                try sema.zirStoreToInferredPtr(block, inst);
+                continue;
+            },
+            .resolve_inferred_alloc => {
+                try sema.zirResolveInferredAlloc(block, inst);
+                continue;
+            },
         };
-        if (map[zir_inst].ty.isNoReturn()) {
-            break;
-        }
+        if (map[inst].ty.isNoReturn())
+            return always_noreturn;
     }
 }
 
@@ -392,7 +450,7 @@ fn zirRetType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError
     return sema.mod.constType(sema.arena, src, ret_type);
 }
 
-fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -400,12 +458,12 @@ fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I
     const operand = try sema.resolveInst(inst_data.operand);
     const src = inst_data.src();
     switch (operand.ty.zigTypeTag()) {
-        .Void, .NoReturn => return sema.mod.constVoid(sema.arena, .unneeded),
+        .Void, .NoReturn => return,
         else => return sema.mod.fail(&block.base, src, "expression value is ignored", .{}),
     }
 }
 
-fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -414,7 +472,7 @@ fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Inde
     const src = inst_data.src();
     switch (operand.ty.zigTypeTag()) {
         .ErrorSet, .ErrorUnion => return sema.mod.fail(&block.base, src, "error is discarded", .{}),
-        else => return sema.mod.constVoid(sema.arena, .unneeded),
+        else => return,
     }
 }
 
@@ -508,11 +566,7 @@ fn zirAllocInferred(
     return result;
 }
 
-fn zirResolveInferredAlloc(
-    sema: *Sema,
-    block: *Scope.Block,
-    inst: zir.Inst.Index,
-) InnerError!*Inst {
+fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -536,15 +590,9 @@ fn zirResolveInferredAlloc(
     // Change it to a normal alloc.
     ptr.ty = final_ptr_ty;
     ptr.tag = .alloc;
-
-    return sema.mod.constVoid(sema.arena, .unneeded);
 }
 
-fn zirStoreToBlockPtr(
-    sema: *Sema,
-    block: *Scope.Block,
-    inst: zir.Inst.Index,
-) InnerError!*Inst {
+fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -560,11 +608,7 @@ fn zirStoreToBlockPtr(
     return sema.storePtr(block, src, bitcasted_ptr, value);
 }
 
-fn zirStoreToInferredPtr(
-    sema: *Sema,
-    block: *Scope.Block,
-    inst: zir.Inst.Index,
-) InnerError!*Inst {
+fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -583,21 +627,16 @@ fn zirStoreToInferredPtr(
     return sema.storePtr(block, src, bitcasted_ptr, value);
 }
 
-fn zirSetEvalBranchQuota(
-    sema: *Sema,
-    block: *Scope.Block,
-    inst: zir.Inst.Index,
-) InnerError!*Inst {
+fn zirSetEvalBranchQuota(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
     try sema.requireFunctionBlock(block, src);
     const quota = try sema.resolveAlreadyCoercedInt(block, src, inst_data.operand, u32);
     if (sema.branch_quota < quota)
         sema.branch_quota = quota;
-    return sema.mod.constVoid(sema.arena, .unneeded);
 }
 
-fn zirStore(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirStore(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -677,7 +716,7 @@ fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*In
     return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int);
 }
 
-fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -688,7 +727,7 @@ fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inner
     return sema.mod.fail(&block.base, src, "{s}", .{msg});
 }
 
-fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     var managed = sema.mod.compile_log_text.toManaged(sema.gpa);
     defer sema.mod.compile_log_text = managed.moveToUnmanaged();
     const writer = managed.writer();
@@ -711,10 +750,9 @@ fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr
     if (!gop.found_existing) {
         gop.entry.value = inst_data.src().toSrcLoc(&block.base);
     }
-    return sema.mod.constVoid(sema.arena, .unneeded);
 }
 
-fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -746,41 +784,13 @@ fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerE
     };
     defer child_block.instructions.deinit(sema.gpa);
 
-    try sema.analyzeBody(&child_block, body);
+    _ = try sema.analyzeBody(&child_block, body);
 
     // Loop repetition is implied so the last instruction may or may not be a noreturn instruction.
 
     try parent_block.instructions.append(sema.gpa, &loop_inst.base);
     loop_inst.body = .{ .instructions = try sema.arena.dupe(*Inst, child_block.instructions.items) };
-    return &loop_inst.base;
-}
-
-fn zirBlockFlat(
-    sema: *Sema,
-    parent_block: *Scope.Block,
-    inst: zir.Inst.Index,
-    is_comptime: bool,
-) InnerError!*Inst {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
-    const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
-    const body = sema.code.extra[extra.end..][0..extra.data.operands_len];
-
-    var child_block = parent_block.makeSubBlock();
-    defer child_block.instructions.deinit(sema.gpa);
-    child_block.is_comptime = child_block.is_comptime or is_comptime;
-
-    try sema.analyzeBody(&child_block, body);
-
-    // Move the analyzed instructions into the parent block arena.
-    const copied_instructions = try sema.arena.dupe(*Inst, child_block.instructions.items);
-    try parent_block.instructions.appendSlice(sema.gpa, copied_instructions);
-
-    // The result of a flat block is the last instruction.
-    return sema.inst_map[body[body.len - 1]];
+    return always_noreturn;
 }
 
 fn zirBlock(
@@ -794,8 +804,8 @@ fn zirBlock(
 
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
-    const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
-    const body = sema.code.extra[extra.end..][0..extra.data.operands_len];
+    const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+    const body = sema.code.extra[extra.end..][0..extra.data.body_len];
 
     // Reserve space for a Block instruction so that generated Break instructions can
     // point to it, even if it doesn't end up getting used because the code ends up being
@@ -833,7 +843,7 @@ fn zirBlock(
     defer merges.results.deinit(sema.gpa);
     defer merges.br_list.deinit(sema.gpa);
 
-    try sema.analyzeBody(&child_block, body);
+    _ = try sema.analyzeBody(&child_block, body);
 
     return sema.analyzeBlockBody(parent_block, &child_block, merges);
 }
@@ -919,17 +929,17 @@ fn analyzeBlockBody(
     return &merges.block_inst.base;
 }
 
-fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
     const src_node = sema.code.instructions.items(.data)[inst].node;
     const src: LazySrcLoc = .{ .node_offset = src_node };
     try sema.requireRuntimeBlock(block, src);
-    return block.addNoOp(src, Type.initTag(.void), .breakpoint);
+    _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
 }
 
-fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -939,7 +949,7 @@ fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*
     return sema.analyzeBreak(block, sema.src, zir_block, operand);
 }
 
-fn zirBreakVoidTok(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirBreakVoidTok(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -955,7 +965,7 @@ fn analyzeBreak(
     src: LazySrcLoc,
     zir_block: zir.Inst.Index,
     operand: *Inst,
-) InnerError!*Inst {
+) InnerError!zir.Inst.Ref {
     var block = start_block;
     while (true) {
         if (block.label) |*label| {
@@ -981,26 +991,24 @@ fn analyzeBreak(
                 try block.instructions.append(sema.gpa, &br.base);
                 try label.merges.results.append(sema.gpa, operand);
                 try label.merges.br_list.append(sema.gpa, br);
-                return &br.base;
+                return always_noreturn;
             }
         }
         block = block.parent.?;
     }
 }
 
-fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    if (block.is_comptime) {
-        return sema.mod.constVoid(sema.arena, .unneeded);
-    }
+    if (block.is_comptime) return;
 
     const src_node = sema.code.instructions.items(.data)[inst].node;
     const src: LazySrcLoc = .{ .node_offset = src_node };
     const src_loc = src.toSrcLoc(&block.base);
     const abs_byte_off = try src_loc.byteOffset();
-    return block.addDbgStmt(src, abs_byte_off);
+    _ = try block.addDbgStmt(src, abs_byte_off);
 }
 
 fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@@ -1185,7 +1193,7 @@ fn analyzeCall(
 
         // This will have return instructions analyzed as break instructions to
         // the block_inst above.
-        try sema.root(&child_block);
+        _ = try sema.root(&child_block);
 
         return sema.analyzeBlockBody(block, &child_block, merges);
     }
@@ -1638,7 +1646,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) In
     return block.addUnOp(src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err_ptr, operand);
 }
 
-fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1650,7 +1658,6 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Inde
     if (operand.ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
         return sema.mod.fail(&block.base, src, "expression value is ignored", .{});
     }
-    return sema.mod.constVoid(sema.arena, .unneeded);
 }
 
 fn zirFnType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, var_args: bool) InnerError!*Inst {
@@ -2067,7 +2074,7 @@ fn zirSwitchBr(
     parent_block: *Scope.Block,
     inst: zir.Inst.Index,
     ref: bool,
-) InnerError!*Inst {
+) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2087,18 +2094,18 @@ fn zirSwitchBr(
             const item = try sema.resolveConstValue(parent_block, case_src, casted);
 
             if (target_val.eql(item)) {
-                try sema.analyzeBody(parent_block, case.body);
-                return sema.mod.constNoReturn(sema.arena, inst.base.src);
+                _ = try sema.analyzeBody(parent_block, case.body);
+                return always_noreturn;
             }
         }
-        try sema.analyzeBody(parent_block, inst.positionals.else_body);
-        return sema.mod.constNoReturn(sema.arena, inst.base.src);
+        _ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
+        return always_noreturn;
     }
 
     if (inst.positionals.cases.len == 0) {
         // no cases just analyze else_branch
-        try sema.analyzeBody(parent_block, inst.positionals.else_body);
-        return sema.mod.constNoReturn(sema.arena, inst.base.src);
+        _ = try sema.analyzeBody(parent_block, inst.positionals.else_body);
+        return always_noreturn;
     }
 
     try sema.requireRuntimeBlock(parent_block, inst.base.src);
@@ -2122,7 +2129,7 @@ fn zirSwitchBr(
         const casted = try sema.coerce(block, target.ty, resolved, resolved_src);
         const item = try sema.resolveConstValue(parent_block, case_src, casted);
 
-        try sema.analyzeBody(&case_block, case.body);
+        _ = try sema.analyzeBody(&case_block, case.body);
 
         cases[i] = .{
             .item = item,
@@ -2131,7 +2138,7 @@ fn zirSwitchBr(
     }
 
     case_block.instructions.items.len = 0;
-    try sema.analyzeBody(&case_block, inst.positionals.else_body);
+    _ = try sema.analyzeBody(&case_block, inst.positionals.else_body);
 
     const else_body: ir.Body = .{
         .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
@@ -2719,6 +2726,75 @@ fn zirBoolOp(
     return block.addBinOp(src, bool_type, tag, lhs, rhs);
 }
 
+fn zirBoolBr(
+    sema: *Sema,
+    parent_block: *Scope.Block,
+    inst: zir.Inst.Index,
+    is_bool_or: bool,
+) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const inst_data = sema.code.instructions.items(.data)[inst].bool_br;
+    const src: LazySrcLoc = .unneeded;
+    const lhs = try sema.resolveInst(inst_data.lhs);
+    const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+    const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+    if (try sema.resolveDefinedValue(parent_block, src, lhs)) |lhs_val| {
+        if (lhs_val.toBool() == is_bool_or) {
+            return sema.mod.constBool(sema.arena, src, is_bool_or);
+        }
+        // comptime-known left-hand side. No need for a block here; the result
+        // is simply the rhs expression. Here we rely on there only being 1
+        // break instruction (`break_flat`).
+        const zir_inst_ref = try sema.analyzeBody(parent_block, body);
+        return sema.resolveInst(zir_inst_ref);
+    }
+
+    const block_inst = try sema.arena.create(Inst.Block);
+    block_inst.* = .{
+        .base = .{
+            .tag = Inst.Block.base_tag,
+            .ty = Type.initTag(.bool),
+            .src = src,
+        },
+        .body = undefined,
+    };
+
+    var child_block = parent_block.makeSubBlock();
+    defer child_block.instructions.deinit(sema.gpa);
+
+    var then_block = child_block.makeSubBlock();
+    defer then_block.instructions.deinit(sema.gpa);
+
+    var else_block = child_block.makeSubBlock();
+    defer else_block.instructions.deinit(sema.gpa);
+
+    const lhs_block = if (is_bool_or) &then_block else &else_block;
+    const rhs_block = if (is_bool_or) &else_block else &then_block;
+
+    const lhs_result = try sema.mod.constInst(sema.arena, src, .{
+        .ty = Type.initTag(.bool),
+        .val = if (is_bool_or) Value.initTag(.bool_true) else Value.initTag(.bool_false),
+    });
+    _ = try lhs_block.addBr(src, block_inst, lhs_result);
+
+    const rhs_result_zir_ref = try sema.analyzeBody(rhs_block, body);
+    const rhs_result = try sema.resolveInst(rhs_result_zir_ref);
+    _ = try rhs_block.addBr(src, block_inst, rhs_result);
+
+    const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) };
+    const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, rhs_block.instructions.items) };
+    _ = try child_block.addCondBr(src, lhs, tzir_then_body, tzir_else_body);
+
+    block_inst.body = .{
+        .instructions = try sema.arena.dupe(*Inst, child_block.instructions.items),
+    };
+    try parent_block.instructions.append(sema.gpa, &block_inst.base);
+    return &block_inst.base;
+}
+
 fn zirIsNull(
     sema: *Sema,
     block: *Scope.Block,
@@ -2770,7 +2846,11 @@ fn zirIsErrPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerErro
     return sema.analyzeIsErr(block, src, loaded);
 }
 
-fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirCondbr(
+    sema: *Sema,
+    parent_block: *Scope.Block,
+    inst: zir.Inst.Index,
+) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2787,8 +2867,8 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
 
     if (try sema.resolveDefinedValue(parent_block, src, cond)) |cond_val| {
         const body = if (cond_val.toBool()) then_body else else_body;
-        try sema.analyzeBody(parent_block, body);
-        return sema.mod.constNoReturn(sema.arena, src);
+        _ = try sema.analyzeBody(parent_block, body);
+        return always_noreturn;
     }
 
     var true_block: Scope.Block = .{
@@ -2800,7 +2880,7 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
         .is_comptime = parent_block.is_comptime,
     };
     defer true_block.instructions.deinit(sema.gpa);
-    try sema.analyzeBody(&true_block, then_body);
+    _ = try sema.analyzeBody(&true_block, then_body);
 
     var false_block: Scope.Block = .{
         .parent = parent_block,
@@ -2811,14 +2891,15 @@ fn zirCondbr(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) Inne
         .is_comptime = parent_block.is_comptime,
     };
     defer false_block.instructions.deinit(sema.gpa);
-    try sema.analyzeBody(&false_block, else_body);
+    _ = try sema.analyzeBody(&false_block, else_body);
 
     const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, true_block.instructions.items) };
     const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, false_block.instructions.items) };
-    return parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
+    _ = try parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
+    return always_noreturn;
 }
 
-fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2830,7 +2911,8 @@ fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE
     if (safety_check and block.wantSafety()) {
         return sema.safetyPanic(block, src, .unreach);
     } else {
-        return block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+        _ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+        return always_noreturn;
     }
 }
 
@@ -2839,7 +2921,7 @@ fn zirRetTok(
     block: *Scope.Block,
     inst: zir.Inst.Index,
     need_coercion: bool,
-) InnerError!*Inst {
+) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2850,7 +2932,7 @@ fn zirRetTok(
     return sema.analyzeRet(block, operand, src, need_coercion);
 }
 
-fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2867,22 +2949,24 @@ fn analyzeRet(
     operand: *Inst,
     src: LazySrcLoc,
     need_coercion: bool,
-) InnerError!*Inst {
+) InnerError!zir.Inst.Ref {
     if (block.inlining) |inlining| {
         // We are inlining a function call; rewrite the `ret` as a `break`.
         try inlining.merges.results.append(sema.gpa, operand);
-        const br = try block.addBr(src, inlining.merges.block_inst, operand);
-        return &br.base;
+        _ = try block.addBr(src, inlining.merges.block_inst, operand);
+        return always_noreturn;
     }
 
     if (need_coercion) {
         if (sema.func) |func| {
             const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty;
             const casted_operand = try sema.coerce(block, fn_ty.fnReturnType(), operand, src);
-            return block.addUnOp(src, Type.initTag(.noreturn), .ret, casted_operand);
+            _ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, casted_operand);
+            return always_noreturn;
         }
     }
-    return block.addUnOp(src, Type.initTag(.noreturn), .ret, operand);
+    _ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, operand);
+    return always_noreturn;
 }
 
 fn floatOpAllowed(tag: zir.Inst.Tag) bool {
@@ -3051,10 +3135,11 @@ fn addSafetyCheck(sema: *Sema, parent_block: *Scope.Block, ok: *Inst, panic_id:
     try parent_block.instructions.append(sema.gpa, &block_inst.base);
 }
 
-fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !*Inst {
+fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !zir.Inst.Ref {
     // TODO Once we have a panic function to call, call it here instead of breakpoint.
     _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
-    return block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+    _ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+    return always_noreturn;
 }
 
 fn emitBackwardBranch(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
@@ -3405,20 +3490,20 @@ fn storePtr(
     src: LazySrcLoc,
     ptr: *Inst,
     uncasted_value: *Inst,
-) !*Inst {
+) !void {
     if (ptr.ty.isConstPtr())
         return sema.mod.fail(&block.base, src, "cannot assign to constant", .{});
 
     const elem_ty = ptr.ty.elemType();
     const value = try sema.coerce(block, elem_ty, uncasted_value, uncasted_value.src);
     if (elem_ty.onePossibleValue() != null)
-        return sema.mod.constVoid(sema.arena, .unneeded);
+        return;
 
     // TODO handle comptime pointer writes
     // TODO handle if the element type requires comptime
 
     try sema.requireRuntimeBlock(block, src);
-    return block.addBinOp(src, Type.initTag(.void), .store, ptr, value);
+    _ = try block.addBinOp(src, Type.initTag(.void), .store, ptr, value);
 }
 
 fn bitcast(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
src/zir.zig
@@ -99,11 +99,7 @@ pub const Code = struct {
         try stderr.print("ZIR {s} {s} {{\n", .{ kind, decl_name });
 
         const root_body = code.extra[code.root_start..][0..code.root_len];
-        for (root_body) |inst| {
-            try stderr.print("  %{d} ", .{inst});
-            try writer.writeInstToStream(stderr, inst);
-            try stderr.writeByte('\n');
-        }
+        try writer.writeBody(stderr, root_body);
 
         try stderr.print("}} // ZIR {s} {s}\n\n", .{ kind, decl_name });
     }
@@ -451,16 +447,10 @@ pub const Inst = struct {
         /// Bitwise OR. `|`
         bit_or,
         /// A labeled block of code, which can return a value.
-        /// Uses the `pl_node` union field. Payload is `MultiOp`.
+        /// Uses the `pl_node` union field. Payload is `Block`.
         block,
-        /// A block of code, which can return a value. There are no instructions that break out of
-        /// this block; it is implied that the final instruction is the result.
-        /// Uses the `pl_node` union field. Payload is `MultiOp`.
-        block_flat,
         /// Same as `block` but additionally makes the inner instructions execute at comptime.
         block_comptime,
-        /// Same as `block_flat` but additionally makes the inner instructions execute at comptime.
-        block_comptime_flat,
         /// Boolean AND. See also `bit_and`.
         /// Uses the `pl_node` union field. Payload is `Bin`.
         bool_and,
@@ -470,6 +460,14 @@ pub const Inst = struct {
         /// Boolean OR. See also `bit_or`.
         /// Uses the `pl_node` union field. Payload is `Bin`.
         bool_or,
+        /// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
+        /// is a block, which is evaluated if `lhs` is `true`.
+        /// Uses the `bool_br` union field.
+        bool_br_and,
+        /// Short-circuiting boolean `or`. `lhs` is a boolean `Ref` and the other operand
+        /// is a block, which is evaluated if `lhs` is `false`.
+        /// Uses the `bool_br` union field.
+        bool_br_or,
         /// Return a value from a block.
         /// Uses the `bin` union field: `lhs` is `Index` to the block (*not* `Ref`!),
         /// `rhs` is operand.
@@ -480,6 +478,12 @@ pub const Inst = struct {
         /// Uses the `un_tok` union field.
         /// Note that the block operand is a `Index`, not `Ref`.
         break_void_tok,
+        /// Return a value from a block. This is a special form that is only valid
+        /// when there is exactly 1 break from a block (this one). This instruction
+        /// allows using the return value from `Sema.analyzeBody`. The block is
+        /// assumed to be the direct parent of this instruction.
+        /// Uses the `un_node` union field. The AST node is unused.
+        break_flat,
         /// Uses the `node` union field.
         breakpoint,
         /// Function call with modifier `.auto`.
@@ -637,7 +641,7 @@ pub const Inst = struct {
         /// A labeled block of code that loops forever. At the end of the body it is implied
         /// to repeat; no explicit "repeat" instruction terminates loop bodies.
         /// Uses the `pl_node` field. The AST node is either a for loop or while loop.
-        /// The payload is `MultiOp`.
+        /// The payload is `Block`.
         loop,
         /// Merge two error sets into one, `E1 || E2`.
         merge_error_sets,
@@ -886,9 +890,9 @@ pub const Inst = struct {
                 .bitcast_result_ptr,
                 .bit_or,
                 .block,
-                .block_flat,
                 .block_comptime,
-                .block_comptime_flat,
+                .bool_br_and,
+                .bool_br_or,
                 .bool_not,
                 .bool_and,
                 .bool_or,
@@ -988,6 +992,7 @@ pub const Inst = struct {
 
                 .@"break",
                 .break_void_tok,
+                .break_flat,
                 .condbr,
                 .compile_error,
                 .ret_node,
@@ -1127,6 +1132,11 @@ pub const Inst = struct {
             /// For `fn_type_cc` this points to `FnTypeCc` in `extra`.
             payload_index: u32,
         },
+        bool_br: struct {
+            lhs: Ref,
+            /// Points to a `Block`.
+            payload_index: u32,
+        },
         param_type: struct {
             callee: Ref,
             param_index: u32,
@@ -1191,6 +1201,12 @@ pub const Inst = struct {
         operands_len: u32,
     };
 
+    /// This data is stored inside extra, with trailing operands according to `body_len`.
+    /// Each operand is an `Index`.
+    pub const Block = struct {
+        body_len: u32,
+    };
+
     /// Stored inside extra, with trailing arguments according to `args_len`.
     /// Each argument is a `Ref`.
     pub const Call = struct {
@@ -1342,6 +1358,7 @@ const Writer = struct {
             .err_union_payload_unsafe_ptr,
             .err_union_code,
             .err_union_code_ptr,
+            .break_flat,
             => try self.writeUnNode(stream, inst),
 
             .break_void_tok,
@@ -1358,6 +1375,10 @@ const Writer = struct {
             .ensure_err_payload_void,
             => try self.writeUnTok(stream, inst),
 
+            .bool_br_and,
+            .bool_br_or,
+            => try self.writeBoolBr(stream, inst),
+
             .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
             .@"const" => try self.writeConst(stream, inst),
             .param_type => try self.writeParamType(stream, inst),
@@ -1370,9 +1391,7 @@ const Writer = struct {
             .@"asm",
             .asm_volatile,
             .block,
-            .block_flat,
             .block_comptime,
-            .block_comptime_flat,
             .call,
             .call_compile_time,
             .compile_log,
@@ -1618,6 +1637,19 @@ const Writer = struct {
         return self.writeFnTypeCommon(stream, param_types, inst_data.return_type, var_args, cc);
     }
 
+    fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].bool_br;
+        const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
+        const body = self.code.extra[extra.end..][0..extra.data.body_len];
+        try self.writeInstRef(stream, inst_data.lhs);
+        try stream.writeAll(", {\n");
+        self.indent += 2;
+        try self.writeBody(stream, body);
+        self.indent -= 2;
+        try stream.writeByteNTimes(' ', self.indent);
+        try stream.writeAll("})");
+    }
+
     fn writeFnTypeCc(
         self: *Writer,
         stream: anytype,
@@ -1713,4 +1745,13 @@ const Writer = struct {
             @tagName(src), delta_line.line + 1, delta_line.column + 1,
         });
     }
+
+    fn writeBody(self: *Writer, stream: anytype, body: []const Inst.Index) !void {
+        for (body) |inst| {
+            try stream.writeByteNTimes(' ', self.indent);
+            try stream.print("%{d} ", .{inst});
+            try self.writeInstToStream(stream, inst);
+            try stream.writeByte('\n');
+        }
+    }
 };