Commit dfb3231959

Andrew Kelley <andrew@ziglang.org>
2021-10-20 05:14:10
stage2: implement switching on unions
* AstGen: Move `refToIndex` and `indexToRef` to Zir * ZIR: the switch_block_*_* instruction tags are collapsed into one switch_block tag which uses 4 bits for flags, and reduces the scalar_cases_len field from 32 to 28 bits. This freed up more ZIR tags, 2 of which are now used for `switch_cond` and `switch_cond_ref` for producing the switch condition value. For example, for union values it returns the corresponding enum value. * switching with multiple cases and ranges is not yet supported because I want to change the ZIR encoding to store index pointers into the extra array rather than storing prong indexes. This will avoid O(N^2) iteration over prongs. * AstGen now adds a `switch_cond` on the operand and then passes the result of that to the `switch_block` instruction. * Sema: partially implement `switch_capture_*` instructions. * Sema: `unionToTag` notices if the enum type has only one possible value.
1 parent 4a76523
src/AstGen.zig
@@ -11,6 +11,8 @@ const StringIndexAdapter = std.hash_map.StringIndexAdapter;
 const StringIndexContext = std.hash_map.StringIndexContext;
 
 const Zir = @import("Zir.zig");
+const refToIndex = Zir.refToIndex;
+const indexToRef = Zir.indexToRef;
 const trace = @import("tracy.zig").trace;
 const BuiltinFn = @import("BuiltinFn.zig");
 
@@ -57,6 +59,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
             Zir.Inst.Ref => @enumToInt(@field(extra, field.name)),
             i32 => @bitCast(u32, @field(extra, field.name)),
             Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)),
+            Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)),
             else => @compileError("bad field type"),
         });
     }
@@ -2133,17 +2136,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
             .slice_sentinel,
             .import,
             .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_cond,
+            .switch_cond_ref,
             .switch_capture,
             .switch_capture_ref,
             .switch_capture_multi,
@@ -5127,11 +5121,12 @@ fn fieldAccess(
     rl: ResultLoc,
     node: Ast.Node.Index,
 ) InnerError!Zir.Inst.Ref {
-    if (rl == .ref) {
-        return addFieldAccess(.field_ptr, gz, scope, .ref, node);
-    } else {
-        const access = try addFieldAccess(.field_val, gz, scope, .none, node);
-        return rvalue(gz, rl, access, node);
+    switch (rl) {
+        .ref => return addFieldAccess(.field_ptr, gz, scope, .ref, node),
+        else => {
+            const access = try addFieldAccess(.field_val, gz, scope, .none, node);
+            return rvalue(gz, rl, access, node);
+        },
     }
 }
 
@@ -6028,11 +6023,13 @@ fn switchExpr(
     }
 
     const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
-    const operand = try expr(parent_gz, scope, operand_rl, operand_node);
+    const raw_operand = try expr(parent_gz, scope, operand_rl, operand_node);
+    const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond;
+    const cond = try parent_gz.addUnNode(cond_tag, raw_operand, 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 };
+    const cond_ty_inst = try parent_gz.addUnNode(typeof_tag, cond, operand_node);
+    const item_rl: ResultLoc = .{ .ty = cond_ty_inst };
 
     // These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti.
     // This is the optional else prong body.
@@ -6050,7 +6047,7 @@ fn switchExpr(
     defer block_scope.instructions.deinit(gpa);
 
     // This gets added to the parent block later, after the item expressions.
-    const switch_block = try parent_gz.addBlock(undefined, switch_node);
+    const switch_block = try parent_gz.addBlock(.switch_block, switch_node);
 
     // We re-use this same scope for all cases, including the special prong, if any.
     var case_scope = parent_gz.makeSubBlock(&block_scope.base);
@@ -6203,44 +6200,32 @@ fn switchExpr(
     // 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 payload_index = astgen.extra.items.len;
-    const zir_datas = astgen.instructions.items(.data);
-    zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index);
-    // Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`.
-    try astgen.extra.ensureUnusedCapacity(gpa, @as(usize, 2) + // operand, scalar_cases_len
+    try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).Struct.fields.len +
         @boolToInt(multi_cases_len != 0) +
         special_case_payload.items.len +
         scalar_cases_payload.items.len +
         multi_cases_payload.items.len);
-    astgen.extra.appendAssumeCapacity(@enumToInt(operand));
-    astgen.extra.appendAssumeCapacity(scalar_cases_len);
+
+    const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
+        .operand = cond,
+        .bits = Zir.Inst.SwitchBlock.Bits{
+            .is_ref = any_payload_is_ref,
+            .has_multi_cases = multi_cases_len != 0,
+            .has_else = special_prong == .@"else",
+            .has_under = special_prong == .under,
+            .scalar_cases_len = @intCast(u28, scalar_cases_len),
+        },
+    });
+
+    const zir_datas = astgen.instructions.items(.data);
+    const zir_tags = astgen.instructions.items(.tag);
+
+    zir_datas[switch_block].pl_node.payload_index = payload_index;
+
     if (multi_cases_len != 0) {
         astgen.extra.appendAssumeCapacity(multi_cases_len);
     }
