Commit 2a1dd174cd

Andrew Kelley <andrew@ziglang.org>
2021-03-31 06:22:30
stage2: rework AstGen for switch expressions
The switch_br ZIR instructions are now switch_block instructions. This avoids a pointless block always surrounding a switchbr in emitted ZIR code. Introduce typeof_elem ZIR instruction for getting the type of the element of a pointer value in 1 instruction. Change typeof to be un_node, not un_tok. Introduce switch_capture ZIR instructions for obtaining the capture value of switch prongs. Introduce Sema.resolveBody for when you want to extract a *Inst out of a block and you know that there is only going to be 1 break from it. What's not working yet: AstGen does not correctly elide store instructions when it turns out that the result location does not need to be used as a pointer. Also Sema validation code for duplicate switch items is not yet implemented.
1 parent 195ddab
src/AstGen.zig
@@ -22,6 +22,7 @@ const Scope = Module.Scope;
 const GenZir = Scope.GenZir;
 const InnerError = Module.InnerError;
 const Decl = Module.Decl;
+const LazySrcLoc = Module.LazySrcLoc;
 const BuiltinFn = @import("BuiltinFn.zig");
 
 instructions: std.MultiArrayList(zir.Inst) = .{},
@@ -1215,6 +1216,7 @@ fn blockExprStmts(
                         .negate,
                         .negate_wrap,
                         .typeof,
+                        .typeof_elem,
                         .xor,
                         .optional_type,
                         .optional_type_from_ptr_elem,
@@ -1243,6 +1245,24 @@ fn blockExprStmts(
                         .slice_sentinel,
                         .import,
                         .typeof_peer,
+                        .switch_block,
+                        .switch_block_multi,
+                        .switch_block_else,
+                        .switch_block_else_multi,
+                        .switch_block_under,
+                        .switch_block_under_multi,
+                        .switch_block_ref,
+                        .switch_block_ref_multi,
+                        .switch_block_ref_else,
+                        .switch_block_ref_else_multi,
+                        .switch_block_ref_under,
+                        .switch_block_ref_under_multi,
+                        .switch_capture,
+                        .switch_capture_ref,
+                        .switch_capture_multi,
+                        .switch_capture_multi_ref,
+                        .switch_capture_else,
+                        .switch_capture_else_ref,
                         => break :b false,
 
                         // ZIR instructions that are always either `noreturn` or `void`.
@@ -1257,18 +1277,6 @@ fn blockExprStmts(
                         .break_inline,
                         .condbr,
                         .condbr_inline,
-                        .switch_br,
-                        .switch_br_multi,
-                        .switch_br_else,
-                        .switch_br_else_multi,
-                        .switch_br_under,
-                        .switch_br_under_multi,
-                        .switch_br_ref,
-                        .switch_br_ref_multi,
-                        .switch_br_ref_else,
-                        .switch_br_ref_else_multi,
-                        .switch_br_ref_under,
-                        .switch_br_ref_under_multi,
                         .compile_error,
                         .ret_node,
                         .ret_tok,
@@ -1543,7 +1551,7 @@ fn assignOp(
 
     const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
     const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
-    const lhs_type = try gz.addUnTok(.typeof, lhs, infix_node);
+    const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
     const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs);
 
     const result = try gz.addPlNode(op_inst_tag, infix_node, zir.Inst.Bin{
@@ -2548,15 +2556,28 @@ fn switchExpr(
     rl: ResultLoc,
     switch_node: ast.Node.Index,
 ) InnerError!zir.Inst.Ref {
+    const astgen = parent_gz.astgen;
+    const mod = astgen.mod;
+    const gpa = mod.gpa;
     const tree = parent_gz.tree();
     const node_datas = tree.nodes.items(.data);
     const node_tags = tree.nodes.items(.tag);
+    const main_tokens = tree.nodes.items(.main_token);
     const token_tags = tree.tokens.items(.tag);
     const operand_node = node_datas[switch_node].lhs;
     const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
     const case_nodes = tree.extra_data[extra.start..extra.end];
 
+    // We perform two passes over the AST. This first pass is to collect information
+    // for the following variables, make note of the special prong AST node index,
+    // and bail out with a compile error if there are multiple special prongs present.
     var any_payload_is_ref = false;
+    var scalar_cases_len: u32 = 0;
+    var multi_cases_len: u32 = 0;
+    var special_prong: zir.SpecialProng = .none;
+    var special_node: ast.Node.Index = 0;
+    var else_src: ?LazySrcLoc = null;
+    var underscore_src: ?LazySrcLoc = null;
     for (case_nodes) |case_node| {
         const case = switch (node_tags[case_node]) {
             .switch_case_one => tree.switchCaseOne(case_node),
@@ -2568,22 +2589,346 @@ fn switchExpr(
                 any_payload_is_ref = true;
             }
         }
+        // Check for else/`_` prong.
+        if (case.ast.values.len == 0) {
+            const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
+            if (else_src) |src| {
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        case_src,
+                        "multiple else prongs in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(gpa);
+                    try mod.errNote(scope, src, msg, "previous else prong is here", .{});
+                    break :msg msg;
+                };
+                return mod.failWithOwnedErrorMsg(scope, msg);
+            } else if (underscore_src) |some_underscore| {
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        parent_gz.nodeSrcLoc(switch_node),
+                        "else and '_' prong in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(gpa);
+                    try mod.errNote(scope, case_src, msg, "else prong is here", .{});
+                    try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
+                    break :msg msg;
+                };
+                return mod.failWithOwnedErrorMsg(scope, msg);
+            }
+            special_node = case_node;
+            special_prong = .@"else";
+            else_src = case_src;
+            continue;
+        } else if (case.ast.values.len == 1 and
+            node_tags[case.ast.values[0]] == .identifier and
+            mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
+        {
+            const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
+            if (underscore_src) |src| {
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        case_src,
+                        "multiple '_' prongs in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(gpa);
+                    try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
+                    break :msg msg;
+                };
+                return mod.failWithOwnedErrorMsg(scope, msg);
+            } else if (else_src) |some_else| {
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        parent_gz.nodeSrcLoc(switch_node),
+                        "else and '_' prong in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(gpa);
+                    try mod.errNote(scope, some_else, msg, "else prong is here", .{});
+                    try mod.errNote(scope, case_src, msg, "'_' prong is here", .{});
+                    break :msg msg;
+                };
+                return mod.failWithOwnedErrorMsg(scope, msg);
+            }
+            special_node = case_node;
+            special_prong = .under;
+            underscore_src = case_src;
+            continue;
+        }
+
+        if (case.ast.values.len == 1 and
+            getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
+        {
+            scalar_cases_len += 1;
+        } else {
+            multi_cases_len += 1;
+        }
     }
 
-    const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{
-        .rl = .ref,
-        .tag = .switch_br_ref,
-    } else .{
-        .rl = .none,
-        .tag = .switch_br,
+    const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
+    const operand = try expr(parent_gz, scope, operand_rl, operand_node);
+    // We need the type of the operand to use as the result location for all the prong items.
+    const typeof_tag: zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof;
+    const operand_ty_inst = try parent_gz.addUnNode(typeof_tag, operand, operand_node);
+    const item_rl: ResultLoc = .{ .ty = operand_ty_inst };
+
+    // Contains the data that goes into the `extra` array for the SwitchBr/SwitchBrMulti.
+    // This is the header as well as the optional else prong body, as well as all the
+    // scalar cases.
+    // At the end we will memcpy this into place.
+    var scalar_cases_payload = std.ArrayListUnmanaged(u32){};
+    defer scalar_cases_payload.deinit(gpa);
+    // Same deal, but this is only the `extra` data for the multi cases.
+    var multi_cases_payload = std.ArrayListUnmanaged(u32){};
+    defer multi_cases_payload.deinit(gpa);
+
+    var block_scope: GenZir = .{
+        .parent = scope,
+        .astgen = astgen,
+        .force_comptime = parent_gz.force_comptime,
+        .instructions = .{},
     };
-    const operand = try expr(parent_gz, scope, rl_and_tag.rl, operand_node);
+    block_scope.setBreakResultLoc(rl);
+    defer block_scope.instructions.deinit(gpa);
 
-    const result = try parent_gz.addPlNode(.switch_br, switch_node, zir.Inst.SwitchBr{
-        .operand = operand,
-        .cases_len = 0,
-    });
-    return rvalue(parent_gz, scope, rl, result, switch_node);
+    // This gets added to the parent block later, after the item expressions.
+    const switch_block = try parent_gz.addBlock(undefined, switch_node);
+
+    // We re-use this same scope for all cases, including the special prong, if any.
+    var case_scope: GenZir = .{
+        .parent = &block_scope.base,
+        .astgen = astgen,
+        .force_comptime = parent_gz.force_comptime,
+        .instructions = .{},
+    };
+    defer case_scope.instructions.deinit(gpa);
+
+    // Do the else/`_` first because it goes first in the payload.
+    var capture_val_scope: Scope.LocalVal = undefined;
+    if (special_node != 0) {
+        const case = switch (node_tags[special_node]) {
+            .switch_case_one => tree.switchCaseOne(special_node),
+            .switch_case => tree.switchCase(special_node),
+            else => unreachable,
+        };
+        const sub_scope = blk: {
+            const payload_token = case.payload_token orelse break :blk &case_scope.base;
+            const ident = if (token_tags[payload_token] == .asterisk)
+                payload_token + 1
+            else
+                payload_token;
+            const is_ptr = ident != payload_token;
+            if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
+                if (is_ptr) {
+                    return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
+                }
+                break :blk &case_scope.base;
+            }
+            const capture_tag: zir.Inst.Tag = if (is_ptr)
+                .switch_capture_else_ref
+            else
+                .switch_capture_else;
+            const capture = try case_scope.add(.{
+                .tag = capture_tag,
+                .data = .{ .switch_capture = .{
+                    .switch_inst = switch_block,
+                    .prong_index = undefined,
+                } },
+            });
+            const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
+            capture_val_scope = .{
+                .parent = &case_scope.base,
+                .gen_zir = &case_scope,
+                .name = capture_name,
+                .inst = capture,
+                .src = parent_gz.tokSrcLoc(payload_token),
+            };
+            break :blk &capture_val_scope.base;
+        };
+        block_scope.break_count += 1;
+        const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+        if (!astgen.refIsNoReturn(case_result)) {
+            _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+        }
+        // Documentation for this: `zir.Inst.SwitchBr` and `zir.Inst.SwitchBrMulti`.
+        try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+            3 + // operand, scalar_cases_len, else body len
+            @boolToInt(multi_cases_len != 0) +
+            case_scope.instructions.items.len);
+        scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
+        scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
+        if (multi_cases_len != 0) {
+            scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
+        }
+        scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
+        scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
+    } else {
+        // Documentation for this: `zir.Inst.SwitchBr` and `zir.Inst.SwitchBrMulti`.
+        try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+            2 + // operand, scalar_cases_len
+            @boolToInt(multi_cases_len != 0));
+        scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
+        scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
+        if (multi_cases_len != 0) {
+            scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
+        }
+    }
+
+    // In this pass we generate all the item and prong expressions except the special case.
+    for (case_nodes) |case_node| {
+        if (case_node == special_node)
+            continue;
+        const case = switch (node_tags[case_node]) {
+            .switch_case_one => tree.switchCaseOne(case_node),
+            .switch_case => tree.switchCase(case_node),
+            else => unreachable,
+        };
+
+        // Reset the scope.
+        case_scope.instructions.shrinkRetainingCapacity(0);
+
+        const is_multi_case = case.ast.values.len != 1 or
+            getRangeNode(node_tags, node_datas, case.ast.values[0]) != null;
+
+        const sub_scope = blk: {
+            const payload_token = case.payload_token orelse break :blk &case_scope.base;
+            const ident = if (token_tags[payload_token] == .asterisk)
+                payload_token + 1
+            else
+                payload_token;
+            const is_ptr = ident != payload_token;
+            if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
+                if (is_ptr) {
+                    return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
+                }
+                break :blk &case_scope.base;
+            }
+            const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
+            const is_ptr_bits: u2 = @boolToInt(is_ptr);
+            const capture_tag: zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) {
+                0b00 => .switch_capture,
+                0b01 => .switch_capture_ref,
+                0b10 => .switch_capture_multi,
+                0b11 => .switch_capture_multi_ref,
+            };
+            const capture_index = if (is_multi_case) multi_cases_len else scalar_cases_len;
+            const capture = try case_scope.add(.{
+                .tag = capture_tag,
+                .data = .{ .switch_capture = .{
+                    .switch_inst = switch_block,
+                    .prong_index = capture_index,
+                } },
+            });
+            const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
+            capture_val_scope = .{
+                .parent = &case_scope.base,
+                .gen_zir = &case_scope,
+                .name = capture_name,
+                .inst = capture,
+                .src = parent_gz.tokSrcLoc(payload_token),
+            };
+            break :blk &capture_val_scope.base;
+        };
+
+        if (is_multi_case) {
+            // items_len, ranges_len, body_len
+            const header_index = multi_cases_payload.items.len;
+            try multi_cases_payload.resize(gpa, multi_cases_payload.items.len + 3);
+
+            // items
+            var items_len: u32 = 0;
+            for (case.ast.values) |item_node| {
+                if (getRangeNode(node_tags, node_datas, item_node) != null) continue;
+                items_len += 1;
+
+                const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
+                try multi_cases_payload.append(gpa, @enumToInt(item_inst));
+            }
+
+            // ranges
+            var ranges_len: u32 = 0;
+            for (case.ast.values) |item_node| {
+                const range = getRangeNode(node_tags, node_datas, item_node) orelse continue;
+                ranges_len += 1;
+
+                const first = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].lhs);
+                const last = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].rhs);
+                try multi_cases_payload.appendSlice(gpa, &[_]u32{
+                    @enumToInt(first), @enumToInt(last),
+                });
+            }
+
+            block_scope.break_count += 1;
+            const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+            if (!astgen.refIsNoReturn(case_result)) {
+                _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+            }
+
+            multi_cases_payload.items[header_index + 0] = items_len;
+            multi_cases_payload.items[header_index + 1] = ranges_len;
+            multi_cases_payload.items[header_index + 2] = @intCast(u32, case_scope.instructions.items.len);
+            try multi_cases_payload.appendSlice(gpa, case_scope.instructions.items);
+        } else {
+            const item_node = case.ast.values[0];
+            const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
+            block_scope.break_count += 1;
+            const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+            if (!astgen.refIsNoReturn(case_result)) {
+                _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+            }
+            try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+                2 + case_scope.instructions.items.len);
+            scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst));
+            scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
+            scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
+        }
+    }
+    // Now that the item expressions are generated we can add this.
+    try parent_gz.instructions.append(gpa, switch_block);
+
+    const ref_bit: u4 = @boolToInt(any_payload_is_ref);
+    const multi_bit: u4 = @boolToInt(multi_cases_len != 0);
+    const special_prong_bits: u4 = @enumToInt(special_prong);
+    comptime {
+        assert(@enumToInt(zir.SpecialProng.none) == 0b00);
+        assert(@enumToInt(zir.SpecialProng.@"else") == 0b01);
+        assert(@enumToInt(zir.SpecialProng.under) == 0b10);
+    }
+    const zir_tags = astgen.instructions.items(.tag);
+    zir_tags[switch_block] = switch ((ref_bit << 3) | (special_prong_bits << 1) | multi_bit) {
+        0b0_00_0 => .switch_block,
+        0b0_00_1 => .switch_block_multi,
+        0b0_01_0 => .switch_block_else,
+        0b0_01_1 => .switch_block_else_multi,
+        0b0_10_0 => .switch_block_under,
+        0b0_10_1 => .switch_block_under_multi,
+        0b1_00_0 => .switch_block_ref,
+        0b1_00_1 => .switch_block_ref_multi,
+        0b1_01_0 => .switch_block_ref_else,
+        0b1_01_1 => .switch_block_ref_else_multi,
+        0b1_10_0 => .switch_block_ref_under,
+        0b1_10_1 => .switch_block_ref_under_multi,
+        else => unreachable,
+    };
+    const zir_datas = astgen.instructions.items(.data);
+    zir_datas[switch_block].pl_node.payload_index = @intCast(u32, astgen.extra.items.len);
+    try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
+        scalar_cases_payload.items.len + multi_cases_payload.items.len);
+    astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items);
+    astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items);
+    const strat = rl.strategy(&block_scope);
+    assert(strat.tag == .break_operand); // TODO
+    assert(!strat.elide_store_to_block_ptr_instructions); // TODO
+    assert(rl != .ref); // TODO
+    const switch_block_ref = astgen.indexToRef(switch_block);
+    return rvalue(parent_gz, scope, rl, switch_block_ref, switch_node);
 }
 
 fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
