Commit c8fb36b36c

Andrew Kelley <andrew@ziglang.org>
2021-12-27 09:14:50
stage2: LLVM backend: implement `@tagName` for enums
Introduced a new AIR instruction: `tag_name`. Reasons to do this instead of lowering it in Sema to a switch, function call, array lookup, or if-else tower: * Sema is a bottleneck; do less work in Sema whenever possible. * If any optimization passes run, and the operand to becomes comptime-known, then it could change to have a comptime result value instead of lowering to a function or array or something which would then have to be garbage-collected. * Backends may want to choose to use a function and a switch branch, or they may want to use a different strategy. Codegen for `@tagName` is implemented for the LLVM backend but not any others yet. Introduced some new `Type` tags: * `const_slice_u8_sentinel_0` * `manyptr_const_u8_sentinel_0` The motivation for this was to make typeof() on the tag_name AIR instruction non-allocating. A bunch more enum tests are passing now.
1 parent f41b9cd
src/arch/aarch64/CodeGen.zig
@@ -504,133 +504,134 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
 
         switch (air_tags[inst]) {
             // zig fmt: off
-                    .add, .ptr_add   => try self.airAdd(inst),
-                    .addwrap         => try self.airAddWrap(inst),
-                    .add_sat         => try self.airAddSat(inst),
-                    .sub, .ptr_sub   => try self.airSub(inst),
-                    .subwrap         => try self.airSubWrap(inst),
-                    .sub_sat         => try self.airSubSat(inst),
-                    .mul             => try self.airMul(inst),
-                    .mulwrap         => try self.airMulWrap(inst),
-                    .mul_sat         => try self.airMulSat(inst),
-                    .rem             => try self.airRem(inst),
-                    .mod             => try self.airMod(inst),
-                    .shl, .shl_exact => try self.airShl(inst),
-                    .shl_sat         => try self.airShlSat(inst),
-                    .min             => try self.airMin(inst),
-                    .max             => try self.airMax(inst),
-                    .slice           => try self.airSlice(inst),
-
-                    .add_with_overflow => try self.airAddWithOverflow(inst),
-                    .sub_with_overflow => try self.airSubWithOverflow(inst),
-                    .mul_with_overflow => try self.airMulWithOverflow(inst),
-                    .shl_with_overflow => try self.airShlWithOverflow(inst),
-
-                    .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
-
-                    .cmp_lt  => try self.airCmp(inst, .lt),
-                    .cmp_lte => try self.airCmp(inst, .lte),
-                    .cmp_eq  => try self.airCmp(inst, .eq),
-                    .cmp_gte => try self.airCmp(inst, .gte),
-                    .cmp_gt  => try self.airCmp(inst, .gt),
-                    .cmp_neq => try self.airCmp(inst, .neq),
-
-                    .bool_and => try self.airBoolOp(inst),
-                    .bool_or  => try self.airBoolOp(inst),
-                    .bit_and  => try self.airBitAnd(inst),
-                    .bit_or   => try self.airBitOr(inst),
-                    .xor      => try self.airXor(inst),
-                    .shr      => try self.airShr(inst),
-
-                    .alloc           => try self.airAlloc(inst),
-                    .ret_ptr         => try self.airRetPtr(inst),
-                    .arg             => try self.airArg(inst),
-                    .assembly        => try self.airAsm(inst),
-                    .bitcast         => try self.airBitCast(inst),
-                    .block           => try self.airBlock(inst),
-                    .br              => try self.airBr(inst),
-                    .breakpoint      => try self.airBreakpoint(),
-                    .ret_addr        => try self.airRetAddr(),
-                    .fence           => try self.airFence(),
-                    .call            => try self.airCall(inst),
-                    .cond_br         => try self.airCondBr(inst),
-                    .dbg_stmt        => try self.airDbgStmt(inst),
-                    .fptrunc         => try self.airFptrunc(inst),
-                    .fpext           => try self.airFpext(inst),
-                    .intcast         => try self.airIntCast(inst),
-                    .trunc           => try self.airTrunc(inst),
-                    .bool_to_int     => try self.airBoolToInt(inst),
-                    .is_non_null     => try self.airIsNonNull(inst),
-                    .is_non_null_ptr => try self.airIsNonNullPtr(inst),
-                    .is_null         => try self.airIsNull(inst),
-                    .is_null_ptr     => try self.airIsNullPtr(inst),
-                    .is_non_err      => try self.airIsNonErr(inst),
-                    .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
-                    .is_err          => try self.airIsErr(inst),
-                    .is_err_ptr      => try self.airIsErrPtr(inst),
-                    .load            => try self.airLoad(inst),
-                    .loop            => try self.airLoop(inst),
-                    .not             => try self.airNot(inst),
-                    .ptrtoint        => try self.airPtrToInt(inst),
-                    .ret             => try self.airRet(inst),
-                    .ret_load        => try self.airRetLoad(inst),
-                    .store           => try self.airStore(inst),
-                    .struct_field_ptr=> try self.airStructFieldPtr(inst),
-                    .struct_field_val=> try self.airStructFieldVal(inst),
-                    .array_to_slice  => try self.airArrayToSlice(inst),
-                    .int_to_float    => try self.airIntToFloat(inst),
-                    .float_to_int    => try self.airFloatToInt(inst),
-                    .cmpxchg_strong  => try self.airCmpxchg(inst),
-                    .cmpxchg_weak    => try self.airCmpxchg(inst),
-                    .atomic_rmw      => try self.airAtomicRmw(inst),
-                    .atomic_load     => try self.airAtomicLoad(inst),
-                    .memcpy          => try self.airMemcpy(inst),
-                    .memset          => try self.airMemset(inst),
-                    .set_union_tag   => try self.airSetUnionTag(inst),
-                    .get_union_tag   => try self.airGetUnionTag(inst),
-                    .clz             => try self.airClz(inst),
-                    .ctz             => try self.airCtz(inst),
-                    .popcount        => try self.airPopcount(inst),
-
-                    .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
-                    .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
-                    .atomic_store_release   => try self.airAtomicStore(inst, .Release),
-                    .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
-
-                    .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
-                    .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
-                    .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
-                    .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
-
-                    .switch_br       => try self.airSwitch(inst),
-                    .slice_ptr       => try self.airSlicePtr(inst),
-                    .slice_len       => try self.airSliceLen(inst),
-
-                    .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
-                    .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
-
-                    .array_elem_val      => try self.airArrayElemVal(inst),
-                    .slice_elem_val      => try self.airSliceElemVal(inst),
-                    .slice_elem_ptr      => try self.airSliceElemPtr(inst),
-                    .ptr_elem_val        => try self.airPtrElemVal(inst),
-                    .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
-
-                    .constant => unreachable, // excluded from function bodies
-                    .const_ty => unreachable, // excluded from function bodies
-                    .unreach  => self.finishAirBookkeeping(),
-
-                    .optional_payload           => try self.airOptionalPayload(inst),
-                    .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
-                    .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
-                    .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
-                    .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
-                    .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
-                    .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
-
-                    .wrap_optional         => try self.airWrapOptional(inst),
-                    .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
-                    .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
-                    // zig fmt: on
+            .add, .ptr_add   => try self.airAdd(inst),
+            .addwrap         => try self.airAddWrap(inst),
+            .add_sat         => try self.airAddSat(inst),
+            .sub, .ptr_sub   => try self.airSub(inst),
+            .subwrap         => try self.airSubWrap(inst),
+            .sub_sat         => try self.airSubSat(inst),
+            .mul             => try self.airMul(inst),
+            .mulwrap         => try self.airMulWrap(inst),
+            .mul_sat         => try self.airMulSat(inst),
+            .rem             => try self.airRem(inst),
+            .mod             => try self.airMod(inst),
+            .shl, .shl_exact => try self.airShl(inst),
+            .shl_sat         => try self.airShlSat(inst),
+            .min             => try self.airMin(inst),
+            .max             => try self.airMax(inst),
+            .slice           => try self.airSlice(inst),
+
+            .add_with_overflow => try self.airAddWithOverflow(inst),
+            .sub_with_overflow => try self.airSubWithOverflow(inst),
+            .mul_with_overflow => try self.airMulWithOverflow(inst),
+            .shl_with_overflow => try self.airShlWithOverflow(inst),
+
+            .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+
+            .cmp_lt  => try self.airCmp(inst, .lt),
+            .cmp_lte => try self.airCmp(inst, .lte),
+            .cmp_eq  => try self.airCmp(inst, .eq),
+            .cmp_gte => try self.airCmp(inst, .gte),
+            .cmp_gt  => try self.airCmp(inst, .gt),
+            .cmp_neq => try self.airCmp(inst, .neq),
+
+            .bool_and => try self.airBoolOp(inst),
+            .bool_or  => try self.airBoolOp(inst),
+            .bit_and  => try self.airBitAnd(inst),
+            .bit_or   => try self.airBitOr(inst),
+            .xor      => try self.airXor(inst),
+            .shr      => try self.airShr(inst),
+
+            .alloc           => try self.airAlloc(inst),
+            .ret_ptr         => try self.airRetPtr(inst),
+            .arg             => try self.airArg(inst),
+            .assembly        => try self.airAsm(inst),
+            .bitcast         => try self.airBitCast(inst),
+            .block           => try self.airBlock(inst),
+            .br              => try self.airBr(inst),
+            .breakpoint      => try self.airBreakpoint(),
+            .ret_addr        => try self.airRetAddr(),
+            .fence           => try self.airFence(),
+            .call            => try self.airCall(inst),
+            .cond_br         => try self.airCondBr(inst),
+            .dbg_stmt        => try self.airDbgStmt(inst),
+            .fptrunc         => try self.airFptrunc(inst),
+            .fpext           => try self.airFpext(inst),
+            .intcast         => try self.airIntCast(inst),
+            .trunc           => try self.airTrunc(inst),
+            .bool_to_int     => try self.airBoolToInt(inst),
+            .is_non_null     => try self.airIsNonNull(inst),
+            .is_non_null_ptr => try self.airIsNonNullPtr(inst),
+            .is_null         => try self.airIsNull(inst),
+            .is_null_ptr     => try self.airIsNullPtr(inst),
+            .is_non_err      => try self.airIsNonErr(inst),
+            .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
+            .is_err          => try self.airIsErr(inst),
+            .is_err_ptr      => try self.airIsErrPtr(inst),
+            .load            => try self.airLoad(inst),
+            .loop            => try self.airLoop(inst),
+            .not             => try self.airNot(inst),
+            .ptrtoint        => try self.airPtrToInt(inst),
+            .ret             => try self.airRet(inst),
+            .ret_load        => try self.airRetLoad(inst),
+            .store           => try self.airStore(inst),
+            .struct_field_ptr=> try self.airStructFieldPtr(inst),
+            .struct_field_val=> try self.airStructFieldVal(inst),
+            .array_to_slice  => try self.airArrayToSlice(inst),
+            .int_to_float    => try self.airIntToFloat(inst),
+            .float_to_int    => try self.airFloatToInt(inst),
+            .cmpxchg_strong  => try self.airCmpxchg(inst),
+            .cmpxchg_weak    => try self.airCmpxchg(inst),
+            .atomic_rmw      => try self.airAtomicRmw(inst),
+            .atomic_load     => try self.airAtomicLoad(inst),
+            .memcpy          => try self.airMemcpy(inst),
+            .memset          => try self.airMemset(inst),
+            .set_union_tag   => try self.airSetUnionTag(inst),
+            .get_union_tag   => try self.airGetUnionTag(inst),
+            .clz             => try self.airClz(inst),
+            .ctz             => try self.airCtz(inst),
+            .popcount        => try self.airPopcount(inst),
+            .tag_name        => try self.airTagName(inst),
+
+            .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
+            .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
+            .atomic_store_release   => try self.airAtomicStore(inst, .Release),
+            .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
+
+            .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
+            .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
+            .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
+            .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
+
+            .switch_br       => try self.airSwitch(inst),
+            .slice_ptr       => try self.airSlicePtr(inst),
+            .slice_len       => try self.airSliceLen(inst),
+
+            .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
+            .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
+
+            .array_elem_val      => try self.airArrayElemVal(inst),
+            .slice_elem_val      => try self.airSliceElemVal(inst),
+            .slice_elem_ptr      => try self.airSliceElemPtr(inst),
+            .ptr_elem_val        => try self.airPtrElemVal(inst),
+            .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
+
+            .constant => unreachable, // excluded from function bodies
+            .const_ty => unreachable, // excluded from function bodies
+            .unreach  => self.finishAirBookkeeping(),
+
+            .optional_payload           => try self.airOptionalPayload(inst),
+            .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
+            .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
+            .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
+            .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
+            .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
+            .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
+
+            .wrap_optional         => try self.airWrapOptional(inst),
+            .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
+            .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
+            // zig fmt: on
         }
         if (std.debug.runtime_safety) {
             if (self.air_bookkeeping < old_air_bookkeeping + 1) {
@@ -2546,6 +2547,16 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+    const un_op = self.air.instructions.items(.data)[inst].un_op;
+    const operand = try self.resolveInst(un_op);
+    const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
+        _ = operand;
+        return self.fail("TODO implement airTagName for aarch64", .{});
+    };
+    return self.finishAir(inst, result, .{ un_op, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/arm/CodeGen.zig
@@ -502,133 +502,134 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
 
         switch (air_tags[inst]) {
             // zig fmt: off
-                    .add, .ptr_add   => try self.airAdd(inst),
-                    .addwrap         => try self.airAddWrap(inst),
-                    .add_sat         => try self.airAddSat(inst),
-                    .sub, .ptr_sub   => try self.airSub(inst),
-                    .subwrap         => try self.airSubWrap(inst),
-                    .sub_sat         => try self.airSubSat(inst),
-                    .mul             => try self.airMul(inst),
-                    .mulwrap         => try self.airMulWrap(inst),
-                    .mul_sat         => try self.airMulSat(inst),
-                    .rem             => try self.airRem(inst),
-                    .mod             => try self.airMod(inst),
-                    .shl, .shl_exact => try self.airShl(inst),
-                    .shl_sat         => try self.airShlSat(inst),
-                    .min             => try self.airMin(inst),
-                    .max             => try self.airMax(inst),
-                    .slice           => try self.airSlice(inst),
-
-                    .add_with_overflow => try self.airAddWithOverflow(inst),
-                    .sub_with_overflow => try self.airSubWithOverflow(inst),
-                    .mul_with_overflow => try self.airMulWithOverflow(inst),
-                    .shl_with_overflow => try self.airShlWithOverflow(inst),
-
-                    .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
-
-                    .cmp_lt  => try self.airCmp(inst, .lt),
-                    .cmp_lte => try self.airCmp(inst, .lte),
-                    .cmp_eq  => try self.airCmp(inst, .eq),
-                    .cmp_gte => try self.airCmp(inst, .gte),
-                    .cmp_gt  => try self.airCmp(inst, .gt),
-                    .cmp_neq => try self.airCmp(inst, .neq),
-
-                    .bool_and => try self.airBoolOp(inst),
-                    .bool_or  => try self.airBoolOp(inst),
-                    .bit_and  => try self.airBitAnd(inst),
-                    .bit_or   => try self.airBitOr(inst),
-                    .xor      => try self.airXor(inst),
-                    .shr      => try self.airShr(inst),
-
-                    .alloc           => try self.airAlloc(inst),
-                    .ret_ptr         => try self.airRetPtr(inst),
-                    .arg             => try self.airArg(inst),
-                    .assembly        => try self.airAsm(inst),
-                    .bitcast         => try self.airBitCast(inst),
-                    .block           => try self.airBlock(inst),
-                    .br              => try self.airBr(inst),
-                    .breakpoint      => try self.airBreakpoint(),
-                    .ret_addr        => try self.airRetAddr(),
-                    .fence           => try self.airFence(),
-                    .call            => try self.airCall(inst),
-                    .cond_br         => try self.airCondBr(inst),
-                    .dbg_stmt        => try self.airDbgStmt(inst),
-                    .fptrunc         => try self.airFptrunc(inst),
-                    .fpext           => try self.airFpext(inst),
-                    .intcast         => try self.airIntCast(inst),
-                    .trunc           => try self.airTrunc(inst),
-                    .bool_to_int     => try self.airBoolToInt(inst),
-                    .is_non_null     => try self.airIsNonNull(inst),
-                    .is_non_null_ptr => try self.airIsNonNullPtr(inst),
-                    .is_null         => try self.airIsNull(inst),
-                    .is_null_ptr     => try self.airIsNullPtr(inst),
-                    .is_non_err      => try self.airIsNonErr(inst),
-                    .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
-                    .is_err          => try self.airIsErr(inst),
-                    .is_err_ptr      => try self.airIsErrPtr(inst),
-                    .load            => try self.airLoad(inst),
-                    .loop            => try self.airLoop(inst),
-                    .not             => try self.airNot(inst),
-                    .ptrtoint        => try self.airPtrToInt(inst),
-                    .ret             => try self.airRet(inst),
-                    .ret_load        => try self.airRetLoad(inst),
-                    .store           => try self.airStore(inst),
-                    .struct_field_ptr=> try self.airStructFieldPtr(inst),
-                    .struct_field_val=> try self.airStructFieldVal(inst),
-                    .array_to_slice  => try self.airArrayToSlice(inst),
-                    .int_to_float    => try self.airIntToFloat(inst),
-                    .float_to_int    => try self.airFloatToInt(inst),
-                    .cmpxchg_strong  => try self.airCmpxchg(inst),
-                    .cmpxchg_weak    => try self.airCmpxchg(inst),
-                    .atomic_rmw      => try self.airAtomicRmw(inst),
-                    .atomic_load     => try self.airAtomicLoad(inst),
-                    .memcpy          => try self.airMemcpy(inst),
-                    .memset          => try self.airMemset(inst),
-                    .set_union_tag   => try self.airSetUnionTag(inst),
-                    .get_union_tag   => try self.airGetUnionTag(inst),
-                    .clz             => try self.airClz(inst),
-                    .ctz             => try self.airCtz(inst),
-                    .popcount        => try self.airPopcount(inst),
-
-                    .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
-                    .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
-                    .atomic_store_release   => try self.airAtomicStore(inst, .Release),
-                    .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
-
-                    .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
-                    .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
-                    .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
-                    .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
-
-                    .switch_br       => try self.airSwitch(inst),
-                    .slice_ptr       => try self.airSlicePtr(inst),
-                    .slice_len       => try self.airSliceLen(inst),
-
-                    .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
-                    .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
-
-                    .array_elem_val      => try self.airArrayElemVal(inst),
-                    .slice_elem_val      => try self.airSliceElemVal(inst),
-                    .slice_elem_ptr      => try self.airSliceElemPtr(inst),
-                    .ptr_elem_val        => try self.airPtrElemVal(inst),
-                    .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
-
-                    .constant => unreachable, // excluded from function bodies
-                    .const_ty => unreachable, // excluded from function bodies
-                    .unreach  => self.finishAirBookkeeping(),
-
-                    .optional_payload           => try self.airOptionalPayload(inst),
-                    .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
-                    .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
-                    .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
-                    .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
-                    .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
-                    .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
-
-                    .wrap_optional         => try self.airWrapOptional(inst),
-                    .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
-                    .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
-                    // zig fmt: on
+            .add, .ptr_add   => try self.airAdd(inst),
+            .addwrap         => try self.airAddWrap(inst),
+            .add_sat         => try self.airAddSat(inst),
+            .sub, .ptr_sub   => try self.airSub(inst),
+            .subwrap         => try self.airSubWrap(inst),
+            .sub_sat         => try self.airSubSat(inst),
+            .mul             => try self.airMul(inst),
+            .mulwrap         => try self.airMulWrap(inst),
+            .mul_sat         => try self.airMulSat(inst),
+            .rem             => try self.airRem(inst),
+            .mod             => try self.airMod(inst),
+            .shl, .shl_exact => try self.airShl(inst),
+            .shl_sat         => try self.airShlSat(inst),
+            .min             => try self.airMin(inst),
+            .max             => try self.airMax(inst),
+            .slice           => try self.airSlice(inst),
+
+            .add_with_overflow => try self.airAddWithOverflow(inst),
+            .sub_with_overflow => try self.airSubWithOverflow(inst),
+            .mul_with_overflow => try self.airMulWithOverflow(inst),
+            .shl_with_overflow => try self.airShlWithOverflow(inst),
+
+            .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+
+            .cmp_lt  => try self.airCmp(inst, .lt),
+            .cmp_lte => try self.airCmp(inst, .lte),
+            .cmp_eq  => try self.airCmp(inst, .eq),
+            .cmp_gte => try self.airCmp(inst, .gte),
+            .cmp_gt  => try self.airCmp(inst, .gt),
+            .cmp_neq => try self.airCmp(inst, .neq),
+
+            .bool_and => try self.airBoolOp(inst),
+            .bool_or  => try self.airBoolOp(inst),
+            .bit_and  => try self.airBitAnd(inst),
+            .bit_or   => try self.airBitOr(inst),
+            .xor      => try self.airXor(inst),
+            .shr      => try self.airShr(inst),
+
+            .alloc           => try self.airAlloc(inst),
+            .ret_ptr         => try self.airRetPtr(inst),
+            .arg             => try self.airArg(inst),
+            .assembly        => try self.airAsm(inst),
+            .bitcast         => try self.airBitCast(inst),
+            .block           => try self.airBlock(inst),
+            .br              => try self.airBr(inst),
+            .breakpoint      => try self.airBreakpoint(),
+            .ret_addr        => try self.airRetAddr(),
+            .fence           => try self.airFence(),
+            .call            => try self.airCall(inst),
+            .cond_br         => try self.airCondBr(inst),
+            .dbg_stmt        => try self.airDbgStmt(inst),
+            .fptrunc         => try self.airFptrunc(inst),
+            .fpext           => try self.airFpext(inst),
+            .intcast         => try self.airIntCast(inst),
+            .trunc           => try self.airTrunc(inst),
+            .bool_to_int     => try self.airBoolToInt(inst),
+            .is_non_null     => try self.airIsNonNull(inst),
+            .is_non_null_ptr => try self.airIsNonNullPtr(inst),
+            .is_null         => try self.airIsNull(inst),
+            .is_null_ptr     => try self.airIsNullPtr(inst),
+            .is_non_err      => try self.airIsNonErr(inst),
+            .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
+            .is_err          => try self.airIsErr(inst),
+            .is_err_ptr      => try self.airIsErrPtr(inst),
+            .load            => try self.airLoad(inst),
+            .loop            => try self.airLoop(inst),
+            .not             => try self.airNot(inst),
+            .ptrtoint        => try self.airPtrToInt(inst),
+            .ret             => try self.airRet(inst),
+            .ret_load        => try self.airRetLoad(inst),
+            .store           => try self.airStore(inst),
+            .struct_field_ptr=> try self.airStructFieldPtr(inst),
+            .struct_field_val=> try self.airStructFieldVal(inst),
+            .array_to_slice  => try self.airArrayToSlice(inst),
+            .int_to_float    => try self.airIntToFloat(inst),
+            .float_to_int    => try self.airFloatToInt(inst),
+            .cmpxchg_strong  => try self.airCmpxchg(inst),
+            .cmpxchg_weak    => try self.airCmpxchg(inst),
+            .atomic_rmw      => try self.airAtomicRmw(inst),
+            .atomic_load     => try self.airAtomicLoad(inst),
+            .memcpy          => try self.airMemcpy(inst),
+            .memset          => try self.airMemset(inst),
+            .set_union_tag   => try self.airSetUnionTag(inst),
+            .get_union_tag   => try self.airGetUnionTag(inst),
+            .clz             => try self.airClz(inst),
+            .ctz             => try self.airCtz(inst),
+            .popcount        => try self.airPopcount(inst),
+            .tag_name        => try self.airTagName(inst),
+
+            .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
+            .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
+            .atomic_store_release   => try self.airAtomicStore(inst, .Release),
+            .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
+
+            .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
+            .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
+            .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
+            .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
+
+            .switch_br       => try self.airSwitch(inst),
+            .slice_ptr       => try self.airSlicePtr(inst),
+            .slice_len       => try self.airSliceLen(inst),
+
+            .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
+            .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
+
+            .array_elem_val      => try self.airArrayElemVal(inst),
+            .slice_elem_val      => try self.airSliceElemVal(inst),
+            .slice_elem_ptr      => try self.airSliceElemPtr(inst),
+            .ptr_elem_val        => try self.airPtrElemVal(inst),
+            .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
+
+            .constant => unreachable, // excluded from function bodies
+            .const_ty => unreachable, // excluded from function bodies
+            .unreach  => self.finishAirBookkeeping(),
+
+            .optional_payload           => try self.airOptionalPayload(inst),
+            .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
+            .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
+            .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
+            .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
+            .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
+            .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
+
+            .wrap_optional         => try self.airWrapOptional(inst),
+            .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
+            .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
+            // zig fmt: on
         }
         if (std.debug.runtime_safety) {
             if (self.air_bookkeeping < old_air_bookkeeping + 1) {
@@ -3301,6 +3302,16 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+    const un_op = self.air.instructions.items(.data)[inst].un_op;
+    const operand = try self.resolveInst(un_op);
+    const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
+        _ = operand;
+        return self.fail("TODO implement airTagName for arm", .{});
+    };
+    return self.finishAir(inst, result, .{ un_op, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/riscv64/CodeGen.zig
@@ -483,133 +483,134 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
 
         switch (air_tags[inst]) {
             // zig fmt: off
-                    .add, .ptr_add   => try self.airAdd(inst),
-                    .addwrap         => try self.airAddWrap(inst),
-                    .add_sat         => try self.airAddSat(inst),
-                    .sub, .ptr_sub   => try self.airSub(inst),
-                    .subwrap         => try self.airSubWrap(inst),
-                    .sub_sat         => try self.airSubSat(inst),
-                    .mul             => try self.airMul(inst),
-                    .mulwrap         => try self.airMulWrap(inst),
-                    .mul_sat         => try self.airMulSat(inst),
-                    .rem             => try self.airRem(inst),
-                    .mod             => try self.airMod(inst),
-                    .shl, .shl_exact => try self.airShl(inst),
-                    .shl_sat         => try self.airShlSat(inst),
-                    .min             => try self.airMin(inst),
-                    .max             => try self.airMax(inst),
-                    .slice           => try self.airSlice(inst),
-
-                    .add_with_overflow => try self.airAddWithOverflow(inst),
-                    .sub_with_overflow => try self.airSubWithOverflow(inst),
-                    .mul_with_overflow => try self.airMulWithOverflow(inst),
-                    .shl_with_overflow => try self.airShlWithOverflow(inst),
-
-                    .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
-
-                    .cmp_lt  => try self.airCmp(inst, .lt),
-                    .cmp_lte => try self.airCmp(inst, .lte),
-                    .cmp_eq  => try self.airCmp(inst, .eq),
-                    .cmp_gte => try self.airCmp(inst, .gte),
-                    .cmp_gt  => try self.airCmp(inst, .gt),
-                    .cmp_neq => try self.airCmp(inst, .neq),
-
-                    .bool_and => try self.airBoolOp(inst),
-                    .bool_or  => try self.airBoolOp(inst),
-                    .bit_and  => try self.airBitAnd(inst),
-                    .bit_or   => try self.airBitOr(inst),
-                    .xor      => try self.airXor(inst),
-                    .shr      => try self.airShr(inst),
-
-                    .alloc           => try self.airAlloc(inst),
-                    .ret_ptr         => try self.airRetPtr(inst),
-                    .arg             => try self.airArg(inst),
-                    .assembly        => try self.airAsm(inst),
-                    .bitcast         => try self.airBitCast(inst),
-                    .block           => try self.airBlock(inst),
-                    .br              => try self.airBr(inst),
-                    .breakpoint      => try self.airBreakpoint(),
-                    .ret_addr        => try self.airRetAddr(),
-                    .fence           => try self.airFence(),
-                    .call            => try self.airCall(inst),
-                    .cond_br         => try self.airCondBr(inst),
-                    .dbg_stmt        => try self.airDbgStmt(inst),
-                    .fptrunc         => try self.airFptrunc(inst),
-                    .fpext           => try self.airFpext(inst),
-                    .intcast         => try self.airIntCast(inst),
-                    .trunc           => try self.airTrunc(inst),
-                    .bool_to_int     => try self.airBoolToInt(inst),
-                    .is_non_null     => try self.airIsNonNull(inst),
-                    .is_non_null_ptr => try self.airIsNonNullPtr(inst),
-                    .is_null         => try self.airIsNull(inst),
-                    .is_null_ptr     => try self.airIsNullPtr(inst),
-                    .is_non_err      => try self.airIsNonErr(inst),
-                    .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
-                    .is_err          => try self.airIsErr(inst),
-                    .is_err_ptr      => try self.airIsErrPtr(inst),
-                    .load            => try self.airLoad(inst),
-                    .loop            => try self.airLoop(inst),
-                    .not             => try self.airNot(inst),
-                    .ptrtoint        => try self.airPtrToInt(inst),
-                    .ret             => try self.airRet(inst),
-                    .ret_load        => try self.airRetLoad(inst),
-                    .store           => try self.airStore(inst),
-                    .struct_field_ptr=> try self.airStructFieldPtr(inst),
-                    .struct_field_val=> try self.airStructFieldVal(inst),
-                    .array_to_slice  => try self.airArrayToSlice(inst),
-                    .int_to_float    => try self.airIntToFloat(inst),
-                    .float_to_int    => try self.airFloatToInt(inst),
-                    .cmpxchg_strong  => try self.airCmpxchg(inst),
-                    .cmpxchg_weak    => try self.airCmpxchg(inst),
-                    .atomic_rmw      => try self.airAtomicRmw(inst),
-                    .atomic_load     => try self.airAtomicLoad(inst),
-                    .memcpy          => try self.airMemcpy(inst),
-                    .memset          => try self.airMemset(inst),
-                    .set_union_tag   => try self.airSetUnionTag(inst),
-                    .get_union_tag   => try self.airGetUnionTag(inst),
-                    .clz             => try self.airClz(inst),
-                    .ctz             => try self.airCtz(inst),
-                    .popcount        => try self.airPopcount(inst),
-
-                    .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
-                    .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
-                    .atomic_store_release   => try self.airAtomicStore(inst, .Release),
-                    .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
-
-                    .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
-                    .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
-                    .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
-                    .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
-
-                    .switch_br       => try self.airSwitch(inst),
-                    .slice_ptr       => try self.airSlicePtr(inst),
-                    .slice_len       => try self.airSliceLen(inst),
-
-                    .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
-                    .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
-
-                    .array_elem_val      => try self.airArrayElemVal(inst),
-                    .slice_elem_val      => try self.airSliceElemVal(inst),
-                    .slice_elem_ptr      => try self.airSliceElemPtr(inst),
-                    .ptr_elem_val        => try self.airPtrElemVal(inst),
-                    .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
-
-                    .constant => unreachable, // excluded from function bodies
-                    .const_ty => unreachable, // excluded from function bodies
-                    .unreach  => self.finishAirBookkeeping(),
-
-                    .optional_payload           => try self.airOptionalPayload(inst),
-                    .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
-                    .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
-                    .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
-                    .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
-                    .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
-                    .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
-
-                    .wrap_optional         => try self.airWrapOptional(inst),
-                    .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
-                    .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
-                    // zig fmt: on
+            .add, .ptr_add   => try self.airAdd(inst),
+            .addwrap         => try self.airAddWrap(inst),
+            .add_sat         => try self.airAddSat(inst),
+            .sub, .ptr_sub   => try self.airSub(inst),
+            .subwrap         => try self.airSubWrap(inst),
+            .sub_sat         => try self.airSubSat(inst),
+            .mul             => try self.airMul(inst),
+            .mulwrap         => try self.airMulWrap(inst),
+            .mul_sat         => try self.airMulSat(inst),
+            .rem             => try self.airRem(inst),
+            .mod             => try self.airMod(inst),
+            .shl, .shl_exact => try self.airShl(inst),
+            .shl_sat         => try self.airShlSat(inst),
+            .min             => try self.airMin(inst),
+            .max             => try self.airMax(inst),
+            .slice           => try self.airSlice(inst),
+
+            .add_with_overflow => try self.airAddWithOverflow(inst),
+            .sub_with_overflow => try self.airSubWithOverflow(inst),
+            .mul_with_overflow => try self.airMulWithOverflow(inst),
+            .shl_with_overflow => try self.airShlWithOverflow(inst),
+
+            .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+
+            .cmp_lt  => try self.airCmp(inst, .lt),
+            .cmp_lte => try self.airCmp(inst, .lte),
+            .cmp_eq  => try self.airCmp(inst, .eq),
+            .cmp_gte => try self.airCmp(inst, .gte),
+            .cmp_gt  => try self.airCmp(inst, .gt),
+            .cmp_neq => try self.airCmp(inst, .neq),
+
+            .bool_and => try self.airBoolOp(inst),
+            .bool_or  => try self.airBoolOp(inst),
+            .bit_and  => try self.airBitAnd(inst),
+            .bit_or   => try self.airBitOr(inst),
+            .xor      => try self.airXor(inst),
+            .shr      => try self.airShr(inst),
+
+            .alloc           => try self.airAlloc(inst),
+            .ret_ptr         => try self.airRetPtr(inst),
+            .arg             => try self.airArg(inst),
+            .assembly        => try self.airAsm(inst),
+            .bitcast         => try self.airBitCast(inst),
+            .block           => try self.airBlock(inst),
+            .br              => try self.airBr(inst),
+            .breakpoint      => try self.airBreakpoint(),
+            .ret_addr        => try self.airRetAddr(),
+            .fence           => try self.airFence(),
+            .call            => try self.airCall(inst),
+            .cond_br         => try self.airCondBr(inst),
+            .dbg_stmt        => try self.airDbgStmt(inst),
+            .fptrunc         => try self.airFptrunc(inst),
+            .fpext           => try self.airFpext(inst),
+            .intcast         => try self.airIntCast(inst),
+            .trunc           => try self.airTrunc(inst),
+            .bool_to_int     => try self.airBoolToInt(inst),
+            .is_non_null     => try self.airIsNonNull(inst),
+            .is_non_null_ptr => try self.airIsNonNullPtr(inst),
+            .is_null         => try self.airIsNull(inst),
+            .is_null_ptr     => try self.airIsNullPtr(inst),
+            .is_non_err      => try self.airIsNonErr(inst),
+            .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
+            .is_err          => try self.airIsErr(inst),
+            .is_err_ptr      => try self.airIsErrPtr(inst),
+            .load            => try self.airLoad(inst),
+            .loop            => try self.airLoop(inst),
+            .not             => try self.airNot(inst),
+            .ptrtoint        => try self.airPtrToInt(inst),
+            .ret             => try self.airRet(inst),
+            .ret_load        => try self.airRetLoad(inst),
+            .store           => try self.airStore(inst),
+            .struct_field_ptr=> try self.airStructFieldPtr(inst),
+            .struct_field_val=> try self.airStructFieldVal(inst),
+            .array_to_slice  => try self.airArrayToSlice(inst),
+            .int_to_float    => try self.airIntToFloat(inst),
+            .float_to_int    => try self.airFloatToInt(inst),
+            .cmpxchg_strong  => try self.airCmpxchg(inst),
+            .cmpxchg_weak    => try self.airCmpxchg(inst),
+            .atomic_rmw      => try self.airAtomicRmw(inst),
+            .atomic_load     => try self.airAtomicLoad(inst),
+            .memcpy          => try self.airMemcpy(inst),
+            .memset          => try self.airMemset(inst),
+            .set_union_tag   => try self.airSetUnionTag(inst),
+            .get_union_tag   => try self.airGetUnionTag(inst),
+            .clz             => try self.airClz(inst),
+            .ctz             => try self.airCtz(inst),
+            .popcount        => try self.airPopcount(inst),
+            .tag_name        => try self.airTagName(inst),
+
+            .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
+            .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
+            .atomic_store_release   => try self.airAtomicStore(inst, .Release),
+            .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
+
+            .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
+            .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
+            .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
+            .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
+
+            .switch_br       => try self.airSwitch(inst),
+            .slice_ptr       => try self.airSlicePtr(inst),
+            .slice_len       => try self.airSliceLen(inst),
+
+            .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
+            .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
+
+            .array_elem_val      => try self.airArrayElemVal(inst),
+            .slice_elem_val      => try self.airSliceElemVal(inst),
+            .slice_elem_ptr      => try self.airSliceElemPtr(inst),
+            .ptr_elem_val        => try self.airPtrElemVal(inst),
+            .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
+
+            .constant => unreachable, // excluded from function bodies
+            .const_ty => unreachable, // excluded from function bodies
+            .unreach  => self.finishAirBookkeeping(),
+
+            .optional_payload           => try self.airOptionalPayload(inst),
+            .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
+            .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
+            .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
+            .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
+            .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
+            .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
+
+            .wrap_optional         => try self.airWrapOptional(inst),
+            .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
+            .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
+            // zig fmt: on
         }
         if (std.debug.runtime_safety) {
             if (self.air_bookkeeping < old_air_bookkeeping + 1) {
@@ -2045,6 +2046,16 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+    const un_op = self.air.instructions.items(.data)[inst].un_op;
+    const operand = try self.resolveInst(un_op);
+    const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
+        _ = operand;
+        return self.fail("TODO implement airTagName for riscv64", .{});
+    };
+    return self.finishAir(inst, result, .{ un_op, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/x86_64/CodeGen.zig
@@ -538,133 +538,134 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
 
         switch (air_tags[inst]) {
             // zig fmt: off
-                    .add, .ptr_add   => try self.airAdd(inst),
-                    .addwrap         => try self.airAddWrap(inst),
-                    .add_sat         => try self.airAddSat(inst),
-                    .sub, .ptr_sub   => try self.airSub(inst),
-                    .subwrap         => try self.airSubWrap(inst),
-                    .sub_sat         => try self.airSubSat(inst),
-                    .mul             => try self.airMul(inst),
-                    .mulwrap         => try self.airMulWrap(inst),
-                    .mul_sat         => try self.airMulSat(inst),
-                    .rem             => try self.airRem(inst),
-                    .mod             => try self.airMod(inst),
-                    .shl, .shl_exact => try self.airShl(inst),
-                    .shl_sat         => try self.airShlSat(inst),
-                    .min             => try self.airMin(inst),
-                    .max             => try self.airMax(inst),
-                    .slice           => try self.airSlice(inst),
-
-                    .add_with_overflow => try self.airAddWithOverflow(inst),
-                    .sub_with_overflow => try self.airSubWithOverflow(inst),
-                    .mul_with_overflow => try self.airMulWithOverflow(inst),
-                    .shl_with_overflow => try self.airShlWithOverflow(inst),
-
-                    .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
-
-                    .cmp_lt  => try self.airCmp(inst, .lt),
-                    .cmp_lte => try self.airCmp(inst, .lte),
-                    .cmp_eq  => try self.airCmp(inst, .eq),
-                    .cmp_gte => try self.airCmp(inst, .gte),
-                    .cmp_gt  => try self.airCmp(inst, .gt),
-                    .cmp_neq => try self.airCmp(inst, .neq),
-
-                    .bool_and => try self.airBoolOp(inst),
-                    .bool_or  => try self.airBoolOp(inst),
-                    .bit_and  => try self.airBitAnd(inst),
-                    .bit_or   => try self.airBitOr(inst),
-                    .xor      => try self.airXor(inst),
-                    .shr      => try self.airShr(inst),
-
-                    .alloc           => try self.airAlloc(inst),
-                    .ret_ptr         => try self.airRetPtr(inst),
-                    .arg             => try self.airArg(inst),
-                    .assembly        => try self.airAsm(inst),
-                    .bitcast         => try self.airBitCast(inst),
-                    .block           => try self.airBlock(inst),
-                    .br              => try self.airBr(inst),
-                    .breakpoint      => try self.airBreakpoint(),
-                    .ret_addr        => try self.airRetAddr(),
-                    .fence           => try self.airFence(),
-                    .call            => try self.airCall(inst),
-                    .cond_br         => try self.airCondBr(inst),
-                    .dbg_stmt        => try self.airDbgStmt(inst),
-                    .fptrunc         => try self.airFptrunc(inst),
-                    .fpext           => try self.airFpext(inst),
-                    .intcast         => try self.airIntCast(inst),
-                    .trunc           => try self.airTrunc(inst),
-                    .bool_to_int     => try self.airBoolToInt(inst),
-                    .is_non_null     => try self.airIsNonNull(inst),
-                    .is_non_null_ptr => try self.airIsNonNullPtr(inst),
-                    .is_null         => try self.airIsNull(inst),
-                    .is_null_ptr     => try self.airIsNullPtr(inst),
-                    .is_non_err      => try self.airIsNonErr(inst),
-                    .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
-                    .is_err          => try self.airIsErr(inst),
-                    .is_err_ptr      => try self.airIsErrPtr(inst),
-                    .load            => try self.airLoad(inst),
-                    .loop            => try self.airLoop(inst),
-                    .not             => try self.airNot(inst),
-                    .ptrtoint        => try self.airPtrToInt(inst),
-                    .ret             => try self.airRet(inst),
-                    .ret_load        => try self.airRetLoad(inst),
-                    .store           => try self.airStore(inst),
-                    .struct_field_ptr=> try self.airStructFieldPtr(inst),
-                    .struct_field_val=> try self.airStructFieldVal(inst),
-                    .array_to_slice  => try self.airArrayToSlice(inst),
-                    .int_to_float    => try self.airIntToFloat(inst),
-                    .float_to_int    => try self.airFloatToInt(inst),
-                    .cmpxchg_strong  => try self.airCmpxchg(inst),
-                    .cmpxchg_weak    => try self.airCmpxchg(inst),
-                    .atomic_rmw      => try self.airAtomicRmw(inst),
-                    .atomic_load     => try self.airAtomicLoad(inst),
-                    .memcpy          => try self.airMemcpy(inst),
-                    .memset          => try self.airMemset(inst),
-                    .set_union_tag   => try self.airSetUnionTag(inst),
-                    .get_union_tag   => try self.airGetUnionTag(inst),
-                    .clz             => try self.airClz(inst),
-                    .ctz             => try self.airCtz(inst),
-                    .popcount        => try self.airPopcount(inst),
-
-                    .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
-                    .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
-                    .atomic_store_release   => try self.airAtomicStore(inst, .Release),
-                    .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
-
-                    .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
-                    .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
-                    .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
-                    .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
-
-                    .switch_br       => try self.airSwitch(inst),
-                    .slice_ptr       => try self.airSlicePtr(inst),
-                    .slice_len       => try self.airSliceLen(inst),
-
-                    .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
-                    .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
-
-                    .array_elem_val      => try self.airArrayElemVal(inst),
-                    .slice_elem_val      => try self.airSliceElemVal(inst),
-                    .slice_elem_ptr      => try self.airSliceElemPtr(inst),
-                    .ptr_elem_val        => try self.airPtrElemVal(inst),
-                    .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
-
-                    .constant => unreachable, // excluded from function bodies
-                    .const_ty => unreachable, // excluded from function bodies
-                    .unreach  => self.finishAirBookkeeping(),
-
-                    .optional_payload           => try self.airOptionalPayload(inst),
-                    .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
-                    .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
-                    .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
-                    .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
-                    .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
-                    .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
-
-                    .wrap_optional         => try self.airWrapOptional(inst),
-                    .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
-                    .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
-                    // zig fmt: on
+            .add, .ptr_add   => try self.airAdd(inst),
+            .addwrap         => try self.airAddWrap(inst),
+            .add_sat         => try self.airAddSat(inst),
+            .sub, .ptr_sub   => try self.airSub(inst),
+            .subwrap         => try self.airSubWrap(inst),
+            .sub_sat         => try self.airSubSat(inst),
+            .mul             => try self.airMul(inst),
+            .mulwrap         => try self.airMulWrap(inst),
+            .mul_sat         => try self.airMulSat(inst),
+            .rem             => try self.airRem(inst),
+            .mod             => try self.airMod(inst),
+            .shl, .shl_exact => try self.airShl(inst),
+            .shl_sat         => try self.airShlSat(inst),
+            .min             => try self.airMin(inst),
+            .max             => try self.airMax(inst),
+            .slice           => try self.airSlice(inst),
+
+            .add_with_overflow => try self.airAddWithOverflow(inst),
+            .sub_with_overflow => try self.airSubWithOverflow(inst),
+            .mul_with_overflow => try self.airMulWithOverflow(inst),
+            .shl_with_overflow => try self.airShlWithOverflow(inst),
+
+            .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+
+            .cmp_lt  => try self.airCmp(inst, .lt),
+            .cmp_lte => try self.airCmp(inst, .lte),
+            .cmp_eq  => try self.airCmp(inst, .eq),
+            .cmp_gte => try self.airCmp(inst, .gte),
+            .cmp_gt  => try self.airCmp(inst, .gt),
+            .cmp_neq => try self.airCmp(inst, .neq),
+
+            .bool_and => try self.airBoolOp(inst),
+            .bool_or  => try self.airBoolOp(inst),
+            .bit_and  => try self.airBitAnd(inst),
+            .bit_or   => try self.airBitOr(inst),
+            .xor      => try self.airXor(inst),
+            .shr      => try self.airShr(inst),
+
+            .alloc           => try self.airAlloc(inst),
+            .ret_ptr         => try self.airRetPtr(inst),
+            .arg             => try self.airArg(inst),
+            .assembly        => try self.airAsm(inst),
+            .bitcast         => try self.airBitCast(inst),
+            .block           => try self.airBlock(inst),
+            .br              => try self.airBr(inst),
+            .breakpoint      => try self.airBreakpoint(),
+            .ret_addr        => try self.airRetAddr(),
+            .fence           => try self.airFence(),
+            .call            => try self.airCall(inst),
+            .cond_br         => try self.airCondBr(inst),
+            .dbg_stmt        => try self.airDbgStmt(inst),
+            .fptrunc         => try self.airFptrunc(inst),
+            .fpext           => try self.airFpext(inst),
+            .intcast         => try self.airIntCast(inst),
+            .trunc           => try self.airTrunc(inst),
+            .bool_to_int     => try self.airBoolToInt(inst),
+            .is_non_null     => try self.airIsNonNull(inst),
+            .is_non_null_ptr => try self.airIsNonNullPtr(inst),
+            .is_null         => try self.airIsNull(inst),
+            .is_null_ptr     => try self.airIsNullPtr(inst),
+            .is_non_err      => try self.airIsNonErr(inst),
+            .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
+            .is_err          => try self.airIsErr(inst),
+            .is_err_ptr      => try self.airIsErrPtr(inst),
+            .load            => try self.airLoad(inst),
+            .loop            => try self.airLoop(inst),
+            .not             => try self.airNot(inst),
+            .ptrtoint        => try self.airPtrToInt(inst),
+            .ret             => try self.airRet(inst),
+            .ret_load        => try self.airRetLoad(inst),
+            .store           => try self.airStore(inst),
+            .struct_field_ptr=> try self.airStructFieldPtr(inst),
+            .struct_field_val=> try self.airStructFieldVal(inst),
+            .array_to_slice  => try self.airArrayToSlice(inst),
+            .int_to_float    => try self.airIntToFloat(inst),
+            .float_to_int    => try self.airFloatToInt(inst),
+            .cmpxchg_strong  => try self.airCmpxchg(inst),
+            .cmpxchg_weak    => try self.airCmpxchg(inst),
+            .atomic_rmw      => try self.airAtomicRmw(inst),
+            .atomic_load     => try self.airAtomicLoad(inst),
+            .memcpy          => try self.airMemcpy(inst),
+            .memset          => try self.airMemset(inst),
+            .set_union_tag   => try self.airSetUnionTag(inst),
+            .get_union_tag   => try self.airGetUnionTag(inst),
+            .clz             => try self.airClz(inst),
+            .ctz             => try self.airCtz(inst),
+            .popcount        => try self.airPopcount(inst),
+            .tag_name        => try self.airTagName(inst),
+
+            .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
+            .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
+            .atomic_store_release   => try self.airAtomicStore(inst, .Release),
+            .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
+
+            .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
+            .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
+            .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
+            .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
+
+            .switch_br       => try self.airSwitch(inst),
+            .slice_ptr       => try self.airSlicePtr(inst),
+            .slice_len       => try self.airSliceLen(inst),
+
+            .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
+            .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
+
+            .array_elem_val      => try self.airArrayElemVal(inst),
+            .slice_elem_val      => try self.airSliceElemVal(inst),
+            .slice_elem_ptr      => try self.airSliceElemPtr(inst),
+            .ptr_elem_val        => try self.airPtrElemVal(inst),
+            .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
+
+            .constant => unreachable, // excluded from function bodies
+            .const_ty => unreachable, // excluded from function bodies
+            .unreach  => self.finishAirBookkeeping(),
+
+            .optional_payload           => try self.airOptionalPayload(inst),
+            .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
+            .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
+            .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
+            .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
+            .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
+            .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
+
+            .wrap_optional         => try self.airWrapOptional(inst),
+            .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
+            .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
+            // zig fmt: on
         }
         if (std.debug.runtime_safety) {
             if (self.air_bookkeeping < old_air_bookkeeping + 1) {
@@ -3174,6 +3175,16 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+    const un_op = self.air.instructions.items(.data)[inst].un_op;
+    const operand = try self.resolveInst(un_op);
+    const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
+        _ = operand;
+        return self.fail("TODO implement airTagName for x86_64", .{});
+    };
+    return self.finishAir(inst, result, .{ un_op, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/codegen/llvm/bindings.zig
@@ -785,8 +785,23 @@ pub const Builder = opaque {
 
     pub const buildExactSDiv = LLVMBuildExactSDiv;
     extern fn LLVMBuildExactSDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+    pub const zigSetCurrentDebugLocation = ZigLLVMSetCurrentDebugLocation;
+    extern fn ZigLLVMSetCurrentDebugLocation(builder: *const Builder, line: c_int, column: c_int, scope: *DIScope) void;
+
+    pub const clearCurrentDebugLocation = ZigLLVMClearCurrentDebugLocation;
+    extern fn ZigLLVMClearCurrentDebugLocation(builder: *const Builder) void;
+
+    pub const getCurrentDebugLocation2 = LLVMGetCurrentDebugLocation2;
+    extern fn LLVMGetCurrentDebugLocation2(Builder: *const Builder) *Metadata;
+
+    pub const setCurrentDebugLocation2 = LLVMSetCurrentDebugLocation2;
+    extern fn LLVMSetCurrentDebugLocation2(Builder: *const Builder, Loc: *Metadata) void;
 };
 
+pub const DIScope = opaque {};
+pub const Metadata = opaque {};
+
 pub const IntPredicate = enum(c_uint) {
     EQ = 32,
     NE = 33,
src/codegen/c.zig
@@ -1230,6 +1230,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .clz              => try airBuiltinCall(f, inst, "clz"),
             .ctz              => try airBuiltinCall(f, inst, "ctz"),
             .popcount         => try airBuiltinCall(f, inst, "popcount"),
+            .tag_name         => try airTagName(f, inst),
 
             .int_to_float,
             .float_to_int,
@@ -2914,6 +2915,24 @@ fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
+fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst)) return CValue.none;
+
+    const un_op = f.air.instructions.items(.data)[inst].un_op;
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const operand = try f.resolveInst(un_op);
+    const local = try f.allocLocal(inst_ty, .Const);
+
+    try writer.writeAll(" = ");
+
+    _ = operand;
+    _ = local;
+    return f.fail("TODO: C backend: implement airTagName", .{});
+    //try writer.writeAll(";\n");
+    //return local;
+}
+
 fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
     return switch (order) {
         .Unordered => "memory_order_relaxed",
src/codegen/llvm.zig
@@ -636,15 +636,6 @@ pub const DeclGen = struct {
             llvm_param_i += 1;
         }
 
-        if (dg.module.comp.bin_file.options.skip_linker_dependencies) {
-            // The intent here is for compiler-rt and libc functions to not generate
-            // infinite recursion. For example, if we are compiling the memcpy function,
-            // and llvm detects that the body is equivalent to memcpy, it may replace the
-            // body of memcpy with a call to memcpy, which would then cause a stack
-            // overflow instead of performing memcpy.
-            dg.addFnAttr(llvm_fn, "nobuiltin");
-        }
-
         // TODO: more attributes. see codegen.cpp `make_fn_llvm_value`.
         if (fn_info.cc == .Naked) {
             dg.addFnAttr(llvm_fn, "naked");
@@ -653,6 +644,16 @@ pub const DeclGen = struct {
         }
 
         // Function attributes that are independent of analysis results of the function body.
+        dg.addCommonFnAttributes(llvm_fn);
+
+        if (return_type.isNoReturn()) {
+            dg.addFnAttr(llvm_fn, "noreturn");
+        }
+
+        return llvm_fn;
+    }
+
+    fn addCommonFnAttributes(dg: *DeclGen, llvm_fn: *const llvm.Value) void {
         if (!dg.module.comp.bin_file.options.red_zone) {
             dg.addFnAttr(llvm_fn, "noredzone");
         }
@@ -665,6 +666,14 @@ pub const DeclGen = struct {
         if (dg.module.comp.unwind_tables) {
             dg.addFnAttr(llvm_fn, "uwtable");
         }
+        if (dg.module.comp.bin_file.options.skip_linker_dependencies) {
+            // The intent here is for compiler-rt and libc functions to not generate
+            // infinite recursion. For example, if we are compiling the memcpy function,
+            // and llvm detects that the body is equivalent to memcpy, it may replace the
+            // body of memcpy with a call to memcpy, which would then cause a stack
+            // overflow instead of performing memcpy.
+            dg.addFnAttr(llvm_fn, "nobuiltin");
+        }
         if (dg.module.comp.bin_file.options.optimize_mode == .ReleaseSmall) {
             dg.addFnAttr(llvm_fn, "minsize");
             dg.addFnAttr(llvm_fn, "optsize");
@@ -673,11 +682,6 @@ pub const DeclGen = struct {
             dg.addFnAttr(llvm_fn, "sanitize_thread");
         }
         // TODO add target-cpu and target-features fn attributes
-        if (return_type.isNoReturn()) {
-            dg.addFnAttr(llvm_fn, "noreturn");
-        }
-
-        return llvm_fn;
     }
 
     fn resolveGlobalDecl(dg: *DeclGen, decl: *Module.Decl) Error!*const llvm.Value {
@@ -1958,6 +1962,7 @@ pub const FuncGen = struct {
                 .clz            => try self.airClzCtz(inst, "ctlz"),
                 .ctz            => try self.airClzCtz(inst, "cttz"),
                 .popcount       => try self.airPopCount(inst, "ctpop"),
+                .tag_name       => try self.airTagName(inst),
 
                 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
                 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -4093,6 +4098,119 @@ pub const FuncGen = struct {
         }
     }
 
+    fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst)) return null;
+
+        var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
+        defer arena_allocator.deinit();
+        const arena = arena_allocator.allocator();
+
+        const un_op = self.air.instructions.items(.data)[inst].un_op;
+        const operand = try self.resolveInst(un_op);
+        const enum_ty = self.air.typeOf(un_op);
+
+        const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{
+            try enum_ty.getOwnerDecl().getFullyQualifiedName(arena),
+        });
+
+        const llvm_fn = try self.getEnumTagNameFunction(enum_ty, llvm_fn_name);
+        const params = [_]*const llvm.Value{operand};
+        return self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, "");
+    }
+
+    fn getEnumTagNameFunction(
+        self: *FuncGen,
+        enum_ty: Type,
+        llvm_fn_name: [:0]const u8,
+    ) !*const llvm.Value {
+        // TODO: detect when the type changes and re-emit this function.
+        if (self.dg.object.llvm_module.getNamedFunction(llvm_fn_name)) |llvm_fn| {
+            return llvm_fn;
+        }
+
+        const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
+        const llvm_ret_ty = try self.dg.llvmType(slice_ty);
+        const usize_llvm_ty = try self.dg.llvmType(Type.usize);
+        const target = self.dg.module.getTarget();
+        const slice_alignment = slice_ty.abiAlignment(target);
+
+        var int_tag_type_buffer: Type.Payload.Bits = undefined;
+        const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
+        const param_types = [_]*const llvm.Type{try self.dg.llvmType(int_tag_ty)};
+
+        const fn_type = llvm.functionType(llvm_ret_ty, &param_types, param_types.len, .False);
+        const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
+        fn_val.setLinkage(.Internal);
+        fn_val.setFunctionCallConv(.Fast);
+        self.dg.addCommonFnAttributes(fn_val);
+
+        const prev_block = self.builder.getInsertBlock();
+        const prev_debug_location = self.builder.getCurrentDebugLocation2();
+        defer {
+            self.builder.positionBuilderAtEnd(prev_block);
+            if (!self.dg.module.comp.bin_file.options.strip) {
+                self.builder.setCurrentDebugLocation2(prev_debug_location);
+            }
+        }
+
+        const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry");
+        self.builder.positionBuilderAtEnd(entry_block);
+        self.builder.clearCurrentDebugLocation();
+
+        const fields = enum_ty.enumFields();
+        const bad_value_block = self.dg.context.appendBasicBlock(fn_val, "BadValue");
+        const tag_int_value = fn_val.getParam(0);
+        const switch_instr = self.builder.buildSwitch(tag_int_value, bad_value_block, @intCast(c_uint, fields.count()));
+
+        const array_ptr_indices = [_]*const llvm.Value{
+            usize_llvm_ty.constNull(), usize_llvm_ty.constNull(),
+        };
+
+        for (fields.keys()) |name, field_index| {
+            const str_init = self.dg.context.constString(name.ptr, @intCast(c_uint, name.len), .False);
+            const str_global = self.dg.object.llvm_module.addGlobal(str_init.typeOf(), "");
+            str_global.setInitializer(str_init);
+            str_global.setLinkage(.Private);
+            str_global.setGlobalConstant(.True);
+            str_global.setUnnamedAddr(.True);
+            str_global.setAlignment(1);
+
+            const slice_fields = [_]*const llvm.Value{
+                str_global.constInBoundsGEP(&array_ptr_indices, array_ptr_indices.len),
+                usize_llvm_ty.constInt(name.len, .False),
+            };
+            const slice_init = llvm_ret_ty.constNamedStruct(&slice_fields, slice_fields.len);
+            const slice_global = self.dg.object.llvm_module.addGlobal(slice_init.typeOf(), "");
+            slice_global.setInitializer(slice_init);
+            slice_global.setLinkage(.Private);
+            slice_global.setGlobalConstant(.True);
+            slice_global.setUnnamedAddr(.True);
+            slice_global.setAlignment(slice_alignment);
+
+            const return_block = self.dg.context.appendBasicBlock(fn_val, "Name");
+            const this_tag_int_value = int: {
+                var tag_val_payload: Value.Payload.U32 = .{
+                    .base = .{ .tag = .enum_field_index },
+                    .data = @intCast(u32, field_index),
+                };
+                break :int try self.dg.genTypedValue(.{
+                    .ty = enum_ty,
+                    .val = Value.initPayload(&tag_val_payload.base),
+                });
+            };
+            switch_instr.addCase(this_tag_int_value, return_block);
+
+            self.builder.positionBuilderAtEnd(return_block);
+            const loaded = self.builder.buildLoad(slice_global, "");
+            loaded.setAlignment(slice_alignment);
+            _ = self.builder.buildRet(loaded);
+        }
+
+        self.builder.positionBuilderAtEnd(bad_value_block);
+        _ = self.builder.buildUnreachable();
+        return fn_val;
+    }
+
     /// Assumes the optional is not pointer-like and payload has bits.
     fn optIsNonNull(self: *FuncGen, opt_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value {
         if (is_by_ref) {
src/Air.zig
@@ -496,6 +496,11 @@ pub const Inst = struct {
         /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`.
         atomic_rmw,
 
+        /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive.
+        /// Result type is always `[:0]const u8`.
+        /// Uses the `un_op` field.
+        tag_name,
+
         pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
             return switch (op) {
                 .lt => .cmp_lt,
@@ -811,6 +816,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
 
         .bool_to_int => return Type.initTag(.u1),
 
+        .tag_name => return Type.initTag(.const_slice_u8_sentinel_0),
+
         .call => {
             const callee_ty = air.typeOf(datas[inst].pl_op.operand);
             switch (callee_ty.zigTypeTag()) {
src/Liveness.zig
@@ -333,6 +333,7 @@ fn analyzeInst(
         .bool_to_int,
         .ret,
         .ret_load,
+        .tag_name,
         => {
             const operand = inst_datas[inst].un_op;
             return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
src/print_air.zig
@@ -155,6 +155,7 @@ const Writer = struct {
             .bool_to_int,
             .ret,
             .ret_load,
+            .tag_name,
             => try w.writeUnOp(s, inst),
 
             .breakpoint,
src/Sema.zig
@@ -2804,8 +2804,11 @@ fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
     const tracy = trace(@src());
     defer tracy.end();
 
-    const zir_bytes = sema.code.instructions.items(.data)[inst].str.get(sema.code);
+    const bytes = sema.code.instructions.items(.data)[inst].str.get(sema.code);
+    return sema.addStrLit(block, bytes);
+}
 
+fn addStrLit(sema: *Sema, block: *Block, zir_bytes: []const u8) CompileError!Air.Inst.Ref {
     // `zir_bytes` references memory inside the ZIR module, which can get deallocated
     // after semantic analysis is complete, for example in the case of the initialization
     // expression of a variable declaration. We need the memory to be in the new
@@ -10045,8 +10048,50 @@ fn zirUnaryMath(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
 fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+    const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
     const src = inst_data.src();
-    return sema.fail(block, src, "TODO: Sema.zirTagName", .{});
+    const operand = sema.resolveInst(inst_data.operand);
+    const operand_ty = sema.typeOf(operand);
+
+    const enum_ty = switch (operand_ty.zigTypeTag()) {
+        .Enum => operand_ty,
+        .Union => operand_ty.unionTagType() orelse {
+            const decl = operand_ty.getOwnerDecl();
+            const msg = msg: {
+                const msg = try sema.errMsg(block, src, "union '{s}' is untagged", .{
+                    decl.name,
+                });
+                errdefer msg.destroy(sema.gpa);
+                try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "declared here", .{});
+                break :msg msg;
+            };
+            return sema.failWithOwnedErrorMsg(msg);
+        },
+        else => return sema.fail(block, operand_src, "expected enum or union; found {}", .{
+            operand_ty,
+        }),
+    };
+    const enum_decl = enum_ty.getOwnerDecl();
+    const casted_operand = try sema.coerce(block, enum_ty, operand, operand_src);
+    if (try sema.resolveDefinedValue(block, operand_src, casted_operand)) |val| {
+        const field_index = enum_ty.enumTagFieldIndex(val) orelse {
+            const msg = msg: {
+                const msg = try sema.errMsg(block, src, "no field with value {} in enum '{s}'", .{
+                    casted_operand, enum_decl.name,
+                });
+                errdefer msg.destroy(sema.gpa);
+                try sema.mod.errNoteNonLazy(enum_decl.srcLoc(), msg, "declared here", .{});
+                break :msg msg;
+            };
+            return sema.failWithOwnedErrorMsg(msg);
+        };
+        const field_name = enum_ty.enumFieldName(field_index);
+        return sema.addStrLit(block, field_name);
+    }
+    // In case the value is runtime-known, we have an AIR instruction for this instead
+    // of trying to lower it in Sema because an optimization pass may result in the operand
+    // being comptime-known, which would let us elide the `tag_name` AIR instruction.
+    return block.addUnOp(.tag_name, casted_operand);
 }
 
 fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -15339,6 +15384,7 @@ fn typeHasOnePossibleValue(
         .array_sentinel,
         .array_u8_sentinel_0,
         .const_slice_u8,
+        .const_slice_u8_sentinel_0,
         .const_slice,
         .mut_slice,
         .anyopaque,
@@ -15356,6 +15402,7 @@ fn typeHasOnePossibleValue(
         .var_args_param,
         .manyptr_u8,
         .manyptr_const_u8,
+        .manyptr_const_u8_sentinel_0,
         .atomic_order,
         .atomic_rmw_op,
         .calling_convention,
src/type.zig
@@ -94,6 +94,7 @@ pub const Type = extern union {
 
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .single_const_pointer,
             .single_mut_pointer,
             .many_const_pointer,
@@ -107,6 +108,7 @@ pub const Type = extern union {
             .inferred_alloc_mut,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => return .Pointer,
 
             .optional,
@@ -254,6 +256,7 @@ pub const Type = extern union {
             .optional_single_mut_pointer,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => self.cast(Payload.ElemType),
 
             .inferred_alloc_const => unreachable,
@@ -275,9 +278,11 @@ pub const Type = extern union {
         return switch (ty.tag()) {
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .single_const_pointer,
             .many_const_pointer,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .c_const_pointer,
             .const_slice,
             => false,
@@ -330,6 +335,18 @@ pub const Type = extern union {
                 .@"volatile" = false,
                 .size = .Slice,
             } },
+            .const_slice_u8_sentinel_0 => return .{ .data = .{
+                .pointee_type = Type.initTag(.u8),
+                .sentinel = Value.zero,
+                .@"align" = 0,
+                .@"addrspace" = .generic,
+                .bit_offset = 0,
+                .host_size = 0,
+                .@"allowzero" = false,
+                .mutable = false,
+                .@"volatile" = false,
+                .size = .Slice,
+            } },
             .single_const_pointer => return .{ .data = .{
                 .pointee_type = self.castPointer().?.data,
                 .sentinel = null,
@@ -378,6 +395,18 @@ pub const Type = extern union {
                 .@"volatile" = false,
                 .size = .Many,
             } },
+            .manyptr_const_u8_sentinel_0 => return .{ .data = .{
+                .pointee_type = Type.initTag(.u8),
+                .sentinel = Value.zero,
+                .@"align" = 0,
+                .@"addrspace" = .generic,
+                .bit_offset = 0,
+                .host_size = 0,
+                .@"allowzero" = false,
+                .mutable = false,
+                .@"volatile" = false,
+                .size = .Many,
+            } },
             .many_mut_pointer => return .{ .data = .{
                 .pointee_type = self.castPointer().?.data,
                 .sentinel = null,
@@ -784,6 +813,7 @@ pub const Type = extern union {
             .fn_ccc_void_no_args,
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .enum_literal,
             .anyerror_void_error_union,
             .inferred_alloc_const,
@@ -792,6 +822,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .atomic_order,
             .atomic_rmw_op,
             .calling_convention,
@@ -1016,6 +1047,7 @@ pub const Type = extern union {
 
                 .anyerror_void_error_union => return writer.writeAll("anyerror!void"),
                 .const_slice_u8 => return writer.writeAll("[]const u8"),
+                .const_slice_u8_sentinel_0 => return writer.writeAll("[:0]const u8"),
                 .fn_noreturn_no_args => return writer.writeAll("fn() noreturn"),
                 .fn_void_no_args => return writer.writeAll("fn() void"),
                 .fn_naked_noreturn_no_args => return writer.writeAll("fn() callconv(.Naked) noreturn"),
@@ -1023,6 +1055,7 @@ pub const Type = extern union {
                 .single_const_pointer_to_comptime_int => return writer.writeAll("*const comptime_int"),
                 .manyptr_u8 => return writer.writeAll("[*]u8"),
                 .manyptr_const_u8 => return writer.writeAll("[*]const u8"),
+                .manyptr_const_u8_sentinel_0 => return writer.writeAll("[*:0]const u8"),
                 .atomic_order => return writer.writeAll("std.builtin.AtomicOrder"),
                 .atomic_rmw_op => return writer.writeAll("std.builtin.AtomicRmwOp"),
                 .calling_convention => return writer.writeAll("std.builtin.CallingConvention"),
@@ -1308,6 +1341,7 @@ pub const Type = extern union {
 
             .anyerror_void_error_union => return "anyerror!void",
             .const_slice_u8 => return "[]const u8",
+            .const_slice_u8_sentinel_0 => return "[:0]const u8",
             .fn_noreturn_no_args => return "fn() noreturn",
             .fn_void_no_args => return "fn() void",
             .fn_naked_noreturn_no_args => return "fn() callconv(.Naked) noreturn",
@@ -1315,6 +1349,7 @@ pub const Type = extern union {
             .single_const_pointer_to_comptime_int => return "*const comptime_int",
             .manyptr_u8 => return "[*]u8",
             .manyptr_const_u8 => return "[*]const u8",
+            .manyptr_const_u8_sentinel_0 => return "[*:0]const u8",
             .atomic_order => return "AtomicOrder",
             .atomic_rmw_op => return "AtomicRmwOp",
             .calling_convention => return "CallingConvention",
@@ -1386,11 +1421,13 @@ pub const Type = extern union {
             .extern_options,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .fn_noreturn_no_args,
             .fn_void_no_args,
             .fn_naked_noreturn_no_args,
             .fn_ccc_void_no_args,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .anyerror_void_error_union,
             .empty_struct_literal,
             .function,
@@ -1498,9 +1535,11 @@ pub const Type = extern union {
             .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type),
             .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type),
             .const_slice_u8 => return Value.initTag(.const_slice_u8_type),
+            .const_slice_u8_sentinel_0 => return Value.initTag(.const_slice_u8_sentinel_0_type),
             .enum_literal => return Value.initTag(.enum_literal_type),
             .manyptr_u8 => return Value.initTag(.manyptr_u8_type),
             .manyptr_const_u8 => return Value.initTag(.manyptr_const_u8_type),
+            .manyptr_const_u8_sentinel_0 => return Value.initTag(.manyptr_const_u8_sentinel_0_type),
             .atomic_order => return Value.initTag(.atomic_order_type),
             .atomic_rmw_op => return Value.initTag(.atomic_rmw_op_type),
             .calling_convention => return Value.initTag(.calling_convention_type),
@@ -1550,6 +1589,7 @@ pub const Type = extern union {
             .anyerror,
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .array_u8_sentinel_0,
             .optional,
             .optional_single_mut_pointer,
@@ -1561,6 +1601,7 @@ pub const Type = extern union {
             .error_set_merged,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .atomic_order,
             .atomic_rmw_op,
             .calling_convention,
@@ -1703,7 +1744,9 @@ pub const Type = extern union {
 
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             => return 1,
 
             .pointer => {
@@ -1723,6 +1766,7 @@ pub const Type = extern union {
         return switch (self.tag()) {
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .single_const_pointer,
             .single_mut_pointer,
             .many_const_pointer,
@@ -1735,6 +1779,7 @@ pub const Type = extern union {
             .inferred_alloc_mut,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => .generic,
 
             .pointer => self.castTag(.pointer).?.data.@"addrspace",
@@ -1785,6 +1830,7 @@ pub const Type = extern union {
             .usize,
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .single_const_pointer,
             .single_mut_pointer,
             .many_const_pointer,
@@ -1798,6 +1844,7 @@ pub const Type = extern union {
             .pointer,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .@"anyframe",
             .anyframe_T,
             => return @divExact(target.cpu.arch.ptrBitWidth(), 8),
@@ -2050,7 +2097,9 @@ pub const Type = extern union {
                 if (self.elemType().hasCodeGenBits()) return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2;
                 return @divExact(target.cpu.arch.ptrBitWidth(), 8);
             },
-            .const_slice_u8 => return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2,
+            .const_slice_u8,
+            .const_slice_u8_sentinel_0,
+            => return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2,
 
             .optional_single_const_pointer,
             .optional_single_mut_pointer,
@@ -2068,6 +2117,7 @@ pub const Type = extern union {
             .pointer,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => return @divExact(target.cpu.arch.ptrBitWidth(), 8),
 
             .c_short => return @divExact(CType.short.sizeInBits(target), 8),
@@ -2223,7 +2273,9 @@ pub const Type = extern union {
                     return target.cpu.arch.ptrBitWidth();
                 }
             },
-            .const_slice_u8 => target.cpu.arch.ptrBitWidth() * 2,
+            .const_slice_u8,
+            .const_slice_u8_sentinel_0,
+            => target.cpu.arch.ptrBitWidth() * 2,
 
             .optional_single_const_pointer,
             .optional_single_mut_pointer,
@@ -2252,6 +2304,7 @@ pub const Type = extern union {
 
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => return target.cpu.arch.ptrBitWidth(),
 
             .c_short => return CType.short.sizeInBits(target),
@@ -2337,12 +2390,14 @@ pub const Type = extern union {
             .const_slice,
             .mut_slice,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             => .Slice,
 
             .many_const_pointer,
             .many_mut_pointer,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => .Many,
 
             .c_const_pointer,
@@ -2367,6 +2422,7 @@ pub const Type = extern union {
             .const_slice,
             .mut_slice,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             => true,
 
             .pointer => self.castTag(.pointer).?.data.size == .Slice,
@@ -2383,6 +2439,7 @@ pub const Type = extern union {
     pub fn slicePtrFieldType(self: Type, buffer: *SlicePtrFieldTypeBuffer) Type {
         switch (self.tag()) {
             .const_slice_u8 => return Type.initTag(.manyptr_const_u8),
+            .const_slice_u8_sentinel_0 => return Type.initTag(.manyptr_const_u8_sentinel_0),
 
             .const_slice => {
                 const elem_type = self.castTag(.const_slice).?.data;
@@ -2464,8 +2521,10 @@ pub const Type = extern union {
             .c_const_pointer,
             .single_const_pointer_to_comptime_int,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .const_slice,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             => true,
 
             .pointer => !self.castTag(.pointer).?.data.mutable,
@@ -2513,6 +2572,7 @@ pub const Type = extern union {
             .many_const_pointer,
             .many_mut_pointer,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .manyptr_u8,
             .optional_single_const_pointer,
             .optional_single_mut_pointer,
@@ -2648,9 +2708,11 @@ pub const Type = extern union {
             .array_u8,
             .array_u8_sentinel_0,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .manyptr_u8,
             .manyptr_const_u8,
-            => Type.initTag(.u8),
+            .manyptr_const_u8_sentinel_0,
+            => Type.u8,
 
             .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
             .pointer => ty.castTag(.pointer).?.data.pointee_type,
@@ -2690,9 +2752,11 @@ pub const Type = extern union {
             .array_u8,
             .array_u8_sentinel_0,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .manyptr_u8,
             .manyptr_const_u8,
-            => Type.initTag(.u8),
+            .manyptr_const_u8_sentinel_0,
+            => Type.u8,
 
             .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
             .pointer => {
@@ -2937,7 +3001,11 @@ pub const Type = extern union {
 
             .pointer => return self.castTag(.pointer).?.data.sentinel,
             .array_sentinel => return self.castTag(.array_sentinel).?.data.sentinel,
-            .array_u8_sentinel_0 => return Value.zero,
+
+            .array_u8_sentinel_0,
+            .const_slice_u8_sentinel_0,
+            .manyptr_const_u8_sentinel_0,
+            => return Value.zero,
 
             else => unreachable,
         };
@@ -3309,6 +3377,7 @@ pub const Type = extern union {
             .array_sentinel,
             .array_u8_sentinel_0,
             .const_slice_u8,
+            .const_slice_u8_sentinel_0,
             .const_slice,
             .mut_slice,
             .anyopaque,
@@ -3326,6 +3395,7 @@ pub const Type = extern union {
             .var_args_param,
             .manyptr_u8,
             .manyptr_const_u8,
+            .manyptr_const_u8_sentinel_0,
             .atomic_order,
             .atomic_rmw_op,
             .calling_convention,
@@ -3956,12 +4026,14 @@ pub const Type = extern union {
         type_info,
         manyptr_u8,
         manyptr_const_u8,
+        manyptr_const_u8_sentinel_0,
         fn_noreturn_no_args,
         fn_void_no_args,
         fn_naked_noreturn_no_args,
         fn_ccc_void_no_args,
         single_const_pointer_to_comptime_int,
         const_slice_u8,
+        const_slice_u8_sentinel_0,
         anyerror_void_error_union,
         generic_poison,
         /// This is a special type for variadic parameters of a function call.
@@ -4064,6 +4136,7 @@ pub const Type = extern union {
                 .single_const_pointer_to_comptime_int,
                 .anyerror_void_error_union,
                 .const_slice_u8,
+                .const_slice_u8_sentinel_0,
                 .generic_poison,
                 .inferred_alloc_const,
                 .inferred_alloc_mut,
@@ -4071,6 +4144,7 @@ pub const Type = extern union {
                 .empty_struct_literal,
                 .manyptr_u8,
                 .manyptr_const_u8,
+                .manyptr_const_u8_sentinel_0,
                 .atomic_order,
                 .atomic_rmw_op,
                 .calling_convention,
@@ -4322,36 +4396,55 @@ pub const Type = extern union {
 
     pub fn ptr(arena: Allocator, d: Payload.Pointer.Data) !Type {
         assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
+        if (d.size == .C) {
+            assert(d.@"allowzero"); // All C pointers must set allowzero to true.
+        }
 
-        if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or
-            d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile")
+        if (d.@"align" == 0 and d.@"addrspace" == .generic and
+            d.bit_offset == 0 and d.host_size == 0 and !d.@"allowzero" and !d.@"volatile")
         {
-            if (d.size == .C) {
-                assert(d.@"allowzero"); // All C pointers must set allowzero to true.
+            if (d.sentinel) |sent| {
+                if (!d.mutable and d.pointee_type.eql(Type.u8)) {
+                    switch (d.size) {
+                        .Slice => {
+                            if (sent.compareWithZero(.eq)) {
+                                return Type.initTag(.const_slice_u8_sentinel_0);
+                            }
+                        },
+                        .Many => {
+                            if (sent.compareWithZero(.eq)) {
+                                return Type.initTag(.manyptr_const_u8_sentinel_0);
+                            }
+                        },
+                        else => {},
+                    }
+                }
+            } else if (!d.mutable and d.pointee_type.eql(Type.u8)) {
+                switch (d.size) {
+                    .Slice => return Type.initTag(.const_slice_u8),
+                    .Many => return Type.initTag(.manyptr_const_u8),
+                    else => {},
+                }
+            } else {
+                // TODO stage1 type inference bug
+                const T = Type.Tag;
+
+                const type_payload = try arena.create(Type.Payload.ElemType);
+                type_payload.* = .{
+                    .base = .{
+                        .tag = switch (d.size) {
+                            .One => if (d.mutable) T.single_mut_pointer else T.single_const_pointer,
+                            .Many => if (d.mutable) T.many_mut_pointer else T.many_const_pointer,
+                            .C => if (d.mutable) T.c_mut_pointer else T.c_const_pointer,
+                            .Slice => if (d.mutable) T.mut_slice else T.const_slice,
+                        },
+                    },
+                    .data = d.pointee_type,
+                };
+                return Type.initPayload(&type_payload.base);
             }
-            return Type.Tag.pointer.create(arena, d);
-        }
-
-        if (!d.mutable and d.size == .Slice and d.pointee_type.eql(Type.initTag(.u8))) {
-            return Type.initTag(.const_slice_u8);
         }
-
-        // TODO stage1 type inference bug
-        const T = Type.Tag;
-
-        const type_payload = try arena.create(Type.Payload.ElemType);
-        type_payload.* = .{
-            .base = .{
-                .tag = switch (d.size) {
-                    .One => if (d.mutable) T.single_mut_pointer else T.single_const_pointer,
-                    .Many => if (d.mutable) T.many_mut_pointer else T.many_const_pointer,
-                    .C => if (d.mutable) T.c_mut_pointer else T.c_const_pointer,
-                    .Slice => if (d.mutable) T.mut_slice else T.const_slice,
-                },
-            },
-            .data = d.pointee_type,
-        };
-        return Type.initPayload(&type_payload.base);
+        return Type.Tag.pointer.create(arena, d);
     }
 
     pub fn array(
src/value.zig
@@ -73,12 +73,14 @@ pub const Value = extern union {
         type_info_type,
         manyptr_u8_type,
         manyptr_const_u8_type,
+        manyptr_const_u8_sentinel_0_type,
         fn_noreturn_no_args_type,
         fn_void_no_args_type,
         fn_naked_noreturn_no_args_type,
         fn_ccc_void_no_args_type,
         single_const_pointer_to_comptime_int_type,
         const_slice_u8_type,
+        const_slice_u8_sentinel_0_type,
         anyerror_void_error_union_type,
         generic_poison_type,
 
@@ -221,6 +223,7 @@ pub const Value = extern union {
                 .single_const_pointer_to_comptime_int_type,
                 .anyframe_type,
                 .const_slice_u8_type,
+                .const_slice_u8_sentinel_0_type,
                 .anyerror_void_error_union_type,
                 .generic_poison_type,
                 .enum_literal_type,
@@ -238,6 +241,7 @@ pub const Value = extern union {
                 .abi_align_default,
                 .manyptr_u8_type,
                 .manyptr_const_u8_type,
+                .manyptr_const_u8_sentinel_0_type,
                 .atomic_order_type,
                 .atomic_rmw_op_type,
                 .calling_convention_type,
@@ -412,6 +416,7 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type,
             .anyframe_type,
             .const_slice_u8_type,
+            .const_slice_u8_sentinel_0_type,
             .anyerror_void_error_union_type,
             .generic_poison_type,
             .enum_literal_type,
@@ -429,6 +434,7 @@ pub const Value = extern union {
             .abi_align_default,
             .manyptr_u8_type,
             .manyptr_const_u8_type,
+            .manyptr_const_u8_sentinel_0_type,
             .atomic_order_type,
             .atomic_rmw_op_type,
             .calling_convention_type,
@@ -642,12 +648,14 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
             .anyframe_type => return out_stream.writeAll("anyframe"),
             .const_slice_u8_type => return out_stream.writeAll("[]const u8"),
+            .const_slice_u8_sentinel_0_type => return out_stream.writeAll("[:0]const u8"),
             .anyerror_void_error_union_type => return out_stream.writeAll("anyerror!void"),
             .generic_poison_type => return out_stream.writeAll("(generic poison type)"),
             .generic_poison => return out_stream.writeAll("(generic poison)"),
             .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
             .manyptr_u8_type => return out_stream.writeAll("[*]u8"),
             .manyptr_const_u8_type => return out_stream.writeAll("[*]const u8"),
+            .manyptr_const_u8_sentinel_0_type => return out_stream.writeAll("[*:0]const u8"),
             .atomic_order_type => return out_stream.writeAll("std.builtin.AtomicOrder"),
             .atomic_rmw_op_type => return out_stream.writeAll("std.builtin.AtomicRmwOp"),
             .calling_convention_type => return out_stream.writeAll("std.builtin.CallingConvention"),
@@ -821,11 +829,13 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
             .anyframe_type => Type.initTag(.@"anyframe"),
             .const_slice_u8_type => Type.initTag(.const_slice_u8),
+            .const_slice_u8_sentinel_0_type => Type.initTag(.const_slice_u8_sentinel_0),
             .anyerror_void_error_union_type => Type.initTag(.anyerror_void_error_union),
             .generic_poison_type => Type.initTag(.generic_poison),
             .enum_literal_type => Type.initTag(.enum_literal),
             .manyptr_u8_type => Type.initTag(.manyptr_u8),
             .manyptr_const_u8_type => Type.initTag(.manyptr_const_u8),
+            .manyptr_const_u8_sentinel_0_type => Type.initTag(.manyptr_const_u8_sentinel_0),
             .atomic_order_type => Type.initTag(.atomic_order),
             .atomic_rmw_op_type => Type.initTag(.atomic_rmw_op),
             .calling_convention_type => Type.initTag(.calling_convention),
test/behavior/enum.zig
@@ -699,3 +699,126 @@ test "single field non-exhaustive enum" {
     try S.doTheTest(23);
     comptime try S.doTheTest(23);
 }
+
+const EnumWithTagValues = enum(u4) {
+    A = 1 << 0,
+    B = 1 << 1,
+    C = 1 << 2,
+    D = 1 << 3,
+};
+test "enum with tag values don't require parens" {
+    try expect(@enumToInt(EnumWithTagValues.C) == 0b0100);
+}
+
+const MultipleChoice2 = enum(u32) {
+    Unspecified1,
+    A = 20,
+    Unspecified2,
+    B = 40,
+    Unspecified3,
+    C = 60,
+    Unspecified4,
+    D = 1000,
+    Unspecified5,
+};
+
+test "cast integer literal to enum" {
+    try expect(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1);
+    try expect(@intToEnum(MultipleChoice2, 40) == MultipleChoice2.B);
+}
+
+test "enum with specified and unspecified tag values" {
+    try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
+    comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
+}
+
+fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
+    try expect(@enumToInt(x) == 1000);
+    try expect(1234 == switch (x) {
+        MultipleChoice2.A => 1,
+        MultipleChoice2.B => 2,
+        MultipleChoice2.C => 3,
+        MultipleChoice2.D => @as(u32, 1234),
+        MultipleChoice2.Unspecified1 => 5,
+        MultipleChoice2.Unspecified2 => 6,
+        MultipleChoice2.Unspecified3 => 7,
+        MultipleChoice2.Unspecified4 => 8,
+        MultipleChoice2.Unspecified5 => 9,
+    });
+}
+
+const Small2 = enum(u2) { One, Two };
+const Small = enum(u2) { One, Two, Three, Four };
+
+test "set enum tag type" {
+    {
+        var x = Small.One;
+        x = Small.Two;
+        comptime try expect(Tag(Small) == u2);
+    }
+    {
+        var x = Small2.One;
+        x = Small2.Two;
+        comptime try expect(Tag(Small2) == u2);
+    }
+}
+
+test "casting enum to its tag type" {
+    try testCastEnumTag(Small2.Two);
+    comptime try testCastEnumTag(Small2.Two);
+}
+
+fn testCastEnumTag(value: Small2) !void {
+    try expect(@enumToInt(value) == 1);
+}
+
+test "enum with 1 field but explicit tag type should still have the tag type" {
+    const Enum = enum(u8) {
+        B = 2,
+    };
+    comptime try expect(@sizeOf(Enum) == @sizeOf(u8));
+}
+
+test "signed integer as enum tag" {
+    const SignedEnum = enum(i2) {
+        A0 = -1,
+        A1 = 0,
+        A2 = 1,
+    };
+
+    try expect(@enumToInt(SignedEnum.A0) == -1);
+    try expect(@enumToInt(SignedEnum.A1) == 0);
+    try expect(@enumToInt(SignedEnum.A2) == 1);
+}
+
+test "enum with one member and custom tag type" {
+    const E = enum(u2) {
+        One,
+    };
+    try expect(@enumToInt(E.One) == 0);
+    const E2 = enum(u2) {
+        One = 2,
+    };
+    try expect(@enumToInt(E2.One) == 2);
+}
+
+test "enum with one member and u1 tag type @enumToInt" {
+    const Enum = enum(u1) {
+        Test,
+    };
+    try expect(@enumToInt(Enum.Test) == 0);
+}
+
+test "enum with comptime_int tag type" {
+    const Enum = enum(comptime_int) {
+        One = 3,
+        Two = 2,
+        Three = 1,
+    };
+    comptime try expect(Tag(Enum) == comptime_int);
+}
+
+test "enum with one member default to u0 tag type" {
+    const E0 = enum { X };
+    comptime try expect(Tag(E0) == u0);
+}
test/behavior/enum_llvm.zig
@@ -0,0 +1,49 @@
+const std = @import("std");
+const expect = std.testing.expect;
+const mem = std.mem;
+const Tag = std.meta.Tag;
+
+test "@tagName" {
+    try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
+    comptime try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
+}
+
+fn testEnumTagNameBare(n: anytype) []const u8 {
+    return @tagName(n);
+}
+
+const BareNumber = enum { One, Two, Three };
+
+test "@tagName non-exhaustive enum" {
+    try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
+    comptime try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
+}
+const NonExhaustive = enum(u8) { A, B, _ };
+
+test "@tagName is null-terminated" {
+    const S = struct {
+        fn doTheTest(n: BareNumber) !void {
+            try expect(@tagName(n)[3] == 0);
+        }
+    };
+    try S.doTheTest(.Two);
+    try comptime S.doTheTest(.Two);
+}
+
+test "tag name with assigned enum values" {
+    const LocalFoo = enum(u8) {
+        A = 1,
+        B = 0,
+    };
+    var b = LocalFoo.B;
+    try expect(mem.eql(u8, @tagName(b), "B"));
+}
+
+const Bar = enum { A, B, C, D };
+
+test "enum literal casting to optional" {
+    var bar: ?Bar = undefined;
+    bar = .B;
+
+    try expect(bar.? == Bar.B);
+}
test/behavior/enum_stage1.zig
@@ -2,47 +2,7 @@ const expect = @import("std").testing.expect;
 const mem = @import("std").mem;
 const Tag = @import("std").meta.Tag;
 
-test "@tagName" {
-    try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
-    comptime try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
-}
-
-test "@tagName non-exhaustive enum" {
-    try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
-    comptime try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
-}
-
-test "@tagName is null-terminated" {
-    const S = struct {
-        fn doTheTest(n: BareNumber) !void {
-            try expect(@tagName(n)[3] == 0);
-        }
-    };
-    try S.doTheTest(.Two);
-    try comptime S.doTheTest(.Two);
-}
-
-fn testEnumTagNameBare(n: anytype) []const u8 {
-    return @tagName(n);
-}
-
-const BareNumber = enum { One, Two, Three };
-const NonExhaustive = enum(u8) { A, B, _ };
 const Small2 = enum(u2) { One, Two };
-const Small = enum(u2) { One, Two, Three, Four };
-
-test "set enum tag type" {
-    {
-        var x = Small.One;
-        x = Small.Two;
-        comptime try expect(Tag(Small) == u2);
-    }
-    {
-        var x = Small2.One;
-        x = Small2.Two;
-        comptime try expect(Tag(Small2) == u2);
-    }
-}
 
 const A = enum(u3) { One, Two, Three, Four, One2, Two2, Three2, Four2 };
 const B = enum(u3) { One3, Two3, Three3, Four3, One23, Two23, Three23, Four23 };
@@ -87,15 +47,6 @@ fn getC(data: *const BitFieldOfEnums) C {
     return data.c;
 }
 
-test "casting enum to its tag type" {
-    try testCastEnumTag(Small2.Two);
-    comptime try testCastEnumTag(Small2.Two);
-}
-
-fn testCastEnumTag(value: Small2) !void {
-    try expect(@enumToInt(value) == 1);
-}
-
 const MultipleChoice2 = enum(u32) {
     Unspecified1,
     A = 20,
@@ -108,31 +59,6 @@ const MultipleChoice2 = enum(u32) {
     Unspecified5,
 };
 
-test "enum with specified and unspecified tag values" {
-    try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
-    comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
-}
-
-fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
-    try expect(@enumToInt(x) == 1000);
-    try expect(1234 == switch (x) {
-        MultipleChoice2.A => 1,
-        MultipleChoice2.B => 2,
-        MultipleChoice2.C => 3,
-        MultipleChoice2.D => @as(u32, 1234),
-        MultipleChoice2.Unspecified1 => 5,
-        MultipleChoice2.Unspecified2 => 6,
-        MultipleChoice2.Unspecified3 => 7,
-        MultipleChoice2.Unspecified4 => 8,
-        MultipleChoice2.Unspecified5 => 9,
-    });
-}
-
-test "cast integer literal to enum" {
-    try expect(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1);
-    try expect(@intToEnum(MultipleChoice2, 40) == MultipleChoice2.B);
-}
-
 const EnumWithOneMember = enum { Eof };
 
 fn doALoopThing(id: EnumWithOneMember) void {
@@ -157,32 +83,6 @@ test "switch on enum with one member is comptime known" {
     @compileError("analysis should not reach here");
 }
 
-const EnumWithTagValues = enum(u4) {
-    A = 1 << 0,
-    B = 1 << 1,
-    C = 1 << 2,
-    D = 1 << 3,
-};
-test "enum with tag values don't require parens" {
-    try expect(@enumToInt(EnumWithTagValues.C) == 0b0100);
-}
-
-test "enum with 1 field but explicit tag type should still have the tag type" {
-    const Enum = enum(u8) {
-        B = 2,
-    };
-    comptime try expect(@sizeOf(Enum) == @sizeOf(u8));
-}
-
-test "tag name with assigned enum values" {
-    const LocalFoo = enum(u8) {
-        A = 1,
-        B = 0,
-    };
-    var b = LocalFoo.B;
-    try expect(mem.eql(u8, @tagName(b), "B"));
-}
-
 test "enum literal in array literal" {
     const Items = enum { one, two };
     const array = [_]Items{ .one, .two };
@@ -191,18 +91,6 @@ test "enum literal in array literal" {
     try expect(array[1] == .two);
 }
 
-test "signed integer as enum tag" {
-    const SignedEnum = enum(i2) {
-        A0 = -1,
-        A1 = 0,
-        A2 = 1,
-    };
-
-    try expect(@enumToInt(SignedEnum.A0) == -1);
-    try expect(@enumToInt(SignedEnum.A1) == 0);
-    try expect(@enumToInt(SignedEnum.A2) == 1);
-}
-
 test "enum value allocation" {
     const LargeEnum = enum(u32) {
         A0 = 0x80000000,
@@ -235,26 +123,8 @@ test "enum literal casting to tagged union" {
     }
 }
 
-test "enum with one member and custom tag type" {
-    const E = enum(u2) {
-        One,
-    };
-    try expect(@enumToInt(E.One) == 0);
-    const E2 = enum(u2) {
-        One = 2,
-    };
-    try expect(@enumToInt(E2.One) == 2);
-}
-
 const Bar = enum { A, B, C, D };
 
-test "enum literal casting to optional" {
-    var bar: ?Bar = undefined;
-    bar = .B;
-
-    try expect(bar.? == Bar.B);
-}
-
 test "enum literal casting to error union with payload enum" {
     var bar: error{B}!Bar = undefined;
     bar = .B; // should never cast to the error set
@@ -262,27 +132,6 @@ test "enum literal casting to error union with payload enum" {
     try expect((try bar) == Bar.B);
 }
 
-test "enum with one member and u1 tag type @enumToInt" {
-    const Enum = enum(u1) {
-        Test,
-    };
-    try expect(@enumToInt(Enum.Test) == 0);
-}
-
-test "enum with comptime_int tag type" {
-    const Enum = enum(comptime_int) {
-        One = 3,
-        Two = 2,
-        Three = 1,
-    };
-    comptime try expect(Tag(Enum) == comptime_int);
-}
-
-test "enum with one member default to u0 tag type" {
-    const E0 = enum { X };
-    comptime try expect(Tag(E0) == u0);
-}
-
 test "tagName on enum literals" {
     try expect(mem.eql(u8, @tagName(.FooBar), "FooBar"));
     comptime try expect(mem.eql(u8, @tagName(.FooBar), "FooBar"));
test/behavior.zig
@@ -75,6 +75,7 @@ test {
             _ = @import("behavior/bugs/3112.zig");
             _ = @import("behavior/bugs/7250.zig");
             _ = @import("behavior/cast_llvm.zig");
+            _ = @import("behavior/enum_llvm.zig");
             _ = @import("behavior/eval.zig");
             _ = @import("behavior/floatop.zig");
             _ = @import("behavior/fn.zig");