+
     const strat = rl.strategy(&block_scope);
     switch (strat.tag) {
         .break_operand => {
@@ -10622,21 +10607,6 @@ fn advanceSourceCursor(astgen: *AstGen, source: []const u8, end: usize) void {
     astgen.source_column = column;
 }
 
-const ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len;
-
-fn indexToRef(inst: Zir.Inst.Index) Zir.Inst.Ref {
-    return @intToEnum(Zir.Inst.Ref, ref_start_index + inst);
-}
-
-fn refToIndex(inst: Zir.Inst.Ref) ?Zir.Inst.Index {
-    const ref_int = @enumToInt(inst);
-    if (ref_int >= ref_start_index) {
-        return ref_int - ref_start_index;
-    } else {
-        return null;
-    }
-}
-
 fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.Node.Index) !void {
     const gpa = astgen.gpa;
     const tree = astgen.tree;
src/print_zir.zig
@@ -234,6 +234,8 @@ const Writer = struct {
             .@"await",
             .await_nosuspend,
             .fence,
+            .switch_cond,
+            .switch_cond_ref,
             => try self.writeUnNode(stream, inst),
 
             .ref,
@@ -379,19 +381,7 @@ const Writer = struct {
             .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon),
             .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func),
 
-            .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),
+            .switch_block => try self.writePlNodeSwitchBlock(stream, inst),
 
             .field_ptr,
             .field_val,
@@ -1649,113 +1639,46 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writePlNodeSwitchBr(
-        self: *Writer,
-        stream: anytype,
-        inst: Zir.Inst.Index,
-        special_prong: Zir.SpecialProng,
-    ) !void {
+    fn writePlNodeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
-        const special: struct {
-            body: []const Zir.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);
-
-        self.indent += 2;
-
-        if (special.body.len != 0) {
-            const prong_name = switch (special_prong) {
-                .@"else" => "else",
-                .under => "_",
-                else => unreachable,
-            };
-            try stream.writeAll(",\n");
-            try stream.writeByteNTimes(' ', self.indent);
-            try stream.print("{s} => ", .{prong_name});
-            try self.writeBracedBody(stream, special.body);
-        }
-
-        var extra_index: usize = special.end;
-        {
-            var scalar_i: usize = 0;
-            while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
-                const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
-                extra_index += 1;
-                const body_len = self.code.extra[extra_index];
-                extra_index += 1;
-                const body = self.code.extra[extra_index..][0..body_len];
-                extra_index += body_len;
-
-                try stream.writeAll(",\n");
-                try stream.writeByteNTimes(' ', self.indent);
-                try self.writeInstRef(stream, item_ref);
-                try stream.writeAll(" => ");
-                try self.writeBracedBody(stream, body);
-            }
-        }
-
-        self.indent -= 2;
 
-        try stream.writeAll(") ");
-        try self.writeSrc(stream, inst_data.src());
-    }
+        var extra_index: usize = extra.end;
 
-    fn writePlNodeSwitchBlockMulti(
-        self: *Writer,
-        stream: anytype,
-        inst: Zir.Inst.Index,
-        special_prong: Zir.SpecialProng,
-    ) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index);
-        const special: struct {
-            body: []const Zir.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,
-                };
-            },
-        };
+        const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
+            const multi_cases_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk multi_cases_len;
+        } else 0;
 
         try self.writeInstRef(stream, extra.data.operand);
+        try self.writeFlag(stream, ", ref", extra.data.bits.is_ref);
 
         self.indent += 2;
 