@@ -3021,7 +3366,7 @@ fn typeOf(
         return gz.astgen.mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{});
     }
     if (params.len == 1) {
-        const result = try gz.addUnTok(.typeof, try expr(gz, scope, .none, params[0]), node);
+        const result = try gz.addUnNode(.typeof, try expr(gz, scope, .none, params[0]), node);
         return rvalue(gz, scope, rl, result, node);
     }
     const arena = gz.astgen.arena;
src/Sema.zig
@@ -84,6 +84,15 @@ pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
     return sema.resolveType(root_block, .unneeded, zir_inst_ref);
 }
 
+/// Returns only the result from the body that is specified.
+/// Only appropriate to call when it is determined at comptime that this body
+/// has no peers.
+fn resolveBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) InnerError!*Inst {
+    const break_inst = try sema.analyzeBody(block, body);
+    const operand_ref = sema.code.instructions.items(.data)[break_inst].@"break".operand;
+    return sema.resolveInst(operand_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
@@ -229,7 +238,26 @@ pub fn analyzeBody(
             .str => try sema.zirStr(block, inst),
             .sub => try sema.zirArithmetic(block, inst),
             .subwrap => try sema.zirArithmetic(block, inst),
+            .switch_block => try sema.zirSwitchBlock(block, inst, false, .none),
+            .switch_block_multi => try sema.zirSwitchBlockMulti(block, inst, false, .none),
+            .switch_block_else => try sema.zirSwitchBlock(block, inst, false, .@"else"),
+            .switch_block_else_multi => try sema.zirSwitchBlockMulti(block, inst, false, .@"else"),
+            .switch_block_under => try sema.zirSwitchBlock(block, inst, false, .under),
+            .switch_block_under_multi => try sema.zirSwitchBlockMulti(block, inst, false, .under),
+            .switch_block_ref => try sema.zirSwitchBlock(block, inst, true, .none),
+            .switch_block_ref_multi => try sema.zirSwitchBlockMulti(block, inst, true, .none),
+            .switch_block_ref_else => try sema.zirSwitchBlock(block, inst, true, .@"else"),
+            .switch_block_ref_else_multi => try sema.zirSwitchBlockMulti(block, inst, true, .@"else"),
+            .switch_block_ref_under => try sema.zirSwitchBlock(block, inst, true, .under),
+            .switch_block_ref_under_multi => try sema.zirSwitchBlockMulti(block, inst, true, .under),
+            .switch_capture => try sema.zirSwitchCapture(block, inst, false, false),
+            .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true),
+            .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false),
+            .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true),
+            .switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false),
+            .switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true),
             .typeof => try sema.zirTypeof(block, inst),