-        if (special.body.len != 0) {
+        else_prong: {
+            const special_prong = extra.data.bits.specialProng();
             const prong_name = switch (special_prong) {
                 .@"else" => "else",
                 .under => "_",
-                else => unreachable,
+                else => break :else_prong,
             };
+
+            const body_len = self.code.extra[extra_index];
+            extra_index += 1;
+            const body = self.code.extra[extra_index..][0..body_len];
+            extra_index += body.len;
+
             try stream.writeAll(",\n");
             try stream.writeByteNTimes(' ', self.indent);
             try stream.print("{s} => ", .{prong_name});
-            try self.writeBracedBody(stream, special.body);
+            try self.writeBracedBody(stream, body);
         }
 
-        var extra_index: usize = special.end;
         {
+            const scalar_cases_len = extra.data.bits.scalar_cases_len;
             var scalar_i: usize = 0;
-            while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
+            while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
                 const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
                 extra_index += 1;
                 const body_len = self.code.extra[extra_index];
@@ -1772,7 +1695,7 @@ const Writer = struct {
         }
         {
             var multi_i: usize = 0;
-            while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
+            while (multi_i < multi_cases_len) : (multi_i += 1) {
                 const items_len = self.code.extra[extra_index];
                 extra_index += 1;
                 const ranges_len = self.code.extra[extra_index];
src/Sema.zig
@@ -550,18 +550,9 @@ pub fn analyzeBody(
             .slice_sentinel               => try sema.zirSliceSentinel(block, inst),
             .slice_start                  => try sema.zirSliceStart(block, inst),
             .str                          => try sema.zirStr(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_block                 => try sema.zirSwitchBlock(block, inst),
+            .switch_cond                  => try sema.zirSwitchCond(block, inst, false),
+            .switch_cond_ref              => try sema.zirSwitchCond(block, inst, true),
             .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),
@@ -5433,11 +5424,80 @@ fn zirSwitchCapture(
     const zir_datas = sema.code.instructions.items(.data);
     const capture_info = zir_datas[inst].switch_capture;
     const switch_info = zir_datas[capture_info.switch_inst].pl_node;
-    const src = switch_info.src();
+    const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index);
+    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node };
+    const switch_src = switch_info.src();
+    const operand_is_ref = switch_extra.data.bits.is_ref;
+    const cond_inst = Zir.refToIndex(switch_extra.data.operand).?;
+    const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node;
+    const operand_ptr = sema.resolveInst(cond_info.operand);
+    const operand_ptr_ty = sema.typeOf(operand_ptr);
+    const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty;
+
+    if (is_multi) {
+        return sema.fail(block, switch_src, "TODO implement Sema for switch capture multi", .{});
+    }
+    const scalar_prong = switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index);
+    const item = sema.resolveInst(scalar_prong.item);
+    // Previous switch validation ensured this will succeed
+    const item_val = sema.resolveConstValue(block, .unneeded, item) catch unreachable;
 
-    _ = is_ref;
-    _ = is_multi;
-    return sema.fail(block, src, "TODO implement Sema for zirSwitchCapture", .{});
+    switch (operand_ty.zigTypeTag()) {
+        .Union => {
+            const union_obj = operand_ty.cast(Type.Payload.Union).?.data;
+            const enum_ty = union_obj.tag_ty;
+
+            const field_index_usize = enum_ty.enumTagFieldIndex(item_val).?;
+            const field_index = @intCast(u32, field_index_usize);
+            const field = union_obj.fields.values()[field_index];
+
+            // TODO handle multiple union tags which have compatible types
+
+            if (is_ref) {
+                assert(operand_is_ref);
+
+                const field_ty_ptr = try Type.ptr(sema.arena, .{
+                    .pointee_type = field.ty,
+                    .@"addrspace" = .generic,
+                    .mutable = operand_ptr_ty.ptrIsMutable(),
+                });
+
+                if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
+                    return sema.addConstant(
+                        field_ty_ptr,
+                        try Value.Tag.field_ptr.create(sema.arena, .{
+                            .container_ptr = op_ptr_val,
+                            .field_index = field_index,
+                        }),
+                    );
+                }
+                try sema.requireRuntimeBlock(block, operand_src);
+                return block.addStructFieldPtr(operand_ptr, field_index, field.ty);
+            }
+
+            const operand = if (operand_is_ref)
+                try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src)
+            else
+                operand_ptr;
+
+            if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
+                return sema.addConstant(
+                    field.ty,
+                    operand_val.castTag(.@"union").?.data.val,
+                );
+            }
+            try sema.requireRuntimeBlock(block, operand_src);
+            return block.addStructFieldVal(operand, field_index, field.ty);
+        },
+        .ErrorSet => {
+            return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCapture for error sets", .{});
+        },
+        else => {
+            return sema.fail(block, operand_src, "switch on type '{}' provides no capture value", .{
+                operand_ty,
+            });
+        },
+    }
 }
 
 fn zirSwitchCaptureElse(
@@ -5452,96 +5512,108 @@ fn zirSwitchCaptureElse(
     const zir_datas = sema.code.instructions.items(.data);
     const capture_info = zir_datas[inst].switch_capture;
     const switch_info = zir_datas[capture_info.switch_inst].pl_node;
+    const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index).data;
     const src = switch_info.src();
+    const operand_is_ref = switch_extra.bits.is_ref;
+    assert(!is_ref or operand_is_ref);
 
-    _ = is_ref;
     return sema.fail(block, src, "TODO implement Sema for zirSwitchCaptureElse", .{});
 }
 
-fn zirSwitchBlock(
+fn zirSwitchCond(
     sema: *Sema,
     block: *Block,
     inst: Zir.Inst.Index,
     is_ref: bool,
-    special_prong: Zir.SpecialProng,
 ) CompileError!Air.Inst.Ref {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
-    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
-    const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
+    const operand_ptr = sema.resolveInst(inst_data.operand);
+    const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, src) else operand_ptr;
+    const operand_ty = sema.typeOf(operand);
 
-    const operand_ptr = sema.resolveInst(extra.data.operand);
-    const operand = if (is_ref)
-        try sema.analyzeLoad(block, src, operand_ptr, operand_src)
-    else
-        operand_ptr;
+    switch (operand_ty.zigTypeTag()) {
+        .Type,
+        .Void,
+        .Bool,
+        .Int,
+        .Float,
+        .ComptimeFloat,
+        .ComptimeInt,
+        .EnumLiteral,
+        .Pointer,
+        .Fn,
+        .ErrorSet,
+        .Enum,
+        => {
+            if ((try sema.typeHasOnePossibleValue(block, src, operand_ty))) |opv| {
+                return sema.addConstant(operand_ty, opv);
+            }
+            return operand;
+        },
 
-    return sema.analyzeSwitch(
-        block,
-        operand,
-        extra.end,
-        special_prong,
-        extra.data.cases_len,
-        0,
-        inst,
-        inst_data.src_node,
-    );
+        .Union => {
+            const enum_ty = operand_ty.unionTagType() orelse {
+                const msg = msg: {
+                    const msg = try sema.errMsg(block, src, "switch on untagged union", .{});
+                    errdefer msg.destroy(sema.gpa);
+                    try sema.addDeclaredHereNote(msg, operand_ty);
+                    break :msg msg;
+                };
+                return sema.failWithOwnedErrorMsg(msg);
+            };
+            return sema.unionToTag(block, enum_ty, operand, src);
+        },
+
+        .ErrorUnion,
+        .NoReturn,
+        .Array,
+        .Struct,
+        .Undefined,
+        .Null,
+        .Optional,
+        .BoundFn,
+        .Opaque,
+        .Vector,
+        .Frame,
+        .AnyFrame,
+        => return sema.fail(block, src, "switch on type '{}'", .{operand_ty}),
+    }
 }
 