+            .typeof_elem => try sema.zirTypeofElem(block, inst),
             .typeof_peer => try sema.zirTypeofPeer(block, inst),
             .xor => try sema.zirBitwise(block, inst, .xor),
 
@@ -245,18 +273,6 @@ pub fn analyzeBody(
             .ret_tok => return sema.zirRetTok(block, inst, false),
             .@"unreachable" => return sema.zirUnreachable(block, inst),
             .repeat => return sema.zirRepeat(block, inst),
-            .switch_br => return sema.zirSwitchBr(block, inst, false, .none),
-            .switch_br_multi => return sema.zirSwitchBrMulti(block, inst, false, .none),
-            .switch_br_else => return sema.zirSwitchBr(block, inst, false, .@"else"),
-            .switch_br_else_multi => return sema.zirSwitchBrMulti(block, inst, false, .@"else"),
-            .switch_br_under => return sema.zirSwitchBr(block, inst, false, .under),
-            .switch_br_under_multi => return sema.zirSwitchBrMulti(block, inst, false, .under),
-            .switch_br_ref => return sema.zirSwitchBr(block, inst, true, .none),
-            .switch_br_ref_multi => return sema.zirSwitchBrMulti(block, inst, true, .none),
-            .switch_br_ref_else => return sema.zirSwitchBr(block, inst, true, .@"else"),
-            .switch_br_ref_else_multi => return sema.zirSwitchBrMulti(block, inst, true, .@"else"),
-            .switch_br_ref_under => return sema.zirSwitchBr(block, inst, true, .under),
-            .switch_br_ref_under_multi => return sema.zirSwitchBrMulti(block, inst, true, .under),
 
             // Instructions that we know can *never* be noreturn based solely on
             // their tag. We avoid needlessly checking if they are noreturn and
@@ -1034,7 +1050,7 @@ fn analyzeBlockBody(
         }
         assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
         // Here we depend on the br instruction having been over-allocated (if necessary)
-        // inide analyzeBreak so that it can be converted into a br_block_flat instruction.
+        // inside zirBreak so that it can be converted into a br_block_flat instruction.
         const br_src = br.base.src;
         const br_ty = br.base.ty;
         const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br);
@@ -1063,22 +1079,15 @@ fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr
     _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
 }
 
-fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
     const tracy = trace(@src());
     defer tracy.end();
 
     const inst_data = sema.code.instructions.items(.data)[inst].@"break";
+    const src = sema.src;
     const operand = try sema.resolveInst(inst_data.operand);
-    return sema.analyzeBreak(block, sema.src, inst_data.block_inst, operand);
-}
+    const zir_block = inst_data.block_inst;
 
-fn analyzeBreak(
-    sema: *Sema,
-    start_block: *Scope.Block,
-    src: LazySrcLoc,
-    zir_block: zir.Inst.Index,
-    operand: *Inst,
-) InnerError!zir.Inst.Index {
     var block = start_block;
     while (true) {
         if (block.label) |*label| {
@@ -1103,7 +1112,7 @@ fn analyzeBreak(
                 try start_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 always_noreturn;
+                return inst;
             }
         }
         block = block.parent.?;
@@ -2208,15 +2217,38 @@ fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inne
     return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src);
 }
 
-const SpecialProng = enum { none, @"else", under };
+fn zirSwitchCapture(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: zir.Inst.Index,
+    is_multi: bool,
+    is_ref: bool,
+) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    @panic("TODO implement Sema for zirSwitchCapture");
+}
 