-fn zirSwitchBlockMulti(
-    sema: *Sema,
-    block: *Block,
-    inst: Zir.Inst.Index,
-    is_ref: bool,
-    special_prong: Zir.SpecialProng,
-) CompileError!Air.Inst.Ref {
+fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const gpa = sema.gpa;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
-    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
-    const extra = sema.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index);
+    const src_node_offset = inst_data.src_node;
+    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
+    const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
+    const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
 
     const operand_ptr = sema.resolveInst(extra.data.operand);
-    const operand = if (is_ref)
+    const operand = if (extra.data.bits.is_ref)
         try sema.analyzeLoad(block, src, operand_ptr, operand_src)
     else
         operand_ptr;
 
-    return sema.analyzeSwitch(
-        block,
-        operand,
-        extra.end,
-        special_prong,
-        extra.data.scalar_cases_len,
-        extra.data.multi_cases_len,
-        inst,
-        inst_data.src_node,
-    );
-}
+    var header_extra_index: usize = extra.end;
 
-fn analyzeSwitch(
-    sema: *Sema,
-    block: *Block,
-    operand: Air.Inst.Ref,
-    extra_end: usize,
-    special_prong: Zir.SpecialProng,
-    scalar_cases_len: usize,
-    multi_cases_len: usize,
-    switch_inst: Zir.Inst.Index,
-    src_node_offset: i32,
-) CompileError!Air.Inst.Ref {
-    const gpa = sema.gpa;
+    const scalar_cases_len = extra.data.bits.scalar_cases_len;
+    const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
+        const multi_cases_len = sema.code.extra[header_extra_index];
+        header_extra_index += 1;
+        break :blk multi_cases_len;
+    } else 0;
 
+    const special_prong = extra.data.bits.specialProng();
     const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) {
-        .none => .{ .body = &.{}, .end = extra_end },
+        .none => .{ .body = &.{}, .end = header_extra_index },
         .under, .@"else" => blk: {
-            const body_len = sema.code.extra[extra_end];
-            const extra_body_start = extra_end + 1;
+            const body_len = sema.code.extra[header_extra_index];
+            const extra_body_start = header_extra_index + 1;
             break :blk .{
                 .body = sema.code.extra[extra_body_start..][0..body_len],
                 .end = extra_body_start + body_len,
@@ -5549,9 +5621,6 @@ fn analyzeSwitch(
         },
     };
 
-    const src: LazySrcLoc = .{ .node_offset = src_node_offset };
-    const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
-    const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
     const operand_ty = sema.typeOf(operand);
 
     // Validate usage of '_' prongs.
@@ -5945,7 +6014,7 @@ fn analyzeSwitch(
         .data = undefined,
     });
     var label: Block.Label = .{
-        .zir_block = switch_inst,
+        .zir_block = inst,
         .merges = .{
             .results = .{},
             .br_list = .{},
@@ -8934,8 +9003,9 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool)
         const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
         const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
         const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
-        const field_index = union_obj.fields.getIndex(field_name) orelse
+        const field_index_usize = union_obj.fields.getIndex(field_name) orelse
             return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
+        const field_index = @intCast(u32, field_index_usize);
 
         if (is_ref) {
             return sema.fail(block, src, "TODO: Sema.zirStructInit is_ref=true union", .{});
@@ -8943,12 +9013,10 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool)
 
         const init_inst = sema.resolveInst(item.data.init);
         if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| {
+            const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index);
             return sema.addConstant(
                 resolved_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try Value.Tag.int_u64.create(sema.arena, field_index),
-                    .val = val,
-                }),
+                try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }),
             );
         }
         return sema.fail(block, src, "TODO: Sema.zirStructInit for runtime-known union values", .{});
@@ -9152,8 +9220,8 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
     const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
     const val = try sema.resolveConstValue(block, operand_src, type_info);
     const union_val = val.cast(Value.Payload.Union).?.data;