-fn zirSwitchBr(
+fn zirSwitchCaptureElse(
     sema: *Sema,
     block: *Scope.Block,
     inst: zir.Inst.Index,
     is_ref: bool,
-    special_prong: SpecialProng,
-) InnerError!zir.Inst.Index {
+) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    @panic("TODO implement Sema for zirSwitchCaptureElse");
+}
+
+fn zirSwitchBlock(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: zir.Inst.Index,
+    is_ref: bool,
+    special_prong: zir.SpecialProng,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2238,17 +2270,18 @@ fn zirSwitchBr(
         special_prong,
         extra.data.cases_len,
         0,
+        inst,
         inst_data.src_node,
     );
 }
 
-fn zirSwitchBrMulti(
+fn zirSwitchBlockMulti(
     sema: *Sema,
     block: *Scope.Block,
     inst: zir.Inst.Index,
     is_ref: bool,
-    special_prong: SpecialProng,
-) InnerError!zir.Inst.Index {
+    special_prong: zir.SpecialProng,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2270,6 +2303,7 @@ fn zirSwitchBrMulti(
         special_prong,
         extra.data.scalar_cases_len,
         extra.data.multi_cases_len,
+        inst,
         inst_data.src_node,
     );
 }
@@ -2279,11 +2313,12 @@ fn analyzeSwitch(
     block: *Scope.Block,
     operand: *Inst,
     extra_end: usize,
-    special_prong: SpecialProng,
+    special_prong: zir.SpecialProng,
     scalar_cases_len: usize,
     multi_cases_len: usize,
+    switch_inst: zir.Inst.Index,
     src_node_offset: i32,
-) InnerError!zir.Inst.Index {
+) InnerError!*Inst {
     const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
         .none => .{ .body = &.{}, .end = extra_end },
         .under, .@"else" => blk: {
@@ -2584,7 +2619,7 @@ fn analyzeSwitch(
                 const item = try sema.resolveInst(item_ref);
                 const item_val = try sema.resolveConstValue(block, item.src, item);
                 if (operand_val.eql(item_val)) {
-                    return sema.analyzeBody(block, body);
+                    return sema.resolveBody(block, body);
                 }
             }
         }
@@ -2605,7 +2640,7 @@ fn analyzeSwitch(
                     const item = try sema.resolveInst(item_ref);
                     const item_val = try sema.resolveConstValue(block, item.src, item);
                     if (operand_val.eql(item_val)) {
-                        return sema.analyzeBody(block, body);
+                        return sema.resolveBody(block, body);
                     }
                 }
 
@@ -2621,26 +2656,59 @@ fn analyzeSwitch(
                     if (Value.compare(operand_val, .gte, first_tv.val) and
                         Value.compare(operand_val, .lte, last_tv.val))
                     {
-                        return sema.analyzeBody(block, body);
+                        return sema.resolveBody(block, body);
                     }
                 }
 
                 extra_index += body_len;
             }
         }
-        return sema.analyzeBody(block, special.body);
+        return sema.resolveBody(block, special.body);
     }
 
     if (scalar_cases_len + multi_cases_len == 0) {
-        return sema.analyzeBody(block, special.body);
+        return sema.resolveBody(block, special.body);
     }
 
     try sema.requireRuntimeBlock(block, src);
+
+    const block_inst = try sema.arena.create(Inst.Block);
+    block_inst.* = .{
+        .base = .{
+            .tag = Inst.Block.base_tag,
+            .ty = undefined, // Set after analysis.
+            .src = src,
+        },
+        .body = undefined,
+    };
+
+    var child_block: Scope.Block = .{
+        .parent = block,
+        .sema = sema,
+        .src_decl = block.src_decl,
+        .instructions = .{},
+        // TODO @as here is working around a stage1 miscompilation bug :(
+        .label = @as(?Scope.Block.Label, Scope.Block.Label{
+            .zir_block = switch_inst,
+            .merges = .{
+                .results = .{},
+                .br_list = .{},
+                .block_inst = block_inst,
+            },
+        }),
+        .inlining = block.inlining,
+        .is_comptime = block.is_comptime,
+    };
+    const merges = &child_block.label.?.merges;
+    defer child_block.instructions.deinit(sema.gpa);
+    defer merges.results.deinit(sema.gpa);
+    defer merges.br_list.deinit(sema.gpa);
+
     // TODO when reworking TZIR memory layout make multi cases get generated as cases,
     // not as part of the "else" block.
     const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
 
-    var case_block = block.makeSubBlock();
+    var case_block = child_block.makeSubBlock();
     defer case_block.instructions.deinit(sema.gpa);
 
     var extra_index: usize = special.end;
@@ -2656,7 +2724,7 @@ fn analyzeSwitch(
 
         case_block.instructions.shrinkRetainingCapacity(0);
         const item = try sema.resolveInst(item_ref);
-        const item_val = try sema.resolveConstValue(block, item.src, item);
+        const item_val = try sema.resolveConstValue(&case_block, item.src, item);
 
         _ = try sema.analyzeBody(&case_block, body);
 
@@ -2687,7 +2755,7 @@ fn analyzeSwitch(
 
         for (items) |item_ref| {
             const item = try sema.resolveInst(item_ref);
-            _ = try sema.resolveConstValue(block, item.src, item);
+            _ = try sema.resolveConstValue(&child_block, item.src, item);
 
             const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item);
             if (any_ok) |some| {
@@ -2707,8 +2775,8 @@ fn analyzeSwitch(
             const item_first = try sema.resolveInst(first_ref);
             const item_last = try sema.resolveInst(last_ref);
 
-            _ = try sema.resolveConstValue(block, item_first.src, item_first);
-            _ = try sema.resolveConstValue(block, item_last.src, item_last);
+            _ = try sema.resolveConstValue(&child_block, item_first.src, item_first);
+            _ = try sema.resolveConstValue(&child_block, item_last.src, item_last);
 
             const range_src = item_first.src;
 
@@ -2779,8 +2847,8 @@ fn analyzeSwitch(
         .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}),
     };
 
-    _ = try block.addSwitchBr(src, operand, cases, final_else_body);
-    return always_noreturn;
+    _ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
+    return sema.analyzeBlockBody(block, &child_block, merges);
 }
 
 fn validateSwitchItem(
@@ -3261,12 +3329,18 @@ fn zirCmp(
 }
 
 fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
+    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+    const src = inst_data.src();
     const operand = try sema.resolveInst(inst_data.operand);
-    return sema.mod.constType(sema.arena, inst_data.src(), operand.ty);
+    return sema.mod.constType(sema.arena, src, operand.ty);
+}
+
+fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+    const src = inst_data.src();
+    const operand_ptr = try sema.resolveInst(inst_data.operand);
+    const elem_ty = operand_ptr.ty.elemType();
+    return sema.mod.constType(sema.arena, src, elem_ty);
 }
 
 fn zirTypeofPeer(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@@ -3360,8 +3434,7 @@ fn zirBoolBr(
         // 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_inline`).
-        const break_inst = try sema.analyzeBody(parent_block, body);
-        return sema.resolveInst(datas[break_inst].@"break".operand);
+        return sema.resolveBody(parent_block, body);
     }
 
     const block_inst = try sema.arena.create(Inst.Block);
@@ -3392,8 +3465,7 @@ fn zirBoolBr(
     });
     _ = try lhs_block.addBr(src, block_inst, lhs_result);
 
-    const rhs_break_inst = try sema.analyzeBody(rhs_block, body);
-    const rhs_result = try sema.resolveInst(datas[rhs_break_inst].@"break".operand);
+    const rhs_result = try sema.resolveBody(rhs_block, body);
     _ = 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) };