-    const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo);
-    const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt());
+    const tag_ty = type_info_ty.unionTagType().?;
+    const tag_index = tag_ty.enumTagFieldIndex(union_val.tag).?;
     switch (@intToEnum(std.builtin.TypeId, tag_index)) {
         .Type => return Air.Inst.Ref.type_type,
         .Void => return Air.Inst.Ref.void_type,
@@ -10819,10 +10887,39 @@ fn fieldVal(
                         try Value.Tag.@"error".create(arena, .{ .name = name }),
                     );
                 },
-                .Struct, .Opaque, .Union => {
+                .Union => {
                     if (child_type.getNamespace()) |namespace| {
-                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
-                            return sema.analyzeLoad(block, src, inst, src);
+                        if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
+                            return inst;
+                        }
+                    }
+                    if (child_type.unionTagType()) |enum_ty| {
+                        if (enum_ty.enumFieldIndex(field_name)) |field_index_usize| {
+                            const field_index = @intCast(u32, field_index_usize);
+                            return sema.addConstant(
+                                enum_ty,
+                                try Value.Tag.enum_field_index.create(sema.arena, field_index),
+                            );
+                        }
+                    }
+                    return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
+                },
+                .Enum => {
+                    if (child_type.getNamespace()) |namespace| {
+                        if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
+                            return inst;
+                        }
+                    }
+                    const field_index_usize = child_type.enumFieldIndex(field_name) orelse
+                        return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
+                    const field_index = @intCast(u32, field_index_usize);
+                    const enum_val = try Value.Tag.enum_field_index.create(arena, field_index);
+                    return sema.addConstant(try child_type.copy(arena), enum_val);
+                },
+                .Struct, .Opaque => {
+                    if (child_type.getNamespace()) |namespace| {
+                        if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
+                            return inst;
                         }
                     }
                     // TODO add note: declared here
@@ -10836,35 +10933,6 @@ fn fieldVal(
                         kw_name, child_type, field_name,
                     });
                 },
-                .Enum => {
-                    if (child_type.getNamespace()) |namespace| {
-                        if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
-                            return sema.analyzeLoad(block, src, inst, src);
-                        }
-                    }
-                    const field_index = child_type.enumFieldIndex(field_name) orelse {
-                        const msg = msg: {
-                            const msg = try sema.errMsg(
-                                block,
-                                src,
-                                "enum '{}' has no member named '{s}'",
-                                .{ child_type, field_name },
-                            );
-                            errdefer msg.destroy(sema.gpa);
-                            try sema.mod.errNoteNonLazy(
-                                child_type.declSrcLoc(),
-                                msg,
-                                "enum declared here",
-                                .{},
-                            );
-                            break :msg msg;
-                        };
-                        return sema.failWithOwnedErrorMsg(msg);
-                    };
-                    const field_index_u32 = @intCast(u32, field_index);
-                    const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32);
-                    return sema.addConstant(try child_type.copy(arena), enum_val);
-                },
                 else => return sema.fail(block, src, "type '{}' has no members", .{child_type}),
             }
         },
@@ -11244,6 +11312,17 @@ fn namespaceLookupRef(
     return try sema.analyzeDeclRef(decl);
 }
 
+fn namespaceLookupVal(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    namespace: *Namespace,
+    decl_name: []const u8,
+) CompileError!?Air.Inst.Ref {
+    const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null;
+    return try sema.analyzeDeclVal(block, src, decl);
+}
+
 fn structFieldPtr(
     sema: *Sema,
     block: *Block,
@@ -11370,10 +11449,9 @@ fn unionFieldVal(
     const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty);
     const union_obj = union_ty.cast(Type.Payload.Union).?.data;
 
-    const field_index_big = union_obj.fields.getIndex(field_name) orelse
+    const field_index_usize = union_obj.fields.getIndex(field_name) orelse
         return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
-    const field_index = @intCast(u32, field_index_big);
-
+    const field_index = @intCast(u32, field_index_usize);
     const field = union_obj.fields.values()[field_index];
 
     if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| {
@@ -12960,15 +13038,18 @@ fn wrapErrorUnion(
 fn unionToTag(
     sema: *Sema,
     block: *Block,
-    dest_ty: Type,
+    enum_ty: Type,
     un: Air.Inst.Ref,
     un_src: LazySrcLoc,
 ) !Air.Inst.Ref {
+    if ((try sema.typeHasOnePossibleValue(block, un_src, enum_ty))) |opv| {
+        return sema.addConstant(enum_ty, opv);
+    }
     if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| {
-        return sema.addConstant(dest_ty, un_val.unionTag());
+        return sema.addConstant(enum_ty, un_val.unionTag());
     }
     try sema.requireRuntimeBlock(block, un_src);
-    return block.addTyOp(.get_union_tag, dest_ty, un);
+    return block.addTyOp(.get_union_tag, enum_ty, un);
 }
 
 fn resolvePeerTypes(
src/value.zig
@@ -116,7 +116,7 @@ pub const Value = extern union {
         decl_ref_mut,
         /// Pointer to a specific element of an array.
         elem_ptr,
-        /// Pointer to a specific field of a struct.
+        /// Pointer to a specific field of a struct or union.
         field_ptr,
         /// A slice of u8 whose memory is managed externally.
         bytes,
src/Zir.zig
@@ -72,6 +72,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en
             Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),
             i32 => @bitCast(i32, code.extra[i]),
             Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]),
+            Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]),
             else => @compileError("bad field type"),
         };
         i += 1;