src/zir.zig
@@ -514,6 +514,9 @@ pub const Inst = struct {
         /// Returns the type of a value.
         /// Uses the `un_tok` field.
         typeof,
+        /// Given a value which is a pointer, returns the element type.
+        /// Uses the `un_node` field.
+        typeof_elem,
         /// The builtin `@TypeOf` which returns the type after Peer Type Resolution
         /// of one or more params.
         /// Uses the `pl_node` field. AST node is the `@TypeOf` call. Payload is `MultiOp`.
@@ -588,32 +591,55 @@ pub const Inst = struct {
         /// A switch expression. Uses the `pl_node` union field.
         /// AST node is the switch, payload is `SwitchBr`.
         /// All prongs of target handled.
-        switch_br,
-        /// Same as switch_br, except one or more prongs have multiple items.
-        switch_br_multi,
-        /// Same as switch_br, except has an else prong.
-        switch_br_else,
-        /// Same as switch_br_else, except one or more prongs have multiple items.
-        switch_br_else_multi,
-        /// Same as switch_br, except has an underscore prong.
-        switch_br_under,
-        /// Same as switch_br, except one or more prongs have multiple items.
-        switch_br_under_multi,
-        /// Same as `switch_br` but the target is a pointer to the value being switched on.
-        switch_br_ref,
-        /// Same as `switch_br_multi` but the target is a pointer to the value being switched on.
-        switch_br_ref_multi,
-        /// Same as `switch_br_else` but the target is a pointer to the value being switched on.
-        switch_br_ref_else,
-        /// Same as `switch_br_else_multi` but the target is a pointer to the
+        switch_block,
+        /// Same as switch_block, except one or more prongs have multiple items.
+        switch_block_multi,
+        /// Same as switch_block, except has an else prong.
+        switch_block_else,
+        /// Same as switch_block_else, except one or more prongs have multiple items.
+        switch_block_else_multi,
+        /// Same as switch_block, except has an underscore prong.
+        switch_block_under,
+        /// Same as switch_block, except one or more prongs have multiple items.
+        switch_block_under_multi,
+        /// Same as `switch_block` but the target is a pointer to the value being switched on.
+        switch_block_ref,
+        /// Same as `switch_block_multi` but the target is a pointer to the value being switched on.
+        switch_block_ref_multi,
+        /// Same as `switch_block_else` but the target is a pointer to the value being switched on.
+        switch_block_ref_else,
+        /// Same as `switch_block_else_multi` but the target is a pointer to the
         /// value being switched on.
-        switch_br_ref_else_multi,
-        /// Same as `switch_br_under` but the target is a pointer to the value
+        switch_block_ref_else_multi,
+        /// Same as `switch_block_under` but the target is a pointer to the value
         /// being switched on.
-        switch_br_ref_under,
-        /// Same as `switch_br_under_multi` but the target is a pointer to
+        switch_block_ref_under,
+        /// Same as `switch_block_under_multi` but the target is a pointer to
         /// the value being switched on.
-        switch_br_ref_under_multi,
+        switch_block_ref_under_multi,
+        /// Produces the capture value for a switch prong.
+        /// Uses the `switch_capture` field.
+        switch_capture,
+        /// Produces the capture value for a switch prong.
+        /// Result is a pointer to the value.
+        /// Uses the `switch_capture` field.
+        switch_capture_ref,
+        /// Produces the capture value for a switch prong.
+        /// The prong is one of the multi cases.
+        /// Uses the `switch_capture` field.
+        switch_capture_multi,
+        /// Produces the capture value for a switch prong.
+        /// The prong is one of the multi cases.
+        /// Result is a pointer to the value.
+        /// Uses the `switch_capture` field.
+        switch_capture_multi_ref,
+        /// Produces the capture value for the else/'_' switch prong.
+        /// Uses the `switch_capture` field.
+        switch_capture_else,
+        /// Produces the capture value for the else/'_' switch prong.
+        /// Result is a pointer to the value.
+        /// Uses the `switch_capture` field.
+        switch_capture_else_ref,
 
         /// Returns whether the instruction is one of the control flow "noreturn" types.
         /// Function calls do not count.
@@ -710,6 +736,7 @@ pub const Inst = struct {
                 .negate,
                 .negate_wrap,
                 .typeof,
+                .typeof_elem,
                 .xor,
                 .optional_type,
                 .optional_type_from_ptr_elem,
@@ -743,6 +770,24 @@ pub const Inst = struct {
                 .set_eval_branch_quota,
                 .compile_log,
                 .elided,
+                .switch_capture,
+                .switch_capture_ref,
+                .switch_capture_multi,
+                .switch_capture_multi_ref,
+                .switch_capture_else,
+                .switch_capture_else_ref,
+                .switch_block,
+                .switch_block_multi,
+                .switch_block_else,
+                .switch_block_else_multi,
+                .switch_block_under,
+                .switch_block_under_multi,
+                .switch_block_ref,
+                .switch_block_ref_multi,
+                .switch_block_ref_else,
+                .switch_block_ref_else_multi,
+                .switch_block_ref_under,
+                .switch_block_ref_under_multi,
                 => false,
 
                 .@"break",
@@ -756,18 +801,6 @@ pub const Inst = struct {
                 .@"unreachable",
                 .repeat,
                 .repeat_inline,
-                .switch_br,
-                .switch_br_multi,
-                .switch_br_else,
-                .switch_br_else_multi,
-                .switch_br_under,
-                .switch_br_under_multi,
-                .switch_br_ref,
-                .switch_br_ref_multi,
-                .switch_br_ref_else,
-                .switch_br_ref_else_multi,
-                .switch_br_ref_under,
-                .switch_br_ref_under_multi,
                 => true,
             };
         }
@@ -1223,6 +1256,10 @@ pub const Inst = struct {
             block_inst: Index,
             operand: Ref,
         },
+        switch_capture: struct {
+            switch_inst: Index,
+            prong_index: u32,
+        },
 
         // Make sure we don't accidentally add a field to make this union
         // bigger than expected. Note that in Debug builds, Zig is allowed
@@ -1394,6 +1431,8 @@ pub const Inst = struct {
     };
 };
 
+pub const SpecialProng = enum { none, @"else", under };
+
 const Writer = struct {
     gpa: *Allocator,
     arena: *Allocator,
@@ -1461,12 +1500,13 @@ const Writer = struct {
             .is_null_ptr,
             .is_err,
             .is_err_ptr,
+            .typeof,
+            .typeof_elem,
             => try self.writeUnNode(stream, inst),
 
             .ref,
             .ret_tok,
             .ret_coerce,
-            .typeof,
             .ensure_err_payload_void,
             => try self.writeUnTok(stream, inst),
 
@@ -1542,21 +1582,19 @@ const Writer = struct {
             .condbr_inline,
             => try self.writePlNodeCondBr(stream, inst),
 
-            .switch_br,
-            .switch_br_else,
-            .switch_br_under,
-            .switch_br_ref,
-            .switch_br_ref_else,
-            .switch_br_ref_under,
-            => try self.writePlNodeSwitchBr(stream, inst),
-
-            .switch_br_multi,
-            .switch_br_else_multi,
-            .switch_br_under_multi,
-            .switch_br_ref_multi,
-            .switch_br_ref_else_multi,
-            .switch_br_ref_under_multi,
-            => try self.writePlNodeSwitchBrMulti(stream, inst),
+            .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none),
+            .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
+            .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under),
+            .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none),
+            .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
+            .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under),
+
+            .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
+            .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
+            .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
+            .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
+            .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
+            .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
 
             .compile_log,
             .typeof_peer,
@@ -1588,10 +1626,19 @@ 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),
 
+            .switch_capture,
+            .switch_capture_ref,
+            .switch_capture_multi,
+            .switch_capture_multi_ref,
+            .switch_capture_else,
+            .switch_capture_else_ref,
+            => try self.writeSwitchCapture(stream, inst),
+
             .bitcast,
             .bitcast_ref,
             .bitcast_result_ptr,
@@ -1763,11 +1810,46 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writePlNodeSwitchBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+    fn writePlNodeSwitchBr(
+        self: *Writer,
+        stream: anytype,
+        inst: Inst.Index,
+        special_prong: SpecialProng,
+    ) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Inst.SwitchBr, inst_data.payload_index);
+        const special: struct {
+            body: []const Inst.Index,
+            end: usize,
+        } = switch (special_prong) {
+            .none => .{ .body = &.{}, .end = extra.end },
+            .under, .@"else" => blk: {
+                const body_len = self.code.extra[extra.end];
+                const extra_body_start = extra.end + 1;
+                break :blk .{
+                    .body = self.code.extra[extra_body_start..][0..body_len],
+                    .end = extra_body_start + body_len,
+                };
+            },
+        };
+
         try self.writeInstRef(stream, extra.data.operand);
-        var extra_index: usize = extra.end;
+
+        if (special.body.len != 0) {
+            const prong_name = switch (special_prong) {
+                .@"else" => "else",
+                .under => "_",
+                else => unreachable,
+            };
+            try stream.print(", {s} => {{\n", .{prong_name});
+            self.indent += 2;
+            try self.writeBody(stream, special.body);
+            self.indent -= 2;
+            try stream.writeByteNTimes(' ', self.indent);
+            try stream.writeAll("}");
+        }
+
+        var extra_index: usize = special.end;
         {
             var scalar_i: usize = 0;
             while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
@@ -1792,11 +1874,46 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writePlNodeSwitchBrMulti(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+    fn writePlNodeSwitchBlockMulti(
+        self: *Writer,
+        stream: anytype,
+        inst: Inst.Index,
+        special_prong: SpecialProng,
+    ) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Inst.SwitchBrMulti, inst_data.payload_index);
+        const special: struct {
+            body: []const Inst.Index,
+            end: usize,
+        } = switch (special_prong) {
+            .none => .{ .body = &.{}, .end = extra.end },
+            .under, .@"else" => blk: {
+                const body_len = self.code.extra[extra.end];
+                const extra_body_start = extra.end + 1;
+                break :blk .{
+                    .body = self.code.extra[extra_body_start..][0..body_len],
+                    .end = extra_body_start + body_len,
+                };
+            },
+        };
+
         try self.writeInstRef(stream, extra.data.operand);
-        var extra_index: usize = extra.end;
+
+        if (special.body.len != 0) {
+            const prong_name = switch (special_prong) {
+                .@"else" => "else",
+                .under => "_",
+                else => unreachable,
+            };
+            try stream.print(", {s} => {{\n", .{prong_name});
+            self.indent += 2;
+            try self.writeBody(stream, special.body);
+            self.indent -= 2;
+            try stream.writeByteNTimes(' ', self.indent);
+            try stream.writeAll("}");
+        }
+
+        var extra_index: usize = special.end;
         {
             var scalar_i: usize = 0;
             while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
@@ -2015,6 +2132,12 @@ const Writer = struct {
         try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)});
     }
 
+    fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].switch_capture;
+        try self.writeInstIndex(stream, inst_data.switch_inst);
+        try stream.print(", {d})", .{inst_data.prong_index});
+    }
+
     fn writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void {
         var i: usize = @enumToInt(ref);
 
BRANCH_TODO
@@ -82,47 +82,3 @@ Performance optimizations to look into:
                 }
             }
 
-
-
-
-
-
-fn switchCaseExpr(
-    gz: *GenZir,
-    scope: *Scope,
-    rl: ResultLoc,
-    block: *zir.Inst.Block,
-    case: ast.full.SwitchCase,
-    target: zir.Inst.Ref,
-) !void {
-    const tree = gz.tree();
-    const node_datas = tree.nodes.items(.data);
-    const main_tokens = tree.nodes.items(.main_token);
-    const token_tags = tree.tokens.items(.tag);
-
-    const case_src = token_starts[case.ast.arrow_token];
-    const sub_scope = blk: {
-        const payload_token = case.payload_token orelse break :blk scope;
-        const ident = if (token_tags[payload_token] == .asterisk)
-            payload_token + 1
-        else
-            payload_token;
-        const is_ptr = ident != payload_token;
-        const value_name = tree.tokenSlice(ident);
-        if (mem.eql(u8, value_name, "_")) {
-            if (is_ptr) {
-                return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{});
-            }
-            break :blk scope;
-        }
-        return mod.failTok(scope, ident, "TODO implement switch value payload", .{});
-    };
-
-    const case_body = try expr(gz, sub_scope, rl, case.ast.target_expr);
-    if (!case_body.tag.isNoReturn()) {
-        _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
-            .block = block,
-            .operand = case_body,
-        }, .{});
-    }
-}