@@ -618,39 +619,16 @@ pub const Inst = struct {
         enum_literal,
         /// A switch expression. Uses the `pl_node` union field.
         /// AST node is the switch, payload is `SwitchBlock`.
-        /// All prongs of target handled.
         switch_block,
-        /// Same as switch_block, except one or more prongs have multiple items.
-        /// Payload is `SwitchBlockMulti`
-        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.
-        /// Payload is `SwitchBlockMulti`
-        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.
-        /// Payload is `SwitchBlockMulti`
-        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.
-        /// Payload is `SwitchBlockMulti`
-        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.
-        /// Payload is `SwitchBlockMulti`
-        switch_block_ref_else_multi,
-        /// Same as `switch_block_under` but the target is a pointer to the value
-        /// being switched on.
-        switch_block_ref_under,
-        /// Same as `switch_block_under_multi` but the target is a pointer to
-        /// the value being switched on.
-        /// Payload is `SwitchBlockMulti`
-        switch_block_ref_under_multi,
+        /// Produces the value that will be switched on. For example, for
+        /// integers, it returns the integer with no modifications. For tagged unions, it
+        /// returns the active enum tag.
+        /// Uses the `un_node` union field.
+        switch_cond,
+        /// Same as `switch_cond`, except the input operand is a pointer to
+        /// what will be switched on.
+        /// Uses the `un_node` union field.
+        switch_cond_ref,
         /// Produces the capture value for a switch prong.
         /// Uses the `switch_capture` field.
         switch_capture,
@@ -1109,17 +1087,8 @@ pub const Inst = struct {
                 .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,
+                .switch_cond,
+                .switch_cond_ref,
                 .validate_struct_init,
                 .validate_array_init,
                 .struct_init_empty,
@@ -1367,17 +1336,8 @@ pub const Inst = struct {
                 .ensure_err_payload_void = .un_tok,
                 .enum_literal = .str_tok,
                 .switch_block = .pl_node,
-                .switch_block_multi = .pl_node,
-                .switch_block_else = .pl_node,
-                .switch_block_else_multi = .pl_node,
-                .switch_block_under = .pl_node,
-                .switch_block_under_multi = .pl_node,
-                .switch_block_ref = .pl_node,
-                .switch_block_ref_multi = .pl_node,
-                .switch_block_ref_else = .pl_node,
-                .switch_block_ref_else_multi = .pl_node,
-                .switch_block_ref_under = .pl_node,
-                .switch_block_ref_under_multi = .pl_node,
+                .switch_cond = .un_node,
+                .switch_cond_ref = .un_node,
                 .switch_capture = .switch_capture,
                 .switch_capture_ref = .switch_capture,
                 .switch_capture_multi = .switch_capture,
@@ -2466,37 +2426,17 @@ pub const Inst = struct {
         index: u32,
     };
 
-    /// This form is supported when there are no ranges, and exactly 1 item per block.
-    /// Depending on zir tag and len fields, extra fields trail
-    /// this one in the extra array.
-    /// 0. else_body { // If the tag has "_else" or "_under" in it.
-    ///        body_len: u32,
-    ///        body member Index for every body_len
-    ///     }
-    /// 1. cases: {
-    ///        item: Ref,
-    ///        body_len: u32,
-    ///        body member Index for every body_len
-    ///    } for every cases_len
-    pub const SwitchBlock = struct {
-        operand: Ref,
-        cases_len: u32,
-    };
-
-    /// This form is required when there exists a block which has more than one item,
-    /// or a range.
-    /// Depending on zir tag and len fields, extra fields trail
-    /// this one in the extra array.
-    /// 0. else_body { // If the tag has "_else" or "_under" in it.
+    /// 0. multi_cases_len: u32 // If has_multi_cases is set.
+    /// 1. else_body { // If has_else or has_under is set.
     ///        body_len: u32,
     ///        body member Index for every body_len
     ///     }
-    /// 1. scalar_cases: { // for every scalar_cases_len
+    /// 2. scalar_cases: { // for every scalar_cases_len
     ///        item: Ref,
     ///        body_len: u32,
     ///        body member Index for every body_len
     ///     }
-    /// 2. multi_cases: { // for every multi_cases_len
+    /// 3. multi_cases: { // for every multi_cases_len
     ///        items_len: u32,
     ///        ranges_len: u32,
     ///        body_len: u32,
@@ -2507,10 +2447,78 @@ pub const Inst = struct {
     ///        }
     ///        body member Index for every body_len
     ///    }
-    pub const SwitchBlockMulti = struct {
+    pub const SwitchBlock = struct {
         operand: Ref,
-        scalar_cases_len: u32,
-        multi_cases_len: u32,
+        bits: Bits,
+
+        pub const Bits = packed struct {
+            /// If true, one or more prongs have multiple items.
+            has_multi_cases: bool,
+            /// If true, there is an else prong. This is mutually exclusive with `has_under`.
+            has_else: bool,
+            /// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
+            has_under: bool,
+            /// If true, the `operand` is a pointer to the value being switched on.
+            is_ref: bool,
+            scalar_cases_len: u28,
+
+            pub fn specialProng(bits: Bits) SpecialProng {
+                const has_else: u2 = @boolToInt(bits.has_else);
+                const has_under: u2 = @boolToInt(bits.has_under);
+                return switch ((has_else << 1) | has_under) {
+                    0b00 => .none,
+                    0b01 => .under,
+                    0b10 => .@"else",
+                    0b11 => unreachable,
+                };
+            }
+        };
+
+        pub const ScalarProng = struct {
+            item: Ref,
+            body: []const Index,
+        };
+
+        /// TODO performance optimization: instead of having this helper method
+        /// change the definition of switch_capture instruction to store extra_index
+        /// instead of prong_index. This way, Sema won't be doing O(N^2) iterations
+        /// over the switch prongs.
+        pub fn getScalarProng(
+            self: SwitchBlock,
+            zir: Zir,
+            extra_end: usize,
+            prong_index: usize,
+        ) ScalarProng {
+            var extra_index: usize = extra_end;
+
+            if (self.bits.has_multi_cases) {
+                extra_index += 1;
+            }
+
+            if (self.bits.specialProng() != .none) {
+                const body_len = zir.extra[extra_index];
+                extra_index += 1;
+                const body = zir.extra[extra_index..][0..body_len];
+                extra_index += body.len;
+            }
+
+            var scalar_i: usize = 0;
+            while (true) : (scalar_i += 1) {
+                const item = @intToEnum(Ref, zir.extra[extra_index]);
+                extra_index += 1;
+                const body_len = zir.extra[extra_index];
+                extra_index += 1;
+                const body = zir.extra[extra_index..][0..body_len];
+                extra_index += body.len;
+
+                if (scalar_i < prong_index) continue;
+
+                return .{
+                    .item = item,
+                    .body = body,
+                };
+            }
+        }
     };
 
     pub const Field = struct {
@@ -2934,7 +2942,7 @@ pub const Inst = struct {
 
     /// Trailing: for each `imports_len` there is an Item
     pub const Imports = struct {
-        imports_len: Zir.Inst.Index,
+        imports_len: Inst.Index,
 
         pub const Item = struct {
             /// null terminated string index
@@ -3077,7 +3085,7 @@ pub fn declIteratorInner(zir: Zir, extra_index: usize, decls_len: u32) DeclItera
 
 /// The iterator would have to allocate memory anyway to iterate. So here we populate
 /// an ArrayList as the result.
-pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index: u32) !void {
+pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_sub_index: u32) !void {
     const block_inst = zir.extra[decl_sub_index + 6];
     list.clearRetainingCapacity();
 
@@ -3086,8 +3094,8 @@ pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index:
 
 fn findDeclsInner(
     zir: Zir,
-    list: *std.ArrayList(Zir.Inst.Index),
-    inst: Zir.Inst.Index,
+    list: *std.ArrayList(Inst.Index),
+    inst: Inst.Index,
 ) Allocator.Error!void {
     const tags = zir.instructions.items(.tag);
     const datas = zir.instructions.items(.data);
@@ -3148,19 +3156,7 @@ fn findDeclsInner(
             try zir.findDeclsBody(list, then_body);
             try zir.findDeclsBody(list, else_body);
         },
-        .switch_block => return findDeclsSwitch(zir, list, inst, .none),
-        .switch_block_else => return findDeclsSwitch(zir, list, inst, .@"else"),
-        .switch_block_under => return findDeclsSwitch(zir, list, inst, .under),
-        .switch_block_ref => return findDeclsSwitch(zir, list, inst, .none),
-        .switch_block_ref_else => return findDeclsSwitch(zir, list, inst, .@"else"),
-        .switch_block_ref_under => return findDeclsSwitch(zir, list, inst, .under),
-
-        .switch_block_multi => return findDeclsSwitchMulti(zir, list, inst, .none),
-        .switch_block_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"),
-        .switch_block_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under),
-        .switch_block_ref_multi => return findDeclsSwitchMulti(zir, list, inst, .none),
-        .switch_block_ref_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"),
-        .switch_block_ref_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under),
+        .switch_block => return findDeclsSwitch(zir, list, inst),
 
         .suspend_block => @panic("TODO iterate suspend block"),
 
@@ -3170,71 +3166,34 @@ fn findDeclsInner(
 
 fn findDeclsSwitch(
     zir: Zir,
-    list: *std.ArrayList(Zir.Inst.Index),
-    inst: Zir.Inst.Index,
-    special_prong: SpecialProng,
+    list: *std.ArrayList(Inst.Index),
+    inst: Inst.Index,
 ) Allocator.Error!void {
     const inst_data = zir.instructions.items(.data)[inst].pl_node;
     const extra = zir.extraData(Inst.SwitchBlock, 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 = zir.extra[extra.end];
-            const extra_body_start = extra.end + 1;
-            break :blk .{
-                .body = zir.extra[extra_body_start..][0..body_len],
-                .end = extra_body_start + body_len,
-            };
-        },
-    };
 
-    try zir.findDeclsBody(list, special.body);
+    var extra_index: usize = extra.end;
 
-    var extra_index: usize = special.end;
-    var scalar_i: usize = 0;
-    while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
+    const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
+        const multi_cases_len = zir.extra[extra_index];
         extra_index += 1;
+        break :blk multi_cases_len;
+    } else 0;
+
+    const special_prong = extra.data.bits.specialProng();
+    if (special_prong != .none) {
         const body_len = zir.extra[extra_index];
         extra_index += 1;
         const body = zir.extra[extra_index..][0..body_len];
-        extra_index += body_len;
+        extra_index += body.len;
 
         try zir.findDeclsBody(list, body);
     }
-}
 
-fn findDeclsSwitchMulti(
-    zir: Zir,
-    list: *std.ArrayList(Zir.Inst.Index),
-    inst: Zir.Inst.Index,
-    special_prong: SpecialProng,
-) Allocator.Error!void {
-    const inst_data = zir.instructions.items(.data)[inst].pl_node;
-    const extra = zir.extraData(Inst.SwitchBlockMulti, 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 = zir.extra[extra.end];
-            const extra_body_start = extra.end + 1;
-            break :blk .{
-                .body = zir.extra[extra_body_start..][0..body_len],
-                .end = extra_body_start + body_len,
-            };
-        },
-    };
-
-    try zir.findDeclsBody(list, special.body);
-
-    var extra_index: usize = special.end;
     {
+        const scalar_cases_len = extra.data.bits.scalar_cases_len;
         var scalar_i: usize = 0;
-        while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
+        while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
             extra_index += 1;
             const body_len = zir.extra[extra_index];
             extra_index += 1;
@@ -3246,7 +3205,7 @@ fn findDeclsSwitchMulti(
     }
     {
         var multi_i: usize = 0;
-        while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
+        while (multi_i < multi_cases_len) : (multi_i += 1) {
             const items_len = zir.extra[extra_index];
             extra_index += 1;
             const ranges_len = zir.extra[extra_index];
@@ -3353,3 +3312,18 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
         .total_params_len = total_params_len,
     };
 }
+
+const ref_start_index: u32 = Inst.Ref.typed_value_map.len;
+
+pub fn indexToRef(inst: Inst.Index) Inst.Ref {
+    return @intToEnum(Inst.Ref, ref_start_index + inst);
+}
+
+pub fn refToIndex(inst: Inst.Ref) ?Inst.Index {
+    const ref_int = @enumToInt(inst);
+    if (ref_int >= ref_start_index) {
+        return ref_int - ref_start_index;
+    } else {
+        return null;
+    }
+}
test/behavior/union.zig
@@ -71,3 +71,34 @@ test "0-sized extern union definition" {
 
     try expect(U.f == 1);
 }
+
+const Value = union(enum) {
+    Int: u64,
+    Array: [9]u8,
+};
+
+const Agg = struct {
+    val1: Value,
+    val2: Value,
+};
+
+const v1 = Value{ .Int = 1234 };
+const v2 = Value{ .Array = [_]u8{3} ** 9 };
+
+const err = @as(anyerror!Agg, Agg{
+    .val1 = v1,
+    .val2 = v2,
+});
+
+const array = [_]Value{ v1, v2, v1, v2 };
+
+test "unions embedded in aggregate types" {
+    switch (array[1]) {
+        Value.Array => |arr| try expect(arr[4] == 3),
+        else => unreachable,
+    }
+    switch ((err catch unreachable).val1) {
+        Value.Int => |x| try expect(x == 1234),
+        else => unreachable,
+    }
+}
test/behavior/union_stage1.zig
@@ -3,37 +3,6 @@ const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
 const Tag = std.meta.Tag;
 
-const Value = union(enum) {
-    Int: u64,
-    Array: [9]u8,
-};
-
-const Agg = struct {
-    val1: Value,
-    val2: Value,
-};
-
-const v1 = Value{ .Int = 1234 };
-const v2 = Value{ .Array = [_]u8{3} ** 9 };
-
-const err = @as(anyerror!Agg, Agg{
-    .val1 = v1,
-    .val2 = v2,
-});
-
-const array = [_]Value{ v1, v2, v1, v2 };
-
-test "unions embedded in aggregate types" {
-    switch (array[1]) {
-        Value.Array => |arr| try expect(arr[4] == 3),
-        else => unreachable,
-    }
-    switch ((err catch unreachable).val1) {
-        Value.Int => |x| try expect(x == 1234),
-        else => unreachable,
-    }
-}
-
 const Letter = enum { A, B, C };
 const Payload = union(Letter) {
     A: i32,
test/stage2/cbe.zig
@@ -852,7 +852,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    _ = E.d;
             \\}
         , &.{
-            ":3:10: error: enum 'tmp.E' has no member named 'd'",
+            ":3:11: error: enum 'tmp.E' has no member named 'd'",
             ":1:11: note: enum declared here",
         });