Commit 1a4626d2cf

Jacob Young <jacobly0@users.noreply.github.com>
2023-05-25 11:47:25
InternPool: remove more legacy values
Reinstate some tags that will be needed for comptime init.
1 parent 6e0de1d
src/arch/aarch64/CodeGen.zig
@@ -846,7 +846,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
 
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
             .unreach  => self.finishAirBookkeeping(),
 
@@ -6169,7 +6168,6 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
             }
             return gop.value_ptr.*;
         },
-        .const_ty => unreachable,
         else => return self.getResolvedInstValue(inst_index),
     }
 }
src/arch/arm/CodeGen.zig
@@ -830,7 +830,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
 
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
             .unreach  => self.finishAirBookkeeping(),
 
@@ -6117,7 +6116,6 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
             }
             return gop.value_ptr.*;
         },
-        .const_ty => unreachable,
         else => return self.getResolvedInstValue(inst_index),
     }
 }
src/arch/riscv64/CodeGen.zig
@@ -660,7 +660,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
 
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
             .unreach  => self.finishAirBookkeeping(),
 
@@ -2571,7 +2570,6 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
             }
             return gop.value_ptr.*;
         },
-        .const_ty => unreachable,
         else => return self.getResolvedInstValue(inst_index),
     }
 }
src/arch/sparc64/CodeGen.zig
@@ -680,7 +680,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
 
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
             .unreach  => self.finishAirBookkeeping(),
 
@@ -4567,7 +4566,6 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
                 }
                 return gop.value_ptr.*;
             },
-            .const_ty => unreachable,
             else => return self.getResolvedInstValue(inst),
         }
     }
src/arch/wasm/CodeGen.zig
@@ -1833,7 +1833,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const air_tags = func.air.instructions.items(.tag);
     return switch (air_tags[inst]) {
         .constant => unreachable,
-        .const_ty => unreachable,
         .interned => unreachable,
 
         .add => func.airBinOp(inst, .add),
@@ -6903,28 +6902,12 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
             .child = .u8_type,
             .sentinel = .zero_u8,
         });
-        const string_bytes = &mod.string_literal_bytes;
-        try string_bytes.ensureUnusedCapacity(mod.gpa, tag_name.len);
-        const gop = try mod.string_literal_table.getOrPutContextAdapted(mod.gpa, @as([]const u8, tag_name), Module.StringLiteralAdapter{
-            .bytes = string_bytes,
-        }, Module.StringLiteralContext{
-            .bytes = string_bytes,
-        });
-        if (!gop.found_existing) {
-            gop.key_ptr.* = .{
-                .index = @intCast(u32, string_bytes.items.len),
-                .len = @intCast(u32, tag_name.len),
-            };
-            string_bytes.appendSliceAssumeCapacity(tag_name);
-            gop.value_ptr.* = .none;
-        }
-        var name_val_payload: Value.Payload.StrLit = .{
-            .base = .{ .tag = .str_lit },
-            .data = gop.key_ptr.*,
-        };
-        const name_val = Value.initPayload(&name_val_payload.base);
+        const name_val = try mod.intern(.{ .aggregate = .{
+            .ty = name_ty.toIntern(),
+            .storage = .{ .bytes = tag_name },
+        } });
         const tag_sym_index = try func.bin_file.lowerUnnamedConst(
-            .{ .ty = name_ty, .val = name_val },
+            .{ .ty = name_ty, .val = name_val.toValue() },
             enum_decl_index,
         );
 
src/arch/x86_64/CodeGen.zig
@@ -1923,7 +1923,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
 
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
             .unreach  => if (self.wantSafety()) try self.airTrap() else self.finishAirBookkeeping(),
 
@@ -2099,7 +2098,7 @@ fn feed(self: *Self, bt: *Liveness.BigTomb, operand: Air.Inst.Ref) void {
 /// Asserts there is already capacity to insert into top branch inst_table.
 fn processDeath(self: *Self, inst: Air.Inst.Index) void {
     switch (self.air.instructions.items(.tag)[inst]) {
-        .constant, .const_ty => unreachable,
+        .constant => unreachable,
         else => self.inst_tracking.getPtr(inst).?.die(self, inst),
     }
 }
@@ -11593,7 +11592,6 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
                 }));
                 break :tracking gop.value_ptr;
             },
-            .const_ty => unreachable,
             else => self.inst_tracking.getPtr(inst).?,
         }.short;
         switch (mcv) {
@@ -11608,7 +11606,6 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
 fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) *InstTracking {
     const tracking = switch (self.air.instructions.items(.tag)[inst]) {
         .constant => &self.const_tracking,
-        .const_ty => unreachable,
         else => &self.inst_tracking,
     }.getPtr(inst).?;
     return switch (tracking.short) {
src/codegen/c.zig
@@ -870,7 +870,7 @@ pub const DeclGen = struct {
                 }
 
                 // First try specific tag representations for more efficiency.
-                switch (val.ip_index) {
+                switch (val.toIntern()) {
                     .undef => {
                         const ai = ty.arrayInfo(mod);
                         try writer.writeByte('{');
@@ -893,24 +893,6 @@ pub const DeclGen = struct {
                         try writer.writeByte('}');
                         return;
                     },
-                    .none => switch (val.tag()) {
-                        .bytes, .str_lit => |t| {
-                            const bytes = switch (t) {
-                                .bytes => val.castTag(.bytes).?.data,
-                                .str_lit => bytes: {
-                                    const str_lit = val.castTag(.str_lit).?.data;
-                                    break :bytes mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                                },
-                                else => unreachable,
-                            };
-                            const sentinel = if (ty.sentinel(mod)) |sentinel| @intCast(u8, sentinel.toUnsignedInt(mod)) else null;
-                            try writer.print("{s}", .{
-                                fmtStringLiteral(bytes[0..@intCast(usize, ty.arrayLen(mod))], sentinel),
-                            });
-                            return;
-                        },
-                        else => {},
-                    },
                     else => {},
                 }
                 // Fall back to generic implementation.
@@ -2909,7 +2891,6 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
         const result_value = switch (air_tags[inst]) {
             // zig fmt: off
             .constant => unreachable, // excluded from function bodies
-            .const_ty => unreachable, // excluded from function bodies
             .interned => unreachable, // excluded from function bodies
 
             .arg      => try airArg(f, inst),
src/codegen/llvm.zig
@@ -1501,7 +1501,7 @@ pub const Object = struct {
                 }
 
                 const ip = &mod.intern_pool;
-                const enum_type = ip.indexToKey(ty.ip_index).enum_type;
+                const enum_type = ip.indexToKey(ty.toIntern()).enum_type;
 
                 const enumerators = try gpa.alloc(*llvm.DIEnumerator, enum_type.names.len);
                 defer gpa.free(enumerators);
@@ -1697,7 +1697,7 @@ pub const Object = struct {
                 return ptr_di_ty;
             },
             .Opaque => {
-                if (ty.ip_index == .anyopaque_type) {
+                if (ty.toIntern() == .anyopaque_type) {
                     const di_ty = dib.createBasicType("anyopaque", 0, DW.ATE.signed);
                     gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
                     return di_ty;
@@ -1981,7 +1981,7 @@ pub const Object = struct {
                     break :blk fwd_decl;
                 };
 
-                switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                switch (mod.intern_pool.indexToKey(ty.toIntern())) {
                     .anon_struct_type => |tuple| {
                         var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
                         defer di_fields.deinit(gpa);
@@ -2466,7 +2466,7 @@ pub const DeclGen = struct {
                 global.setGlobalConstant(.True);
                 break :init_val decl.val;
             };
-            if (init_val.ip_index != .unreachable_value) {
+            if (init_val.toIntern() != .unreachable_value) {
                 const llvm_init = try dg.lowerValue(.{ .ty = decl.ty, .val = init_val });
                 if (global.globalGetValueType() == llvm_init.typeOf()) {
                     global.setInitializer(llvm_init);
@@ -2802,12 +2802,12 @@ pub const DeclGen = struct {
                 return dg.context.pointerType(llvm_addrspace);
             },
             .Opaque => {
-                if (t.ip_index == .anyopaque_type) return dg.context.intType(8);
+                if (t.toIntern() == .anyopaque_type) return dg.context.intType(8);
 
                 const gop = try dg.object.type_map.getOrPut(gpa, t.toIntern());
                 if (gop.found_existing) return gop.value_ptr.*;
 
-                const opaque_type = mod.intern_pool.indexToKey(t.ip_index).opaque_type;
+                const opaque_type = mod.intern_pool.indexToKey(t.toIntern()).opaque_type;
                 const name = try mod.opaqueFullyQualifiedName(opaque_type);
                 defer gpa.free(name);
 
@@ -2897,7 +2897,7 @@ pub const DeclGen = struct {
                 const gop = try dg.object.type_map.getOrPut(gpa, t.toIntern());
                 if (gop.found_existing) return gop.value_ptr.*;
 
-                const struct_type = switch (mod.intern_pool.indexToKey(t.ip_index)) {
+                const struct_type = switch (mod.intern_pool.indexToKey(t.toIntern())) {
                     .anon_struct_type => |tuple| {
                         const llvm_struct_ty = dg.context.structCreateNamed("");
                         gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
@@ -3199,7 +3199,7 @@ pub const DeclGen = struct {
         const mod = dg.module;
         const target = mod.getTarget();
         var tv = arg_tv;
-        switch (mod.intern_pool.indexToKey(tv.val.ip_index)) {
+        switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
             .runtime_value => |rt| tv.val = rt.val.toValue(),
             else => {},
         }
@@ -3208,284 +3208,7 @@ pub const DeclGen = struct {
             return llvm_type.getUndef();
         }
 
-        if (tv.val.ip_index == .none) switch (tv.ty.zigTypeTag(mod)) {
-            .Array => switch (tv.val.tag()) {
-                .bytes => {
-                    const bytes = tv.val.castTag(.bytes).?.data;
-                    return dg.context.constString(
-                        bytes.ptr,
-                        @intCast(c_uint, tv.ty.arrayLenIncludingSentinel(mod)),
-                        .True, // Don't null terminate. Bytes has the sentinel, if any.
-                    );
-                },
-                .str_lit => {
-                    const str_lit = tv.val.castTag(.str_lit).?.data;
-                    const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                    if (tv.ty.sentinel(mod)) |sent_val| {
-                        const byte = @intCast(u8, sent_val.toUnsignedInt(mod));
-                        if (byte == 0 and bytes.len > 0) {
-                            return dg.context.constString(
-                                bytes.ptr,
-                                @intCast(c_uint, bytes.len),
-                                .False, // Yes, null terminate.
-                            );
-                        }
-                        var array = std.ArrayList(u8).init(dg.gpa);
-                        defer array.deinit();
-                        try array.ensureUnusedCapacity(bytes.len + 1);
-                        array.appendSliceAssumeCapacity(bytes);
-                        array.appendAssumeCapacity(byte);
-                        return dg.context.constString(
-                            array.items.ptr,
-                            @intCast(c_uint, array.items.len),
-                            .True, // Don't null terminate.
-                        );
-                    } else {
-                        return dg.context.constString(
-                            bytes.ptr,
-                            @intCast(c_uint, bytes.len),
-                            .True, // Don't null terminate. `bytes` has the sentinel, if any.
-                        );
-                    }
-                },
-                else => unreachable,
-            },
-            .Struct => {
-                const llvm_struct_ty = try dg.lowerType(tv.ty);
-                const gpa = dg.gpa;
-
-                const struct_type = switch (mod.intern_pool.indexToKey(tv.ty.ip_index)) {
-                    .anon_struct_type => |tuple| {
-                        var llvm_fields: std.ArrayListUnmanaged(*llvm.Value) = .{};
-                        defer llvm_fields.deinit(gpa);
-
-                        try llvm_fields.ensureUnusedCapacity(gpa, tuple.types.len);
-
-                        comptime assert(struct_layout_version == 2);
-                        var offset: u64 = 0;
-                        var big_align: u32 = 0;
-                        var need_unnamed = false;
-
-                        for (tuple.types, tuple.values, 0..) |field_ty, field_val, i| {
-                            if (field_val != .none) continue;
-                            if (!field_ty.toType().hasRuntimeBitsIgnoreComptime(mod)) continue;
-
-                            const field_align = field_ty.toType().abiAlignment(mod);
-                            big_align = @max(big_align, field_align);
-                            const prev_offset = offset;
-                            offset = std.mem.alignForwardGeneric(u64, offset, field_align);
-
-                            const padding_len = offset - prev_offset;
-                            if (padding_len > 0) {
-                                const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
-                                // TODO make this and all other padding elsewhere in debug
-                                // builds be 0xaa not undef.
-                                llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
-                            }
-
-                            const field_llvm_val = try dg.lowerValue(.{
-                                .ty = field_ty.toType(),
-                                .val = try tv.val.fieldValue(mod, i),
-                            });
-
-                            need_unnamed = need_unnamed or dg.isUnnamedType(field_ty.toType(), field_llvm_val);
-
-                            llvm_fields.appendAssumeCapacity(field_llvm_val);
-
-                            offset += field_ty.toType().abiSize(mod);
-                        }
-                        {
-                            const prev_offset = offset;
-                            offset = std.mem.alignForwardGeneric(u64, offset, big_align);
-                            const padding_len = offset - prev_offset;
-                            if (padding_len > 0) {
-                                const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
-                                llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
-                            }
-                        }
-
-                        if (need_unnamed) {
-                            return dg.context.constStruct(
-                                llvm_fields.items.ptr,
-                                @intCast(c_uint, llvm_fields.items.len),
-                                .False,
-                            );
-                        } else {
-                            return llvm_struct_ty.constNamedStruct(
-                                llvm_fields.items.ptr,
-                                @intCast(c_uint, llvm_fields.items.len),
-                            );
-                        }
-                    },
-                    .struct_type => |struct_type| struct_type,
-                    else => unreachable,
-                };
-
-                const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
-
-                if (struct_obj.layout == .Packed) {
-                    assert(struct_obj.haveLayout());
-                    const big_bits = struct_obj.backing_int_ty.bitSize(mod);
-                    const int_llvm_ty = dg.context.intType(@intCast(c_uint, big_bits));
-                    const fields = struct_obj.fields.values();
-                    comptime assert(Type.packed_struct_layout_version == 2);
-                    var running_int: *llvm.Value = int_llvm_ty.constNull();
-                    var running_bits: u16 = 0;
-                    for (fields, 0..) |field, i| {
-                        if (!field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
-
-                        const non_int_val = try dg.lowerValue(.{
-                            .ty = field.ty,
-                            .val = try tv.val.fieldValue(mod, i),
-                        });
-                        const ty_bit_size = @intCast(u16, field.ty.bitSize(mod));
-                        const small_int_ty = dg.context.intType(ty_bit_size);
-                        const small_int_val = if (field.ty.isPtrAtRuntime(mod))
-                            non_int_val.constPtrToInt(small_int_ty)
-                        else
-                            non_int_val.constBitCast(small_int_ty);
-                        const shift_rhs = int_llvm_ty.constInt(running_bits, .False);
-                        // If the field is as large as the entire packed struct, this
-                        // zext would go from, e.g. i16 to i16. This is legal with
-                        // constZExtOrBitCast but not legal with constZExt.
-                        const extended_int_val = small_int_val.constZExtOrBitCast(int_llvm_ty);
-                        const shifted = extended_int_val.constShl(shift_rhs);
-                        running_int = running_int.constOr(shifted);
-                        running_bits += ty_bit_size;
-                    }
-                    return running_int;
-                }
-
-                const llvm_field_count = llvm_struct_ty.countStructElementTypes();
-                var llvm_fields = try std.ArrayListUnmanaged(*llvm.Value).initCapacity(gpa, llvm_field_count);
-                defer llvm_fields.deinit(gpa);
-
-                comptime assert(struct_layout_version == 2);
-                var offset: u64 = 0;
-                var big_align: u32 = 0;
-                var need_unnamed = false;
-
-                var it = struct_obj.runtimeFieldIterator(mod);
-                while (it.next()) |field_and_index| {
-                    const field = field_and_index.field;
-                    const field_align = field.alignment(mod, struct_obj.layout);
-                    big_align = @max(big_align, field_align);
-                    const prev_offset = offset;
-                    offset = std.mem.alignForwardGeneric(u64, offset, field_align);
-
-                    const padding_len = offset - prev_offset;
-                    if (padding_len > 0) {
-                        const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
-                        // TODO make this and all other padding elsewhere in debug
-                        // builds be 0xaa not undef.
-                        llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
-                    }
-
-                    const field_llvm_val = try dg.lowerValue(.{
-                        .ty = field.ty,
-                        .val = try tv.val.fieldValue(mod, field_and_index.index),
-                    });
-
-                    need_unnamed = need_unnamed or dg.isUnnamedType(field.ty, field_llvm_val);
-
-                    llvm_fields.appendAssumeCapacity(field_llvm_val);
-
-                    offset += field.ty.abiSize(mod);
-                }
-                {
-                    const prev_offset = offset;
-                    offset = std.mem.alignForwardGeneric(u64, offset, big_align);
-                    const padding_len = offset - prev_offset;
-                    if (padding_len > 0) {
-                        const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
-                        llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
-                    }
-                }
-
-                if (need_unnamed) {
-                    return dg.context.constStruct(
-                        llvm_fields.items.ptr,
-                        @intCast(c_uint, llvm_fields.items.len),
-                        .False,
-                    );
-                } else {
-                    return llvm_struct_ty.constNamedStruct(
-                        llvm_fields.items.ptr,
-                        @intCast(c_uint, llvm_fields.items.len),
-                    );
-                }
-            },
-            .Vector => switch (tv.val.tag()) {
-                .bytes => {
-                    // Note, sentinel is not stored even if the type has a sentinel.
-                    const bytes = tv.val.castTag(.bytes).?.data;
-                    const vector_len = @intCast(usize, tv.ty.arrayLen(mod));
-                    assert(vector_len == bytes.len or vector_len + 1 == bytes.len);
-
-                    const elem_ty = tv.ty.childType(mod);
-                    const llvm_elems = try dg.gpa.alloc(*llvm.Value, vector_len);
-                    defer dg.gpa.free(llvm_elems);
-                    for (llvm_elems, 0..) |*elem, i| {
-                        elem.* = try dg.lowerValue(.{
-                            .ty = elem_ty,
-                            .val = try mod.intValue(elem_ty, bytes[i]),
-                        });
-                    }
-                    return llvm.constVector(
-                        llvm_elems.ptr,
-                        @intCast(c_uint, llvm_elems.len),
-                    );
-                },
-                .str_lit => {
-                    // Note, sentinel is not stored
-                    const str_lit = tv.val.castTag(.str_lit).?.data;
-                    const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                    const vector_len = @intCast(usize, tv.ty.arrayLen(mod));
-                    assert(vector_len == bytes.len);
-
-                    const elem_ty = tv.ty.childType(mod);
-                    const llvm_elems = try dg.gpa.alloc(*llvm.Value, vector_len);
-                    defer dg.gpa.free(llvm_elems);
-                    for (llvm_elems, 0..) |*elem, i| {
-                        elem.* = try dg.lowerValue(.{
-                            .ty = elem_ty,
-                            .val = try mod.intValue(elem_ty, bytes[i]),
-                        });
-                    }
-                    return llvm.constVector(
-                        llvm_elems.ptr,
-                        @intCast(c_uint, llvm_elems.len),
-                    );
-                },
-                else => unreachable,
-            },
-            .Float,
-            .Union,
-            .Optional,
-            .ErrorUnion,
-            .ErrorSet,
-            .Int,
-            .Enum,
-            .Bool,
-            .Pointer,
-            => unreachable, // handled below
-            .Frame,
-            .AnyFrame,
-            => return dg.todo("implement const of type '{}'", .{tv.ty.fmtDebug()}),
-            .Type,
-            .Void,
-            .NoReturn,
-            .ComptimeFloat,
-            .ComptimeInt,
-            .Undefined,
-            .Null,
-            .Opaque,
-            .EnumLiteral,
-            .Fn,
-            => unreachable, // comptime-only types
-        };
-
-        switch (mod.intern_pool.indexToKey(tv.val.ip_index)) {
+        switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
             .int_type,
             .ptr_type,
             .array_type,
@@ -3553,7 +3276,7 @@ pub const DeclGen = struct {
                 const llvm_payload_value = try dg.lowerValue(.{
                     .ty = payload_type,
                     .val = switch (error_union.val) {
-                        .err_name => try mod.intern(.{ .undef = payload_type.ip_index }),
+                        .err_name => try mod.intern(.{ .undef = payload_type.toIntern() }),
                         .payload => |payload| payload,
                     }.toValue(),
                 });
@@ -3700,7 +3423,7 @@ pub const DeclGen = struct {
                 fields_buf[0] = try dg.lowerValue(.{
                     .ty = payload_ty,
                     .val = switch (opt.val) {
-                        .none => try mod.intern(.{ .undef = payload_ty.ip_index }),
+                        .none => try mod.intern(.{ .undef = payload_ty.toIntern() }),
                         else => |payload| payload,
                     }.toValue(),
                 });
@@ -3711,7 +3434,7 @@ pub const DeclGen = struct {
                 }
                 return dg.context.constStruct(&fields_buf, llvm_field_count, .False);
             },
-            .aggregate => |aggregate| switch (mod.intern_pool.indexToKey(tv.ty.ip_index)) {
+            .aggregate => |aggregate| switch (mod.intern_pool.indexToKey(tv.ty.toIntern())) {
                 .array_type => switch (aggregate.storage) {
                     .bytes => |bytes| return dg.context.constString(
                         bytes.ptr,
@@ -3802,7 +3525,7 @@ pub const DeclGen = struct {
                     const llvm_struct_ty = try dg.lowerType(tv.ty);
                     const gpa = dg.gpa;
 
-                    const struct_type = switch (mod.intern_pool.indexToKey(tv.ty.ip_index)) {
+                    const struct_type = switch (mod.intern_pool.indexToKey(tv.ty.toIntern())) {
                         .anon_struct_type => |tuple| {
                             var llvm_fields: std.ArrayListUnmanaged(*llvm.Value) = .{};
                             defer llvm_fields.deinit(gpa);
@@ -3967,9 +3690,9 @@ pub const DeclGen = struct {
             },
             .un => {
                 const llvm_union_ty = try dg.lowerType(tv.ty);
-                const tag_and_val: Value.Payload.Union.Data = switch (tv.val.ip_index) {
+                const tag_and_val: Value.Payload.Union.Data = switch (tv.val.toIntern()) {
                     .none => tv.val.castTag(.@"union").?.data,
-                    else => switch (mod.intern_pool.indexToKey(tv.val.ip_index)) {
+                    else => switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
                         .un => |un| .{ .tag = un.tag.toValue(), .val = un.val.toValue() },
                         else => unreachable,
                     },
@@ -4107,7 +3830,7 @@ pub const DeclGen = struct {
     fn lowerParentPtr(dg: *DeclGen, ptr_val: Value, byte_aligned: bool) Error!*llvm.Value {
         const mod = dg.module;
         const target = mod.getTarget();
-        return switch (mod.intern_pool.indexToKey(ptr_val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) {
             .int => |int| dg.lowerIntAsPtr(int),
             .ptr => |ptr| switch (ptr.addr) {
                 .decl => |decl| dg.lowerParentPtrDecl(ptr_val, decl),
@@ -4799,7 +4522,6 @@ pub const FuncGen = struct {
                 .vector_store_elem => try self.airVectorStoreElem(inst),
 
                 .constant => unreachable,
-                .const_ty => unreachable,
                 .interned => unreachable,
 
                 .unreach  => self.airUnreach(inst),
@@ -6108,7 +5830,7 @@ pub const FuncGen = struct {
                 const struct_llvm_ty = try self.dg.lowerType(struct_ty);
                 const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, struct_llvm_val, llvm_field.index, "");
                 const field_ptr_ty = try mod.ptrType(.{
-                    .elem_type = llvm_field.ty.ip_index,
+                    .elem_type = llvm_field.ty.toIntern(),
                     .alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
                 });
                 if (isByRef(field_ty, mod)) {
@@ -6984,7 +6706,7 @@ pub const FuncGen = struct {
         const struct_llvm_ty = try self.dg.lowerType(struct_ty);
         const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, self.err_ret_trace.?, llvm_field.index, "");
         const field_ptr_ty = try mod.ptrType(.{
-            .elem_type = llvm_field.ty.ip_index,
+            .elem_type = llvm_field.ty.toIntern(),
             .alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
         });
         return self.load(field_ptr, field_ptr_ty);
@@ -8915,7 +8637,7 @@ pub const FuncGen = struct {
 
     fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*llvm.Value {
         const mod = self.dg.module;
-        const enum_type = mod.intern_pool.indexToKey(enum_ty.ip_index).enum_type;
+        const enum_type = mod.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
 
         // TODO: detect when the type changes and re-emit this function.
         const gop = try self.dg.object.named_enum_map.getOrPut(self.dg.gpa, enum_type.decl);
@@ -8988,7 +8710,7 @@ pub const FuncGen = struct {
 
     fn getEnumTagNameFunction(self: *FuncGen, enum_ty: Type) !*llvm.Value {
         const mod = self.dg.module;
-        const enum_type = mod.intern_pool.indexToKey(enum_ty.ip_index).enum_type;
+        const enum_type = mod.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
 
         // TODO: detect when the type changes and re-emit this function.
         const gop = try self.dg.object.decl_map.getOrPut(self.dg.gpa, enum_type.decl);
@@ -10529,7 +10251,7 @@ fn llvmField(ty: Type, field_index: usize, mod: *Module) ?LlvmField {
     var offset: u64 = 0;
     var big_align: u32 = 0;
 
-    const struct_type = switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+    const struct_type = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
         .anon_struct_type => |tuple| {
             var llvm_field_index: c_uint = 0;
             for (tuple.types, tuple.values, 0..) |field_ty, field_val, i| {
@@ -10927,7 +10649,7 @@ const ParamTypeIterator = struct {
                     .riscv32, .riscv64 => {
                         it.zig_index += 1;
                         it.llvm_index += 1;
-                        if (ty.ip_index == .f16_type) {
+                        if (ty.toIntern() == .f16_type) {
                             return .as_u16;
                         }
                         switch (riscv_c_abi.classifyType(ty, mod)) {
@@ -11146,7 +10868,7 @@ fn isByRef(ty: Type, mod: *Module) bool {
         .Struct => {
             // Packed structs are represented to LLVM as integers.
             if (ty.containerLayout(mod) == .Packed) return false;
-            const struct_type = switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+            const struct_type = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
                 .anon_struct_type => |tuple| {
                     var count: usize = 0;
                     for (tuple.types, tuple.values) |field_ty, field_val| {
@@ -11261,7 +10983,7 @@ fn backendSupportsF128(target: std.Target) bool {
 /// LLVM does not support all relevant intrinsics for all targets, so we
 /// may need to manually generate a libc call
 fn intrinsicsAllowed(scalar_ty: Type, target: std.Target) bool {
-    return switch (scalar_ty.ip_index) {
+    return switch (scalar_ty.toIntern()) {
         .f16_type => backendSupportsF16(target),
         .f80_type => (target.c_type_bit_size(.longdouble) == 80) and backendSupportsF80(target),
         .f128_type => (target.c_type_bit_size(.longdouble) == 128) and backendSupportsF128(target),
src/codegen/spirv.zig
@@ -616,7 +616,7 @@ pub const DeclGen = struct {
             const mod = dg.module;
 
             var val = arg_val;
-            switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .runtime_value => |rt| val = rt.val.toValue(),
                 else => {},
             }
@@ -626,75 +626,7 @@ pub const DeclGen = struct {
                 return try self.addUndef(size);
             }
 
-            if (val.ip_index == .none) switch (ty.zigTypeTag(mod)) {
-                .Array => switch (val.tag()) {
-                    .str_lit => {
-                        const str_lit = val.castTag(.str_lit).?.data;
-                        const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                        try self.addBytes(bytes);
-                        if (ty.sentinel(mod)) |sentinel| {
-                            try self.addByte(@intCast(u8, sentinel.toUnsignedInt(mod)));
-                        }
-                    },
-                    .bytes => {
-                        const bytes = val.castTag(.bytes).?.data;
-                        try self.addBytes(bytes);
-                    },
-                    else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}),
-                },
-                .Struct => {
-                    if (ty.isSimpleTupleOrAnonStruct(mod)) {
-                        unreachable; // TODO
-                    } else {
-                        const struct_ty = mod.typeToStruct(ty).?;
-
-                        if (struct_ty.layout == .Packed) {
-                            return dg.todo("packed struct constants", .{});
-                        }
-
-                        const struct_begin = self.size;
-                        const field_vals = val.castTag(.aggregate).?.data;
-                        for (struct_ty.fields.values(), 0..) |field, i| {
-                            if (field.is_comptime or !field.ty.hasRuntimeBits(mod)) continue;
-                            try self.lower(field.ty, field_vals[i]);
-
-                            // Add padding if required.
-                            // TODO: Add to type generation as well?
-                            const unpadded_field_end = self.size - struct_begin;
-                            const padded_field_end = ty.structFieldOffset(i + 1, mod);
-                            const padding = padded_field_end - unpadded_field_end;
-                            try self.addUndef(padding);
-                        }
-                    }
-                },
-                .Vector,
-                .Frame,
-                .AnyFrame,
-                => return dg.todo("indirect constant of type {}", .{ty.fmt(mod)}),
-                .Float,
-                .Union,
-                .Optional,
-                .ErrorUnion,
-                .ErrorSet,
-                .Int,
-                .Enum,
-                .Bool,
-                .Pointer,
-                => unreachable, // handled below
-                .Type,
-                .Void,
-                .NoReturn,
-                .ComptimeFloat,
-                .ComptimeInt,
-                .Undefined,
-                .Null,
-                .Opaque,
-                .EnumLiteral,
-                .Fn,
-                => unreachable, // comptime-only types
-            };
-
-            switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .int_type,
                 .ptr_type,
                 .array_type,
@@ -1876,7 +1808,6 @@ pub const DeclGen = struct {
             .breakpoint     => return,
             .cond_br        => return self.airCondBr(inst),
             .constant       => unreachable,
-            .const_ty       => unreachable,
             .dbg_stmt       => return self.airDbgStmt(inst),
             .loop           => return self.airLoop(inst),
             .ret            => return self.airRet(inst),
src/Liveness/Verify.zig
@@ -43,7 +43,6 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
             .alloc,
             .ret_ptr,
             .constant,
-            .const_ty,
             .interned,
             .breakpoint,
             .dbg_stmt,
@@ -557,7 +556,7 @@ fn verifyDeath(self: *Verify, inst: Air.Inst.Index, operand: Air.Inst.Index) Err
 fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies: bool) Error!void {
     const operand = Air.refToIndexAllowNone(op_ref) orelse return;
     switch (self.air.instructions.items(.tag)[operand]) {
-        .constant, .const_ty, .interned => {},
+        .constant, .interned => {},
         else => {
             if (dies) {
                 if (!self.live.remove(operand)) return invalid("%{}: dead operand %{} reused and killed again", .{ inst, operand });
@@ -579,7 +578,7 @@ fn verifyInst(
     }
     const tag = self.air.instructions.items(.tag);
     switch (tag[inst]) {
-        .constant, .const_ty, .interned => unreachable,
+        .constant, .interned => unreachable,
         else => {
             if (self.liveness.isUnused(inst)) {
                 assert(!self.live.contains(inst));
src/Air.zig
@@ -400,8 +400,6 @@ pub const Inst = struct {
         /// A comptime-known value. Uses the `ty_pl` field, payload is index of
         /// `values` array.
         constant,
-        /// A comptime-known type. Uses the `ty` field.
-        const_ty,
         /// A comptime-known value via an index into the InternPool.
         /// Uses the `interned` field.
         interned,
@@ -1257,8 +1255,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index, ip: InternPool) Type {
         .error_set_has_value,
         => return Type.bool,
 
-        .const_ty => return Type.type,
-
         .alloc,
         .ret_ptr,
         .err_return_trace,
@@ -1435,7 +1431,6 @@ pub fn getRefType(air: Air, ref: Air.Inst.Ref) Type {
     const air_tags = air.instructions.items(.tag);
     const air_datas = air.instructions.items(.data);
     return switch (air_tags[inst_index]) {
-        .const_ty => air_datas[inst_index].ty,
         .interned => air_datas[inst_index].interned.toType(),
         else => unreachable,
     };
@@ -1501,7 +1496,6 @@ pub fn value(air: Air, inst: Inst.Ref, mod: *Module) !?Value {
     const air_datas = air.instructions.items(.data);
     switch (air.instructions.items(.tag)[inst_index]) {
         .constant => return air.values[air_datas[inst_index].ty_pl.payload],
-        .const_ty => unreachable,
         .interned => return air_datas[inst_index].interned.toValue(),
         else => return air.typeOfIndex(inst_index, mod.intern_pool).onePossibleValue(mod),
     }
@@ -1658,7 +1652,6 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: InternPool) bool {
         .cmp_vector,
         .cmp_vector_optimized,
         .constant,
-        .const_ty,
         .interned,
         .is_null,
         .is_non_null,
src/codegen.zig
@@ -204,150 +204,6 @@ pub fn generateSymbol(
         return .ok;
     }
 
-    if (typed_value.val.ip_index == .none) switch (typed_value.ty.zigTypeTag(mod)) {
-        .Array => switch (typed_value.val.tag()) {
-            .bytes => {
-                const bytes = typed_value.val.castTag(.bytes).?.data;
-                const len = @intCast(usize, typed_value.ty.arrayLenIncludingSentinel(mod));
-                // The bytes payload already includes the sentinel, if any
-                try code.ensureUnusedCapacity(len);
-                code.appendSliceAssumeCapacity(bytes[0..len]);
-                return Result.ok;
-            },
-            .str_lit => {
-                const str_lit = typed_value.val.castTag(.str_lit).?.data;
-                const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                try code.ensureUnusedCapacity(bytes.len + 1);
-                code.appendSliceAssumeCapacity(bytes);
-                if (typed_value.ty.sentinel(mod)) |sent_val| {
-                    const byte = @intCast(u8, sent_val.toUnsignedInt(mod));
-                    code.appendAssumeCapacity(byte);
-                }
-                return Result.ok;
-            },
-            else => return Result{
-                .fail = try ErrorMsg.create(
-                    bin_file.allocator,
-                    src_loc,
-                    "TODO implement generateSymbol for array type value: {s}",
-                    .{@tagName(typed_value.val.tag())},
-                ),
-            },
-        },
-        .Struct => {
-            if (typed_value.ty.containerLayout(mod) == .Packed) {
-                const struct_obj = mod.typeToStruct(typed_value.ty).?;
-                const fields = struct_obj.fields.values();
-                const field_vals = typed_value.val.castTag(.aggregate).?.data;
-                const abi_size = math.cast(usize, typed_value.ty.abiSize(mod)) orelse return error.Overflow;
-                const current_pos = code.items.len;
-                try code.resize(current_pos + abi_size);
-                var bits: u16 = 0;
-
-                for (field_vals, 0..) |field_val, index| {
-                    const field_ty = fields[index].ty;
-                    // pointer may point to a decl which must be marked used
-                    // but can also result in a relocation. Therefore we handle those seperately.
-                    if (field_ty.zigTypeTag(mod) == .Pointer) {
-                        const field_size = math.cast(usize, field_ty.abiSize(mod)) orelse return error.Overflow;
-                        var tmp_list = try std.ArrayList(u8).initCapacity(code.allocator, field_size);
-                        defer tmp_list.deinit();
-                        switch (try generateSymbol(bin_file, src_loc, .{
-                            .ty = field_ty,
-                            .val = field_val,
-                        }, &tmp_list, debug_output, reloc_info)) {
-                            .ok => @memcpy(code.items[current_pos..][0..tmp_list.items.len], tmp_list.items),
-                            .fail => |em| return Result{ .fail = em },
-                        }
-                    } else {
-                        field_val.writeToPackedMemory(field_ty, mod, code.items[current_pos..], bits) catch unreachable;
-                    }
-                    bits += @intCast(u16, field_ty.bitSize(mod));
-                }
-
-                return Result.ok;
-            }
-
-            const struct_begin = code.items.len;
-            const field_vals = typed_value.val.castTag(.aggregate).?.data;
-            for (field_vals, 0..) |field_val, index| {
-                const field_ty = typed_value.ty.structFieldType(index, mod);
-                if (!field_ty.hasRuntimeBits(mod)) continue;
-
-                switch (try generateSymbol(bin_file, src_loc, .{
-                    .ty = field_ty,
-                    .val = field_val,
-                }, code, debug_output, reloc_info)) {
-                    .ok => {},
-                    .fail => |em| return Result{ .fail = em },
-                }
-                const unpadded_field_end = code.items.len - struct_begin;
-
-                // Pad struct members if required
-                const padded_field_end = typed_value.ty.structFieldOffset(index + 1, mod);
-                const padding = math.cast(usize, padded_field_end - unpadded_field_end) orelse return error.Overflow;
-
-                if (padding > 0) {
-                    try code.writer().writeByteNTimes(0, padding);
-                }
-            }
-
-            return Result.ok;
-        },
-        .Vector => switch (typed_value.val.tag()) {
-            .bytes => {
-                const bytes = typed_value.val.castTag(.bytes).?.data;
-                const len = math.cast(usize, typed_value.ty.arrayLen(mod)) orelse return error.Overflow;
-                const padding = math.cast(usize, typed_value.ty.abiSize(mod) - len) orelse
-                    return error.Overflow;
-                try code.ensureUnusedCapacity(len + padding);
-                code.appendSliceAssumeCapacity(bytes[0..len]);
-                if (padding > 0) try code.writer().writeByteNTimes(0, padding);
-                return Result.ok;
-            },
-            .str_lit => {
-                const str_lit = typed_value.val.castTag(.str_lit).?.data;
-                const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                const padding = math.cast(usize, typed_value.ty.abiSize(mod) - str_lit.len) orelse
-                    return error.Overflow;
-                try code.ensureUnusedCapacity(str_lit.len + padding);
-                code.appendSliceAssumeCapacity(bytes);
-                if (padding > 0) try code.writer().writeByteNTimes(0, padding);
-                return Result.ok;
-            },
-            else => unreachable,
-        },
-        .Frame,
-        .AnyFrame,
-        => return .{ .fail = try ErrorMsg.create(
-            bin_file.allocator,
-            src_loc,
-            "TODO generateSymbol for type {}",
-            .{typed_value.ty.fmt(mod)},
-        ) },
-        .Float,
-        .Union,
-        .Optional,
-        .ErrorUnion,
-        .ErrorSet,
-        .Int,
-        .Enum,
-        .Bool,
-        .Pointer,
-        => unreachable, // handled below
-        .Type,
-        .Void,
-        .NoReturn,
-        .ComptimeFloat,
-        .ComptimeInt,
-        .Undefined,
-        .Null,
-        .Opaque,
-        .EnumLiteral,
-        .Fn,
-        => unreachable, // comptime-only types
-    };
-
     switch (mod.intern_pool.indexToKey(typed_value.val.ip_index)) {
         .int_type,
         .ptr_type,
src/InternPool.zig
@@ -553,10 +553,10 @@ pub const Key = union(enum) {
         pub const Addr = union(enum) {
             decl: Module.Decl.Index,
             mut_decl: MutDecl,
+            comptime_field: Index,
             int: Index,
             eu_payload: Index,
             opt_payload: Index,
-            comptime_field: Index,
             elem: BaseIndex,
             field: BaseIndex,
 
@@ -703,24 +703,27 @@ pub const Key = union(enum) {
             .aggregate => |aggregate| {
                 std.hash.autoHash(hasher, aggregate.ty);
                 switch (ip.indexToKey(aggregate.ty)) {
-                    .array_type => |array_type| if (array_type.child == .u8_type) switch (aggregate.storage) {
-                        .bytes => |bytes| for (bytes) |byte| std.hash.autoHash(hasher, byte),
-                        .elems => |elems| {
-                            var buffer: Key.Int.Storage.BigIntSpace = undefined;
-                            for (elems) |elem| std.hash.autoHash(
-                                hasher,
-                                ip.indexToKey(elem).int.storage.toBigInt(&buffer).to(u8) catch
-                                    unreachable,
-                            );
-                        },
-                        .repeated_elem => |elem| {
-                            const len = ip.aggregateTypeLen(aggregate.ty);
-                            var buffer: Key.Int.Storage.BigIntSpace = undefined;
-                            const byte = ip.indexToKey(elem).int.storage.toBigInt(&buffer).to(u8) catch
-                                unreachable;
-                            var i: u64 = 0;
-                            while (i < len) : (i += 1) std.hash.autoHash(hasher, byte);
-                        },
+                    .array_type => |array_type| if (array_type.child == .u8_type) {
+                        switch (aggregate.storage) {
+                            .bytes => |bytes| for (bytes) |byte| std.hash.autoHash(hasher, byte),
+                            .elems => |elems| {
+                                var buffer: Key.Int.Storage.BigIntSpace = undefined;
+                                for (elems) |elem| std.hash.autoHash(
+                                    hasher,
+                                    ip.indexToKey(elem).int.storage.toBigInt(&buffer).to(u8) catch
+                                        unreachable,
+                                );
+                            },
+                            .repeated_elem => |elem| {
+                                const len = ip.aggregateTypeLen(aggregate.ty);
+                                var buffer: Key.Int.Storage.BigIntSpace = undefined;
+                                const byte = ip.indexToKey(elem).int.storage.toBigInt(&buffer).to(u8) catch
+                                    unreachable;
+                                var i: u64 = 0;
+                                while (i < len) : (i += 1) std.hash.autoHash(hasher, byte);
+                            },
+                        }
+                        return;
                     },
                     else => {},
                 }
@@ -2860,6 +2863,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
         },
         .array_type => |array_type| {
             assert(array_type.child != .none);
+            assert(array_type.sentinel == .none or ip.typeOf(array_type.sentinel) == array_type.child);
 
             if (std.math.cast(u32, array_type.len)) |len| {
                 if (array_type.sentinel == .none) {
@@ -3230,7 +3234,23 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
         },
 
         .int => |int| b: {
-            assert(int.ty == .comptime_int_type or ip.indexToKey(int.ty) == .int_type);
+            switch (int.ty) {
+                .usize_type,
+                .isize_type,
+                .c_char_type,
+                .c_short_type,
+                .c_ushort_type,
+                .c_int_type,
+                .c_uint_type,
+                .c_long_type,
+                .c_ulong_type,
+                .c_longlong_type,
+                .c_ulonglong_type,
+                .c_longdouble_type,
+                .comptime_int_type,
+                => {},
+                else => assert(ip.indexToKey(int.ty) == .int_type),
+            }
             switch (int.storage) {
                 .u64, .i64, .big_int => {},
                 .lazy_align, .lazy_size => |lazy_ty| {
src/Liveness.zig
@@ -323,7 +323,6 @@ pub fn categorizeOperand(
         .alloc,
         .ret_ptr,
         .constant,
-        .const_ty,
         .interned,
         .trap,
         .breakpoint,
@@ -975,7 +974,6 @@ fn analyzeInst(
         => return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }),
 
         .constant,
-        .const_ty,
         .interned,
         => unreachable,
 
@@ -1272,7 +1270,7 @@ fn analyzeOperands(
 
                 // Don't compute any liveness for constants
                 switch (inst_tags[operand]) {
-                    .constant, .const_ty, .interned => continue,
+                    .constant, .interned => continue,
                     else => {},
                 }
 
@@ -1308,7 +1306,7 @@ fn analyzeOperands(
 
                     // Don't compute any liveness for constants
                     switch (inst_tags[operand]) {
-                        .constant, .const_ty, .interned => continue,
+                        .constant, .interned => continue,
                         else => {},
                     }
 
@@ -1842,7 +1840,7 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
             // Don't compute any liveness for constants
             const inst_tags = big.a.air.instructions.items(.tag);
             switch (inst_tags[operand]) {
-                .constant, .const_ty, .interned => return,
+                .constant, .interned => return,
                 else => {},
             }
 
src/Module.zig
@@ -85,20 +85,13 @@ import_table: std.StringArrayHashMapUnmanaged(*File) = .{},
 /// Keys are fully resolved file paths. This table owns the keys and values.
 embed_table: std.StringHashMapUnmanaged(*EmbedFile) = .{},
 
-/// This is a temporary addition to stage2 in order to match legacy behavior,
-/// however the end-game once the lang spec is settled will be to use a global
-/// InternPool for comptime memoized objects, making this behavior consistent across all types,
-/// not only string literals. Or, we might decide to not guarantee string literals
-/// to have equal comptime pointers, in which case this field can be deleted (perhaps
-/// the commit that introduced it can simply be reverted).
-/// This table uses an optional index so that when a Decl is destroyed, the string literal
-/// is still reclaimable by a future Decl.
-string_literal_table: std.HashMapUnmanaged(StringLiteralContext.Key, Decl.OptionalIndex, StringLiteralContext, std.hash_map.default_max_load_percentage) = .{},
-string_literal_bytes: ArrayListUnmanaged(u8) = .{},
-
 /// Stores all Type and Value objects; periodically garbage collected.
 intern_pool: InternPool = .{},
 
+/// This is currently only used for string literals, however the end-game once the lang spec
+/// is settled will be to make this behavior consistent across all types.
+memoized_decls: std.AutoHashMapUnmanaged(InternPool.Index, Decl.Index) = .{},
+
 /// The set of all the generic function instantiations. This is used so that when a generic
 /// function is called twice with the same comptime parameter arguments, both calls dispatch
 /// to the same function.
@@ -208,39 +201,6 @@ pub const CImportError = struct {
     }
 };
 
-pub const StringLiteralContext = struct {
-    bytes: *ArrayListUnmanaged(u8),
-
-    pub const Key = struct {
-        index: u32,
-        len: u32,
-    };
-
-    pub fn eql(self: @This(), a: Key, b: Key) bool {
-        _ = self;
-        return a.index == b.index and a.len == b.len;
-    }
-
-    pub fn hash(self: @This(), x: Key) u64 {
-        const x_slice = self.bytes.items[x.index..][0..x.len];
-        return std.hash_map.hashString(x_slice);
-    }
-};
-
-pub const StringLiteralAdapter = struct {
-    bytes: *ArrayListUnmanaged(u8),
-
-    pub fn eql(self: @This(), a_slice: []const u8, b: StringLiteralContext.Key) bool {
-        const b_slice = self.bytes.items[b.index..][0..b.len];
-        return mem.eql(u8, a_slice, b_slice);
-    }
-
-    pub fn hash(self: @This(), adapted_key: []const u8) u64 {
-        _ = self;
-        return std.hash_map.hashString(adapted_key);
-    }
-};
-
 const MonomorphedFuncsSet = std.HashMapUnmanaged(
     Fn.Index,
     void,
@@ -660,14 +620,8 @@ pub const Decl = struct {
             }
             mod.destroyFunc(func);
         }
+        _ = mod.memoized_decls.remove(decl.val.ip_index);
         if (decl.value_arena) |value_arena| {
-            if (decl.owns_tv) {
-                if (decl.val.castTag(.str_lit)) |str_lit| {
-                    mod.string_literal_table.getPtrContext(str_lit.data, .{
-                        .bytes = &mod.string_literal_bytes,
-                    }).?.* = .none;
-                }
-            }
             value_arena.deinit(gpa);
             decl.value_arena = null;
             decl.has_tv = false;
@@ -834,7 +788,7 @@ pub const Decl = struct {
     pub fn getStructIndex(decl: Decl, mod: *Module) Struct.OptionalIndex {
         if (!decl.owns_tv) return .none;
         if (decl.val.ip_index == .none) return .none;
-        return mod.intern_pool.indexToStructType(decl.val.ip_index);
+        return mod.intern_pool.indexToStructType(decl.val.toIntern());
     }
 
     /// If the Decl has a value and it is a union, return it,
@@ -875,7 +829,7 @@ pub const Decl = struct {
         return switch (decl.val.ip_index) {
             .empty_struct_type => .none,
             .none => .none,
-            else => switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(decl.val.toIntern())) {
                 .opaque_type => |opaque_type| opaque_type.namespace.toOptional(),
                 .struct_type => |struct_type| struct_type.namespace,
                 .union_type => |union_type| mod.unionPtr(union_type.index).namespace.toOptional(),
@@ -919,7 +873,7 @@ pub const Decl = struct {
 
     pub fn isExtern(decl: Decl, mod: *Module) bool {
         assert(decl.has_tv);
-        return switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(decl.val.toIntern())) {
             .variable => |variable| variable.is_extern,
             .extern_func => true,
             else => false,
@@ -1577,11 +1531,11 @@ pub const Fn = struct {
             ip: *InternPool,
             gpa: Allocator,
         ) !void {
-            switch (err_set_ty.ip_index) {
+            switch (err_set_ty.toIntern()) {
                 .anyerror_type => {
                     self.is_anyerror = true;
                 },
-                else => switch (ip.indexToKey(err_set_ty.ip_index)) {
+                else => switch (ip.indexToKey(err_set_ty.toIntern())) {
                     .error_set_type => |error_set_type| {
                         for (error_set_type.names) |name| {
                             try self.errors.put(gpa, name, {});
@@ -3396,8 +3350,7 @@ pub fn deinit(mod: *Module) void {
     mod.namespaces_free_list.deinit(gpa);
     mod.allocated_namespaces.deinit(gpa);
 
-    mod.string_literal_table.deinit(gpa);
-    mod.string_literal_bytes.deinit(gpa);
+    mod.memoized_decls.deinit(gpa);
 
     mod.intern_pool.deinit(gpa);
 }
@@ -4702,7 +4655,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         return true;
     }
 
-    if (mod.intern_pool.indexToFunc(decl_tv.val.ip_index).unwrap()) |func_index| {
+    if (mod.intern_pool.indexToFunc(decl_tv.val.toIntern()).unwrap()) |func_index| {
         const func = mod.funcPtr(func_index);
         const owns_tv = func.owner_decl == decl_index;
         if (owns_tv) {
@@ -4749,10 +4702,10 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
     decl.owns_tv = false;
     var queue_linker_work = false;
     var is_extern = false;
-    switch (decl_tv.val.ip_index) {
+    switch (decl_tv.val.toIntern()) {
         .generic_poison => unreachable,
         .unreachable_value => unreachable,
-        else => switch (mod.intern_pool.indexToKey(decl_tv.val.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(decl_tv.val.toIntern())) {
             .variable => |variable| if (variable.decl == decl_index) {
                 decl.owns_tv = true;
                 queue_linker_work = true;
@@ -4792,7 +4745,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         break :blk (try decl_arena_allocator.dupeZ(u8, bytes)).ptr;
     };
     decl.@"addrspace" = blk: {
-        const addrspace_ctx: Sema.AddressSpaceContext = switch (mod.intern_pool.indexToKey(decl_tv.val.ip_index)) {
+        const addrspace_ctx: Sema.AddressSpaceContext = switch (mod.intern_pool.indexToKey(decl_tv.val.toIntern())) {
             .variable => .variable,
             .extern_func, .func => .function,
             else => .constant,
@@ -6497,40 +6450,33 @@ pub fn populateTestFunctions(
     const array_decl_index = d: {
         // Add mod.test_functions to an array decl then make the test_functions
         // decl reference it as a slice.
-        var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
-        errdefer new_decl_arena.deinit();
-        const arena = new_decl_arena.allocator();
-
-        const test_fn_vals = try arena.alloc(Value, mod.test_functions.count());
-        const array_decl_index = try mod.createAnonymousDeclFromDecl(decl, decl.src_namespace, null, .{
-            .ty = try mod.arrayType(.{
-                .len = test_fn_vals.len,
-                .child = test_fn_ty.ip_index,
-                .sentinel = .none,
-            }),
-            .val = try Value.Tag.aggregate.create(arena, test_fn_vals),
-        });
-        const array_decl = mod.declPtr(array_decl_index);
+        const test_fn_vals = try gpa.alloc(InternPool.Index, mod.test_functions.count());
+        defer gpa.free(test_fn_vals);
 
         // Add a dependency on each test name and function pointer.
-        try array_decl.dependencies.ensureUnusedCapacity(gpa, test_fn_vals.len * 2);
+        var array_decl_dependencies = std.ArrayListUnmanaged(Decl.Index){};
+        defer array_decl_dependencies.deinit(gpa);
+        try array_decl_dependencies.ensureUnusedCapacity(gpa, test_fn_vals.len * 2);
 
-        for (mod.test_functions.keys(), 0..) |test_decl_index, i| {
+        for (test_fn_vals, mod.test_functions.keys()) |*test_fn_val, test_decl_index| {
             const test_decl = mod.declPtr(test_decl_index);
-            const test_name_slice = mem.sliceTo(test_decl.name, 0);
             const test_name_decl_index = n: {
-                var name_decl_arena = std.heap.ArenaAllocator.init(gpa);
-                errdefer name_decl_arena.deinit();
-                const bytes = try name_decl_arena.allocator().dupe(u8, test_name_slice);
-                const test_name_decl_index = try mod.createAnonymousDeclFromDecl(array_decl, array_decl.src_namespace, null, .{
-                    .ty = try mod.arrayType(.{ .len = bytes.len, .child = .u8_type }),
-                    .val = try Value.Tag.bytes.create(name_decl_arena.allocator(), bytes),
+                const test_decl_name = mem.span(test_decl.name);
+                const test_name_decl_ty = try mod.arrayType(.{
+                    .len = test_decl_name.len,
+                    .child = .u8_type,
+                });
+                const test_name_decl_index = try mod.createAnonymousDeclFromDecl(decl, decl.src_namespace, null, .{
+                    .ty = test_name_decl_ty,
+                    .val = (try mod.intern(.{ .aggregate = .{
+                        .ty = test_name_decl_ty.toIntern(),
+                        .storage = .{ .bytes = test_decl_name },
+                    } })).toValue(),
                 });
-                try mod.declPtr(test_name_decl_index).finalizeNewArena(&name_decl_arena);
                 break :n test_name_decl_index;
             };
-            array_decl.dependencies.putAssumeCapacityNoClobber(test_decl_index, .normal);
-            array_decl.dependencies.putAssumeCapacityNoClobber(test_name_decl_index, .normal);
+            array_decl_dependencies.appendAssumeCapacity(test_decl_index);
+            array_decl_dependencies.appendAssumeCapacity(test_name_decl_index);
             try mod.linkerUpdateDecl(test_name_decl_index);
 
             const test_fn_fields = .{
@@ -6541,36 +6487,51 @@ pub fn populateTestFunctions(
                 } }),
                 // func
                 try mod.intern(.{ .ptr = .{
-                    .ty = test_decl.ty.ip_index,
+                    .ty = test_decl.ty.toIntern(),
                     .addr = .{ .decl = test_decl_index },
                 } }),
                 // async_frame_size
                 null_usize,
             };
-            test_fn_vals[i] = (try mod.intern(.{ .aggregate = .{
-                .ty = test_fn_ty.ip_index,
+            test_fn_val.* = try mod.intern(.{ .aggregate = .{
+                .ty = test_fn_ty.toIntern(),
                 .storage = .{ .elems = &test_fn_fields },
-            } })).toValue();
+            } });
+        }
+
+        const array_decl_ty = try mod.arrayType(.{
+            .len = test_fn_vals.len,
+            .child = test_fn_ty.toIntern(),
+            .sentinel = .none,
+        });
+        const array_decl_index = try mod.createAnonymousDeclFromDecl(decl, decl.src_namespace, null, .{
+            .ty = array_decl_ty,
+            .val = (try mod.intern(.{ .aggregate = .{
+                .ty = array_decl_ty.toIntern(),
+                .storage = .{ .elems = test_fn_vals },
+            } })).toValue(),
+        });
+        for (array_decl_dependencies.items) |array_decl_dependency| {
+            try mod.declareDeclDependency(array_decl_index, array_decl_dependency);
         }
 
-        try array_decl.finalizeNewArena(&new_decl_arena);
         break :d array_decl_index;
     };
     try mod.linkerUpdateDecl(array_decl_index);
 
     {
         const new_ty = try mod.ptrType(.{
-            .elem_type = test_fn_ty.ip_index,
+            .elem_type = test_fn_ty.toIntern(),
             .is_const = true,
             .size = .Slice,
         });
         const new_val = decl.val;
         const new_init = try mod.intern(.{ .ptr = .{
-            .ty = new_ty.ip_index,
+            .ty = new_ty.toIntern(),
             .addr = .{ .decl = array_decl_index },
-            .len = (try mod.intValue(Type.usize, mod.test_functions.count())).ip_index,
+            .len = (try mod.intValue(Type.usize, mod.test_functions.count())).toIntern(),
         } });
-        mod.intern_pool.mutateVarInit(decl.val.ip_index, new_init);
+        mod.intern_pool.mutateVarInit(decl.val.toIntern(), new_init);
 
         // Since we are replacing the Decl's value we must perform cleanup on the
         // previous value.
@@ -6650,47 +6611,32 @@ fn reportRetryableFileError(
 }
 
 pub fn markReferencedDeclsAlive(mod: *Module, val: Value) void {
-    switch (val.ip_index) {
-        .none => switch (val.tag()) {
-            .aggregate => {
-                for (val.castTag(.aggregate).?.data) |field_val| {
-                    mod.markReferencedDeclsAlive(field_val);
-                }
-            },
-            .@"union" => {
-                const data = val.castTag(.@"union").?.data;
-                mod.markReferencedDeclsAlive(data.tag);
-                mod.markReferencedDeclsAlive(data.val);
-            },
-            else => {},
+    switch (mod.intern_pool.indexToKey(val.toIntern())) {
+        .variable => |variable| mod.markDeclIndexAlive(variable.decl),
+        .extern_func => |extern_func| mod.markDeclIndexAlive(extern_func.decl),
+        .func => |func| mod.markDeclIndexAlive(mod.funcPtr(func.index).owner_decl),
+        .error_union => |error_union| switch (error_union.val) {
+            .err_name => {},
+            .payload => |payload| mod.markReferencedDeclsAlive(payload.toValue()),
         },
-        else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
-            .variable => |variable| mod.markDeclIndexAlive(variable.decl),
-            .extern_func => |extern_func| mod.markDeclIndexAlive(extern_func.decl),
-            .func => |func| mod.markDeclIndexAlive(mod.funcPtr(func.index).owner_decl),
-            .error_union => |error_union| switch (error_union.val) {
-                .err_name => {},
-                .payload => |payload| mod.markReferencedDeclsAlive(payload.toValue()),
-            },
-            .ptr => |ptr| {
-                switch (ptr.addr) {
-                    .decl => |decl| mod.markDeclIndexAlive(decl),
-                    .mut_decl => |mut_decl| mod.markDeclIndexAlive(mut_decl.decl),
-                    .int, .comptime_field => {},
-                    .eu_payload, .opt_payload => |parent| mod.markReferencedDeclsAlive(parent.toValue()),
-                    .elem, .field => |base_index| mod.markReferencedDeclsAlive(base_index.base.toValue()),
-                }
-                if (ptr.len != .none) mod.markReferencedDeclsAlive(ptr.len.toValue());
-            },
-            .opt => |opt| if (opt.val != .none) mod.markReferencedDeclsAlive(opt.val.toValue()),
-            .aggregate => |aggregate| for (aggregate.storage.values()) |elem|
-                mod.markReferencedDeclsAlive(elem.toValue()),
-            .un => |un| {
-                mod.markReferencedDeclsAlive(un.tag.toValue());
-                mod.markReferencedDeclsAlive(un.val.toValue());
-            },
-            else => {},
+        .ptr => |ptr| {
+            switch (ptr.addr) {
+                .decl => |decl| mod.markDeclIndexAlive(decl),
+                .mut_decl => |mut_decl| mod.markDeclIndexAlive(mut_decl.decl),
+                .int, .comptime_field => {},
+                .eu_payload, .opt_payload => |parent| mod.markReferencedDeclsAlive(parent.toValue()),
+                .elem, .field => |base_index| mod.markReferencedDeclsAlive(base_index.base.toValue()),
+            }
+            if (ptr.len != .none) mod.markReferencedDeclsAlive(ptr.len.toValue());
+        },
+        .opt => |opt| if (opt.val != .none) mod.markReferencedDeclsAlive(opt.val.toValue()),
+        .aggregate => |aggregate| for (aggregate.storage.values()) |elem|
+            mod.markReferencedDeclsAlive(elem.toValue()),
+        .un => |un| {
+            mod.markReferencedDeclsAlive(un.tag.toValue());
+            mod.markReferencedDeclsAlive(un.val.toValue());
         },
+        else => {},
     }
 }
 
@@ -6796,11 +6742,11 @@ pub fn ptrType(mod: *Module, info: InternPool.Key.PtrType) Allocator.Error!Type
 }
 
 pub fn singleMutPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
-    return ptrType(mod, .{ .elem_type = child_type.ip_index });
+    return ptrType(mod, .{ .elem_type = child_type.toIntern() });
 }
 
 pub fn singleConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
-    return ptrType(mod, .{ .elem_type = child_type.ip_index, .is_const = true });
+    return ptrType(mod, .{ .elem_type = child_type.toIntern(), .is_const = true });
 }
 
 pub fn adjustPtrTypeChild(mod: *Module, ptr_ty: Type, new_child: Type) Allocator.Error!Type {
@@ -6871,9 +6817,9 @@ pub fn errorSetFromUnsortedNames(
 pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
     if (ty.isPtrLikeOptional(mod)) {
         const i = try intern(mod, .{ .opt = .{
-            .ty = ty.ip_index,
+            .ty = ty.toIntern(),
             .val = try intern(mod, .{ .ptr = .{
-                .ty = ty.childType(mod).ip_index,
+                .ty = ty.childType(mod).toIntern(),
                 .addr = .{ .int = try intern(mod, .{ .int = .{
                     .ty = .usize_type,
                     .storage = .{ .u64 = x },
@@ -6890,7 +6836,7 @@ pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
 pub fn ptrIntValue_ptronly(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
     assert(ty.zigTypeTag(mod) == .Pointer);
     const i = try intern(mod, .{ .ptr = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .addr = .{ .int = try intern(mod, .{ .int = .{
             .ty = .usize_type,
             .storage = .{ .u64 = x },
@@ -6906,7 +6852,7 @@ pub fn enumValue(mod: *Module, ty: Type, tag_int: InternPool.Index) Allocator.Er
         assert(tag == .Enum);
     }
     const i = try intern(mod, .{ .enum_tag = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .int = tag_int,
     } });
     return i.toValue();
@@ -6917,12 +6863,12 @@ pub fn enumValue(mod: *Module, ty: Type, tag_int: InternPool.Index) Allocator.Er
 pub fn enumValueFieldIndex(mod: *Module, ty: Type, field_index: u32) Allocator.Error!Value {
     const ip = &mod.intern_pool;
     const gpa = mod.gpa;
-    const enum_type = ip.indexToKey(ty.ip_index).enum_type;
+    const enum_type = ip.indexToKey(ty.toIntern()).enum_type;
 
     if (enum_type.values.len == 0) {
         // Auto-numbered fields.
         return (try ip.get(gpa, .{ .enum_tag = .{
-            .ty = ty.ip_index,
+            .ty = ty.toIntern(),
             .int = try ip.get(gpa, .{ .int = .{
                 .ty = enum_type.tag_ty,
                 .storage = .{ .u64 = field_index },
@@ -6931,7 +6877,7 @@ pub fn enumValueFieldIndex(mod: *Module, ty: Type, field_index: u32) Allocator.E
     }
 
     return (try ip.get(gpa, .{ .enum_tag = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .int = enum_type.values[field_index],
     } })).toValue();
 }
@@ -6950,7 +6896,7 @@ pub fn intValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value {
 
 pub fn intValue_big(mod: *Module, ty: Type, x: BigIntConst) Allocator.Error!Value {
     const i = try intern(mod, .{ .int = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .storage = .{ .big_int = x },
     } });
     return i.toValue();
@@ -6958,7 +6904,7 @@ pub fn intValue_big(mod: *Module, ty: Type, x: BigIntConst) Allocator.Error!Valu
 
 pub fn intValue_u64(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
     const i = try intern(mod, .{ .int = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .storage = .{ .u64 = x },
     } });
     return i.toValue();
@@ -6966,7 +6912,7 @@ pub fn intValue_u64(mod: *Module, ty: Type, x: u64) Allocator.Error!Value {
 
 pub fn intValue_i64(mod: *Module, ty: Type, x: i64) Allocator.Error!Value {
     const i = try intern(mod, .{ .int = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .storage = .{ .i64 = x },
     } });
     return i.toValue();
@@ -6974,9 +6920,9 @@ pub fn intValue_i64(mod: *Module, ty: Type, x: i64) Allocator.Error!Value {
 
 pub fn unionValue(mod: *Module, union_ty: Type, tag: Value, val: Value) Allocator.Error!Value {
     const i = try intern(mod, .{ .un = .{
-        .ty = union_ty.ip_index,
-        .tag = tag.ip_index,
-        .val = val.ip_index,
+        .ty = union_ty.toIntern(),
+        .tag = tag.toIntern(),
+        .val = val.toIntern(),
     } });
     return i.toValue();
 }
@@ -6993,7 +6939,7 @@ pub fn floatValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value {
         else => unreachable,
     };
     const i = try intern(mod, .{ .float = .{
-        .ty = ty.ip_index,
+        .ty = ty.toIntern(),
         .storage = storage,
     } });
     return i.toValue();
@@ -7001,9 +6947,9 @@ pub fn floatValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value {
 
 pub fn nullValue(mod: *Module, opt_ty: Type) Allocator.Error!Value {
     const ip = &mod.intern_pool;
-    assert(ip.isOptionalType(opt_ty.ip_index));
+    assert(ip.isOptionalType(opt_ty.toIntern()));
     const result = try ip.get(mod.gpa, .{ .opt = .{
-        .ty = opt_ty.ip_index,
+        .ty = opt_ty.toIntern(),
         .val = .none,
     } });
     return result.toValue();
@@ -7042,7 +6988,7 @@ pub fn intFittingRange(mod: *Module, min: Value, max: Value) !Type {
 pub fn intBitsForValue(mod: *Module, val: Value, sign: bool) u16 {
     assert(!val.isUndef(mod));
 
-    const key = mod.intern_pool.indexToKey(val.ip_index);
+    const key = mod.intern_pool.indexToKey(val.toIntern());
     switch (key.int.storage) {
         .i64 => |x| {
             if (std.math.cast(u64, x)) |casted| return Type.smallestUnsignedBits(casted);
@@ -7221,19 +7167,19 @@ pub fn namespaceDeclIndex(mod: *Module, namespace_index: Namespace.Index) Decl.I
 /// * Not a struct.
 pub fn typeToStruct(mod: *Module, ty: Type) ?*Struct {
     if (ty.ip_index == .none) return null;
-    const struct_index = mod.intern_pool.indexToStructType(ty.ip_index).unwrap() orelse return null;
+    const struct_index = mod.intern_pool.indexToStructType(ty.toIntern()).unwrap() orelse return null;
     return mod.structPtr(struct_index);
 }
 
 pub fn typeToUnion(mod: *Module, ty: Type) ?*Union {
     if (ty.ip_index == .none) return null;
-    const union_index = mod.intern_pool.indexToUnionType(ty.ip_index).unwrap() orelse return null;
+    const union_index = mod.intern_pool.indexToUnionType(ty.toIntern()).unwrap() orelse return null;
     return mod.unionPtr(union_index);
 }
 
 pub fn typeToFunc(mod: *Module, ty: Type) ?InternPool.Key.FuncType {
     if (ty.ip_index == .none) return null;
-    return mod.intern_pool.indexToFuncType(ty.ip_index);
+    return mod.intern_pool.indexToFuncType(ty.toIntern());
 }
 
 pub fn typeToInferredErrorSet(mod: *Module, ty: Type) ?*Fn.InferredErrorSet {
@@ -7243,7 +7189,7 @@ pub fn typeToInferredErrorSet(mod: *Module, ty: Type) ?*Fn.InferredErrorSet {
 
 pub fn typeToInferredErrorSetIndex(mod: *Module, ty: Type) Fn.InferredErrorSet.OptionalIndex {
     if (ty.ip_index == .none) return .none;
-    return mod.intern_pool.indexToInferredErrorSetType(ty.ip_index);
+    return mod.intern_pool.indexToInferredErrorSetType(ty.toIntern());
 }
 
 pub fn fieldSrcLoc(mod: *Module, owner_decl_index: Decl.Index, query: FieldSrcQuery) SrcLoc {
@@ -7268,5 +7214,5 @@ pub fn fieldSrcLoc(mod: *Module, owner_decl_index: Decl.Index, query: FieldSrcQu
 }
 
 pub fn toEnum(mod: *Module, comptime E: type, val: Value) E {
-    return mod.intern_pool.toEnum(E, val.ip_index);
+    return mod.intern_pool.toEnum(E, val.toIntern());
 }
src/print_air.zig
@@ -95,7 +95,7 @@ const Writer = struct {
         for (w.air.instructions.items(.tag), 0..) |tag, i| {
             const inst = @intCast(Air.Inst.Index, i);
             switch (tag) {
-                .constant, .const_ty, .interned => {
+                .constant, .interned => {
                     try w.writeInst(s, inst);
                     try s.writeByte('\n');
                 },
@@ -226,7 +226,6 @@ const Writer = struct {
             .save_err_return_trace_index,
             => try w.writeNoOp(s, inst),
 
-            .const_ty,
             .alloc,
             .ret_ptr,
             .err_return_trace,
src/Sema.zig
@@ -1866,9 +1866,9 @@ fn resolveConstMaybeUndefVal(
     reason: []const u8,
 ) CompileError!Value {
     if (try sema.resolveMaybeUndefValAllowVariables(inst)) |val| {
-        switch (val.ip_index) {
+        switch (val.toIntern()) {
             .generic_poison => return error.GenericPoison,
-            else => switch (sema.mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (sema.mod.intern_pool.indexToKey(val.toIntern())) {
                 .variable => return sema.failWithNeededComptime(block, src, reason),
                 else => return val,
             },
@@ -1887,10 +1887,10 @@ fn resolveConstValue(
     reason: []const u8,
 ) CompileError!Value {
     if (try sema.resolveMaybeUndefValAllowVariables(air_ref)) |val| {
-        switch (val.ip_index) {
+        switch (val.toIntern()) {
             .generic_poison => return error.GenericPoison,
             .undef => return sema.failWithUseOfUndef(block, src),
-            else => switch (sema.mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (sema.mod.intern_pool.indexToKey(val.toIntern())) {
                 .undef => return sema.failWithUseOfUndef(block, src),
                 .variable => return sema.failWithNeededComptime(block, src, reason),
                 else => return val,
@@ -1930,7 +1930,7 @@ fn resolveMaybeUndefVal(
     switch (val.ip_index) {
         .generic_poison => return error.GenericPoison,
         .none => return val,
-        else => switch (sema.mod.intern_pool.indexToKey(val.ip_index)) {
+        else => switch (sema.mod.intern_pool.indexToKey(val.toIntern())) {
             .variable => return null,
             else => return val,
         },
@@ -1950,7 +1950,7 @@ fn resolveMaybeUndefValIntable(
     while (true) switch (check.ip_index) {
         .generic_poison => return error.GenericPoison,
         .none => break,
-        else => switch (sema.mod.intern_pool.indexToKey(check.ip_index)) {
+        else => switch (sema.mod.intern_pool.indexToKey(check.toIntern())) {
             .variable => return null,
             .ptr => |ptr| switch (ptr.addr) {
                 .decl, .mut_decl, .comptime_field => return null,
@@ -2007,7 +2007,6 @@ fn resolveMaybeUndefValAllowVariablesMaybeRuntime(
             if (val.isPtrToThreadLocal(sema.mod)) make_runtime.* = true;
             return val;
         },
-        .const_ty => return air_datas[i].ty.toValue(),
         .interned => return air_datas[i].interned.toValue(),
         else => return null,
     }
@@ -2490,7 +2489,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
                     });
                     try sema.maybeQueueFuncBodyAnalysis(iac.data.decl_index);
                     return sema.addConstant(ptr_ty, (try sema.mod.intern(.{ .ptr = .{
-                        .ty = ptr_ty.ip_index,
+                        .ty = ptr_ty.toIntern(),
                         .addr = .{ .mut_decl = .{
                             .decl = iac.data.decl_index,
                             .runtime_index = block.runtime_index,
@@ -2988,7 +2987,7 @@ fn zirEnumDecl(
             if (ty.zigTypeTag(mod) != .Int and ty.zigTypeTag(mod) != .ComptimeInt) {
                 return sema.fail(block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(sema.mod)});
             }
-            incomplete_enum.setTagType(&mod.intern_pool, ty.ip_index);
+            incomplete_enum.setTagType(&mod.intern_pool, ty.toIntern());
             break :ty ty;
         } else if (fields_len == 0) {
             break :ty try mod.intType(.unsigned, 0);
@@ -2998,7 +2997,7 @@ fn zirEnumDecl(
         }
     };
 
-    if (small.nonexhaustive and int_tag_ty.ip_index != .comptime_int_type) {
+    if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) {
         if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(mod)) {
             return sema.fail(block, src, "non-exhaustive enum specifies every value", .{});
         }
@@ -3051,7 +3050,7 @@ fn zirEnumDecl(
                 else => |e| return e,
             };
             last_tag_val = tag_val;
-            if (try incomplete_enum.addFieldValue(&mod.intern_pool, gpa, tag_val.ip_index)) |other_index| {
+            if (try incomplete_enum.addFieldValue(&mod.intern_pool, gpa, tag_val.toIntern())) |other_index| {
                 const value_src = mod.fieldSrcLoc(new_decl_index, .{
                     .index = field_i,
                     .range = .value,
@@ -3071,7 +3070,7 @@ fn zirEnumDecl(
             else
                 try mod.intValue(int_tag_ty, 0);
             last_tag_val = tag_val;
-            if (try incomplete_enum.addFieldValue(&mod.intern_pool, gpa, tag_val.ip_index)) |other_index| {
+            if (try incomplete_enum.addFieldValue(&mod.intern_pool, gpa, tag_val.toIntern())) |other_index| {
                 const field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = field_i }).lazy;
                 const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = other_index }).lazy;
                 const msg = msg: {
@@ -3742,7 +3741,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
 
             try sema.maybeQueueFuncBodyAnalysis(decl_index);
             sema.air_values.items[value_index] = (try sema.mod.intern(.{ .ptr = .{
-                .ty = final_ptr_ty.ip_index,
+                .ty = final_ptr_ty.toIntern(),
                 .addr = if (var_is_mut) .{ .mut_decl = .{
                     .decl = decl_index,
                     .runtime_index = block.runtime_index,
@@ -3842,7 +3841,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
                 block.instructions.shrinkRetainingCapacity(search_index);
                 try sema.maybeQueueFuncBodyAnalysis(new_decl_index);
                 sema.air_values.items[value_index] = (try sema.mod.intern(.{ .ptr = .{
-                    .ty = final_elem_ty.ip_index,
+                    .ty = final_elem_ty.toIntern(),
                     .addr = .{ .decl = new_decl_index },
                 } })).toValue();
                 // if bitcast ty ref needs to be made const, make_ptr_const
@@ -4341,12 +4340,12 @@ fn validateUnionInit(
         block.instructions.shrinkRetainingCapacity(first_block_index);
 
         var union_val = try mod.intern(.{ .un = .{
-            .ty = union_ty.ip_index,
-            .tag = tag_val.ip_index,
-            .val = val.ip_index,
+            .ty = union_ty.toIntern(),
+            .tag = tag_val.toIntern(),
+            .val = val.toIntern(),
         } });
         if (make_runtime) union_val = try mod.intern(.{ .runtime_value = .{
-            .ty = union_ty.ip_index,
+            .ty = union_ty.toIntern(),
             .val = union_val,
         } });
         const union_init = try sema.addConstant(union_ty, union_val.toValue());
@@ -4417,7 +4416,7 @@ fn validateStructInit(
             if (field_ptr != 0) continue;
 
             const default_val = struct_ty.structFieldDefaultValue(i, mod);
-            if (default_val.ip_index == .unreachable_value) {
+            if (default_val.toIntern() == .unreachable_value) {
                 if (struct_ty.isTuple(mod)) {
                     const template = "missing tuple field with index {d}";
                     if (root_msg) |msg| {
@@ -4476,15 +4475,14 @@ fn validateStructInit(
 
     // We collect the comptime field values in case the struct initialization
     // ends up being comptime-known.
-    const field_values = try sema.gpa.alloc(InternPool.Index, struct_ty.structFieldCount(mod));
-    defer sema.gpa.free(field_values);
+    const field_values = try sema.arena.alloc(InternPool.Index, struct_ty.structFieldCount(mod));
 
     field: for (found_fields, 0..) |field_ptr, i| {
         if (field_ptr != 0) {
             // Determine whether the value stored to this pointer is comptime-known.
             const field_ty = struct_ty.structFieldType(i, mod);
             if (try sema.typeHasOnePossibleValue(field_ty)) |opv| {
-                field_values[i] = opv.ip_index;
+                field_values[i] = opv.toIntern();
                 continue;
             }
 
@@ -4549,7 +4547,7 @@ fn validateStructInit(
                     first_block_index = @min(first_block_index, block_index);
                 }
                 if (try sema.resolveMaybeUndefValAllowVariablesMaybeRuntime(bin_op.rhs, &make_runtime)) |val| {
-                    field_values[i] = val.ip_index;
+                    field_values[i] = val.toIntern();
                 } else if (require_comptime) {
                     const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
                     return sema.failWithNeededComptime(block, field_ptr_data.src(), "initializer of comptime only struct must be comptime-known");
@@ -4563,7 +4561,7 @@ fn validateStructInit(
         }
 
         const default_val = struct_ty.structFieldDefaultValue(i, mod);
-        if (default_val.ip_index == .unreachable_value) {
+        if (default_val.toIntern() == .unreachable_value) {
             if (struct_ty.isTuple(mod)) {
                 const template = "missing tuple field with index {d}";
                 if (root_msg) |msg| {
@@ -4583,7 +4581,7 @@ fn validateStructInit(
             }
             continue;
         }
-        field_values[i] = default_val.ip_index;
+        field_values[i] = default_val.toIntern();
     }
 
     if (root_msg) |msg| {
@@ -4607,11 +4605,11 @@ fn validateStructInit(
 
         block.instructions.shrinkRetainingCapacity(first_block_index);
         var struct_val = try mod.intern(.{ .aggregate = .{
-            .ty = struct_ty.ip_index,
+            .ty = struct_ty.toIntern(),
             .storage = .{ .elems = field_values },
         } });
         if (make_runtime) struct_val = try mod.intern(.{ .runtime_value = .{
-            .ty = struct_ty.ip_index,
+            .ty = struct_ty.toIntern(),
             .val = struct_val,
         } });
         const struct_init = try sema.addConstant(struct_ty, struct_val.toValue());
@@ -4659,7 +4657,7 @@ fn zirValidateArrayInit(
             var i = instrs.len;
             while (i < array_len) : (i += 1) {
                 const default_val = array_ty.structFieldDefaultValue(i, mod);
-                if (default_val.ip_index == .unreachable_value) {
+                if (default_val.toIntern() == .unreachable_value) {
                     const template = "missing tuple field with index {d}";
                     if (root_msg) |msg| {
                         try sema.errNote(block, init_src, msg, template, .{i});
@@ -4710,8 +4708,7 @@ fn zirValidateArrayInit(
     // Collect the comptime element values in case the array literal ends up
     // being comptime-known.
     const array_len_s = try sema.usizeCast(block, init_src, array_ty.arrayLenIncludingSentinel(mod));
-    const element_vals = try sema.gpa.alloc(InternPool.Index, array_len_s);
-    defer sema.gpa.free(element_vals);
+    const element_vals = try sema.arena.alloc(InternPool.Index, array_len_s);
     const opt_opv = try sema.typeHasOnePossibleValue(array_ty);
     const air_tags = sema.air_instructions.items(.tag);
     const air_datas = sema.air_instructions.items(.data);
@@ -4721,13 +4718,13 @@ fn zirValidateArrayInit(
 
         if (array_ty.isTuple(mod)) {
             if (try array_ty.structFieldValueComptime(mod, i)) |opv| {
-                element_vals[i] = opv.ip_index;
+                element_vals[i] = opv.toIntern();
                 continue;
             }
         } else {
             // Array has one possible value, so value is always comptime-known
             if (opt_opv) |opv| {
-                element_vals[i] = opv.ip_index;
+                element_vals[i] = opv.toIntern();
                 continue;
             }
         }
@@ -4788,7 +4785,7 @@ fn zirValidateArrayInit(
                 first_block_index = @min(first_block_index, block_index);
             }
             if (try sema.resolveMaybeUndefValAllowVariablesMaybeRuntime(bin_op.rhs, &make_runtime)) |val| {
-                element_vals[i] = val.ip_index;
+                element_vals[i] = val.toIntern();
             } else {
                 array_is_comptime = false;
             }
@@ -4800,7 +4797,7 @@ fn zirValidateArrayInit(
 
     if (array_is_comptime) {
         if (try sema.resolveDefinedValue(block, init_src, array_ptr)) |ptr_val| {
-            switch (mod.intern_pool.indexToKey(ptr_val.ip_index)) {
+            switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) {
                 .ptr => |ptr| switch (ptr.addr) {
                     .comptime_field => return, // This store was validated by the individual elem ptrs.
                     else => {},
@@ -4813,17 +4810,17 @@ fn zirValidateArrayInit(
         // instead a single `store` to the array_ptr with a comptime struct value.
         // Also to populate the sentinel value, if any.
         if (array_ty.sentinel(mod)) |sentinel_val| {
-            element_vals[instrs.len] = sentinel_val.ip_index;
+            element_vals[instrs.len] = sentinel_val.toIntern();
         }
 
         block.instructions.shrinkRetainingCapacity(first_block_index);
 
         var array_val = try mod.intern(.{ .aggregate = .{
-            .ty = array_ty.ip_index,
+            .ty = array_ty.toIntern(),
             .storage = .{ .elems = element_vals },
         } });
         if (make_runtime) array_val = try mod.intern(.{ .runtime_value = .{
-            .ty = array_ty.ip_index,
+            .ty = array_ty.toIntern(),
             .val = array_val,
         } });
         const array_init = try sema.addConstant(array_ty, array_val.toValue());
@@ -5144,41 +5141,26 @@ fn addStrLit(sema: *Sema, block: *Block, zir_bytes: []const u8) CompileError!Air
     // expression of a variable declaration.
     const mod = sema.mod;
     const gpa = sema.gpa;
-    const string_bytes = &mod.string_literal_bytes;
-    const StringLiteralAdapter = Module.StringLiteralAdapter;
-    const StringLiteralContext = Module.StringLiteralContext;
-    try string_bytes.ensureUnusedCapacity(gpa, zir_bytes.len);
-    const gop = try mod.string_literal_table.getOrPutContextAdapted(gpa, zir_bytes, StringLiteralAdapter{
-        .bytes = string_bytes,
-    }, StringLiteralContext{
-        .bytes = string_bytes,
+    const ty = try mod.arrayType(.{
+        .len = zir_bytes.len,
+        .child = .u8_type,
+        .sentinel = .zero_u8,
     });
+    const val = try mod.intern(.{ .aggregate = .{
+        .ty = ty.toIntern(),
+        .storage = .{ .bytes = zir_bytes },
+    } });
+    const gop = try mod.memoized_decls.getOrPut(gpa, val);
     if (!gop.found_existing) {
-        gop.key_ptr.* = .{
-            .index = @intCast(u32, string_bytes.items.len),
-            .len = @intCast(u32, zir_bytes.len),
-        };
-        string_bytes.appendSliceAssumeCapacity(zir_bytes);
-        gop.value_ptr.* = .none;
-    }
-    const decl_index = gop.value_ptr.unwrap() orelse di: {
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
 
-        const decl_index = try anon_decl.finish(
-            try Type.array(anon_decl.arena(), gop.key_ptr.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-            try Value.Tag.str_lit.create(anon_decl.arena(), gop.key_ptr.*),
-            0, // default alignment
-        );
-
-        // Needed so that `Decl.clearValues` will additionally set the corresponding
-        // string literal table value back to `Decl.OptionalIndex.none`.
-        mod.declPtr(decl_index).owns_tv = true;
+        const decl_index = try anon_decl.finish(ty, val.toValue(), 0);
 
-        gop.value_ptr.* = decl_index.toOptional();
-        break :di decl_index;
-    };
-    return sema.analyzeDeclRef(decl_index);
+        gop.key_ptr.* = val;
+        gop.value_ptr.* = decl_index;
+    }
+    return sema.analyzeDeclRef(gop.value_ptr.*);
 }
 
 fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -6218,7 +6200,7 @@ fn funcDeclSrc(sema: *Sema, func_inst: Air.Inst.Ref) !?*Decl {
     const mod = sema.mod;
     const func_val = (try sema.resolveMaybeUndefVal(func_inst)) orelse return null;
     if (func_val.isUndef(mod)) return null;
-    const owner_decl_index = switch (mod.intern_pool.indexToKey(func_val.ip_index)) {
+    const owner_decl_index = switch (mod.intern_pool.indexToKey(func_val.toIntern())) {
         .extern_func => |extern_func| extern_func.decl,
         .func => |func| mod.funcPtr(func.index).owner_decl,
         .ptr => |ptr| switch (ptr.addr) {
@@ -6792,7 +6774,7 @@ fn analyzeCall(
             if (err == error.AnalysisFail and comptime_reason != null) try comptime_reason.?.explain(sema, sema.err);
             return err;
         };
-        const module_fn_index = switch (mod.intern_pool.indexToKey(func_val.ip_index)) {
+        const module_fn_index = switch (mod.intern_pool.indexToKey(func_val.toIntern())) {
             .extern_func => return sema.fail(block, call_src, "{s} call of extern function", .{
                 @as([]const u8, if (is_comptime_call) "comptime" else "inline"),
             }),
@@ -6996,7 +6978,7 @@ fn analyzeCall(
             }
             break :blk bare_return_type;
         };
-        new_fn_info.return_type = fn_ret_ty.ip_index;
+        new_fn_info.return_type = fn_ret_ty.toIntern();
         const parent_fn_ret_ty = sema.fn_ret_ty;
         sema.fn_ret_ty = fn_ret_ty;
         defer sema.fn_ret_ty = parent_fn_ret_ty;
@@ -7289,7 +7271,7 @@ fn analyzeInlineCallArg(
                     if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);
                     return err;
                 };
-                switch (arg_val.ip_index) {
+                switch (arg_val.toIntern()) {
                     .generic_poison, .generic_poison_type => {
                         // This function is currently evaluated as part of an as-of-yet unresolvable
                         // parameter or return type.
@@ -7328,7 +7310,7 @@ fn analyzeInlineCallArg(
                     if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);
                     return err;
                 };
-                switch (arg_val.ip_index) {
+                switch (arg_val.toIntern()) {
                     .generic_poison, .generic_poison_type => {
                         // This function is currently evaluated as part of an as-of-yet unresolvable
                         // parameter or return type.
@@ -7426,7 +7408,7 @@ fn instantiateGenericCall(
     const gpa = sema.gpa;
 
     const func_val = try sema.resolveConstValue(block, func_src, func, "generic function being called must be comptime-known");
-    const module_fn = mod.funcPtr(switch (mod.intern_pool.indexToKey(func_val.ip_index)) {
+    const module_fn = mod.funcPtr(switch (mod.intern_pool.indexToKey(func_val.toIntern())) {
         .func => |function| function.index,
         .ptr => |ptr| mod.declPtr(ptr.addr.decl).getFunctionIndex(mod).unwrap().?,
         else => unreachable,
@@ -7911,7 +7893,7 @@ fn resolveGenericInstantiationType(
     }
 
     new_decl.val = (try mod.intern(.{ .func = .{
-        .ty = new_decl.ty.ip_index,
+        .ty = new_decl.ty.toIntern(),
         .index = new_func,
     } })).toValue();
     new_decl.@"align" = 0;
@@ -7932,7 +7914,7 @@ fn resolveGenericInstantiationType(
 
 fn resolveTupleLazyValues(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void {
     const mod = sema.mod;
-    const tuple = switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+    const tuple = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
         .anon_struct_type => |tuple| tuple,
         else => return,
     };
@@ -7957,7 +7939,7 @@ fn emitDbgInline(
     if (old_func == new_func) return;
 
     try sema.air_values.append(sema.gpa, (try sema.mod.intern(.{ .func = .{
-        .ty = new_func_ty.ip_index,
+        .ty = new_func_ty.toIntern(),
         .index = new_func,
     } })).toValue());
     _ = try block.addInst(.{
@@ -8019,7 +8001,7 @@ fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
     try sema.checkVectorElemType(block, elem_type_src, elem_type);
     const vector_type = try mod.vectorType(.{
         .len = len,
-        .child = elem_type.ip_index,
+        .child = elem_type.toIntern(),
     });
     return sema.addType(vector_type);
 }
@@ -8129,7 +8111,7 @@ fn zirErrorValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
     const kv = try sema.mod.getErrorValue(name);
     const error_set_type = try mod.singleErrorSetType(kv.key);
     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
-        .ty = error_set_type.ip_index,
+        .ty = error_set_type.toIntern(),
         .name = try mod.intern_pool.getOrPutString(sema.gpa, kv.key),
     } })).toValue());
 }
@@ -8149,7 +8131,7 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
         if (val.isUndef(mod)) {
             return sema.addConstUndef(Type.err_int);
         }
-        const err_name = mod.intern_pool.indexToKey(val.ip_index).err.name;
+        const err_name = mod.intern_pool.indexToKey(val.toIntern()).err.name;
         return sema.addConstant(Type.err_int, try mod.intValue(
             Type.err_int,
             (try mod.getErrorValue(mod.intern_pool.stringToSlice(err_name))).value,
@@ -8240,7 +8222,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
         return sema.fail(block, rhs_src, "expected error set type, found '{}'", .{rhs_ty.fmt(sema.mod)});
 
     // Anything merged with anyerror is anyerror.
-    if (lhs_ty.ip_index == .anyerror_type or rhs_ty.ip_index == .anyerror_type) {
+    if (lhs_ty.toIntern() == .anyerror_type or rhs_ty.toIntern() == .anyerror_type) {
         return Air.Inst.Ref.anyerror_type;
     }
 
@@ -8445,8 +8427,8 @@ fn analyzeOptionalPayloadPtr(
                 _ = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
             }
             return sema.addConstant(child_pointer, (try mod.intern(.{ .ptr = .{
-                .ty = child_pointer.ip_index,
-                .addr = .{ .opt_payload = ptr_val.ip_index },
+                .ty = child_pointer.toIntern(),
+                .addr = .{ .opt_payload = ptr_val.toIntern() },
             } })).toValue());
         }
         if (try sema.pointerDeref(block, src, ptr_val, optional_ptr_ty)) |val| {
@@ -8455,8 +8437,8 @@ fn analyzeOptionalPayloadPtr(
             }
             // The same Value represents the pointer to the optional and the payload.
             return sema.addConstant(child_pointer, (try mod.intern(.{ .ptr = .{
-                .ty = child_pointer.ip_index,
-                .addr = .{ .opt_payload = ptr_val.ip_index },
+                .ty = child_pointer.toIntern(),
+                .addr = .{ .opt_payload = ptr_val.toIntern() },
             } })).toValue());
         }
     }
@@ -8565,7 +8547,7 @@ fn analyzeErrUnionPayload(
         }
         return sema.addConstant(
             payload_ty,
-            mod.intern_pool.indexToKey(val.ip_index).error_union.val.payload.toValue(),
+            mod.intern_pool.indexToKey(val.toIntern()).error_union.val.payload.toValue(),
         );
     }
 
@@ -8633,8 +8615,8 @@ fn analyzeErrUnionPayloadPtr(
                 _ = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
             }
             return sema.addConstant(operand_pointer_ty, (try mod.intern(.{ .ptr = .{
-                .ty = operand_pointer_ty.ip_index,
-                .addr = .{ .eu_payload = ptr_val.ip_index },
+                .ty = operand_pointer_ty.toIntern(),
+                .addr = .{ .eu_payload = ptr_val.toIntern() },
             } })).toValue());
         }
         if (try sema.pointerDeref(block, src, ptr_val, operand_ty)) |val| {
@@ -8642,8 +8624,8 @@ fn analyzeErrUnionPayloadPtr(
                 return sema.fail(block, src, "caught unexpected error '{s}'", .{name});
             }
             return sema.addConstant(operand_pointer_ty, (try mod.intern(.{ .ptr = .{
-                .ty = operand_pointer_ty.ip_index,
-                .addr = .{ .eu_payload = ptr_val.ip_index },
+                .ty = operand_pointer_ty.toIntern(),
+                .addr = .{ .eu_payload = ptr_val.toIntern() },
             } })).toValue());
         }
     }
@@ -8828,7 +8810,7 @@ fn resolveGenericBody(
     };
     switch (err) {
         error.GenericPoison => {
-            if (dest_ty.ip_index == .type_type) {
+            if (dest_ty.toIntern() == .type_type) {
                 return Value.generic_poison_type;
             } else {
                 return Value.generic_poison;
@@ -9183,7 +9165,7 @@ fn funcCommon(
 
     if (is_extern) {
         return sema.addConstant(fn_ty, (try mod.intern(.{ .extern_func = .{
-            .ty = fn_ty.ip_index,
+            .ty = fn_ty.toIntern(),
             .decl = sema.owner_decl_index,
             .lib_name = if (opt_lib_name) |lib_name| (try mod.intern_pool.getOrPutString(
                 gpa,
@@ -9223,7 +9205,7 @@ fn funcCommon(
         .is_noinline = is_noinline,
     };
     return sema.addConstant(fn_ty, (try mod.intern(.{ .func = .{
-        .ty = fn_ty.ip_index,
+        .ty = fn_ty.toIntern(),
         .index = new_func_index,
     } })).toValue());
 }
@@ -10151,16 +10133,16 @@ fn zirSwitchCapture(
                         .@"addrspace" = operand_ptr_ty.ptrAddressSpace(mod),
                     });
                     return sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{
-                        .ty = ptr_field_ty.ip_index,
+                        .ty = ptr_field_ty.toIntern(),
                         .addr = .{ .field = .{
-                            .base = union_val.ip_index,
+                            .base = union_val.toIntern(),
                             .index = field_index,
                         } },
                     } })).toValue());
                 }
                 return sema.addConstant(
                     field_ty,
-                    mod.intern_pool.indexToKey(union_val.ip_index).un.val.toValue(),
+                    mod.intern_pool.indexToKey(union_val.toIntern()).un.val.toValue(),
                 );
             }
             if (is_ref) {
@@ -10256,9 +10238,9 @@ fn zirSwitchCapture(
 
                 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
                     return sema.addConstant(field_ty_ptr, (try mod.intern(.{ .ptr = .{
-                        .ty = field_ty_ptr.ip_index,
+                        .ty = field_ty_ptr.toIntern(),
                         .addr = .{ .field = .{
-                            .base = op_ptr_val.ip_index,
+                            .base = op_ptr_val.toIntern(),
                             .index = first_field_index,
                         } },
                     } })).toValue());
@@ -11502,7 +11484,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                     cases_len += 1;
 
                     const item_val = try mod.intern(.{ .err = .{
-                        .ty = operand_ty.ip_index,
+                        .ty = operand_ty.toIntern(),
                         .name = error_name_ip,
                     } });
                     const item_ref = try sema.addConstant(operand_ty, item_val.toValue());
@@ -11802,7 +11784,7 @@ fn validateSwitchItemError(
     const ip = &sema.mod.intern_pool;
     const item_tv = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
     // TODO: Do i need to typecheck here?
-    const error_name = ip.stringToSlice(ip.indexToKey(item_tv.val.ip_index).err.name);
+    const error_name = ip.stringToSlice(ip.indexToKey(item_tv.val.toIntern()).err.name);
     const maybe_prev_src = if (try seen_errors.fetchPut(error_name, switch_prong_src)) |prev|
         prev.value
     else
@@ -12035,7 +12017,7 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const ip = &mod.intern_pool;
 
     const has_field = hf: {
-        switch (ip.indexToKey(ty.ip_index)) {
+        switch (ip.indexToKey(ty.toIntern())) {
             .ptr_type => |ptr_type| switch (ptr_type.size) {
                 .Slice => {
                     if (mem.eql(u8, field_name, "ptr")) break :hf true;
@@ -12160,17 +12142,23 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     var anon_decl = try block.startAnonDecl();
     defer anon_decl.deinit();
 
-    const bytes_including_null = embed_file.bytes[0 .. embed_file.bytes.len + 1];
-
-    // TODO instead of using `Value.Tag.bytes`, create a new value tag for pointing at
+    // TODO instead of using `.bytes`, create a new value tag for pointing at
     // a `*Module.EmbedFile`. The purpose of this would be:
     // - If only the length is read and the bytes are not inspected by comptime code,
     //   there can be an optimization where the codegen backend does a copy_file_range
     //   into the final binary, and never loads the data into memory.
     // - When a Decl is destroyed, it can free the `*Module.EmbedFile`.
+    const ty = try mod.arrayType(.{
+        .len = embed_file.bytes.len,
+        .child = .u8_type,
+        .sentinel = .zero_u8,
+    });
     embed_file.owner_decl = try anon_decl.finish(
-        try Type.array(anon_decl.arena(), embed_file.bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-        try Value.Tag.bytes.create(anon_decl.arena(), bytes_including_null),
+        ty,
+        (try mod.intern(.{ .aggregate = .{
+            .ty = ty.toIntern(),
+            .storage = .{ .bytes = embed_file.bytes },
+        } })).toValue(),
         0, // default alignment
     );
 
@@ -12186,7 +12174,7 @@ fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.R
     const kv = try mod.getErrorValue(err_name);
     const error_set_type = try mod.singleErrorSetType(kv.key);
     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
-        .ty = error_set_type.ip_index,
+        .ty = error_set_type.toIntern(),
         .name = mod.intern_pool.getString(kv.key).unwrap().?,
     } })).toValue());
 }
@@ -12597,15 +12585,15 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             return sema.addConstUndef(operand_type);
         } else if (operand_type.zigTypeTag(mod) == .Vector) {
             const vec_len = try sema.usizeCast(block, operand_src, operand_type.vectorLen(mod));
-            const elems = try sema.arena.alloc(Value, vec_len);
+            const elems = try sema.arena.alloc(InternPool.Index, vec_len);
             for (elems, 0..) |*elem, i| {
                 const elem_val = try val.elemValue(sema.mod, i);
-                elem.* = try elem_val.bitwiseNot(scalar_type, sema.arena, sema.mod);
+                elem.* = try (try elem_val.bitwiseNot(scalar_type, sema.arena, sema.mod)).intern(scalar_type, mod);
             }
-            return sema.addConstant(
-                operand_type,
-                try Value.Tag.aggregate.create(sema.arena, elems),
-            );
+            return sema.addConstant(operand_type, (try mod.intern(.{ .aggregate = .{
+                .ty = operand_type.toIntern(),
+                .storage = .{ .elems = elems },
+            } })).toValue());
         } else {
             const result_val = try val.bitwiseNot(operand_type, sema.arena, sema.mod);
             return sema.addConstant(operand_type, result_val);
@@ -12652,22 +12640,22 @@ fn analyzeTupleCat(
         var runtime_src: ?LazySrcLoc = null;
         var i: u32 = 0;
         while (i < lhs_len) : (i += 1) {
-            types[i] = lhs_ty.structFieldType(i, mod).ip_index;
+            types[i] = lhs_ty.structFieldType(i, mod).toIntern();
             const default_val = lhs_ty.structFieldDefaultValue(i, mod);
-            values[i] = default_val.ip_index;
+            values[i] = default_val.toIntern();
             const operand_src = lhs_src; // TODO better source location
-            if (default_val.ip_index == .unreachable_value) {
+            if (default_val.toIntern() == .unreachable_value) {
                 runtime_src = operand_src;
                 values[i] = .none;
             }
         }
         i = 0;
         while (i < rhs_len) : (i += 1) {
-            types[i + lhs_len] = rhs_ty.structFieldType(i, mod).ip_index;
+            types[i + lhs_len] = rhs_ty.structFieldType(i, mod).toIntern();
             const default_val = rhs_ty.structFieldDefaultValue(i, mod);
-            values[i + lhs_len] = default_val.ip_index;
+            values[i + lhs_len] = default_val.toIntern();
             const operand_src = rhs_src; // TODO better source location
-            if (default_val.ip_index == .unreachable_value) {
+            if (default_val.toIntern() == .unreachable_value) {
                 runtime_src = operand_src;
                 values[i + lhs_len] = .none;
             }
@@ -12824,34 +12812,32 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             else
                 rhs_val;
 
-            const final_len_including_sent = result_len + @boolToInt(res_sent_val != null);
-            const element_vals = try sema.arena.alloc(Value, final_len_including_sent);
+            const element_vals = try sema.arena.alloc(InternPool.Index, result_len);
             var elem_i: usize = 0;
             while (elem_i < lhs_len) : (elem_i += 1) {
                 const lhs_elem_i = elem_i;
                 const elem_ty = if (lhs_is_tuple) lhs_ty.structFieldType(lhs_elem_i, mod) else lhs_info.elem_type;
                 const elem_default_val = if (lhs_is_tuple) lhs_ty.structFieldDefaultValue(lhs_elem_i, mod) else Value.@"unreachable";
-                const elem_val = if (elem_default_val.ip_index == .unreachable_value) try lhs_sub_val.elemValue(mod, lhs_elem_i) else elem_default_val;
+                const elem_val = if (elem_default_val.toIntern() == .unreachable_value) try lhs_sub_val.elemValue(mod, lhs_elem_i) else elem_default_val;
                 const elem_val_inst = try sema.addConstant(elem_ty, elem_val);
                 const coerced_elem_val_inst = try sema.coerce(block, resolved_elem_ty, elem_val_inst, .unneeded);
                 const coerced_elem_val = try sema.resolveConstMaybeUndefVal(block, .unneeded, coerced_elem_val_inst, "");
-                element_vals[elem_i] = coerced_elem_val;
+                element_vals[elem_i] = try coerced_elem_val.intern(resolved_elem_ty, mod);
             }
             while (elem_i < result_len) : (elem_i += 1) {
                 const rhs_elem_i = elem_i - lhs_len;
                 const elem_ty = if (rhs_is_tuple) rhs_ty.structFieldType(rhs_elem_i, mod) else rhs_info.elem_type;
                 const elem_default_val = if (rhs_is_tuple) rhs_ty.structFieldDefaultValue(rhs_elem_i, mod) else Value.@"unreachable";
-                const elem_val = if (elem_default_val.ip_index == .unreachable_value) try rhs_sub_val.elemValue(mod, rhs_elem_i) else elem_default_val;
+                const elem_val = if (elem_default_val.toIntern() == .unreachable_value) try rhs_sub_val.elemValue(mod, rhs_elem_i) else elem_default_val;
                 const elem_val_inst = try sema.addConstant(elem_ty, elem_val);
                 const coerced_elem_val_inst = try sema.coerce(block, resolved_elem_ty, elem_val_inst, .unneeded);
                 const coerced_elem_val = try sema.resolveConstMaybeUndefVal(block, .unneeded, coerced_elem_val_inst, "");
-                element_vals[elem_i] = coerced_elem_val;
-            }
-            if (res_sent_val) |sent_val| {
-                element_vals[result_len] = sent_val;
+                element_vals[elem_i] = try coerced_elem_val.intern(resolved_elem_ty, mod);
             }
-            const val = try Value.Tag.aggregate.create(sema.arena, element_vals);
-            return sema.addConstantMaybeRef(block, result_ty, val, ptr_addrspace != null);
+            return sema.addConstantMaybeRef(block, result_ty, (try mod.intern(.{ .aggregate = .{
+                .ty = result_ty.toIntern(),
+                .storage = .{ .elems = element_vals },
+            } })).toValue(), ptr_addrspace != null);
         } else break :rs rhs_src;
     } else lhs_src;
 
@@ -12978,8 +12964,8 @@ fn analyzeTupleMul(
     const opt_runtime_src = rs: {
         var runtime_src: ?LazySrcLoc = null;
         for (0..tuple_len) |i| {
-            types[i] = operand_ty.structFieldType(i, mod).ip_index;
-            values[i] = operand_ty.structFieldDefaultValue(i, mod).ip_index;
+            types[i] = operand_ty.structFieldType(i, mod).toIntern();
+            values[i] = operand_ty.structFieldDefaultValue(i, mod).toIntern();
             const operand_src = lhs_src; // TODO better source location
             if (values[i] == .unreachable_value) {
                 runtime_src = operand_src;
@@ -13086,8 +13072,8 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             if (lhs_len == 1) {
                 const elem_val = try lhs_sub_val.elemValue(mod, 0);
                 break :v try mod.intern(.{ .aggregate = .{
-                    .ty = result_ty.ip_index,
-                    .storage = .{ .repeated_elem = elem_val.ip_index },
+                    .ty = result_ty.toIntern(),
+                    .storage = .{ .repeated_elem = elem_val.toIntern() },
                 } });
             }
 
@@ -13097,16 +13083,15 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 var lhs_i: usize = 0;
                 while (lhs_i < lhs_len) : (lhs_i += 1) {
                     const elem_val = try lhs_sub_val.elemValue(mod, lhs_i);
-                    assert(elem_val.ip_index != .none);
-                    element_vals[elem_i] = elem_val.ip_index;
+                    element_vals[elem_i] = elem_val.toIntern();
                     elem_i += 1;
                 }
             }
             if (lhs_info.sentinel) |sent_val| {
-                element_vals[result_len] = sent_val.ip_index;
+                element_vals[result_len] = sent_val.toIntern();
             }
             break :v try mod.intern(.{ .aggregate = .{
-                .ty = result_ty.ip_index,
+                .ty = result_ty.toIntern(),
                 .storage = .{ .elems = element_vals },
             } });
         };
@@ -13998,8 +13983,8 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
                         else => unreachable,
                     };
                     const zero_val = if (is_vector) (try mod.intern(.{ .aggregate = .{
-                        .ty = resolved_type.ip_index,
-                        .storage = .{ .repeated_elem = scalar_zero.ip_index },
+                        .ty = resolved_type.toIntern(),
+                        .storage = .{ .repeated_elem = scalar_zero.toIntern() },
                     } })).toValue() else scalar_zero;
                     return sema.addConstant(resolved_type, zero_val);
                 }
@@ -14079,14 +14064,17 @@ fn intRem(
 ) CompileError!Value {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) == .Vector) {
-        const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
+        const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(mod));
         const scalar_ty = ty.scalarType(mod);
         for (result_data, 0..) |*scalar, i| {
             const lhs_elem = try lhs.elemValue(sema.mod, i);
             const rhs_elem = try rhs.elemValue(sema.mod, i);
-            scalar.* = try sema.intRemScalar(lhs_elem, rhs_elem, scalar_ty);
+            scalar.* = try (try sema.intRemScalar(lhs_elem, rhs_elem, scalar_ty)).intern(scalar_ty, mod);
         }
-        return Value.Tag.aggregate.create(sema.arena, result_data);
+        return (try mod.intern(.{ .aggregate = .{
+            .ty = ty.toIntern(),
+            .storage = .{ .elems = result_data },
+        } })).toValue();
     }
     return sema.intRemScalar(lhs, rhs, ty);
 }
@@ -14517,11 +14505,13 @@ fn zirOverflowArithmetic(
     }
 
     if (result.inst == .none) {
-        const values = try sema.arena.alloc(Value, 2);
-        values[0] = result.wrapped;
-        values[1] = result.overflow_bit;
-        const tuple_val = try Value.Tag.aggregate.create(sema.arena, values);
-        return sema.addConstant(tuple_ty, tuple_val);
+        return sema.addConstant(tuple_ty, (try mod.intern(.{ .aggregate = .{
+            .ty = tuple_ty.toIntern(),
+            .storage = .{ .elems = &.{
+                result.wrapped.toIntern(),
+                result.overflow_bit.toIntern(),
+            } },
+        } })).toValue());
     }
 
     const element_refs = try sema.arena.alloc(Air.Inst.Ref, 2);
@@ -14534,8 +14524,8 @@ fn splat(sema: *Sema, ty: Type, val: Value) !Value {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) != .Vector) return val;
     const repeated = try mod.intern(.{ .aggregate = .{
-        .ty = ty.ip_index,
-        .storage = .{ .repeated_elem = val.ip_index },
+        .ty = ty.toIntern(),
+        .storage = .{ .repeated_elem = val.toIntern() },
     } });
     return repeated.toValue();
 }
@@ -14547,7 +14537,7 @@ fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type {
         .child = .u1_type,
     }) else Type.u1;
 
-    const types = [2]InternPool.Index{ ty.ip_index, ov_ty.ip_index };
+    const types = [2]InternPool.Index{ ty.toIntern(), ov_ty.toIntern() };
     const values = [2]InternPool.Index{ .none, .none };
     const tuple_ty = try mod.intern(.{ .anon_struct_type = .{
         .types = &types,
@@ -15731,7 +15721,7 @@ fn zirClosureGet(
         scope = scope.parent.?;
     };
 
-    if (tv.val.ip_index == .unreachable_value and !block.is_typeof and sema.func_index == .none) {
+    if (tv.val.toIntern() == .unreachable_value and !block.is_typeof and sema.func_index == .none) {
         const msg = msg: {
             const name = name: {
                 const file = sema.owner_decl.getFileScope(mod);
@@ -15759,7 +15749,7 @@ fn zirClosureGet(
         return sema.failWithOwnedErrorMsg(msg);
     }
 
-    if (tv.val.ip_index == .unreachable_value and !block.is_typeof and !block.is_comptime and sema.func_index != .none) {
+    if (tv.val.toIntern() == .unreachable_value and !block.is_typeof and !block.is_comptime and sema.func_index != .none) {
         const msg = msg: {
             const name = name: {
                 const file = sema.owner_decl.getFileScope(mod);
@@ -15789,7 +15779,7 @@ fn zirClosureGet(
         return sema.failWithOwnedErrorMsg(msg);
     }
 
-    if (tv.val.ip_index == .unreachable_value) {
+    if (tv.val.toIntern() == .unreachable_value) {
         assert(block.is_typeof);
         // We need a dummy runtime instruction with the correct type.
         return block.addTy(.alloc, tv.ty);
@@ -15840,10 +15830,17 @@ fn zirBuiltinSrc(
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
         const name = mem.span(fn_owner_decl.name);
-        const bytes = try anon_decl.arena().dupe(u8, name[0 .. name.len + 1]);
+        const new_decl_ty = try mod.arrayType(.{
+            .len = name.len,
+            .child = .u8_type,
+            .sentinel = .zero_u8,
+        });
         const new_decl = try anon_decl.finish(
-            try Type.array(anon_decl.arena(), bytes.len - 1, try mod.intValue(Type.u8, 0), Type.u8, mod),
-            try Value.Tag.bytes.create(anon_decl.arena(), bytes),
+            new_decl_ty,
+            (try mod.intern(.{ .aggregate = .{
+                .ty = new_decl_ty.toIntern(),
+                .storage = .{ .bytes = name },
+            } })).toValue(),
             0, // default alignment
         );
         break :blk try mod.intern(.{ .ptr = .{
@@ -15857,9 +15854,17 @@ fn zirBuiltinSrc(
         defer anon_decl.deinit();
         // The compiler must not call realpath anywhere.
         const name = try fn_owner_decl.getFileScope(mod).fullPathZ(anon_decl.arena());
+        const new_decl_ty = try mod.arrayType(.{
+            .len = name.len,
+            .child = .u8_type,
+            .sentinel = .zero_u8,
+        });
         const new_decl = try anon_decl.finish(
-            try Type.array(anon_decl.arena(), name.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-            try Value.Tag.bytes.create(anon_decl.arena(), name[0 .. name.len + 1]),
+            new_decl_ty,
+            (try mod.intern(.{ .aggregate = .{
+                .ty = new_decl_ty.toIntern(),
+                .storage = .{ .bytes = name },
+            } })).toValue(),
             0, // default alignment
         );
         break :blk try mod.intern(.{ .ptr = .{
@@ -15877,13 +15882,13 @@ fn zirBuiltinSrc(
         // line: u32,
         try mod.intern(.{ .runtime_value = .{
             .ty = .u32_type,
-            .val = (try mod.intValue(Type.u32, extra.line + 1)).ip_index,
+            .val = (try mod.intValue(Type.u32, extra.line + 1)).toIntern(),
         } }),
         // column: u32,
-        (try mod.intValue(Type.u32, extra.column + 1)).ip_index,
+        (try mod.intValue(Type.u32, extra.column + 1)).toIntern(),
     };
     return sema.addConstant(src_loc_ty, (try mod.intern(.{ .aggregate = .{
-        .ty = src_loc_ty.ip_index,
+        .ty = src_loc_ty.toIntern(),
         .storage = .{ .elems = &fields },
     } })).toValue());
 }
@@ -15908,8 +15913,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         .Null,
         .EnumLiteral,
         => |type_info_tag| return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-            .ty = type_info_ty.ip_index,
-            .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(type_info_tag))).ip_index,
+            .ty = type_info_ty.toIntern(),
+            .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(type_info_tag))).toIntern(),
             .val = .void_value,
         } })).toValue()),
         .Fn => {
@@ -15941,8 +15946,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             const param_info_decl = mod.declPtr(param_info_decl_index);
             const param_info_ty = param_info_decl.val.toType();
 
-            const param_vals = try gpa.alloc(InternPool.Index, info.param_types.len);
-            defer gpa.free(param_vals);
+            const param_vals = try sema.arena.alloc(InternPool.Index, info.param_types.len);
             for (param_vals, info.param_types, 0..) |*param_val, param_ty, i| {
                 const is_generic = param_ty == .generic_poison_type;
                 const param_ty_val = try mod.intern_pool.get(gpa, .{ .opt = .{
@@ -15957,40 +15961,40 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
                 const param_fields = .{
                     // is_generic: bool,
-                    Value.makeBool(is_generic).ip_index,
+                    Value.makeBool(is_generic).toIntern(),
                     // is_noalias: bool,
-                    Value.makeBool(is_noalias).ip_index,
+                    Value.makeBool(is_noalias).toIntern(),
                     // type: ?type,
                     param_ty_val,
                 };
                 param_val.* = try mod.intern(.{ .aggregate = .{
-                    .ty = param_info_ty.ip_index,
+                    .ty = param_info_ty.toIntern(),
                     .storage = .{ .elems = &param_fields },
                 } });
             }
 
             const args_val = v: {
                 const args_slice_ty = try mod.ptrType(.{
-                    .elem_type = param_info_ty.ip_index,
+                    .elem_type = param_info_ty.toIntern(),
                     .size = .Slice,
                     .is_const = true,
                 });
                 const new_decl = try params_anon_decl.finish(
                     try mod.arrayType(.{
                         .len = param_vals.len,
-                        .child = param_info_ty.ip_index,
+                        .child = param_info_ty.toIntern(),
                         .sentinel = .none,
                     }),
                     (try mod.intern(.{ .aggregate = .{
-                        .ty = args_slice_ty.ip_index,
+                        .ty = args_slice_ty.toIntern(),
                         .storage = .{ .elems = param_vals },
                     } })).toValue(),
                     0, // default alignment
                 );
                 break :v try mod.intern(.{ .ptr = .{
-                    .ty = args_slice_ty.ip_index,
+                    .ty = args_slice_ty.toIntern(),
                     .addr = .{ .decl = new_decl },
-                    .len = (try mod.intValue(Type.usize, param_vals.len)).ip_index,
+                    .len = (try mod.intValue(Type.usize, param_vals.len)).toIntern(),
                 } });
             };
 
@@ -16003,43 +16007,55 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const field_values = .{
                 // calling_convention: CallingConvention,
-                (try mod.enumValueFieldIndex(callconv_ty, @enumToInt(info.cc))).ip_index,
+                (try mod.enumValueFieldIndex(callconv_ty, @enumToInt(info.cc))).toIntern(),
                 // alignment: comptime_int,
-                (try mod.intValue(Type.comptime_int, ty.abiAlignment(mod))).ip_index,
+                (try mod.intValue(Type.comptime_int, ty.abiAlignment(mod))).toIntern(),
                 // is_generic: bool,
-                Value.makeBool(info.is_generic).ip_index,
+                Value.makeBool(info.is_generic).toIntern(),
                 // is_var_args: bool,
-                Value.makeBool(info.is_var_args).ip_index,
+                Value.makeBool(info.is_var_args).toIntern(),
                 // return_type: ?type,
                 ret_ty_opt,
                 // args: []const Fn.Param,
                 args_val,
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Fn))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Fn))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = fn_info_ty.ip_index,
+                    .ty = fn_info_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
         },
         .Int => {
+            const int_info_decl_index = (try sema.namespaceLookup(
+                block,
+                src,
+                type_info_ty.getNamespaceIndex(mod).unwrap().?,
+                "Int",
+            )).?;
+            try mod.declareDeclDependency(sema.owner_decl_index, int_info_decl_index);
+            try sema.ensureDeclAnalyzed(int_info_decl_index);
+            const int_info_decl = mod.declPtr(int_info_decl_index);
+            const int_info_ty = int_info_decl.val.toType();
+
             const signedness_ty = try sema.getBuiltinType("Signedness");
             const info = ty.intInfo(mod);
-            const field_values = try sema.arena.alloc(Value, 2);
-            // signedness: Signedness,
-            field_values[0] = try mod.enumValueFieldIndex(signedness_ty, @enumToInt(info.signedness));
-            // bits: u16,
-            field_values[1] = try mod.intValue(Type.u16, info.bits);
-
-            return sema.addConstant(
-                type_info_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Int)),
-                    .val = try Value.Tag.aggregate.create(sema.arena, field_values),
-                }),
-            );
+            const field_values = .{
+                // signedness: Signedness,
+                try (try mod.enumValueFieldIndex(signedness_ty, @enumToInt(info.signedness))).intern(signedness_ty, mod),
+                // bits: u16,
+                (try mod.intValue(Type.u16, info.bits)).toIntern(),
+            };
+            return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Int))).toIntern(),
+                .val = try mod.intern(.{ .aggregate = .{
+                    .ty = int_info_ty.toIntern(),
+                    .storage = .{ .elems = &field_values },
+                } }),
+            } })).toValue());
         },
         .Float => {
             const float_info_decl_index = (try sema.namespaceLookup(
@@ -16051,17 +16067,17 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             try mod.declareDeclDependency(sema.owner_decl_index, float_info_decl_index);
             try sema.ensureDeclAnalyzed(float_info_decl_index);
             const float_info_decl = mod.declPtr(float_info_decl_index);
-            const float_ty = float_info_decl.val.toType();
+            const float_info_ty = float_info_decl.val.toType();
 
             const field_vals = .{
                 // bits: u16,
-                (try mod.intValue(Type.u16, ty.bitSize(mod))).ip_index,
+                (try mod.intValue(Type.u16, ty.bitSize(mod))).toIntern(),
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Float))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Float))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = float_ty.ip_index,
+                    .ty = float_info_ty.toIntern(),
                     .storage = .{ .elems = &field_vals },
                 } }),
             } })).toValue());
@@ -16099,80 +16115,121 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 break :t decl.val.toType();
             };
 
-            const field_values = try sema.arena.create([8]Value);
-            field_values.* = .{
+            const field_values = .{
                 // size: Size,
-                try mod.enumValueFieldIndex(ptr_size_ty, @enumToInt(info.size)),
+                try (try mod.enumValueFieldIndex(ptr_size_ty, @enumToInt(info.size))).intern(ptr_size_ty, mod),
                 // is_const: bool,
-                Value.makeBool(!info.mutable),
+                Value.makeBool(!info.mutable).toIntern(),
                 // is_volatile: bool,
-                Value.makeBool(info.@"volatile"),
+                Value.makeBool(info.@"volatile").toIntern(),
                 // alignment: comptime_int,
-                alignment,
+                alignment.toIntern(),
                 // address_space: AddressSpace
-                try mod.enumValueFieldIndex(addrspace_ty, @enumToInt(info.@"addrspace")),
+                try (try mod.enumValueFieldIndex(addrspace_ty, @enumToInt(info.@"addrspace"))).intern(addrspace_ty, mod),
                 // child: type,
-                info.pointee_type.toValue(),
+                info.pointee_type.toIntern(),
                 // is_allowzero: bool,
-                Value.makeBool(info.@"allowzero"),
+                Value.makeBool(info.@"allowzero").toIntern(),
                 // sentinel: ?*const anyopaque,
-                try sema.optRefValue(block, info.pointee_type, info.sentinel),
+                (try sema.optRefValue(block, info.pointee_type, info.sentinel)).toIntern(),
             };
-
-            return sema.addConstant(
-                type_info_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Pointer)),
-                    .val = try Value.Tag.aggregate.create(sema.arena, field_values),
-                }),
-            );
+            return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Pointer))).toIntern(),
+                .val = try mod.intern(.{ .aggregate = .{
+                    .ty = pointer_ty.toIntern(),
+                    .storage = .{ .elems = &field_values },
+                } }),
+            } })).toValue());
         },
         .Array => {
-            const info = ty.arrayInfo(mod);
-            const field_values = try sema.arena.alloc(Value, 3);
-            // len: comptime_int,
-            field_values[0] = try mod.intValue(Type.comptime_int, info.len);
-            // child: type,
-            field_values[1] = info.elem_type.toValue();
-            // sentinel: ?*const anyopaque,
-            field_values[2] = try sema.optRefValue(block, info.elem_type, info.sentinel);
+            const array_field_ty = t: {
+                const array_field_ty_decl_index = (try sema.namespaceLookup(
+                    block,
+                    src,
+                    type_info_ty.getNamespaceIndex(mod).unwrap().?,
+                    "Array",
+                )).?;
+                try mod.declareDeclDependency(sema.owner_decl_index, array_field_ty_decl_index);
+                try sema.ensureDeclAnalyzed(array_field_ty_decl_index);
+                const array_field_ty_decl = mod.declPtr(array_field_ty_decl_index);
+                break :t array_field_ty_decl.val.toType();
+            };
 
-            return sema.addConstant(
-                type_info_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Array)),
-                    .val = try Value.Tag.aggregate.create(sema.arena, field_values),
-                }),
-            );
+            const info = ty.arrayInfo(mod);
+            const field_values = .{
+                // len: comptime_int,
+                (try mod.intValue(Type.comptime_int, info.len)).toIntern(),
+                // child: type,
+                info.elem_type.toIntern(),
+                // sentinel: ?*const anyopaque,
+                (try sema.optRefValue(block, info.elem_type, info.sentinel)).toIntern(),
+            };
+            return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Array))).toIntern(),
+                .val = try mod.intern(.{ .aggregate = .{
+                    .ty = array_field_ty.toIntern(),
+                    .storage = .{ .elems = &field_values },
+                } }),
+            } })).toValue());
         },
         .Vector => {
-            const info = ty.arrayInfo(mod);
-            const field_values = try sema.arena.alloc(Value, 2);
-            // len: comptime_int,
-            field_values[0] = try mod.intValue(Type.comptime_int, info.len);
-            // child: type,
-            field_values[1] = info.elem_type.toValue();
+            const vector_field_ty = t: {
+                const vector_field_ty_decl_index = (try sema.namespaceLookup(
+                    block,
+                    src,
+                    type_info_ty.getNamespaceIndex(mod).unwrap().?,
+                    "Vector",
+                )).?;
+                try mod.declareDeclDependency(sema.owner_decl_index, vector_field_ty_decl_index);
+                try sema.ensureDeclAnalyzed(vector_field_ty_decl_index);
+                const vector_field_ty_decl = mod.declPtr(vector_field_ty_decl_index);
+                break :t vector_field_ty_decl.val.toType();
+            };
 
-            return sema.addConstant(
-                type_info_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Vector)),
-                    .val = try Value.Tag.aggregate.create(sema.arena, field_values),
-                }),
-            );
+            const info = ty.arrayInfo(mod);
+            const field_values = .{
+                // len: comptime_int,
+                (try mod.intValue(Type.comptime_int, info.len)).toIntern(),
+                // child: type,
+                info.elem_type.toIntern(),
+            };
+            return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Vector))).toIntern(),
+                .val = try mod.intern(.{ .aggregate = .{
+                    .ty = vector_field_ty.toIntern(),
+                    .storage = .{ .elems = &field_values },
+                } }),
+            } })).toValue());
         },
         .Optional => {
-            const field_values = try sema.arena.alloc(Value, 1);
-            // child: type,
-            field_values[0] = ty.optionalChild(mod).toValue();
+            const optional_field_ty = t: {
+                const optional_field_ty_decl_index = (try sema.namespaceLookup(
+                    block,
+                    src,
+                    type_info_ty.getNamespaceIndex(mod).unwrap().?,
+                    "Optional",
+                )).?;
+                try mod.declareDeclDependency(sema.owner_decl_index, optional_field_ty_decl_index);
+                try sema.ensureDeclAnalyzed(optional_field_ty_decl_index);
+                const optional_field_ty_decl = mod.declPtr(optional_field_ty_decl_index);
+                break :t optional_field_ty_decl.val.toType();
+            };
 
-            return sema.addConstant(
-                type_info_ty,
-                try Value.Tag.@"union".create(sema.arena, .{
-                    .tag = try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Optional)),
-                    .val = try Value.Tag.aggregate.create(sema.arena, field_values),
-                }),
-            );
+            const field_values = .{
+                // child: type,
+                ty.optionalChild(mod).toIntern(),
+            };
+            return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Vector))).toIntern(),
+                .val = try mod.intern(.{ .aggregate = .{
+                    .ty = optional_field_ty.toIntern(),
+                    .storage = .{ .elems = &field_values },
+                } }),
+            } })).toValue());
         },
         .ErrorSet => {
             var fields_anon_decl = try block.startAnonDecl();
@@ -16202,21 +16259,27 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             // Value can be zero-length slice otherwise
             const error_field_vals = if (ty.isAnyError(mod)) null else blk: {
                 const names = ty.errorSetNames(mod);
-                const vals = try gpa.alloc(InternPool.Index, names.len);
-                defer gpa.free(vals);
+                const vals = try sema.arena.alloc(InternPool.Index, names.len);
                 for (vals, names) |*field_val, name_ip| {
                     const name = mod.intern_pool.stringToSlice(name_ip);
                     const name_val = v: {
                         var anon_decl = try block.startAnonDecl();
                         defer anon_decl.deinit();
-                        const bytes = try anon_decl.arena().dupeZ(u8, name);
+                        const new_decl_ty = try mod.arrayType(.{
+                            .len = name.len,
+                            .child = .u8_type,
+                            .sentinel = .zero_u8,
+                        });
                         const new_decl = try anon_decl.finish(
-                            try Type.array(anon_decl.arena(), bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-                            try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                            new_decl_ty,
+                            (try mod.intern(.{ .aggregate = .{
+                                .ty = new_decl_ty.toIntern(),
+                                .storage = .{ .bytes = name },
+                            } })).toValue(),
                             0, // default alignment
                         );
                         break :v try mod.intern(.{ .ptr = .{
-                            .ty = .slice_const_u8_type,
+                            .ty = .slice_const_u8_sentinel_0_type,
                             .addr = .{ .decl = new_decl },
                         } });
                     };
@@ -16226,7 +16289,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         name_val,
                     };
                     field_val.* = try mod.intern(.{ .aggregate = .{
-                        .ty = error_field_ty.ip_index,
+                        .ty = error_field_ty.toIntern(),
                         .storage = .{ .elems = &error_field_fields },
                     } });
                 }
@@ -16236,39 +16299,39 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             // Build our ?[]const Error value
             const slice_errors_ty = try mod.ptrType(.{
-                .elem_type = error_field_ty.ip_index,
+                .elem_type = error_field_ty.toIntern(),
                 .size = .Slice,
                 .is_const = true,
             });
-            const opt_slice_errors_ty = try mod.optionalType(slice_errors_ty.ip_index);
+            const opt_slice_errors_ty = try mod.optionalType(slice_errors_ty.toIntern());
             const errors_payload_val: InternPool.Index = if (error_field_vals) |vals| v: {
                 const array_errors_ty = try mod.arrayType(.{
                     .len = vals.len,
-                    .child = error_field_ty.ip_index,
+                    .child = error_field_ty.toIntern(),
                     .sentinel = .none,
                 });
                 const new_decl = try fields_anon_decl.finish(
                     array_errors_ty,
                     (try mod.intern(.{ .aggregate = .{
-                        .ty = array_errors_ty.ip_index,
+                        .ty = array_errors_ty.toIntern(),
                         .storage = .{ .elems = vals },
                     } })).toValue(),
                     0, // default alignment
                 );
                 break :v try mod.intern(.{ .ptr = .{
-                    .ty = slice_errors_ty.ip_index,
+                    .ty = slice_errors_ty.toIntern(),
                     .addr = .{ .decl = new_decl },
                 } });
             } else .none;
             const errors_val = try mod.intern(.{ .opt = .{
-                .ty = opt_slice_errors_ty.ip_index,
+                .ty = opt_slice_errors_ty.toIntern(),
                 .val = errors_payload_val,
             } });
 
             // Construct Type{ .ErrorSet = errors_val }
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.ErrorSet))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.ErrorSet))).toIntern(),
                 .val = errors_val,
             } })).toValue());
         },
@@ -16288,22 +16351,22 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const field_values = .{
                 // error_set: type,
-                ty.errorUnionSet(mod).ip_index,
+                ty.errorUnionSet(mod).toIntern(),
                 // payload: type,
-                ty.errorUnionPayload(mod).ip_index,
+                ty.errorUnionPayload(mod).toIntern(),
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.ErrorUnion))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.ErrorUnion))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = error_union_field_ty.ip_index,
+                    .ty = error_union_field_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
         },
         .Enum => {
             // TODO: look into memoizing this result.
-            const enum_type = mod.intern_pool.indexToKey(ty.ip_index).enum_type;
+            const enum_type = mod.intern_pool.indexToKey(ty.toIntern()).enum_type;
 
             const is_exhaustive = Value.makeBool(enum_type.tag_mode != .nonexhaustive);
 
@@ -16323,23 +16386,28 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 break :t enum_field_ty_decl.val.toType();
             };
 
-            const enum_field_vals = try gpa.alloc(InternPool.Index, enum_type.names.len);
-            defer gpa.free(enum_field_vals);
-
+            const enum_field_vals = try sema.arena.alloc(InternPool.Index, enum_type.names.len);
             for (enum_field_vals, 0..) |*field_val, i| {
                 const name_ip = enum_type.names[i];
                 const name = mod.intern_pool.stringToSlice(name_ip);
                 const name_val = v: {
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
-                    const bytes = try anon_decl.arena().dupeZ(u8, name);
+                    const new_decl_ty = try mod.arrayType(.{
+                        .len = name.len,
+                        .child = .u8_type,
+                        .sentinel = .zero_u8,
+                    });
                     const new_decl = try anon_decl.finish(
-                        try Type.array(anon_decl.arena(), bytes.len, Value.zero_u8, Type.u8, mod),
-                        try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                        new_decl_ty,
+                        (try mod.intern(.{ .aggregate = .{
+                            .ty = new_decl_ty.toIntern(),
+                            .storage = .{ .bytes = name },
+                        } })).toValue(),
                         0, // default alignment
                     );
                     break :v try mod.intern(.{ .ptr = .{
-                        .ty = .slice_const_u8_type,
+                        .ty = .slice_const_u8_sentinel_0_type,
                         .addr = .{ .decl = new_decl },
                     } });
                 };
@@ -16348,10 +16416,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     // name: []const u8,
                     name_val,
                     // value: comptime_int,
-                    (try mod.intValue(Type.comptime_int, i)).ip_index,
+                    (try mod.intValue(Type.comptime_int, i)).toIntern(),
                 };
                 field_val.* = try mod.intern(.{ .aggregate = .{
-                    .ty = enum_field_ty.ip_index,
+                    .ty = enum_field_ty.toIntern(),
                     .storage = .{ .elems = &enum_field_fields },
                 } });
             }
@@ -16359,23 +16427,23 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             const fields_val = v: {
                 const fields_array_ty = try mod.arrayType(.{
                     .len = enum_field_vals.len,
-                    .child = enum_field_ty.ip_index,
+                    .child = enum_field_ty.toIntern(),
                     .sentinel = .none,
                 });
                 const new_decl = try fields_anon_decl.finish(
                     fields_array_ty,
                     (try mod.intern(.{ .aggregate = .{
-                        .ty = fields_array_ty.ip_index,
+                        .ty = fields_array_ty.toIntern(),
                         .storage = .{ .elems = enum_field_vals },
                     } })).toValue(),
                     0, // default alignment
                 );
                 break :v try mod.intern(.{ .ptr = .{
                     .ty = (try mod.ptrType(.{
-                        .elem_type = enum_field_ty.ip_index,
+                        .elem_type = enum_field_ty.toIntern(),
                         .size = .Slice,
                         .is_const = true,
-                    })).ip_index,
+                    })).toIntern(),
                     .addr = .{ .decl = new_decl },
                 } });
             };
@@ -16403,13 +16471,13 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // decls: []const Declaration,
                 decls_val,
                 // is_exhaustive: bool,
-                is_exhaustive.ip_index,
+                is_exhaustive.toIntern(),
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Enum))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Enum))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = type_enum_ty.ip_index,
+                    .ty = type_enum_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
@@ -16460,14 +16528,21 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 const name_val = v: {
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
-                    const bytes = try anon_decl.arena().dupeZ(u8, name);
+                    const new_decl_ty = try mod.arrayType(.{
+                        .len = name.len,
+                        .child = .u8_type,
+                        .sentinel = .zero_u8,
+                    });
                     const new_decl = try anon_decl.finish(
-                        try Type.array(anon_decl.arena(), bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-                        try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                        new_decl_ty,
+                        (try mod.intern(.{ .aggregate = .{
+                            .ty = new_decl_ty.toIntern(),
+                            .storage = .{ .bytes = name },
+                        } })).toValue(),
                         0, // default alignment
                     );
                     break :v try mod.intern(.{ .ptr = .{
-                        .ty = .slice_const_u8_type,
+                        .ty = .slice_const_u8_sentinel_0_type,
                         .addr = .{ .decl = new_decl },
                     } });
                 };
@@ -16481,12 +16556,12 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     // name: []const u8,
                     name_val,
                     // type: type,
-                    field.ty.ip_index,
+                    field.ty.toIntern(),
                     // alignment: comptime_int,
-                    (try mod.intValue(Type.comptime_int, alignment)).ip_index,
+                    (try mod.intValue(Type.comptime_int, alignment)).toIntern(),
                 };
                 field_val.* = try mod.intern(.{ .aggregate = .{
-                    .ty = union_field_ty.ip_index,
+                    .ty = union_field_ty.toIntern(),
                     .storage = .{ .elems = &union_field_fields },
                 } });
             }
@@ -16494,33 +16569,33 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             const fields_val = v: {
                 const array_fields_ty = try mod.arrayType(.{
                     .len = union_field_vals.len,
-                    .child = union_field_ty.ip_index,
+                    .child = union_field_ty.toIntern(),
                     .sentinel = .none,
                 });
                 const new_decl = try fields_anon_decl.finish(
                     array_fields_ty,
                     (try mod.intern(.{ .aggregate = .{
-                        .ty = array_fields_ty.ip_index,
+                        .ty = array_fields_ty.toIntern(),
                         .storage = .{ .elems = union_field_vals },
                     } })).toValue(),
                     0, // default alignment
                 );
                 break :v try mod.intern(.{ .ptr = .{
                     .ty = (try mod.ptrType(.{
-                        .elem_type = union_field_ty.ip_index,
+                        .elem_type = union_field_ty.toIntern(),
                         .size = .Slice,
                         .is_const = true,
-                    })).ip_index,
+                    })).toIntern(),
                     .addr = .{ .decl = new_decl },
-                    .len = (try mod.intValue(Type.usize, union_field_vals.len)).ip_index,
+                    .len = (try mod.intValue(Type.usize, union_field_vals.len)).toIntern(),
                 } });
             };
 
             const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, union_ty.getNamespaceIndex(mod));
 
             const enum_tag_ty_val = try mod.intern(.{ .opt = .{
-                .ty = (try mod.optionalType(.type_type)).ip_index,
-                .val = if (union_ty.unionTagType(mod)) |tag_ty| tag_ty.ip_index else .none,
+                .ty = (try mod.optionalType(.type_type)).toIntern(),
+                .val = if (union_ty.unionTagType(mod)) |tag_ty| tag_ty.toIntern() else .none,
             } });
 
             const container_layout_ty = t: {
@@ -16538,7 +16613,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const field_values = .{
                 // layout: ContainerLayout,
-                (try mod.enumValueFieldIndex(container_layout_ty, @enumToInt(layout))).ip_index,
+                (try mod.enumValueFieldIndex(container_layout_ty, @enumToInt(layout))).toIntern(),
 
                 // tag_type: ?type,
                 enum_tag_ty_val,
@@ -16548,10 +16623,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 decls_val,
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Union))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Union))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = type_union_ty.ip_index,
+                    .ty = type_union_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
@@ -16595,7 +16670,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             var struct_field_vals: []InternPool.Index = &.{};
             defer gpa.free(struct_field_vals);
             fv: {
-                const struct_type = switch (mod.intern_pool.indexToKey(struct_ty.ip_index)) {
+                const struct_type = switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
                     .anon_struct_type => |tuple| {
                         struct_field_vals = try gpa.alloc(InternPool.Index, tuple.types.len);
                         for (
@@ -16611,16 +16686,24 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                     // https://github.com/ziglang/zig/issues/15709
                                     @as([]const u8, mod.intern_pool.stringToSlice(tuple.names[i]))
                                 else
-                                    try std.fmt.allocPrintZ(anon_decl.arena(), "{d}", .{i});
+                                    try std.fmt.allocPrint(sema.arena, "{d}", .{i});
+                                const new_decl_ty = try mod.arrayType(.{
+                                    .len = bytes.len,
+                                    .child = .u8_type,
+                                    .sentinel = .zero_u8,
+                                });
                                 const new_decl = try anon_decl.finish(
-                                    try Type.array(anon_decl.arena(), bytes.len, Value.zero_u8, Type.u8, mod),
-                                    try Value.Tag.bytes.create(anon_decl.arena(), bytes.ptr[0 .. bytes.len + 1]),
+                                    new_decl_ty,
+                                    (try mod.intern(.{ .aggregate = .{
+                                        .ty = new_decl_ty.toIntern(),
+                                        .storage = .{ .bytes = bytes },
+                                    } })).toValue(),
                                     0, // default alignment
                                 );
                                 break :v try mod.intern(.{ .ptr = .{
-                                    .ty = .slice_const_u8_type,
+                                    .ty = .slice_const_u8_sentinel_0_type,
                                     .addr = .{ .decl = new_decl },
-                                    .len = (try mod.intValue(Type.usize, bytes.len)).ip_index,
+                                    .len = (try mod.intValue(Type.usize, bytes.len)).toIntern(),
                                 } });
                             };
 
@@ -16633,14 +16716,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                 // type: type,
                                 field_ty,
                                 // default_value: ?*const anyopaque,
-                                default_val_ptr.ip_index,
+                                default_val_ptr.toIntern(),
                                 // is_comptime: bool,
-                                Value.makeBool(is_comptime).ip_index,
+                                Value.makeBool(is_comptime).toIntern(),
                                 // alignment: comptime_int,
-                                (try mod.intValue(Type.comptime_int, field_ty.toType().abiAlignment(mod))).ip_index,
+                                (try mod.intValue(Type.comptime_int, field_ty.toType().abiAlignment(mod))).toIntern(),
                             };
                             struct_field_val.* = try mod.intern(.{ .aggregate = .{
-                                .ty = struct_field_ty.ip_index,
+                                .ty = struct_field_ty.toIntern(),
                                 .storage = .{ .elems = &struct_field_fields },
                             } });
                         }
@@ -16660,20 +16743,27 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     const name_val = v: {
                         var anon_decl = try block.startAnonDecl();
                         defer anon_decl.deinit();
-                        const bytes = try anon_decl.arena().dupeZ(u8, name);
+                        const new_decl_ty = try mod.arrayType(.{
+                            .len = name.len,
+                            .child = .u8_type,
+                            .sentinel = .zero_u8,
+                        });
                         const new_decl = try anon_decl.finish(
-                            try Type.array(anon_decl.arena(), bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-                            try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                            new_decl_ty,
+                            (try mod.intern(.{ .aggregate = .{
+                                .ty = new_decl_ty.toIntern(),
+                                .storage = .{ .bytes = name },
+                            } })).toValue(),
                             0, // default alignment
                         );
                         break :v try mod.intern(.{ .ptr = .{
-                            .ty = .slice_const_u8_type,
+                            .ty = .slice_const_u8_sentinel_0_type,
                             .addr = .{ .decl = new_decl },
-                            .len = (try mod.intValue(Type.usize, bytes.len)).ip_index,
+                            .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
                         } });
                     };
 
-                    const opt_default_val = if (field.default_val.ip_index == .unreachable_value)
+                    const opt_default_val = if (field.default_val.toIntern() == .unreachable_value)
                         null
                     else
                         field.default_val;
@@ -16684,16 +16774,16 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         // name: []const u8,
                         name_val,
                         // type: type,
-                        field.ty.ip_index,
+                        field.ty.toIntern(),
                         // default_value: ?*const anyopaque,
-                        default_val_ptr.ip_index,
+                        default_val_ptr.toIntern(),
                         // is_comptime: bool,
-                        Value.makeBool(field.is_comptime).ip_index,
+                        Value.makeBool(field.is_comptime).toIntern(),
                         // alignment: comptime_int,
-                        (try mod.intValue(Type.comptime_int, alignment)).ip_index,
+                        (try mod.intValue(Type.comptime_int, alignment)).toIntern(),
                     };
                     field_val.* = try mod.intern(.{ .aggregate = .{
-                        .ty = struct_field_ty.ip_index,
+                        .ty = struct_field_ty.toIntern(),
                         .storage = .{ .elems = &struct_field_fields },
                     } });
                 }
@@ -16702,37 +16792,37 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             const fields_val = v: {
                 const array_fields_ty = try mod.arrayType(.{
                     .len = struct_field_vals.len,
-                    .child = struct_field_ty.ip_index,
+                    .child = struct_field_ty.toIntern(),
                     .sentinel = .none,
                 });
                 const new_decl = try fields_anon_decl.finish(
                     array_fields_ty,
                     (try mod.intern(.{ .aggregate = .{
-                        .ty = array_fields_ty.ip_index,
+                        .ty = array_fields_ty.toIntern(),
                         .storage = .{ .elems = struct_field_vals },
                     } })).toValue(),
                     0, // default alignment
                 );
                 break :v try mod.intern(.{ .ptr = .{
                     .ty = (try mod.ptrType(.{
-                        .elem_type = struct_field_ty.ip_index,
+                        .elem_type = struct_field_ty.toIntern(),
                         .size = .Slice,
                         .is_const = true,
-                    })).ip_index,
+                    })).toIntern(),
                     .addr = .{ .decl = new_decl },
-                    .len = (try mod.intValue(Type.usize, struct_field_vals.len)).ip_index,
+                    .len = (try mod.intValue(Type.usize, struct_field_vals.len)).toIntern(),
                 } });
             };
 
             const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, struct_ty.getNamespaceIndex(mod));
 
             const backing_integer_val = try mod.intern(.{ .opt = .{
-                .ty = (try mod.optionalType(.type_type)).ip_index,
+                .ty = (try mod.optionalType(.type_type)).toIntern(),
                 .val = if (layout == .Packed) val: {
                     const struct_obj = mod.typeToStruct(struct_ty).?;
                     assert(struct_obj.haveLayout());
                     assert(struct_obj.backing_int_ty.isInt(mod));
-                    break :val struct_obj.backing_int_ty.ip_index;
+                    break :val struct_obj.backing_int_ty.toIntern();
                 } else .none,
             } });
 
@@ -16751,7 +16841,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const field_values = [_]InternPool.Index{
                 // layout: ContainerLayout,
-                (try mod.enumValueFieldIndex(container_layout_ty, @enumToInt(layout))).ip_index,
+                (try mod.enumValueFieldIndex(container_layout_ty, @enumToInt(layout))).toIntern(),
                 // backing_integer: ?type,
                 backing_integer_val,
                 // fields: []const StructField,
@@ -16759,13 +16849,13 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // decls: []const Declaration,
                 decls_val,
                 // is_tuple: bool,
-                Value.makeBool(struct_ty.isTuple(mod)).ip_index,
+                Value.makeBool(struct_ty.isTuple(mod)).toIntern(),
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Struct))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Struct))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = type_struct_ty.ip_index,
+                    .ty = type_struct_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
@@ -16794,10 +16884,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 decls_val,
             };
             return sema.addConstant(type_info_ty, (try mod.intern(.{ .un = .{
-                .ty = type_info_ty.ip_index,
-                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Opaque))).ip_index,
+                .ty = type_info_ty.toIntern(),
+                .tag = (try mod.enumValueFieldIndex(type_info_tag_ty, @enumToInt(std.builtin.TypeId.Opaque))).toIntern(),
                 .val = try mod.intern(.{ .aggregate = .{
-                    .ty = type_opaque_ty.ip_index,
+                    .ty = type_opaque_ty.toIntern(),
                     .storage = .{ .elems = &field_values },
                 } }),
             } })).toValue());
@@ -16845,25 +16935,25 @@ fn typeInfoDecls(
 
     const array_decl_ty = try mod.arrayType(.{
         .len = decl_vals.items.len,
-        .child = declaration_ty.ip_index,
+        .child = declaration_ty.toIntern(),
         .sentinel = .none,
     });
     const new_decl = try decls_anon_decl.finish(
         array_decl_ty,
         (try mod.intern(.{ .aggregate = .{
-            .ty = array_decl_ty.ip_index,
+            .ty = array_decl_ty.toIntern(),
             .storage = .{ .elems = decl_vals.items },
         } })).toValue(),
         0, // default alignment
     );
     return try mod.intern(.{ .ptr = .{
         .ty = (try mod.ptrType(.{
-            .elem_type = declaration_ty.ip_index,
+            .elem_type = declaration_ty.toIntern(),
             .size = .Slice,
             .is_const = true,
-        })).ip_index,
+        })).toIntern(),
         .addr = .{ .decl = new_decl },
-        .len = (try mod.intValue(Type.usize, decl_vals.items.len)).ip_index,
+        .len = (try mod.intValue(Type.usize, decl_vals.items.len)).toIntern(),
     } });
 }
 
@@ -16892,16 +16982,24 @@ fn typeInfoNamespaceDecls(
         const name_val = v: {
             var anon_decl = try block.startAnonDecl();
             defer anon_decl.deinit();
-            const bytes = try anon_decl.arena().dupeZ(u8, mem.sliceTo(decl.name, 0));
+            const name = mem.span(decl.name);
+            const new_decl_ty = try mod.arrayType(.{
+                .len = name.len,
+                .child = .u8_type,
+                .sentinel = .zero_u8,
+            });
             const new_decl = try anon_decl.finish(
-                try Type.array(anon_decl.arena(), bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-                try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                new_decl_ty,
+                (try mod.intern(.{ .aggregate = .{
+                    .ty = new_decl_ty.toIntern(),
+                    .storage = .{ .bytes = name },
+                } })).toValue(),
                 0, // default alignment
             );
             break :v try mod.intern(.{ .ptr = .{
-                .ty = .slice_const_u8_type,
+                .ty = .slice_const_u8_sentinel_0_type,
                 .addr = .{ .decl = new_decl },
-                .len = (try mod.intValue(Type.usize, bytes.len)).ip_index,
+                .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
             } });
         };
 
@@ -16909,10 +17007,10 @@ fn typeInfoNamespaceDecls(
             //name: []const u8,
             name_val,
             //is_pub: bool,
-            Value.makeBool(decl.is_pub).ip_index,
+            Value.makeBool(decl.is_pub).toIntern(),
         };
         try decl_vals.append(try mod.intern(.{ .aggregate = .{
-            .ty = declaration_ty.ip_index,
+            .ty = declaration_ty.toIntern(),
             .storage = .{ .elems = &fields },
         } }));
     }
@@ -16985,7 +17083,7 @@ fn log2IntType(sema: *Sema, block: *Block, operand: Type, src: LazySrcLoc) Compi
             const log2_elem_ty = try sema.log2IntType(block, elem_ty, src);
             return mod.vectorType(.{
                 .len = operand.vectorLen(mod),
-                .child = log2_elem_ty.ip_index,
+                .child = log2_elem_ty.toIntern(),
             });
         },
         else => {},
@@ -17527,7 +17625,7 @@ fn zirRetErrValue(
     const kv = try mod.getErrorValue(err_name);
     const error_set_type = try mod.singleErrorSetType(err_name);
     const result_inst = try sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
-        .ty = error_set_type.ip_index,
+        .ty = error_set_type.toIntern(),
         .name = try mod.intern_pool.getOrPutString(sema.gpa, kv.key),
     } })).toValue());
     return sema.analyzeRet(block, result_inst, src);
@@ -17854,9 +17952,9 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
         const val = try sema.resolveConstValue(block, align_src, coerced, "pointer alignment must be comptime-known");
         // Check if this happens to be the lazy alignment of our element type, in
         // which case we can make this 0 without resolving it.
-        switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .int => |int| switch (int.storage) {
-                .lazy_align => |lazy_ty| if (lazy_ty == elem_ty.ip_index) break :blk .none,
+                .lazy_align => |lazy_ty| if (lazy_ty == elem_ty.toIntern()) break :blk .none,
                 else => {},
             },
             else => {},
@@ -17985,7 +18083,7 @@ fn arrayInitEmpty(sema: *Sema, block: *Block, src: LazySrcLoc, obj_ty: Type) Com
         }
     }
     return sema.addConstant(obj_ty, (try mod.intern(.{ .aggregate = .{
-        .ty = obj_ty.ip_index,
+        .ty = obj_ty.toIntern(),
         .storage = .{ .elems = &.{} },
     } })).toValue());
 }
@@ -18021,10 +18119,11 @@ fn unionInit(
         const tag_ty = union_ty.unionTagTypeHypothetical(mod);
         const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name, mod).?);
         const tag_val = try mod.enumValueFieldIndex(tag_ty, enum_field_index);
-        return sema.addConstant(union_ty, try Value.Tag.@"union".create(sema.arena, .{
-            .tag = tag_val,
-            .val = init_val,
-        }));
+        return sema.addConstant(union_ty, (try mod.intern(.{ .un = .{
+            .ty = union_ty.toIntern(),
+            .tag = try tag_val.intern(tag_ty, mod),
+            .val = try init_val.intern(field.ty, mod),
+        } })).toValue());
     }
 
     try sema.requireRuntimeBlock(block, init_src, null);
@@ -18125,12 +18224,12 @@ fn zirStructInit(
 
         const init_inst = try sema.resolveInst(item.data.init);
         if (try sema.resolveMaybeUndefVal(init_inst)) |val| {
-            return sema.addConstantMaybeRef(
-                block,
-                resolved_ty,
-                try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }),
-                is_ref,
-            );
+            const field = resolved_ty.unionFields(mod).values()[field_index];
+            return sema.addConstantMaybeRef(block, resolved_ty, (try mod.intern(.{ .un = .{
+                .ty = resolved_ty.toIntern(),
+                .tag = try tag_val.intern(tag_ty, mod),
+                .val = try val.intern(field.ty, mod),
+            } })).toValue(), is_ref);
         }
 
         if (is_ref) {
@@ -18171,7 +18270,7 @@ fn finishStructInit(
     var root_msg: ?*Module.ErrorMsg = null;
     errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
 
-    switch (mod.intern_pool.indexToKey(struct_ty.ip_index)) {
+    switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
         .anon_struct_type => |anon_struct| {
             for (anon_struct.types, anon_struct.values, 0..) |field_ty, default_val, i| {
                 if (field_inits[i] != .none) continue;
@@ -18204,7 +18303,7 @@ fn finishStructInit(
             for (struct_obj.fields.values(), 0..) |field, i| {
                 if (field_inits[i] != .none) continue;
 
-                if (field.default_val.ip_index == .unreachable_value) {
+                if (field.default_val.toIntern() == .unreachable_value) {
                     const field_name = struct_obj.fields.keys()[i];
                     const template = "missing struct field: {s}";
                     const args = .{field_name};
@@ -18250,7 +18349,7 @@ fn finishStructInit(
                 .intern(struct_ty.structFieldType(field_i, mod), mod);
         }
         const struct_val = try mod.intern(.{ .aggregate = .{
-            .ty = struct_ty.ip_index,
+            .ty = struct_ty.toIntern(),
             .storage = .{ .elems = elems },
         } });
         return sema.addConstantMaybeRef(block, struct_ty, struct_val.toValue(), is_ref);
@@ -18331,7 +18430,7 @@ fn zirStructInitAnon(
             gop.value_ptr.* = i;
 
             const init = try sema.resolveInst(item.data.init);
-            field_ty.* = sema.typeOf(init).ip_index;
+            field_ty.* = sema.typeOf(init).toIntern();
             if (field_ty.toType().zigTypeTag(mod) == .Opaque) {
                 const msg = msg: {
                     const decl = sema.mod.declPtr(block.src_decl);
@@ -18464,15 +18563,19 @@ fn zirArrayInit(
     } else null;
 
     const runtime_index = opt_runtime_index orelse {
-        const elem_vals = try sema.arena.alloc(Value, resolved_args.len);
-
-        for (resolved_args, 0..) |arg, i| {
+        const elem_vals = try sema.arena.alloc(InternPool.Index, resolved_args.len);
+        for (elem_vals, resolved_args, 0..) |*val, arg, i| {
+            const elem_ty = if (array_ty.zigTypeTag(mod) == .Struct)
+                array_ty.structFieldType(i, mod)
+            else
+                array_ty.elemType2(mod);
             // We checked that all args are comptime above.
-            elem_vals[i] = (sema.resolveMaybeUndefVal(arg) catch unreachable).?;
+            val.* = try ((sema.resolveMaybeUndefVal(arg) catch unreachable).?).intern(elem_ty, mod);
         }
-
-        const array_val = try Value.Tag.aggregate.create(sema.arena, elem_vals);
-        return sema.addConstantMaybeRef(block, array_ty, array_val, is_ref);
+        return sema.addConstantMaybeRef(block, array_ty, (try mod.intern(.{ .aggregate = .{
+            .ty = array_ty.toIntern(),
+            .storage = .{ .elems = elem_vals },
+        } })).toValue(), is_ref);
     };
 
     sema.requireRuntimeBlock(block, .unneeded, null) catch |err| switch (err) {
@@ -18548,7 +18651,7 @@ fn zirArrayInitAnon(
         for (operands, 0..) |operand, i| {
             const operand_src = src; // TODO better source location
             const elem = try sema.resolveInst(operand);
-            types[i] = sema.typeOf(elem).ip_index;
+            types[i] = sema.typeOf(elem).toIntern();
             if (types[i].toType().zigTypeTag(mod) == .Opaque) {
                 const msg = msg: {
                     const msg = try sema.errMsg(block, operand_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
@@ -18560,7 +18663,7 @@ fn zirArrayInitAnon(
                 return sema.failWithOwnedErrorMsg(msg);
             }
             if (try sema.resolveMaybeUndefVal(elem)) |val| {
-                values[i] = val.ip_index;
+                values[i] = val.toIntern();
             } else {
                 values[i] = .none;
                 runtime_src = operand_src;
@@ -18676,7 +18779,7 @@ fn fieldType(
         const resolved_ty = try sema.resolveTypeFields(cur_ty);
         cur_ty = resolved_ty;
         switch (cur_ty.zigTypeTag(mod)) {
-            .Struct => switch (mod.intern_pool.indexToKey(cur_ty.ip_index)) {
+            .Struct => switch (mod.intern_pool.indexToKey(cur_ty.toIntern())) {
                 .anon_struct_type => |anon_struct| {
                     const field_index = try sema.anonStructFieldIndex(block, cur_ty, field_name, field_src);
                     return sema.addType(anon_struct.types[field_index].toType());
@@ -18698,7 +18801,7 @@ fn fieldType(
             .Optional => {
                 // Struct/array init through optional requires the child type to not be a pointer.
                 // If the child of .optional is a pointer it'll error on the next loop.
-                cur_ty = mod.intern_pool.indexToKey(cur_ty.ip_index).opt_type.toType();
+                cur_ty = mod.intern_pool.indexToKey(cur_ty.toIntern()).opt_type.toType();
                 continue;
             },
             .ErrorUnion => {
@@ -18776,7 +18879,7 @@ fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
 
     if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
-        const err_name = sema.mod.intern_pool.indexToKey(val.ip_index).err.name;
+        const err_name = sema.mod.intern_pool.indexToKey(val.toIntern()).err.name;
         const bytes = sema.mod.intern_pool.stringToSlice(err_name);
         return sema.addStrLit(block, bytes);
     }
@@ -18820,21 +18923,21 @@ fn zirUnaryMath(
             const vec_len = operand_ty.vectorLen(mod);
             const result_ty = try mod.vectorType(.{
                 .len = vec_len,
-                .child = scalar_ty.ip_index,
+                .child = scalar_ty.toIntern(),
             });
             if (try sema.resolveMaybeUndefVal(operand)) |val| {
                 if (val.isUndef(mod))
                     return sema.addConstUndef(result_ty);
 
-                const elems = try sema.arena.alloc(Value, vec_len);
+                const elems = try sema.arena.alloc(InternPool.Index, vec_len);
                 for (elems, 0..) |*elem, i| {
                     const elem_val = try val.elemValue(sema.mod, i);
-                    elem.* = try eval(elem_val, scalar_ty, sema.arena, sema.mod);
+                    elem.* = try (try eval(elem_val, scalar_ty, sema.arena, sema.mod)).intern(scalar_ty, mod);
                 }
-                return sema.addConstant(
-                    result_ty,
-                    try Value.Tag.aggregate.create(sema.arena, elems),
-                );
+                return sema.addConstant(result_ty, (try mod.intern(.{ .aggregate = .{
+                    .ty = result_ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue());
             }
 
             try sema.requireRuntimeBlock(block, operand_src, null);
@@ -18867,7 +18970,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const enum_ty = switch (operand_ty.zigTypeTag(mod)) {
         .EnumLiteral => {
             const val = try sema.resolveConstValue(block, .unneeded, operand, "");
-            const tag_name = mod.intern_pool.indexToKey(val.ip_index).enum_literal;
+            const tag_name = mod.intern_pool.indexToKey(val.toIntern()).enum_literal;
             const bytes = mod.intern_pool.stringToSlice(tag_name);
             return sema.addStrLit(block, bytes);
         },
@@ -18956,7 +19059,7 @@ fn zirReify(
         .AnyFrame => return sema.failWithUseOfAsync(block, src),
         .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
         .Int => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const signedness_val = try union_val.val.fieldValue(mod, fields.getIndex("signedness").?);
             const bits_val = try union_val.val.fieldValue(mod, fields.getIndex("bits").?);
 
@@ -18966,7 +19069,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .Vector => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const len_val = try union_val.val.fieldValue(mod, fields.getIndex("len").?);
             const child_val = try union_val.val.fieldValue(mod, fields.getIndex("child").?);
 
@@ -18977,12 +19080,12 @@ fn zirReify(
 
             const ty = try mod.vectorType(.{
                 .len = len,
-                .child = child_ty.ip_index,
+                .child = child_ty.toIntern(),
             });
             return sema.addType(ty);
         },
         .Float => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const bits_val = try union_val.val.fieldValue(mod, fields.getIndex("bits").?);
 
             const bits = @intCast(u16, bits_val.toUnsignedInt(mod));
@@ -18997,7 +19100,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .Pointer => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const size_val = try union_val.val.fieldValue(mod, fields.getIndex("size").?);
             const is_const_val = try union_val.val.fieldValue(mod, fields.getIndex("is_const").?);
             const is_volatile_val = try union_val.val.fieldValue(mod, fields.getIndex("is_volatile").?);
@@ -19088,7 +19191,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .Array => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const len_val = try union_val.val.fieldValue(mod, fields.getIndex("len").?);
             const child_val = try union_val.val.fieldValue(mod, fields.getIndex("child").?);
             const sentinel_val = try union_val.val.fieldValue(mod, fields.getIndex("sentinel").?);
@@ -19107,7 +19210,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .Optional => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const child_val = try union_val.val.fieldValue(mod, fields.getIndex("child").?);
 
             const child_ty = child_val.toType();
@@ -19116,7 +19219,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .ErrorUnion => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const error_set_val = try union_val.val.fieldValue(mod, fields.getIndex("error_set").?);
             const payload_val = try union_val.val.fieldValue(mod, fields.getIndex("payload").?);
 
@@ -19155,7 +19258,7 @@ fn zirReify(
             return sema.addType(ty);
         },
         .Struct => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const layout_val = try union_val.val.fieldValue(mod, fields.getIndex("layout").?);
             const backing_integer_val = try union_val.val.fieldValue(mod, fields.getIndex("backing_integer").?);
             const fields_val = try union_val.val.fieldValue(mod, fields.getIndex("fields").?);
@@ -19176,7 +19279,7 @@ fn zirReify(
             return try sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_val, name_strategy, is_tuple_val.toBool(mod));
         },
         .Enum => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const tag_type_val = try union_val.val.fieldValue(mod, fields.getIndex("tag_type").?);
             const fields_val = try union_val.val.fieldValue(mod, fields.getIndex("fields").?);
             const decls_val = try union_val.val.fieldValue(mod, fields.getIndex("decls").?);
@@ -19517,7 +19620,7 @@ fn zirReify(
             return sema.analyzeDeclVal(block, src, new_decl_index);
         },
         .Fn => {
-            const fields = ip.typeOf(union_val.val.ip_index).toType().structFields(mod);
+            const fields = ip.typeOf(union_val.val.toIntern()).toType().structFields(mod);
             const calling_convention_val = try union_val.val.fieldValue(mod, fields.getIndex("calling_convention").?);
             const alignment_val = try union_val.val.fieldValue(mod, fields.getIndex("alignment").?);
             const is_generic_val = try union_val.val.fieldValue(mod, fields.getIndex("is_generic").?);
@@ -19571,7 +19674,7 @@ fn zirReify(
 
                 const param_type_val = param_type_opt_val.optionalValue(mod) orelse
                     return sema.fail(block, src, "Type.Fn.Param.arg_type must be non-null for @Type", .{});
-                param_type.* = param_type_val.ip_index;
+                param_type.* = param_type_val.toIntern();
 
                 if (arg_is_noalias) {
                     if (!param_type.toType().isPtrAtRuntime(mod)) {
@@ -19733,7 +19836,7 @@ fn reifyStruct(
                 opt_val;
             break :blk try payload_val.copy(new_decl_arena_allocator);
         } else Value.@"unreachable";
-        if (is_comptime_val.toBool(mod) and default_val.ip_index == .unreachable_value) {
+        if (is_comptime_val.toBool(mod) and default_val.toIntern() == .unreachable_value) {
             return sema.fail(block, src, "comptime field without default initialization value", .{});
         }
 
@@ -19956,9 +20059,17 @@ fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
     const bytes = try ty.nameAllocArena(anon_decl.arena(), mod);
 
+    const decl_ty = try mod.arrayType(.{
+        .len = bytes.len,
+        .child = .u8_type,
+        .sentinel = .zero_u8,
+    });
     const new_decl = try anon_decl.finish(
-        try Type.array(anon_decl.arena(), bytes.len, try mod.intValue(Type.u8, 0), Type.u8, mod),
-        try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+        decl_ty,
+        (try mod.intern(.{ .aggregate = .{
+            .ty = decl_ty.toIntern(),
+            .storage = .{ .bytes = bytes },
+        } })).toValue(),
         0, // default alignment
     );
 
@@ -20125,8 +20236,8 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
                 break :disjoint false;
         }
 
-        if (!ip.isInferredErrorSetType(dest_ty.ip_index) and
-            !ip.isInferredErrorSetType(operand_ty.ip_index))
+        if (!ip.isInferredErrorSetType(dest_ty.toIntern()) and
+            !ip.isInferredErrorSetType(operand_ty.toIntern()))
         {
             break :disjoint true;
         }
@@ -20157,7 +20268,7 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
 
     if (maybe_operand_val) |val| {
         if (!dest_ty.isAnyError(mod)) {
-            const error_name = mod.intern_pool.stringToSlice(mod.intern_pool.indexToKey(val.ip_index).err.name);
+            const error_name = mod.intern_pool.stringToSlice(mod.intern_pool.indexToKey(val.toIntern()).err.name);
             if (!dest_ty.errorSetHasField(error_name, mod)) {
                 const msg = msg: {
                     const msg = try sema.errMsg(
@@ -20257,11 +20368,11 @@ fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
         if (dest_ty.zigTypeTag(mod) == .Optional) {
             var dest_ptr_info = dest_ty.optionalChild(mod).ptrInfo(mod);
             dest_ptr_info.@"align" = operand_align;
-            break :blk try Type.optional(sema.arena, try Type.ptr(sema.arena, sema.mod, dest_ptr_info), mod);
+            break :blk try Type.optional(sema.arena, try Type.ptr(sema.arena, mod, dest_ptr_info), mod);
         } else {
             var dest_ptr_info = dest_ty.ptrInfo(mod);
             dest_ptr_info.@"align" = operand_align;
-            break :blk try Type.ptr(sema.arena, sema.mod, dest_ptr_info);
+            break :blk try Type.ptr(sema.arena, mod, dest_ptr_info);
         }
     };
 
@@ -20279,10 +20390,10 @@ fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
             errdefer msg.destroy(sema.gpa);
 
             try sema.errNote(block, operand_src, msg, "'{}' has alignment '{d}'", .{
-                operand_ty.fmt(sema.mod), operand_align,
+                operand_ty.fmt(mod), operand_align,
             });
             try sema.errNote(block, dest_ty_src, msg, "'{}' has alignment '{d}'", .{
-                dest_ty.fmt(sema.mod), dest_align,
+                dest_ty.fmt(mod), dest_align,
             });
 
             try sema.errNote(block, src, msg, "consider using '@alignCast'", .{});
@@ -20296,11 +20407,11 @@ fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
             return sema.failWithUseOfUndef(block, operand_src);
         }
         if (!dest_ty.ptrAllowsZero(mod) and operand_val.isNull(mod)) {
-            return sema.fail(block, operand_src, "null pointer casted to type '{}'", .{dest_ty.fmt(sema.mod)});
+            return sema.fail(block, operand_src, "null pointer casted to type '{}'", .{dest_ty.fmt(mod)});
         }
         if (dest_ty.zigTypeTag(mod) == .Optional and sema.typeOf(ptr).zigTypeTag(mod) != .Optional) {
             return sema.addConstant(dest_ty, (try mod.intern(.{ .opt = .{
-                .ty = dest_ty.ip_index,
+                .ty = dest_ty.toIntern(),
                 .val = operand_val.toIntern(),
             } })).toValue());
         }
@@ -20335,7 +20446,7 @@ fn zirConstCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData
 
     var ptr_info = operand_ty.ptrInfo(mod);
     ptr_info.mutable = true;
-    const dest_ty = try Type.ptr(sema.arena, sema.mod, ptr_info);
+    const dest_ty = try Type.ptr(sema.arena, mod, ptr_info);
 
     if (try sema.resolveMaybeUndefVal(operand)) |operand_val| {
         return sema.addConstant(dest_ty, operand_val);
@@ -20356,7 +20467,7 @@ fn zirVolatileCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
 
     var ptr_info = operand_ty.ptrInfo(mod);
     ptr_info.@"volatile" = false;
-    const dest_ty = try Type.ptr(sema.arena, sema.mod, ptr_info);
+    const dest_ty = try Type.ptr(sema.arena, mod, ptr_info);
 
     if (try sema.resolveMaybeUndefVal(operand)) |operand_val| {
         return sema.addConstant(dest_ty, operand_val);
@@ -20382,7 +20493,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const dest_ty = if (is_vector)
         try mod.vectorType(.{
             .len = operand_ty.vectorLen(mod),
-            .child = dest_scalar_ty.ip_index,
+            .child = dest_scalar_ty.toIntern(),
         })
     else
         dest_scalar_ty;
@@ -20405,7 +20516,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
         if (operand_info.signedness != dest_info.signedness) {
             return sema.fail(block, operand_src, "expected {s} integer type, found '{}'", .{
-                @tagName(dest_info.signedness), operand_ty.fmt(sema.mod),
+                @tagName(dest_info.signedness), operand_ty.fmt(mod),
             });
         }
         if (operand_info.bits < dest_info.bits) {
@@ -20414,7 +20525,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     "destination type '{}' has more bits than source type '{}'",
-                    .{ dest_ty.fmt(sema.mod), operand_ty.fmt(sema.mod) },
+                    .{ dest_ty.fmt(mod), operand_ty.fmt(mod) },
                 );
                 errdefer msg.destroy(sema.gpa);
                 try sema.errNote(block, dest_ty_src, msg, "destination type has {d} bits", .{
@@ -20434,18 +20545,18 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         if (!is_vector) {
             return sema.addConstant(
                 dest_ty,
-                try val.intTrunc(operand_ty, sema.arena, dest_info.signedness, dest_info.bits, sema.mod),
+                try val.intTrunc(operand_ty, sema.arena, dest_info.signedness, dest_info.bits, mod),
             );
         }
-        const elems = try sema.arena.alloc(Value, operand_ty.vectorLen(mod));
+        const elems = try sema.arena.alloc(InternPool.Index, operand_ty.vectorLen(mod));
         for (elems, 0..) |*elem, i| {
-            const elem_val = try val.elemValue(sema.mod, i);
-            elem.* = try elem_val.intTrunc(operand_scalar_ty, sema.arena, dest_info.signedness, dest_info.bits, sema.mod);
+            const elem_val = try val.elemValue(mod, i);
+            elem.* = try (try elem_val.intTrunc(operand_scalar_ty, sema.arena, dest_info.signedness, dest_info.bits, mod)).intern(dest_scalar_ty, mod);
         }
-        return sema.addConstant(
-            dest_ty,
-            try Value.Tag.aggregate.create(sema.arena, elems),
-        );
+        return sema.addConstant(dest_ty, (try mod.intern(.{ .aggregate = .{
+            .ty = dest_ty.toIntern(),
+            .storage = .{ .elems = elems },
+        } })).toValue());
     }
 
     try sema.requireRuntimeBlock(block, src, operand_src);
@@ -20466,7 +20577,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
     var ptr_info = ptr_ty.ptrInfo(mod);
     ptr_info.@"align" = dest_align;
-    var dest_ty = try Type.ptr(sema.arena, sema.mod, ptr_info);
+    var dest_ty = try Type.ptr(sema.arena, mod, ptr_info);
     if (ptr_ty.zigTypeTag(mod) == .Optional) {
         dest_ty = try mod.optionalType(dest_ty.toIntern());
     }
@@ -20531,22 +20642,22 @@ fn zirBitCount(
             const vec_len = operand_ty.vectorLen(mod);
             const result_ty = try mod.vectorType(.{
                 .len = vec_len,
-                .child = result_scalar_ty.ip_index,
+                .child = result_scalar_ty.toIntern(),
             });
             if (try sema.resolveMaybeUndefVal(operand)) |val| {
                 if (val.isUndef(mod)) return sema.addConstUndef(result_ty);
 
-                const elems = try sema.arena.alloc(Value, vec_len);
+                const elems = try sema.arena.alloc(InternPool.Index, vec_len);
                 const scalar_ty = operand_ty.scalarType(mod);
                 for (elems, 0..) |*elem, i| {
-                    const elem_val = try val.elemValue(sema.mod, i);
+                    const elem_val = try val.elemValue(mod, i);
                     const count = comptimeOp(elem_val, scalar_ty, mod);
-                    elem.* = try mod.intValue(scalar_ty, count);
+                    elem.* = (try mod.intValue(scalar_ty, count)).toIntern();
                 }
-                return sema.addConstant(
-                    result_ty,
-                    try Value.Tag.aggregate.create(sema.arena, elems),
-                );
+                return sema.addConstant(result_ty, (try mod.intern(.{ .aggregate = .{
+                    .ty = result_ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue());
             } else {
                 try sema.requireRuntimeBlock(block, src, operand_src);
                 return block.addTyOp(air_tag, result_ty, operand);
@@ -20580,7 +20691,7 @@ fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             block,
             operand_src,
             "@byteSwap requires the number of bits to be evenly divisible by 8, but {} has {} bits",
-            .{ scalar_ty.fmt(sema.mod), bits },
+            .{ scalar_ty.fmt(mod), bits },
         );
     }
 
@@ -20605,15 +20716,15 @@ fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     return sema.addConstUndef(operand_ty);
 
                 const vec_len = operand_ty.vectorLen(mod);
-                const elems = try sema.arena.alloc(Value, vec_len);
+                const elems = try sema.arena.alloc(InternPool.Index, vec_len);
                 for (elems, 0..) |*elem, i| {
-                    const elem_val = try val.elemValue(sema.mod, i);
-                    elem.* = try elem_val.byteSwap(operand_ty, mod, sema.arena);
+                    const elem_val = try val.elemValue(mod, i);
+                    elem.* = try (try elem_val.byteSwap(operand_ty, mod, sema.arena)).intern(scalar_ty, mod);
                 }
-                return sema.addConstant(
-                    operand_ty,
-                    try Value.Tag.aggregate.create(sema.arena, elems),
-                );
+                return sema.addConstant(operand_ty, (try mod.intern(.{ .aggregate = .{
+                    .ty = operand_ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue());
             } else operand_src;
 
             try sema.requireRuntimeBlock(block, src, runtime_src);
@@ -20653,15 +20764,15 @@ fn zirBitReverse(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
                     return sema.addConstUndef(operand_ty);
 
                 const vec_len = operand_ty.vectorLen(mod);
-                const elems = try sema.arena.alloc(Value, vec_len);
+                const elems = try sema.arena.alloc(InternPool.Index, vec_len);
                 for (elems, 0..) |*elem, i| {
-                    const elem_val = try val.elemValue(sema.mod, i);
-                    elem.* = try elem_val.bitReverse(scalar_ty, mod, sema.arena);
+                    const elem_val = try val.elemValue(mod, i);
+                    elem.* = try (try elem_val.bitReverse(scalar_ty, mod, sema.arena)).intern(scalar_ty, mod);
                 }
-                return sema.addConstant(
-                    operand_ty,
-                    try Value.Tag.aggregate.create(sema.arena, elems),
-                );
+                return sema.addConstant(operand_ty, (try mod.intern(.{ .aggregate = .{
+                    .ty = operand_ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue());
             } else operand_src;
 
             try sema.requireRuntimeBlock(block, src, runtime_src);
@@ -20699,7 +20810,7 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6
         .Struct => {},
         else => {
             const msg = msg: {
-                const msg = try sema.errMsg(block, lhs_src, "expected struct type, found '{}'", .{ty.fmt(sema.mod)});
+                const msg = try sema.errMsg(block, lhs_src, "expected struct type, found '{}'", .{ty.fmt(mod)});
                 errdefer msg.destroy(sema.gpa);
                 try sema.addDeclaredHereNote(msg, ty);
                 break :msg msg;
@@ -20738,7 +20849,7 @@ fn checkNamespaceType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Com
     const mod = sema.mod;
     switch (ty.zigTypeTag(mod)) {
         .Struct, .Enum, .Union, .Opaque => return,
-        else => return sema.fail(block, src, "expected struct, enum, union, or opaque; found '{}'", .{ty.fmt(sema.mod)}),
+        else => return sema.fail(block, src, "expected struct, enum, union, or opaque; found '{}'", .{ty.fmt(mod)}),
     }
 }
 
@@ -20748,7 +20859,7 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr
     switch (try ty.zigTypeTagOrPoison(mod)) {
         .ComptimeInt => return true,
         .Int => return false,
-        else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty.fmt(sema.mod)}),
+        else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty.fmt(mod)}),
     }
 }
 
@@ -20807,7 +20918,7 @@ fn checkPtrOperand(
                     block,
                     ty_src,
                     "expected pointer, found '{}'",
-                    .{ty.fmt(sema.mod)},
+                    .{ty.fmt(mod)},
                 );
                 errdefer msg.destroy(sema.gpa);
 
@@ -20820,7 +20931,7 @@ fn checkPtrOperand(
         .Optional => if (ty.isPtrLikeOptional(mod)) return,
         else => {},
     }
-    return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(sema.mod)});
+    return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(mod)});
 }
 
 fn checkPtrType(
@@ -20838,7 +20949,7 @@ fn checkPtrType(
                     block,
                     ty_src,
                     "expected pointer type, found '{}'",
-                    .{ty.fmt(sema.mod)},
+                    .{ty.fmt(mod)},
                 );
                 errdefer msg.destroy(sema.gpa);
 
@@ -20851,7 +20962,7 @@ fn checkPtrType(
         .Optional => if (ty.isPtrLikeOptional(mod)) return,
         else => {},
     }
-    return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(sema.mod)});
+    return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(mod)});
 }
 
 fn checkVectorElemType(
@@ -20865,7 +20976,7 @@ fn checkVectorElemType(
         .Int, .Float, .Bool => return,
         else => if (ty.isPtrAtRuntime(mod)) return,
     }
-    return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty.fmt(sema.mod)});
+    return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty.fmt(mod)});
 }
 
 fn checkFloatType(
@@ -20877,7 +20988,7 @@ fn checkFloatType(
     const mod = sema.mod;
     switch (ty.zigTypeTag(mod)) {
         .ComptimeInt, .ComptimeFloat, .Float => {},
-        else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty.fmt(sema.mod)}),
+        else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty.fmt(mod)}),
     }
 }
 
@@ -20894,7 +21005,7 @@ fn checkNumericType(
             .ComptimeFloat, .Float, .ComptimeInt, .Int => {},
             else => |t| return sema.fail(block, ty_src, "expected number, found '{}'", .{t}),
         },
-        else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty.fmt(sema.mod)}),
+        else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty.fmt(mod)}),
     }
 }
 
@@ -20928,7 +21039,7 @@ fn checkAtomicPtrOperand(
             block,
             elem_ty_src,
             "expected bool, integer, float, enum, or pointer type; found '{}'",
-            .{elem_ty.fmt(sema.mod)},
+            .{elem_ty.fmt(mod)},
         ),
     };
 
@@ -20943,7 +21054,7 @@ fn checkAtomicPtrOperand(
     const ptr_data = switch (try ptr_ty.zigTypeTagOrPoison(mod)) {
         .Pointer => ptr_ty.ptrInfo(mod),
         else => {
-            const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data);
+            const wanted_ptr_ty = try Type.ptr(sema.arena, mod, wanted_ptr_data);
             _ = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
             unreachable;
         },
@@ -20953,7 +21064,7 @@ fn checkAtomicPtrOperand(
     wanted_ptr_data.@"allowzero" = ptr_data.@"allowzero";
     wanted_ptr_data.@"volatile" = ptr_data.@"volatile";
 
-    const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data);
+    const wanted_ptr_ty = try Type.ptr(sema.arena, mod, wanted_ptr_data);
     const casted_ptr = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
 
     return casted_ptr;
@@ -21016,12 +21127,12 @@ fn checkIntOrVector(
             switch (try elem_ty.zigTypeTagOrPoison(mod)) {
                 .Int => return elem_ty,
                 else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{
-                    elem_ty.fmt(sema.mod),
+                    elem_ty.fmt(mod),
                 }),
             }
         },
         else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{
-            operand_ty.fmt(sema.mod),
+            operand_ty.fmt(mod),
         }),
     }
 }
@@ -21040,12 +21151,12 @@ fn checkIntOrVectorAllowComptime(
             switch (try elem_ty.zigTypeTagOrPoison(mod)) {
                 .Int, .ComptimeInt => return elem_ty,
                 else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{
-                    elem_ty.fmt(sema.mod),
+                    elem_ty.fmt(mod),
                 }),
             }
         },
         else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{
-            operand_ty.fmt(sema.mod),
+            operand_ty.fmt(mod),
         }),
     }
 }
@@ -21054,7 +21165,7 @@ fn checkErrorSetType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Comp
     const mod = sema.mod;
     switch (ty.zigTypeTag(mod)) {
         .ErrorSet => return,
-        else => return sema.fail(block, src, "expected error set type, found '{}'", .{ty.fmt(sema.mod)}),
+        else => return sema.fail(block, src, "expected error set type, found '{}'", .{ty.fmt(mod)}),
     }
 }
 
@@ -21142,7 +21253,7 @@ fn checkVectorizableBinaryOperands(
     } else {
         const msg = msg: {
             const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: '{}' and '{}'", .{
-                lhs_ty.fmt(sema.mod), rhs_ty.fmt(sema.mod),
+                lhs_ty.fmt(mod), rhs_ty.fmt(mod),
             });
             errdefer msg.destroy(sema.gpa);
             if (lhs_is_vector) {
@@ -21161,7 +21272,7 @@ fn checkVectorizableBinaryOperands(
 fn maybeOptionsSrc(sema: *Sema, block: *Block, base_src: LazySrcLoc, wanted: []const u8) LazySrcLoc {
     if (base_src == .unneeded) return .unneeded;
     const mod = sema.mod;
-    return mod.optionsSrc(sema.mod.declPtr(block.src_decl), base_src, wanted);
+    return mod.optionsSrc(mod.declPtr(block.src_decl), base_src, wanted);
 }
 
 fn resolveExportOptions(
@@ -21282,7 +21393,7 @@ fn zirCmpxchg(
             block,
             elem_ty_src,
             "expected bool, integer, enum, or pointer type; found '{}'",
-            .{elem_ty.fmt(sema.mod)},
+            .{elem_ty.fmt(mod)},
         );
     }
     const uncasted_ptr = try sema.resolveInst(extra.ptr);
@@ -21322,8 +21433,8 @@ fn zirCmpxchg(
                 const ptr_ty = sema.typeOf(ptr);
                 const stored_val = (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) orelse break :rs ptr_src;
                 const result_val = try mod.intern(.{ .opt = .{
-                    .ty = result_ty.ip_index,
-                    .val = if (stored_val.eql(expected_val, elem_ty, sema.mod)) blk: {
+                    .ty = result_ty.toIntern(),
+                    .val = if (stored_val.eql(expected_val, elem_ty, mod)) blk: {
                         try sema.storePtr(block, src, ptr, new_value);
                         break :blk .none;
                     } else stored_val.toIntern(),
@@ -21363,7 +21474,7 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
     try sema.checkVectorElemType(block, scalar_src, scalar_ty);
     const vector_ty = try mod.vectorType(.{
         .len = len,
-        .child = scalar_ty.ip_index,
+        .child = scalar_ty.toIntern(),
     });
     if (try sema.resolveMaybeUndefVal(scalar)) |scalar_val| {
         if (scalar_val.isUndef(mod)) return sema.addConstUndef(vector_ty);
@@ -21489,7 +21600,7 @@ fn analyzeShuffle(
 
     const res_ty = try mod.vectorType(.{
         .len = mask_len,
-        .child = elem_ty.ip_index,
+        .child = elem_ty.toIntern(),
     });
 
     var maybe_a_len = switch (sema.typeOf(a).zigTypeTag(mod)) {
@@ -21516,11 +21627,11 @@ fn analyzeShuffle(
 
     const a_ty = try mod.vectorType(.{
         .len = a_len,
-        .child = elem_ty.ip_index,
+        .child = elem_ty.toIntern(),
     });
     const b_ty = try mod.vectorType(.{
         .len = b_len,
-        .child = elem_ty.ip_index,
+        .child = elem_ty.toIntern(),
     });
 
     if (maybe_a_len == null) a = try sema.addConstUndef(a_ty) else a = try sema.coerce(block, a_ty, a, a_src);
@@ -21567,25 +21678,21 @@ fn analyzeShuffle(
 
     if (try sema.resolveMaybeUndefVal(a)) |a_val| {
         if (try sema.resolveMaybeUndefVal(b)) |b_val| {
-            const values = try sema.arena.alloc(Value, mask_len);
-
-            i = 0;
-            while (i < mask_len) : (i += 1) {
+            const values = try sema.arena.alloc(InternPool.Index, mask_len);
+            for (values) |*value| {
                 const mask_elem_val = try mask.elemValue(sema.mod, i);
                 if (mask_elem_val.isUndef(mod)) {
-                    values[i] = Value.undef;
+                    value.* = try mod.intern(.{ .undef = elem_ty.toIntern() });
                     continue;
                 }
                 const int = mask_elem_val.toSignedInt(mod);
                 const unsigned = if (int >= 0) @intCast(u32, int) else @intCast(u32, ~int);
-                if (int >= 0) {
-                    values[i] = try a_val.elemValue(sema.mod, unsigned);
-                } else {
-                    values[i] = try b_val.elemValue(sema.mod, unsigned);
-                }
+                values[i] = try (try (if (int >= 0) a_val else b_val).elemValue(mod, unsigned)).intern(elem_ty, mod);
             }
-            const res_val = try Value.Tag.aggregate.create(sema.arena, values);
-            return sema.addConstant(res_ty, res_val);
+            return sema.addConstant(res_ty, (try mod.intern(.{ .aggregate = .{
+                .ty = res_ty.toIntern(),
+                .storage = .{ .elems = values },
+            } })).toValue());
         }
     }
 
@@ -21599,22 +21706,25 @@ fn analyzeShuffle(
         const max_src = if (a_len > b_len) a_src else b_src;
         const max_len = try sema.usizeCast(block, max_src, std.math.max(a_len, b_len));
 
-        const expand_mask_values = try sema.arena.alloc(Value, max_len);
+        const expand_mask_values = try sema.arena.alloc(InternPool.Index, max_len);
         i = 0;
         while (i < min_len) : (i += 1) {
-            expand_mask_values[i] = try mod.intValue(Type.comptime_int, i);
+            expand_mask_values[i] = (try mod.intValue(Type.comptime_int, i)).toIntern();
         }
         while (i < max_len) : (i += 1) {
-            expand_mask_values[i] = try mod.intValue(Type.comptime_int, -1);
+            expand_mask_values[i] = (try mod.intValue(Type.comptime_int, -1)).toIntern();
         }
-        const expand_mask = try Value.Tag.aggregate.create(sema.arena, expand_mask_values);
+        const expand_mask = try mod.intern(.{ .aggregate = .{
+            .ty = (try mod.vectorType(.{ .len = @intCast(u32, max_len), .child = .comptime_int_type })).toIntern(),
+            .storage = .{ .elems = expand_mask_values },
+        } });
 
         if (a_len < b_len) {
             const undef = try sema.addConstUndef(a_ty);
-            a = try sema.analyzeShuffle(block, src_node, elem_ty, a, undef, expand_mask, @intCast(u32, max_len));
+            a = try sema.analyzeShuffle(block, src_node, elem_ty, a, undef, expand_mask.toValue(), @intCast(u32, max_len));
         } else {
             const undef = try sema.addConstUndef(b_ty);
-            b = try sema.analyzeShuffle(block, src_node, elem_ty, b, undef, expand_mask, @intCast(u32, max_len));
+            b = try sema.analyzeShuffle(block, src_node, elem_ty, b, undef, expand_mask.toValue(), @intCast(u32, max_len));
         }
     }
 
@@ -21651,7 +21761,7 @@ fn zirSelect(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) C
 
     const vec_len_u64 = switch (try pred_ty.zigTypeTagOrPoison(mod)) {
         .Vector, .Array => pred_ty.arrayLen(mod),
-        else => return sema.fail(block, pred_src, "expected vector or array, found '{}'", .{pred_ty.fmt(sema.mod)}),
+        else => return sema.fail(block, pred_src, "expected vector or array, found '{}'", .{pred_ty.fmt(mod)}),
     };
     const vec_len = @intCast(u32, try sema.usizeCast(block, pred_src, vec_len_u64));
 
@@ -21663,7 +21773,7 @@ fn zirSelect(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) C
 
     const vec_ty = try mod.vectorType(.{
         .len = vec_len,
-        .child = elem_ty.ip_index,
+        .child = elem_ty.toIntern(),
     });
     const a = try sema.coerce(block, vec_ty, try sema.resolveInst(extra.a), a_src);
     const b = try sema.coerce(block, vec_ty, try sema.resolveInst(extra.b), b_src);
@@ -21681,21 +21791,17 @@ fn zirSelect(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) C
             if (maybe_b) |b_val| {
                 if (b_val.isUndef(mod)) return sema.addConstUndef(vec_ty);
 
-                const elems = try sema.gpa.alloc(Value, vec_len);
+                const elems = try sema.gpa.alloc(InternPool.Index, vec_len);
                 for (elems, 0..) |*elem, i| {
-                    const pred_elem_val = try pred_val.elemValue(sema.mod, i);
+                    const pred_elem_val = try pred_val.elemValue(mod, i);
                     const should_choose_a = pred_elem_val.toBool(mod);
-                    if (should_choose_a) {
-                        elem.* = try a_val.elemValue(sema.mod, i);
-                    } else {
-                        elem.* = try b_val.elemValue(sema.mod, i);
-                    }
+                    elem.* = try (try (if (should_choose_a) a_val else b_val).elemValue(mod, i)).intern(elem_ty, mod);
                 }
 
-                return sema.addConstant(
-                    vec_ty,
-                    try Value.Tag.aggregate.create(sema.arena, elems),
-                );
+                return sema.addConstant(vec_ty, (try mod.intern(.{ .aggregate = .{
+                    .ty = vec_ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue());
             } else {
                 break :rs b_src;
             }
@@ -22019,7 +22125,7 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
     const args = try sema.resolveInst(extra.args);
 
     const args_ty = sema.typeOf(args);
-    if (!args_ty.isTuple(mod) and args_ty.ip_index != .empty_struct_type) {
+    if (!args_ty.isTuple(mod) and args_ty.toIntern() != .empty_struct_type) {
         return sema.fail(block, args_src, "expected a tuple, found '{}'", .{args_ty.fmt(sema.mod)});
     }
 
@@ -22102,7 +22208,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     const result_ptr = try Type.ptr(sema.arena, sema.mod, ptr_ty_data);
 
     if (try sema.resolveDefinedValue(block, src, casted_field_ptr)) |field_ptr_val| {
-        const field = switch (mod.intern_pool.indexToKey(field_ptr_val.ip_index)) {
+        const field = switch (mod.intern_pool.indexToKey(field_ptr_val.toIntern())) {
             .ptr => |ptr| switch (ptr.addr) {
                 .field => |field| field,
                 else => null,
@@ -22244,16 +22350,16 @@ fn analyzeMinMax(
                 cur_minmax = try sema.addConstant(simd_op.result_ty, result_val);
                 continue;
             };
-            const elems = try sema.arena.alloc(Value, vec_len);
+            const elems = try sema.arena.alloc(InternPool.Index, vec_len);
             for (elems, 0..) |*elem, i| {
                 const lhs_elem_val = try cur_val.elemValue(mod, i);
                 const rhs_elem_val = try operand_val.elemValue(mod, i);
-                elem.* = opFunc(lhs_elem_val, rhs_elem_val, mod);
+                elem.* = try opFunc(lhs_elem_val, rhs_elem_val, mod).intern(simd_op.scalar_ty, mod);
             }
-            cur_minmax = try sema.addConstant(
-                simd_op.result_ty,
-                try Value.Tag.aggregate.create(sema.arena, elems),
-            );
+            cur_minmax = try sema.addConstant(simd_op.result_ty, (try mod.intern(.{ .aggregate = .{
+                .ty = simd_op.result_ty.toIntern(),
+                .storage = .{ .elems = elems },
+            } })).toValue());
         } else {
             runtime_known.unset(operand_idx);
             cur_minmax = try sema.addConstant(sema.typeOf(operand), uncasted_operand_val);
@@ -22292,7 +22398,7 @@ fn analyzeMinMax(
             const refined_elem_ty = try mod.intFittingRange(cur_min, cur_max);
             break :blk try mod.vectorType(.{
                 .len = len,
-                .child = refined_elem_ty.ip_index,
+                .child = refined_elem_ty.toIntern(),
             });
         } else blk: {
             if (orig_ty.isAnyFloat()) break :blk orig_ty; // can't refine floats
@@ -22377,7 +22483,7 @@ fn analyzeMinMax(
         const final_ty = if (is_vector)
             try mod.vectorType(.{
                 .len = unrefined_ty.vectorLen(mod),
-                .child = final_elem_ty.ip_index,
+                .child = final_elem_ty.toIntern(),
             })
         else
             final_elem_ty;
@@ -22765,7 +22871,7 @@ fn zirVarExtended(
     try sema.validateVarType(block, ty_src, var_ty, small.is_extern);
 
     return sema.addConstant(var_ty, (try mod.intern(.{ .variable = .{
-        .ty = var_ty.ip_index,
+        .ty = var_ty.toIntern(),
         .init = init_val.toIntern(),
         .decl = sema.owner_decl_index,
         .lib_name = if (lib_name) |lname| (try mod.intern_pool.getOrPutString(
@@ -23239,7 +23345,7 @@ fn zirBuiltinExtern(
 
     {
         const new_var = try mod.intern(.{ .variable = .{
-            .ty = ty.ip_index,
+            .ty = ty.toIntern(),
             .init = .none,
             .decl = sema.owner_decl_index,
             .is_extern = true,
@@ -23264,7 +23370,7 @@ fn zirBuiltinExtern(
     try sema.ensureDeclAnalyzed(new_decl_index);
 
     const ref = try mod.intern(.{ .ptr = .{
-        .ty = (try mod.singleConstPtrType(ty)).ip_index,
+        .ty = (try mod.singleConstPtrType(ty)).toIntern(),
         .addr = .{ .decl = new_decl_index },
     } });
     return sema.addConstant(ty, ref.toValue());
@@ -24207,7 +24313,7 @@ fn fieldVal(
             switch (try child_type.zigTypeTagOrPoison(mod)) {
                 .ErrorSet => {
                     const name = try ip.getOrPutString(gpa, field_name);
-                    switch (ip.indexToKey(child_type.ip_index)) {
+                    switch (ip.indexToKey(child_type.toIntern())) {
                         .error_set_type => |error_set_type| blk: {
                             if (error_set_type.nameIndex(ip, name) != null) break :blk;
                             const msg = msg: {
@@ -24232,7 +24338,7 @@ fn fieldVal(
                     else
                         try mod.singleErrorSetTypeNts(name);
                     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
-                        .ty = error_set_type.ip_index,
+                        .ty = error_set_type.toIntern(),
                         .name = name,
                     } })).toValue());
                 },
@@ -24376,9 +24482,9 @@ fn fieldPtr(
 
                 if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| {
                     return sema.addConstant(result_ty, (try mod.intern(.{ .ptr = .{
-                        .ty = result_ty.ip_index,
+                        .ty = result_ty.toIntern(),
                         .addr = .{ .field = .{
-                            .base = val.ip_index,
+                            .base = val.toIntern(),
                             .index = Value.slice_ptr_index,
                         } },
                     } })).toValue());
@@ -24396,9 +24502,9 @@ fn fieldPtr(
 
                 if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| {
                     return sema.addConstant(result_ty, (try mod.intern(.{ .ptr = .{
-                        .ty = result_ty.ip_index,
+                        .ty = result_ty.toIntern(),
                         .addr = .{ .field = .{
-                            .base = val.ip_index,
+                            .base = val.toIntern(),
                             .index = Value.slice_len_index,
                         } },
                     } })).toValue());
@@ -24429,7 +24535,7 @@ fn fieldPtr(
             switch (child_type.zigTypeTag(mod)) {
                 .ErrorSet => {
                     const name = try ip.getOrPutString(gpa, field_name);
-                    switch (ip.indexToKey(child_type.ip_index)) {
+                    switch (ip.indexToKey(child_type.toIntern())) {
                         .error_set_type => |error_set_type| blk: {
                             if (error_set_type.nameIndex(ip, name) != null) {
                                 break :blk;
@@ -24454,7 +24560,7 @@ fn fieldPtr(
                     return sema.analyzeDeclRef(try anon_decl.finish(
                         error_set_type,
                         (try mod.intern(.{ .err = .{
-                            .ty = error_set_type.ip_index,
+                            .ty = error_set_type.toIntern(),
                             .name = name,
                         } })).toValue(),
                         0, // default alignment
@@ -24722,9 +24828,9 @@ fn finishFieldCallBind(
 
     if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| {
         const pointer = try sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
+            .ty = ptr_field_ty.toIntern(),
             .addr = .{ .field = .{
-                .base = struct_ptr_val.ip_index,
+                .base = struct_ptr_val.toIntern(),
                 .index = field_index,
             } },
         } })).toValue());
@@ -24908,7 +25014,7 @@ fn structFieldPtrByIndex(
 
     if (field.is_comptime) {
         const val = try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
+            .ty = ptr_field_ty.toIntern(),
             .addr = .{ .comptime_field = try field.default_val.intern(field.ty, mod) },
         } });
         return sema.addConstant(ptr_field_ty, val.toValue());
@@ -24916,7 +25022,7 @@ fn structFieldPtrByIndex(
 
     if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
         const val = try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
+            .ty = ptr_field_ty.toIntern(),
             .addr = .{ .field = .{
                 .base = try struct_ptr_val.intern(struct_ptr_ty, mod),
                 .index = field_index,
@@ -24942,7 +25048,7 @@ fn structFieldVal(
     assert(unresolved_struct_ty.zigTypeTag(mod) == .Struct);
 
     const struct_ty = try sema.resolveTypeFields(unresolved_struct_ty);
-    switch (mod.intern_pool.indexToKey(struct_ty.ip_index)) {
+    switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
         .struct_type => |struct_type| {
             const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
             if (struct_obj.is_tuple) return sema.tupleFieldVal(block, src, struct_byval, field_name, field_name_src, struct_ty);
@@ -25116,9 +25222,9 @@ fn unionFieldPtr(
             .Packed, .Extern => {},
         }
         return sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
+            .ty = ptr_field_ty.toIntern(),
             .addr = .{ .field = .{
-                .base = union_ptr_val.ip_index,
+                .base = union_ptr_val.toIntern(),
                 .index = field_index,
             } },
         } })).toValue());
@@ -25413,16 +25519,16 @@ fn tupleFieldPtr(
 
     if (try tuple_ty.structFieldValueComptime(mod, field_index)) |default_val| {
         return sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
-            .addr = .{ .comptime_field = default_val.ip_index },
+            .ty = ptr_field_ty.toIntern(),
+            .addr = .{ .comptime_field = default_val.toIntern() },
         } })).toValue());
     }
 
     if (try sema.resolveMaybeUndefVal(tuple_ptr)) |tuple_ptr_val| {
         return sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{
-            .ty = ptr_field_ty.ip_index,
+            .ty = ptr_field_ty.toIntern(),
             .addr = .{ .field = .{
-                .base = tuple_ptr_val.ip_index,
+                .base = tuple_ptr_val.toIntern(),
                 .index = field_index,
             } },
         } })).toValue());
@@ -25787,11 +25893,11 @@ fn coerceExtra(
     var in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src);
     if (in_memory_result == .ok) {
         if (maybe_inst_val) |val| {
-            if (val.ip_index == .none or val.ip_index == .null_value) {
+            if (val.ip_index == .none) {
                 // Keep the comptime Value representation; take the new type.
                 return sema.addConstant(dest_ty, val);
             } else {
-                const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.ip_index, dest_ty.ip_index);
+                const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.toIntern(), dest_ty.toIntern());
                 return sema.addConstant(dest_ty, new_val.toValue());
             }
         }
@@ -25816,7 +25922,7 @@ fn coerceExtra(
             // cast from ?*T and ?[*]T to ?*anyopaque
             // but don't do it if the source type is a double pointer
             if (dest_ty.isPtrLikeOptional(mod) and
-                dest_ty.elemType2(mod).ip_index == .anyopaque_type and
+                dest_ty.elemType2(mod).toIntern() == .anyopaque_type and
                 inst_ty.isPtrAtRuntime(mod))
             anyopaque_check: {
                 if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :optional;
@@ -25954,7 +26060,7 @@ fn coerceExtra(
 
             // cast from *T and [*]T to *anyopaque
             // but don't do it if the source type is a double pointer
-            if (dest_info.pointee_type.ip_index == .anyopaque_type and inst_ty.zigTypeTag(mod) == .Pointer) to_anyopaque: {
+            if (dest_info.pointee_type.toIntern() == .anyopaque_type and inst_ty.zigTypeTag(mod) == .Pointer) to_anyopaque: {
                 if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer;
                 const elem_ty = inst_ty.elemType2(mod);
                 if (elem_ty.zigTypeTag(mod) == .Pointer or elem_ty.isPtrLikeOptional(mod)) {
@@ -26084,12 +26190,12 @@ fn coerceExtra(
                         // Optional slice is represented with a null pointer so
                         // we use a dummy pointer value with the required alignment.
                         return sema.addConstant(dest_ty, (try mod.intern(.{ .ptr = .{
-                            .ty = dest_ty.ip_index,
+                            .ty = dest_ty.toIntern(),
                             .addr = .{ .int = (if (dest_info.@"align" != 0)
                                 try mod.intValue(Type.usize, dest_info.@"align")
                             else
-                                try dest_info.pointee_type.lazyAbiAlignment(mod)).ip_index },
-                            .len = (try mod.intValue(Type.usize, 0)).ip_index,
+                                try dest_info.pointee_type.lazyAbiAlignment(mod)).toIntern() },
+                            .len = (try mod.intValue(Type.usize, 0)).toIntern(),
                         } })).toValue());
                     }
 
@@ -26166,7 +26272,7 @@ fn coerceExtra(
                         if (!opts.report_err) return error.NotCoercible;
                         return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) });
                     }
-                    const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.ip_index, dest_ty.ip_index);
+                    const new_val = try mod.intern_pool.getCoerced(sema.gpa, val.toIntern(), dest_ty.toIntern());
                     return try sema.addConstant(dest_ty, new_val.toValue());
                 }
                 if (dest_ty.zigTypeTag(mod) == .ComptimeInt) {
@@ -26258,7 +26364,7 @@ fn coerceExtra(
             .EnumLiteral => {
                 // enum literal to enum
                 const val = try sema.resolveConstValue(block, .unneeded, inst, "");
-                const string = mod.intern_pool.indexToKey(val.ip_index).enum_literal;
+                const string = mod.intern_pool.indexToKey(val.toIntern()).enum_literal;
                 const bytes = mod.intern_pool.stringToSlice(string);
                 const field_index = dest_ty.enumFieldIndex(bytes, mod) orelse {
                     const msg = msg: {
@@ -26294,14 +26400,14 @@ fn coerceExtra(
         .ErrorUnion => switch (inst_ty.zigTypeTag(mod)) {
             .ErrorUnion => eu: {
                 if (maybe_inst_val) |inst_val| {
-                    switch (inst_val.ip_index) {
+                    switch (inst_val.toIntern()) {
                         .undef => return sema.addConstUndef(dest_ty),
-                        else => switch (mod.intern_pool.indexToKey(inst_val.ip_index)) {
+                        else => switch (mod.intern_pool.indexToKey(inst_val.toIntern())) {
                             .error_union => |error_union| switch (error_union.val) {
                                 .err_name => |err_name| {
                                     const error_set_ty = inst_ty.errorUnionSet(mod);
                                     const error_set_val = try sema.addConstant(error_set_ty, (try mod.intern(.{ .err = .{
-                                        .ty = error_set_ty.ip_index,
+                                        .ty = error_set_ty.toIntern(),
                                         .name = err_name,
                                     } })).toValue());
                                     return sema.wrapErrorUnionSet(block, dest_ty, error_set_val, inst_src);
@@ -26597,7 +26703,7 @@ const InMemoryCoercionResult = union(enum) {
                 break;
             },
             .array_sentinel => |sentinel| {
-                if (sentinel.actual.ip_index != .unreachable_value) {
+                if (sentinel.actual.toIntern() != .unreachable_value) {
                     try sema.errNote(block, src, msg, "array sentinel '{}' cannot cast into array sentinel '{}'", .{
                         sentinel.actual.fmtValue(sentinel.ty, sema.mod), sentinel.wanted.fmtValue(sentinel.ty, sema.mod),
                     });
@@ -26724,7 +26830,7 @@ const InMemoryCoercionResult = union(enum) {
                 break;
             },
             .ptr_sentinel => |sentinel| {
-                if (sentinel.actual.ip_index != .unreachable_value) {
+                if (sentinel.actual.toIntern() != .unreachable_value) {
                     try sema.errNote(block, src, msg, "pointer sentinel '{}' cannot cast into pointer sentinel '{}'", .{
                         sentinel.actual.fmtValue(sentinel.ty, sema.mod), sentinel.wanted.fmtValue(sentinel.ty, sema.mod),
                     });
@@ -27016,9 +27122,9 @@ fn coerceInMemoryAllowedErrorSets(
         const dst_ies = mod.inferredErrorSetPtr(dst_ies_index);
         // We will make an effort to return `ok` without resolving either error set, to
         // avoid unnecessary "unable to resolve error set" dependency loop errors.
-        switch (src_ty.ip_index) {
+        switch (src_ty.toIntern()) {
             .anyerror_type => {},
-            else => switch (ip.indexToKey(src_ty.ip_index)) {
+            else => switch (ip.indexToKey(src_ty.toIntern())) {
                 .inferred_error_set_type => |src_index| {
                     // If both are inferred error sets of functions, and
                     // the dest includes the source function, the coercion is OK.
@@ -27054,15 +27160,15 @@ fn coerceInMemoryAllowedErrorSets(
     var missing_error_buf = std.ArrayList(InternPool.NullTerminatedString).init(gpa);
     defer missing_error_buf.deinit();
 
-    switch (src_ty.ip_index) {
-        .anyerror_type => switch (ip.indexToKey(dest_ty.ip_index)) {
+    switch (src_ty.toIntern()) {
+        .anyerror_type => switch (ip.indexToKey(dest_ty.toIntern())) {
             .inferred_error_set_type => unreachable, // Caught by dest_ty.isAnyError(mod) above.
             .simple_type => unreachable, // filtered out above
             .error_set_type => return .from_anyerror,
             else => unreachable,
         },
 
-        else => switch (ip.indexToKey(src_ty.ip_index)) {
+        else => switch (ip.indexToKey(src_ty.toIntern())) {
             .inferred_error_set_type => |src_index| {
                 const src_data = mod.inferredErrorSetPtr(src_index);
 
@@ -27520,9 +27626,9 @@ fn obtainBitCastedVectorPtr(sema: *Sema, ptr: Air.Inst.Ref) ?Air.Inst.Ref {
         // allocations is relevant to this function, or why it would have
         // different behavior depending on whether the types were inferred.
         // Something seems wrong here.
-        if (prev_ptr_ty.ip_index == .none) {
-            if (prev_ptr_ty.ip_index == .inferred_alloc_mut_type) return null;
-            if (prev_ptr_ty.ip_index == .inferred_alloc_const_type) return null;
+        switch (prev_ptr_ty.ip_index) {
+            .inferred_alloc_mut_type, .inferred_alloc_const_type => return null,
+            else => {},
         }
 
         const prev_ptr_child_ty = prev_ptr_ty.childType(mod);
@@ -27554,11 +27660,11 @@ fn storePtrVal(
 ) !void {
     const mod = sema.mod;
     var mut_kit = try sema.beginComptimePtrMutation(block, src, ptr_val, operand_ty);
-    try sema.checkComptimeVarStore(block, src, mut_kit.decl_ref_mut);
+    try sema.checkComptimeVarStore(block, src, mut_kit.mut_decl);
 
     switch (mut_kit.pointee) {
         .direct => |val_ptr| {
-            if (mut_kit.decl_ref_mut.runtime_index == .comptime_field_ptr) {
+            if (mut_kit.mut_decl.runtime_index == .comptime_field_ptr) {
                 if (!operand_val.eql(val_ptr.*, operand_ty, sema.mod)) {
                     // TODO use failWithInvalidComptimeFieldStore
                     return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{});
@@ -27601,7 +27707,7 @@ fn storePtrVal(
 }
 
 const ComptimePtrMutationKit = struct {
-    decl_ref_mut: InternPool.Key.Ptr.Addr.MutDecl,
+    mut_decl: InternPool.Key.Ptr.Addr.MutDecl,
     pointee: union(enum) {
         /// The pointer type matches the actual comptime Value so a direct
         /// modification is possible.
@@ -27627,12 +27733,12 @@ const ComptimePtrMutationKit = struct {
     decl_arena: std.heap.ArenaAllocator = undefined,
 
     fn beginArena(self: *ComptimePtrMutationKit, mod: *Module) Allocator {
-        const decl = mod.declPtr(self.decl_ref_mut.decl);
+        const decl = mod.declPtr(self.mut_decl.decl);
         return decl.value_arena.?.acquire(mod.gpa, &self.decl_arena);
     }
 
     fn finishArena(self: *ComptimePtrMutationKit, mod: *Module) void {
-        const decl = mod.declPtr(self.decl_ref_mut.decl);
+        const decl = mod.declPtr(self.mut_decl.decl);
         decl.value_arena.?.release(&self.decl_arena);
         self.decl_arena = undefined;
     }
@@ -27645,99 +27751,85 @@ fn beginComptimePtrMutation(
     ptr_val: Value,
     ptr_elem_ty: Type,
 ) CompileError!ComptimePtrMutationKit {
-    if (true) unreachable;
     const mod = sema.mod;
-    switch (ptr_val.tag()) {
-        .decl_ref_mut => {
-            const decl_ref_mut = ptr_val.castTag(.decl_ref_mut).?.data;
-            const decl = sema.mod.declPtr(decl_ref_mut.decl_index);
-            return sema.beginComptimePtrMutationInner(block, src, decl.ty, &decl.val, ptr_elem_ty, decl_ref_mut);
+    const ptr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr;
+    switch (ptr.addr) {
+        .decl => unreachable, // isComptimeMutablePtr has been checked already
+        .mut_decl => |mut_decl| {
+            const decl = mod.declPtr(mut_decl.decl);
+            return sema.beginComptimePtrMutationInner(block, src, decl.ty, &decl.val, ptr_elem_ty, mut_decl);
         },
-        .comptime_field_ptr => {
-            const payload = ptr_val.castTag(.comptime_field_ptr).?.data;
+        .comptime_field => |comptime_field| {
             const duped = try sema.arena.create(Value);
-            duped.* = payload.field_val;
-            return sema.beginComptimePtrMutationInner(block, src, payload.field_ty, duped, ptr_elem_ty, .{
-                .decl_index = @intToEnum(Module.Decl.Index, 0),
+            duped.* = comptime_field.toValue();
+            return sema.beginComptimePtrMutationInner(block, src, mod.intern_pool.typeOf(ptr_val.toIntern()).toType(), duped, ptr_elem_ty, .{
+                .decl = undefined,
                 .runtime_index = .comptime_field_ptr,
             });
         },
-        .elem_ptr => {
-            const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
-            var parent = try sema.beginComptimePtrMutation(block, src, elem_ptr.array_ptr, elem_ptr.elem_ty);
-
-            switch (parent.pointee) {
-                .direct => |val_ptr| switch (parent.ty.zigTypeTag(mod)) {
-                    .Array, .Vector => {
-                        const check_len = parent.ty.arrayLenIncludingSentinel(mod);
-                        if (elem_ptr.index >= check_len) {
-                            // TODO have the parent include the decl so we can say "declared here"
-                            return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{
-                                elem_ptr.index, check_len,
-                            });
-                        }
-                        const elem_ty = parent.ty.childType(mod);
-
-                        // We might have a pointer to multiple elements of the array (e.g. a pointer
-                        // to a sub-array). In this case, we just have to reinterpret the relevant
-                        // bytes of the whole array rather than any single element.
-                        const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty);
-                        if (elem_abi_size_u64 < try sema.typeAbiSize(ptr_elem_ty)) {
-                            const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
-                            return .{
-                                .decl_ref_mut = parent.decl_ref_mut,
-                                .pointee = .{ .reinterpret = .{
-                                    .val_ptr = val_ptr,
-                                    .byte_offset = elem_abi_size * elem_ptr.index,
-                                } },
-                                .ty = parent.ty,
-                            };
-                        }
-
-                        switch (val_ptr.ip_index) {
-                            .undef => {
-                                // An array has been initialized to undefined at comptime and now we
-                                // are for the first time setting an element. We must change the representation
-                                // of the array from `undef` to `array`.
-                                const arena = parent.beginArena(sema.mod);
-                                defer parent.finishArena(sema.mod);
-
-                                const array_len_including_sentinel =
-                                    try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod));
-                                const elems = try arena.alloc(Value, array_len_including_sentinel);
-                                @memset(elems, Value.undef);
-
-                                val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
+        else => unreachable,
+    }
+    if (true) unreachable;
+    switch (ptr_val.toIntern()) {
+        .none => switch (ptr_val.tag()) {
+            .decl_ref_mut => {
+                const decl_ref_mut = ptr_val.castTag(.decl_ref_mut).?.data;
+                const decl = sema.mod.declPtr(decl_ref_mut.decl_index);
+                return sema.beginComptimePtrMutationInner(block, src, decl.ty, &decl.val, ptr_elem_ty, decl_ref_mut);
+            },
+            .comptime_field_ptr => {
+                const payload = ptr_val.castTag(.comptime_field_ptr).?.data;
+                const duped = try sema.arena.create(Value);
+                duped.* = payload.field_val;
+                return sema.beginComptimePtrMutationInner(block, src, payload.field_ty, duped, ptr_elem_ty, .{
+                    .decl_index = @intToEnum(Module.Decl.Index, 0),
+                    .runtime_index = .comptime_field_ptr,
+                });
+            },
+            .elem_ptr => {
+                const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
+                var parent = try sema.beginComptimePtrMutation(block, src, elem_ptr.array_ptr, elem_ptr.elem_ty);
+
+                switch (parent.pointee) {
+                    .direct => |val_ptr| switch (parent.ty.zigTypeTag(mod)) {
+                        .Array, .Vector => {
+                            const check_len = parent.ty.arrayLenIncludingSentinel(mod);
+                            if (elem_ptr.index >= check_len) {
+                                // TODO have the parent include the decl so we can say "declared here"
+                                return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{
+                                    elem_ptr.index, check_len,
+                                });
+                            }
+                            const elem_ty = parent.ty.childType(mod);
+
+                            // We might have a pointer to multiple elements of the array (e.g. a pointer
+                            // to a sub-array). In this case, we just have to reinterpret the relevant
+                            // bytes of the whole array rather than any single element.
+                            const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty);
+                            if (elem_abi_size_u64 < try sema.typeAbiSize(ptr_elem_ty)) {
+                                const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
+                                return .{
+                                    .decl_ref_mut = parent.decl_ref_mut,
+                                    .pointee = .{ .reinterpret = .{
+                                        .val_ptr = val_ptr,
+                                        .byte_offset = elem_abi_size * elem_ptr.index,
+                                    } },
+                                    .ty = parent.ty,
+                                };
+                            }
 
-                                return beginComptimePtrMutationInner(
-                                    sema,
-                                    block,
-                                    src,
-                                    elem_ty,
-                                    &elems[elem_ptr.index],
-                                    ptr_elem_ty,
-                                    parent.decl_ref_mut,
-                                );
-                            },
-                            .none => switch (val_ptr.tag()) {
-                                .bytes => {
-                                    // An array is memory-optimized to store a slice of bytes, but we are about
-                                    // to modify an individual field and the representation has to change.
-                                    // If we wanted to avoid this, there would need to be special detection
-                                    // elsewhere to identify when writing a value to an array element that is stored
-                                    // using the `bytes` tag, and handle it without making a call to this function.
+                            switch (val_ptr.toIntern()) {
+                                .undef => {
+                                    // An array has been initialized to undefined at comptime and now we
+                                    // are for the first time setting an element. We must change the representation
+                                    // of the array from `undef` to `array`.
                                     const arena = parent.beginArena(sema.mod);
                                     defer parent.finishArena(sema.mod);
 
-                                    const bytes = val_ptr.castTag(.bytes).?.data;
-                                    const dest_len = parent.ty.arrayLenIncludingSentinel(mod);
-                                    // bytes.len may be one greater than dest_len because of the case when
-                                    // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
-                                    assert(bytes.len >= dest_len);
-                                    const elems = try arena.alloc(Value, @intCast(usize, dest_len));
-                                    for (elems, 0..) |*elem, i| {
-                                        elem.* = try mod.intValue(elem_ty, bytes[i]);
-                                    }
+                                    const array_len_including_sentinel =
+                                        try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod));
+                                    const elems = try arena.alloc(Value, array_len_including_sentinel);
+                                    @memset(elems, Value.undef);
 
                                     val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
 
@@ -27751,392 +27843,383 @@ fn beginComptimePtrMutation(
                                         parent.decl_ref_mut,
                                     );
                                 },
-                                .str_lit => {
-                                    // An array is memory-optimized to store a slice of bytes, but we are about
-                                    // to modify an individual field and the representation has to change.
-                                    // If we wanted to avoid this, there would need to be special detection
-                                    // elsewhere to identify when writing a value to an array element that is stored
-                                    // using the `str_lit` tag, and handle it without making a call to this function.
-                                    const arena = parent.beginArena(sema.mod);
-                                    defer parent.finishArena(sema.mod);
-
-                                    const str_lit = val_ptr.castTag(.str_lit).?.data;
-                                    const dest_len = parent.ty.arrayLenIncludingSentinel(mod);
-                                    const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                                    const elems = try arena.alloc(Value, @intCast(usize, dest_len));
-                                    for (bytes, 0..) |byte, i| {
-                                        elems[i] = try mod.intValue(elem_ty, byte);
-                                    }
-                                    if (parent.ty.sentinel(mod)) |sent_val| {
-                                        assert(elems.len == bytes.len + 1);
-                                        elems[bytes.len] = sent_val;
-                                    }
-
-                                    val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
-
-                                    return beginComptimePtrMutationInner(
+                                .none => switch (val_ptr.tag()) {
+                                    .bytes => {
+                                        // An array is memory-optimized to store a slice of bytes, but we are about
+                                        // to modify an individual field and the representation has to change.
+                                        // If we wanted to avoid this, there would need to be special detection
+                                        // elsewhere to identify when writing a value to an array element that is stored
+                                        // using the `bytes` tag, and handle it without making a call to this function.
+                                        const arena = parent.beginArena(sema.mod);
+                                        defer parent.finishArena(sema.mod);
+
+                                        const bytes = val_ptr.castTag(.bytes).?.data;
+                                        const dest_len = parent.ty.arrayLenIncludingSentinel(mod);
+                                        // bytes.len may be one greater than dest_len because of the case when
+                                        // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
+                                        assert(bytes.len >= dest_len);
+                                        const elems = try arena.alloc(Value, @intCast(usize, dest_len));
+                                        for (elems, 0..) |*elem, i| {
+                                            elem.* = try mod.intValue(elem_ty, bytes[i]);
+                                        }
+
+                                        val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
+
+                                        return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            elem_ty,
+                                            &elems[elem_ptr.index],
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        );
+                                    },
+                                    .str_lit => {
+                                        // An array is memory-optimized to store a slice of bytes, but we are about
+                                        // to modify an individual field and the representation has to change.
+                                        // If we wanted to avoid this, there would need to be special detection
+                                        // elsewhere to identify when writing a value to an array element that is stored
+                                        // using the `str_lit` tag, and handle it without making a call to this function.
+                                        const arena = parent.beginArena(sema.mod);
+                                        defer parent.finishArena(sema.mod);
+
+                                        const str_lit = val_ptr.castTag(.str_lit).?.data;
+                                        const dest_len = parent.ty.arrayLenIncludingSentinel(mod);
+                                        const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
+                                        const elems = try arena.alloc(Value, @intCast(usize, dest_len));
+                                        for (bytes, 0..) |byte, i| {
+                                            elems[i] = try mod.intValue(elem_ty, byte);
+                                        }
+                                        if (parent.ty.sentinel(mod)) |sent_val| {
+                                            assert(elems.len == bytes.len + 1);
+                                            elems[bytes.len] = sent_val;
+                                        }
+
+                                        val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
+
+                                        return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            elem_ty,
+                                            &elems[elem_ptr.index],
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        );
+                                    },
+                                    .repeated => {
+                                        // An array is memory-optimized to store only a single element value, and
+                                        // that value is understood to be the same for the entire length of the array.
+                                        // However, now we want to modify an individual field and so the
+                                        // representation has to change.  If we wanted to avoid this, there would
+                                        // need to be special detection elsewhere to identify when writing a value to an
+                                        // array element that is stored using the `repeated` tag, and handle it
+                                        // without making a call to this function.
+                                        const arena = parent.beginArena(sema.mod);
+                                        defer parent.finishArena(sema.mod);
+
+                                        const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena);
+                                        const array_len_including_sentinel =
+                                            try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod));
+                                        const elems = try arena.alloc(Value, array_len_including_sentinel);
+                                        if (elems.len > 0) elems[0] = repeated_val;
+                                        for (elems[1..]) |*elem| {
+                                            elem.* = try repeated_val.copy(arena);
+                                        }
+
+                                        val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
+
+                                        return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            elem_ty,
+                                            &elems[elem_ptr.index],
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        );
+                                    },
+
+                                    .aggregate => return beginComptimePtrMutationInner(
                                         sema,
                                         block,
                                         src,
                                         elem_ty,
-                                        &elems[elem_ptr.index],
+                                        &val_ptr.castTag(.aggregate).?.data[elem_ptr.index],
                                         ptr_elem_ty,
                                         parent.decl_ref_mut,
-                                    );
+                                    ),
+
+                                    .the_only_possible_value => {
+                                        const duped = try sema.arena.create(Value);
+                                        duped.* = Value.initTag(.the_only_possible_value);
+                                        return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            elem_ty,
+                                            duped,
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        );
+                                    },
+
+                                    else => unreachable,
                                 },
-                                .repeated => {
-                                    // An array is memory-optimized to store only a single element value, and
-                                    // that value is understood to be the same for the entire length of the array.
-                                    // However, now we want to modify an individual field and so the
-                                    // representation has to change.  If we wanted to avoid this, there would
-                                    // need to be special detection elsewhere to identify when writing a value to an
-                                    // array element that is stored using the `repeated` tag, and handle it
-                                    // without making a call to this function.
-                                    const arena = parent.beginArena(sema.mod);
-                                    defer parent.finishArena(sema.mod);
+                                else => unreachable,
+                            }
+                        },
+                        else => {
+                            if (elem_ptr.index != 0) {
+                                // TODO include a "declared here" note for the decl
+                                return sema.fail(block, src, "out of bounds comptime store of index {d}", .{
+                                    elem_ptr.index,
+                                });
+                            }
+                            return beginComptimePtrMutationInner(
+                                sema,
+                                block,
+                                src,
+                                parent.ty,
+                                val_ptr,
+                                ptr_elem_ty,
+                                parent.decl_ref_mut,
+                            );
+                        },
+                    },
+                    .reinterpret => |reinterpret| {
+                        if (!elem_ptr.elem_ty.hasWellDefinedLayout(mod)) {
+                            // Even though the parent value type has well-defined memory layout, our
+                            // pointer type does not.
+                            return ComptimePtrMutationKit{
+                                .decl_ref_mut = parent.decl_ref_mut,
+                                .pointee = .bad_ptr_ty,
+                                .ty = elem_ptr.elem_ty,
+                            };
+                        }
 
-                                    const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena);
-                                    const array_len_including_sentinel =
-                                        try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod));
-                                    const elems = try arena.alloc(Value, array_len_including_sentinel);
-                                    if (elems.len > 0) elems[0] = repeated_val;
-                                    for (elems[1..]) |*elem| {
-                                        elem.* = try repeated_val.copy(arena);
-                                    }
+                        const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty);
+                        const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
+                        return ComptimePtrMutationKit{
+                            .decl_ref_mut = parent.decl_ref_mut,
+                            .pointee = .{ .reinterpret = .{
+                                .val_ptr = reinterpret.val_ptr,
+                                .byte_offset = reinterpret.byte_offset + elem_abi_size * elem_ptr.index,
+                            } },
+                            .ty = parent.ty,
+                        };
+                    },
+                    .bad_decl_ty, .bad_ptr_ty => return parent,
+                }
+            },
+            .field_ptr => {
+                const field_ptr = ptr_val.castTag(.field_ptr).?.data;
+                const field_index = @intCast(u32, field_ptr.field_index);
 
-                                    val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
+                var parent = try sema.beginComptimePtrMutation(block, src, field_ptr.container_ptr, field_ptr.container_ty);
+                switch (parent.pointee) {
+                    .direct => |val_ptr| switch (val_ptr.toIntern()) {
+                        .undef => {
+                            // A struct or union has been initialized to undefined at comptime and now we
+                            // are for the first time setting a field. We must change the representation
+                            // of the struct/union from `undef` to `struct`/`union`.
+                            const arena = parent.beginArena(sema.mod);
+                            defer parent.finishArena(sema.mod);
+
+                            switch (parent.ty.zigTypeTag(mod)) {
+                                .Struct => {
+                                    const fields = try arena.alloc(Value, parent.ty.structFieldCount(mod));
+                                    @memset(fields, Value.undef);
+
+                                    val_ptr.* = try Value.Tag.aggregate.create(arena, fields);
 
                                     return beginComptimePtrMutationInner(
                                         sema,
                                         block,
                                         src,
-                                        elem_ty,
-                                        &elems[elem_ptr.index],
+                                        parent.ty.structFieldType(field_index, mod),
+                                        &fields[field_index],
                                         ptr_elem_ty,
                                         parent.decl_ref_mut,
                                     );
                                 },
+                                .Union => {
+                                    const payload = try arena.create(Value.Payload.Union);
+                                    const tag_ty = parent.ty.unionTagTypeHypothetical(mod);
+                                    payload.* = .{ .data = .{
+                                        .tag = try mod.enumValueFieldIndex(tag_ty, field_index),
+                                        .val = Value.undef,
+                                    } };
 
-                                .aggregate => return beginComptimePtrMutationInner(
-                                    sema,
-                                    block,
-                                    src,
-                                    elem_ty,
-                                    &val_ptr.castTag(.aggregate).?.data[elem_ptr.index],
-                                    ptr_elem_ty,
-                                    parent.decl_ref_mut,
-                                ),
+                                    val_ptr.* = Value.initPayload(&payload.base);
 
-                                .the_only_possible_value => {
-                                    const duped = try sema.arena.create(Value);
-                                    duped.* = Value.initTag(.the_only_possible_value);
                                     return beginComptimePtrMutationInner(
                                         sema,
                                         block,
                                         src,
-                                        elem_ty,
-                                        duped,
+                                        parent.ty.structFieldType(field_index, mod),
+                                        &payload.data.val,
                                         ptr_elem_ty,
                                         parent.decl_ref_mut,
                                     );
                                 },
-
+                                .Pointer => {
+                                    assert(parent.ty.isSlice(mod));
+                                    val_ptr.* = try Value.Tag.slice.create(arena, .{
+                                        .ptr = Value.undef,
+                                        .len = Value.undef,
+                                    });
+
+                                    switch (field_index) {
+                                        Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            parent.ty.slicePtrFieldType(mod),
+                                            &val_ptr.castTag(.slice).?.data.ptr,
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        ),
+                                        Value.Payload.Slice.len_index => return beginComptimePtrMutationInner(
+                                            sema,
+                                            block,
+                                            src,
+                                            Type.usize,
+                                            &val_ptr.castTag(.slice).?.data.len,
+                                            ptr_elem_ty,
+                                            parent.decl_ref_mut,
+                                        ),
+
+                                        else => unreachable,
+                                    }
+                                },
                                 else => unreachable,
-                            },
-                            else => unreachable,
-                        }
-                    },
-                    else => {
-                        if (elem_ptr.index != 0) {
-                            // TODO include a "declared here" note for the decl
-                            return sema.fail(block, src, "out of bounds comptime store of index {d}", .{
-                                elem_ptr.index,
-                            });
-                        }
-                        return beginComptimePtrMutationInner(
-                            sema,
-                            block,
-                            src,
-                            parent.ty,
-                            val_ptr,
-                            ptr_elem_ty,
-                            parent.decl_ref_mut,
-                        );
-                    },
-                },
-                .reinterpret => |reinterpret| {
-                    if (!elem_ptr.elem_ty.hasWellDefinedLayout(mod)) {
-                        // Even though the parent value type has well-defined memory layout, our
-                        // pointer type does not.
-                        return ComptimePtrMutationKit{
-                            .decl_ref_mut = parent.decl_ref_mut,
-                            .pointee = .bad_ptr_ty,
-                            .ty = elem_ptr.elem_ty,
-                        };
-                    }
+                            }
+                        },
+                        .empty_struct => {
+                            const duped = try sema.arena.create(Value);
+                            duped.* = Value.initTag(.the_only_possible_value);
+                            return beginComptimePtrMutationInner(
+                                sema,
+                                block,
+                                src,
+                                parent.ty.structFieldType(field_index, mod),
+                                duped,
+                                ptr_elem_ty,
+                                parent.decl_ref_mut,
+                            );
+                        },
+                        .none => switch (val_ptr.tag()) {
+                            .aggregate => return beginComptimePtrMutationInner(
+                                sema,
+                                block,
+                                src,
+                                parent.ty.structFieldType(field_index, mod),
+                                &val_ptr.castTag(.aggregate).?.data[field_index],
+                                ptr_elem_ty,
+                                parent.decl_ref_mut,
+                            ),
+                            .repeated => {
+                                const arena = parent.beginArena(sema.mod);
+                                defer parent.finishArena(sema.mod);
 
-                    const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty);
-                    const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
-                    return ComptimePtrMutationKit{
-                        .decl_ref_mut = parent.decl_ref_mut,
-                        .pointee = .{ .reinterpret = .{
-                            .val_ptr = reinterpret.val_ptr,
-                            .byte_offset = reinterpret.byte_offset + elem_abi_size * elem_ptr.index,
-                        } },
-                        .ty = parent.ty,
-                    };
-                },
-                .bad_decl_ty, .bad_ptr_ty => return parent,
-            }
-        },
-        .field_ptr => {
-            const field_ptr = ptr_val.castTag(.field_ptr).?.data;
-            const field_index = @intCast(u32, field_ptr.field_index);
-
-            var parent = try sema.beginComptimePtrMutation(block, src, field_ptr.container_ptr, field_ptr.container_ty);
-            switch (parent.pointee) {
-                .direct => |val_ptr| switch (val_ptr.ip_index) {
-                    .undef => {
-                        // A struct or union has been initialized to undefined at comptime and now we
-                        // are for the first time setting a field. We must change the representation
-                        // of the struct/union from `undef` to `struct`/`union`.
-                        const arena = parent.beginArena(sema.mod);
-                        defer parent.finishArena(sema.mod);
-
-                        switch (parent.ty.zigTypeTag(mod)) {
-                            .Struct => {
-                                const fields = try arena.alloc(Value, parent.ty.structFieldCount(mod));
-                                @memset(fields, Value.undef);
-
-                                val_ptr.* = try Value.Tag.aggregate.create(arena, fields);
+                                const elems = try arena.alloc(Value, parent.ty.structFieldCount(mod));
+                                @memset(elems, val_ptr.castTag(.repeated).?.data);
+                                val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
 
                                 return beginComptimePtrMutationInner(
                                     sema,
                                     block,
                                     src,
                                     parent.ty.structFieldType(field_index, mod),
-                                    &fields[field_index],
+                                    &elems[field_index],
                                     ptr_elem_ty,
                                     parent.decl_ref_mut,
                                 );
                             },
-                            .Union => {
-                                const payload = try arena.create(Value.Payload.Union);
-                                const tag_ty = parent.ty.unionTagTypeHypothetical(mod);
-                                payload.* = .{ .data = .{
-                                    .tag = try mod.enumValueFieldIndex(tag_ty, field_index),
-                                    .val = Value.undef,
-                                } };
+                            .@"union" => {
+                                // We need to set the active field of the union.
+                                const union_tag_ty = field_ptr.container_ty.unionTagTypeHypothetical(mod);
 
-                                val_ptr.* = Value.initPayload(&payload.base);
+                                const payload = &val_ptr.castTag(.@"union").?.data;
+                                payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
 
                                 return beginComptimePtrMutationInner(
                                     sema,
                                     block,
                                     src,
                                     parent.ty.structFieldType(field_index, mod),
-                                    &payload.data.val,
+                                    &payload.val,
                                     ptr_elem_ty,
                                     parent.decl_ref_mut,
                                 );
                             },
-                            .Pointer => {
-                                assert(parent.ty.isSlice(mod));
-                                val_ptr.* = try Value.Tag.slice.create(arena, .{
-                                    .ptr = Value.undef,
-                                    .len = Value.undef,
-                                });
+                            .slice => switch (field_index) {
+                                Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner(
+                                    sema,
+                                    block,
+                                    src,
+                                    parent.ty.slicePtrFieldType(mod),
+                                    &val_ptr.castTag(.slice).?.data.ptr,
+                                    ptr_elem_ty,
+                                    parent.decl_ref_mut,
+                                ),
 
-                                switch (field_index) {
-                                    Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        parent.ty.slicePtrFieldType(mod),
-                                        &val_ptr.castTag(.slice).?.data.ptr,
-                                        ptr_elem_ty,
-                                        parent.decl_ref_mut,
-                                    ),
-                                    Value.Payload.Slice.len_index => return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        Type.usize,
-                                        &val_ptr.castTag(.slice).?.data.len,
-                                        ptr_elem_ty,
-                                        parent.decl_ref_mut,
-                                    ),
+                                Value.Payload.Slice.len_index => return beginComptimePtrMutationInner(
+                                    sema,
+                                    block,
+                                    src,
+                                    Type.usize,
+                                    &val_ptr.castTag(.slice).?.data.len,
+                                    ptr_elem_ty,
+                                    parent.decl_ref_mut,
+                                ),
 
-                                    else => unreachable,
-                                }
+                                else => unreachable,
                             },
-                            else => unreachable,
-                        }
-                    },
-                    .empty_struct => {
-                        const duped = try sema.arena.create(Value);
-                        duped.* = Value.initTag(.the_only_possible_value);
-                        return beginComptimePtrMutationInner(
-                            sema,
-                            block,
-                            src,
-                            parent.ty.structFieldType(field_index, mod),
-                            duped,
-                            ptr_elem_ty,
-                            parent.decl_ref_mut,
-                        );
-                    },
-                    .none => switch (val_ptr.tag()) {
-                        .aggregate => return beginComptimePtrMutationInner(
-                            sema,
-                            block,
-                            src,
-                            parent.ty.structFieldType(field_index, mod),
-                            &val_ptr.castTag(.aggregate).?.data[field_index],
-                            ptr_elem_ty,
-                            parent.decl_ref_mut,
-                        ),
-                        .repeated => {
-                            const arena = parent.beginArena(sema.mod);
-                            defer parent.finishArena(sema.mod);
-
-                            const elems = try arena.alloc(Value, parent.ty.structFieldCount(mod));
-                            @memset(elems, val_ptr.castTag(.repeated).?.data);
-                            val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
-
-                            return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                parent.ty.structFieldType(field_index, mod),
-                                &elems[field_index],
-                                ptr_elem_ty,
-                                parent.decl_ref_mut,
-                            );
-                        },
-                        .@"union" => {
-                            // We need to set the active field of the union.
-                            const union_tag_ty = field_ptr.container_ty.unionTagTypeHypothetical(mod);
-
-                            const payload = &val_ptr.castTag(.@"union").?.data;
-                            payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
-
-                            return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                parent.ty.structFieldType(field_index, mod),
-                                &payload.val,
-                                ptr_elem_ty,
-                                parent.decl_ref_mut,
-                            );
-                        },
-                        .slice => switch (field_index) {
-                            Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                parent.ty.slicePtrFieldType(mod),
-                                &val_ptr.castTag(.slice).?.data.ptr,
-                                ptr_elem_ty,
-                                parent.decl_ref_mut,
-                            ),
-
-                            Value.Payload.Slice.len_index => return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                Type.usize,
-                                &val_ptr.castTag(.slice).?.data.len,
-                                ptr_elem_ty,
-                                parent.decl_ref_mut,
-                            ),
 
                             else => unreachable,
                         },
-
                         else => unreachable,
                     },
-                    else => unreachable,
-                },
-                .reinterpret => |reinterpret| {
-                    const field_offset_u64 = field_ptr.container_ty.structFieldOffset(field_index, mod);
-                    const field_offset = try sema.usizeCast(block, src, field_offset_u64);
-                    return ComptimePtrMutationKit{
-                        .decl_ref_mut = parent.decl_ref_mut,
-                        .pointee = .{ .reinterpret = .{
-                            .val_ptr = reinterpret.val_ptr,
-                            .byte_offset = reinterpret.byte_offset + field_offset,
-                        } },
-                        .ty = parent.ty,
-                    };
-                },
-                .bad_decl_ty, .bad_ptr_ty => return parent,
-            }
-        },
-        .eu_payload_ptr => {
-            const eu_ptr = ptr_val.castTag(.eu_payload_ptr).?.data;
-            var parent = try sema.beginComptimePtrMutation(block, src, eu_ptr.container_ptr, eu_ptr.container_ty);
-            switch (parent.pointee) {
-                .direct => |val_ptr| {
-                    const payload_ty = parent.ty.errorUnionPayload(mod);
-                    if (val_ptr.ip_index == .none and val_ptr.tag() == .eu_payload) {
-                        return ComptimePtrMutationKit{
-                            .decl_ref_mut = parent.decl_ref_mut,
-                            .pointee = .{ .direct = &val_ptr.castTag(.eu_payload).?.data },
-                            .ty = payload_ty,
-                        };
-                    } else {
-                        // An error union has been initialized to undefined at comptime and now we
-                        // are for the first time setting the payload. We must change the
-                        // representation of the error union from `undef` to `opt_payload`.
-                        const arena = parent.beginArena(sema.mod);
-                        defer parent.finishArena(sema.mod);
-
-                        const payload = try arena.create(Value.Payload.SubValue);
-                        payload.* = .{
-                            .base = .{ .tag = .eu_payload },
-                            .data = Value.undef,
-                        };
-
-                        val_ptr.* = Value.initPayload(&payload.base);
-
+                    .reinterpret => |reinterpret| {
+                        const field_offset_u64 = field_ptr.container_ty.structFieldOffset(field_index, mod);
+                        const field_offset = try sema.usizeCast(block, src, field_offset_u64);
                         return ComptimePtrMutationKit{
                             .decl_ref_mut = parent.decl_ref_mut,
-                            .pointee = .{ .direct = &payload.data },
-                            .ty = payload_ty,
+                            .pointee = .{ .reinterpret = .{
+                                .val_ptr = reinterpret.val_ptr,
+                                .byte_offset = reinterpret.byte_offset + field_offset,
+                            } },
+                            .ty = parent.ty,
                         };
-                    }
-                },
-                .bad_decl_ty, .bad_ptr_ty => return parent,
-                // Even though the parent value type has well-defined memory layout, our
-                // pointer type does not.
-                .reinterpret => return ComptimePtrMutationKit{
-                    .decl_ref_mut = parent.decl_ref_mut,
-                    .pointee = .bad_ptr_ty,
-                    .ty = eu_ptr.container_ty,
-                },
-            }
-        },
-        .opt_payload_ptr => {
-            const opt_ptr = if (ptr_val.castTag(.opt_payload_ptr)) |some| some.data else {
-                return sema.beginComptimePtrMutation(block, src, ptr_val, ptr_elem_ty.optionalChild(mod));
-            };
-            var parent = try sema.beginComptimePtrMutation(block, src, opt_ptr.container_ptr, opt_ptr.container_ty);
-            switch (parent.pointee) {
-                .direct => |val_ptr| {
-                    const payload_ty = parent.ty.optionalChild(mod);
-                    switch (val_ptr.ip_index) {
-                        .undef, .null_value => {
-                            // An optional has been initialized to undefined at comptime and now we
+                    },
+                    .bad_decl_ty, .bad_ptr_ty => return parent,
+                }
+            },
+            .eu_payload_ptr => {
+                const eu_ptr = ptr_val.castTag(.eu_payload_ptr).?.data;
+                var parent = try sema.beginComptimePtrMutation(block, src, eu_ptr.container_ptr, eu_ptr.container_ty);
+                switch (parent.pointee) {
+                    .direct => |val_ptr| {
+                        const payload_ty = parent.ty.errorUnionPayload(mod);
+                        if (val_ptr.ip_index == .none and val_ptr.tag() == .eu_payload) {
+                            return ComptimePtrMutationKit{
+                                .decl_ref_mut = parent.decl_ref_mut,
+                                .pointee = .{ .direct = &val_ptr.castTag(.eu_payload).?.data },
+                                .ty = payload_ty,
+                            };
+                        } else {
+                            // An error union has been initialized to undefined at comptime and now we
                             // are for the first time setting the payload. We must change the
-                            // representation of the optional from `undef` to `opt_payload`.
+                            // representation of the error union from `undef` to `opt_payload`.
                             const arena = parent.beginArena(sema.mod);
                             defer parent.finishArena(sema.mod);
 
                             const payload = try arena.create(Value.Payload.SubValue);
                             payload.* = .{
-                                .base = .{ .tag = .opt_payload },
+                                .base = .{ .tag = .eu_payload },
                                 .data = Value.undef,
                             };
 
@@ -28147,39 +28230,84 @@ fn beginComptimePtrMutation(
                                 .pointee = .{ .direct = &payload.data },
                                 .ty = payload_ty,
                             };
-                        },
-                        .none => switch (val_ptr.tag()) {
-                            .opt_payload => return ComptimePtrMutationKit{
-                                .decl_ref_mut = parent.decl_ref_mut,
-                                .pointee = .{ .direct = &val_ptr.castTag(.opt_payload).?.data },
-                                .ty = payload_ty,
+                        }
+                    },
+                    .bad_decl_ty, .bad_ptr_ty => return parent,
+                    // Even though the parent value type has well-defined memory layout, our
+                    // pointer type does not.
+                    .reinterpret => return ComptimePtrMutationKit{
+                        .decl_ref_mut = parent.decl_ref_mut,
+                        .pointee = .bad_ptr_ty,
+                        .ty = eu_ptr.container_ty,
+                    },
+                }
+            },
+            .opt_payload_ptr => {
+                const opt_ptr = if (ptr_val.castTag(.opt_payload_ptr)) |some| some.data else {
+                    return sema.beginComptimePtrMutation(block, src, ptr_val, ptr_elem_ty.optionalChild(mod));
+                };
+                var parent = try sema.beginComptimePtrMutation(block, src, opt_ptr.container_ptr, opt_ptr.container_ty);
+                switch (parent.pointee) {
+                    .direct => |val_ptr| {
+                        const payload_ty = parent.ty.optionalChild(mod);
+                        switch (val_ptr.toIntern()) {
+                            .undef, .null_value => {
+                                // An optional has been initialized to undefined at comptime and now we
+                                // are for the first time setting the payload. We must change the
+                                // representation of the optional from `undef` to `opt_payload`.
+                                const arena = parent.beginArena(sema.mod);
+                                defer parent.finishArena(sema.mod);
+
+                                const payload = try arena.create(Value.Payload.SubValue);
+                                payload.* = .{
+                                    .base = .{ .tag = .opt_payload },
+                                    .data = Value.undef,
+                                };
+
+                                val_ptr.* = Value.initPayload(&payload.base);
+
+                                return ComptimePtrMutationKit{
+                                    .decl_ref_mut = parent.decl_ref_mut,
+                                    .pointee = .{ .direct = &payload.data },
+                                    .ty = payload_ty,
+                                };
                             },
+                            .none => switch (val_ptr.tag()) {
+                                .opt_payload => return ComptimePtrMutationKit{
+                                    .decl_ref_mut = parent.decl_ref_mut,
+                                    .pointee = .{ .direct = &val_ptr.castTag(.opt_payload).?.data },
+                                    .ty = payload_ty,
+                                },
 
+                                else => return ComptimePtrMutationKit{
+                                    .decl_ref_mut = parent.decl_ref_mut,
+                                    .pointee = .{ .direct = val_ptr },
+                                    .ty = payload_ty,
+                                },
+                            },
                             else => return ComptimePtrMutationKit{
                                 .decl_ref_mut = parent.decl_ref_mut,
                                 .pointee = .{ .direct = val_ptr },
                                 .ty = payload_ty,
                             },
-                        },
-                        else => return ComptimePtrMutationKit{
-                            .decl_ref_mut = parent.decl_ref_mut,
-                            .pointee = .{ .direct = val_ptr },
-                            .ty = payload_ty,
-                        },
-                    }
-                },
-                .bad_decl_ty, .bad_ptr_ty => return parent,
-                // Even though the parent value type has well-defined memory layout, our
-                // pointer type does not.
-                .reinterpret => return ComptimePtrMutationKit{
-                    .decl_ref_mut = parent.decl_ref_mut,
-                    .pointee = .bad_ptr_ty,
-                    .ty = opt_ptr.container_ty,
-                },
-            }
+                        }
+                    },
+                    .bad_decl_ty, .bad_ptr_ty => return parent,
+                    // Even though the parent value type has well-defined memory layout, our
+                    // pointer type does not.
+                    .reinterpret => return ComptimePtrMutationKit{
+                        .decl_ref_mut = parent.decl_ref_mut,
+                        .pointee = .bad_ptr_ty,
+                        .ty = opt_ptr.container_ty,
+                    },
+                }
+            },
+            .decl_ref => unreachable, // isComptimeMutablePtr has been checked already
+            else => unreachable,
+        },
+        else => switch (mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr) {
+            else => unreachable,
         },
-        .decl_ref => unreachable, // isComptimeMutablePtr has been checked already
-        else => unreachable,
     }
 }
 
@@ -28190,13 +28318,13 @@ fn beginComptimePtrMutationInner(
     decl_ty: Type,
     decl_val: *Value,
     ptr_elem_ty: Type,
-    decl_ref_mut: Value.Payload.DeclRefMut.Data,
+    mut_decl: InternPool.Key.Ptr.Addr.MutDecl,
 ) CompileError!ComptimePtrMutationKit {
     const mod = sema.mod;
     const target = mod.getTarget();
     const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok;
 
-    const decl = mod.declPtr(decl_ref_mut.decl_index);
+    const decl = mod.declPtr(mut_decl.decl);
     var decl_arena: std.heap.ArenaAllocator = undefined;
     const allocator = decl.value_arena.?.acquire(sema.gpa, &decl_arena);
     defer decl.value_arena.?.release(&decl_arena);
@@ -28204,7 +28332,7 @@ fn beginComptimePtrMutationInner(
 
     if (coerce_ok) {
         return ComptimePtrMutationKit{
-            .decl_ref_mut = decl_ref_mut,
+            .mut_decl = mut_decl,
             .pointee = .{ .direct = decl_val },
             .ty = decl_ty,
         };
@@ -28215,7 +28343,7 @@ fn beginComptimePtrMutationInner(
         const decl_elem_ty = decl_ty.childType(mod);
         if ((try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_elem_ty, true, target, src, src)) == .ok) {
             return ComptimePtrMutationKit{
-                .decl_ref_mut = decl_ref_mut,
+                .mut_decl = mut_decl,
                 .pointee = .{ .direct = decl_val },
                 .ty = decl_ty,
             };
@@ -28224,20 +28352,20 @@ fn beginComptimePtrMutationInner(
 
     if (!decl_ty.hasWellDefinedLayout(mod)) {
         return ComptimePtrMutationKit{
-            .decl_ref_mut = decl_ref_mut,
-            .pointee = .{ .bad_decl_ty = {} },
+            .mut_decl = mut_decl,
+            .pointee = .bad_decl_ty,
             .ty = decl_ty,
         };
     }
     if (!ptr_elem_ty.hasWellDefinedLayout(mod)) {
         return ComptimePtrMutationKit{
-            .decl_ref_mut = decl_ref_mut,
-            .pointee = .{ .bad_ptr_ty = {} },
+            .mut_decl = mut_decl,
+            .pointee = .bad_ptr_ty,
             .ty = ptr_elem_ty,
         };
     }
     return ComptimePtrMutationKit{
-        .decl_ref_mut = decl_ref_mut,
+        .mut_decl = mut_decl,
         .pointee = .{ .reinterpret = .{
             .val_ptr = decl_val,
             .byte_offset = 0,
@@ -28282,7 +28410,7 @@ fn beginComptimePtrLoad(
     const mod = sema.mod;
     const target = mod.getTarget();
 
-    var deref: ComptimePtrLoadKit = switch (mod.intern_pool.indexToKey(ptr_val.ip_index)) {
+    var deref: ComptimePtrLoadKit = switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) {
         .ptr => |ptr| switch (ptr.addr) {
             .decl, .mut_decl => blk: {
                 const decl_index = switch (ptr.addr) {
@@ -28319,7 +28447,7 @@ fn beginComptimePtrLoad(
                         (try sema.coerceInMemoryAllowed(block, container_ty, tv.ty, false, target, src, src)) == .ok or
                         (try sema.coerceInMemoryAllowed(block, tv.ty, container_ty, false, target, src, src)) == .ok;
                     if (coerce_in_mem_ok) {
-                        const payload_val = switch (mod.intern_pool.indexToKey(tv.val.ip_index)) {
+                        const payload_val = switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
                             .error_union => |error_union| switch (error_union.val) {
                                 .err_name => |err_name| return sema.fail(block, src, "attempt to unwrap error: {s}", .{mod.intern_pool.stringToSlice(err_name)}),
                                 .payload => |payload| payload,
@@ -28462,7 +28590,7 @@ fn beginComptimePtrLoad(
                         },
                         Value.slice_len_index => TypedValue{
                             .ty = Type.usize,
-                            .val = mod.intern_pool.indexToKey(tv.val.ip_index).ptr.len.toValue(),
+                            .val = mod.intern_pool.indexToKey(tv.val.toIntern()).ptr.len.toValue(),
                         },
                         else => unreachable,
                     };
@@ -28565,9 +28693,9 @@ fn coerceArrayPtrToSlice(
         const ptr_array_ty = sema.typeOf(inst);
         const array_ty = ptr_array_ty.childType(mod);
         const slice_val = try mod.intern(.{ .ptr = .{
-            .ty = dest_ty.ip_index,
-            .addr = mod.intern_pool.indexToKey(val.ip_index).ptr.addr,
-            .len = (try mod.intValue(Type.usize, array_ty.arrayLen(mod))).ip_index,
+            .ty = dest_ty.toIntern(),
+            .addr = mod.intern_pool.indexToKey(val.toIntern()).ptr.addr,
+            .len = (try mod.intValue(Type.usize, array_ty.arrayLen(mod))).toIntern(),
         } });
         return sema.addConstant(dest_ty, slice_val.toValue());
     }
@@ -28643,7 +28771,7 @@ fn coerceCompatiblePtrs(
         return sema.addConstant(dest_ty, (try mod.intern_pool.getCoerced(
             sema.gpa,
             try val.intern(inst_ty, mod),
-            dest_ty.ip_index,
+            dest_ty.toIntern(),
         )).toValue());
     }
     try sema.requireRuntimeBlock(block, inst_src, null);
@@ -28840,7 +28968,7 @@ fn coerceAnonStructToUnion(
         return sema.failWithOwnedErrorMsg(msg);
     }
 
-    const anon_struct = mod.intern_pool.indexToKey(inst_ty.ip_index).anon_struct_type;
+    const anon_struct = mod.intern_pool.indexToKey(inst_ty.toIntern()).anon_struct_type;
     const field_name = mod.intern_pool.stringToSlice(anon_struct.names[0]);
     const init = try sema.structFieldVal(block, inst_src, inst, field_name, inst_src, inst_ty);
     return sema.unionInit(block, init, inst_src, union_ty, union_ty_src, field_name, inst_src);
@@ -28916,23 +29044,20 @@ fn coerceArrayLike(
         return block.addBitCast(dest_ty, inst);
     }
 
-    const element_vals = try sema.arena.alloc(Value, dest_len);
+    const element_vals = try sema.arena.alloc(InternPool.Index, dest_len);
     const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len);
     var runtime_src: ?LazySrcLoc = null;
 
-    for (element_vals, 0..) |*elem, i| {
-        const index_ref = try sema.addConstant(
-            Type.usize,
-            try mod.intValue(Type.usize, i),
-        );
+    for (element_vals, element_refs, 0..) |*val, *ref, i| {
+        const index_ref = try sema.addConstant(Type.usize, try mod.intValue(Type.usize, i));
         const src = inst_src; // TODO better source location
         const elem_src = inst_src; // TODO better source location
         const elem_ref = try sema.elemValArray(block, src, inst_src, inst, elem_src, index_ref, true);
         const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
-        element_refs[i] = coerced;
+        ref.* = coerced;
         if (runtime_src == null) {
             if (try sema.resolveMaybeUndefVal(coerced)) |elem_val| {
-                elem.* = elem_val;
+                val.* = try elem_val.intern(dest_elem_ty, mod);
             } else {
                 runtime_src = elem_src;
             }
@@ -28944,10 +29069,10 @@ fn coerceArrayLike(
         return block.addAggregateInit(dest_ty, element_refs);
     }
 
-    return sema.addConstant(
-        dest_ty,
-        try Value.Tag.aggregate.create(sema.arena, element_vals),
-    );
+    return sema.addConstant(dest_ty, (try mod.intern(.{ .aggregate = .{
+        .ty = dest_ty.toIntern(),
+        .storage = .{ .elems = element_vals },
+    } })).toValue());
 }
 
 /// If the lengths match, coerces element-wise.
@@ -28978,25 +29103,26 @@ fn coerceTupleToArray(
     }
 
     const dest_elems = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLenIncludingSentinel(mod));
-    const element_vals = try sema.arena.alloc(Value, dest_elems);
+    const element_vals = try sema.arena.alloc(InternPool.Index, dest_elems);
     const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_elems);
     const dest_elem_ty = dest_ty.childType(mod);
 
     var runtime_src: ?LazySrcLoc = null;
-    for (element_vals, 0..) |*elem, i_usize| {
+    for (element_vals, element_refs, 0..) |*val, *ref, i_usize| {
         const i = @intCast(u32, i_usize);
         if (i_usize == inst_len) {
-            elem.* = dest_ty.sentinel(mod).?;
-            element_refs[i] = try sema.addConstant(dest_elem_ty, elem.*);
+            const sentinel_val = dest_ty.sentinel(mod).?;
+            val.* = sentinel_val.toIntern();
+            ref.* = try sema.addConstant(dest_elem_ty, sentinel_val);
             break;
         }
         const elem_src = inst_src; // TODO better source location
         const elem_ref = try sema.tupleField(block, inst_src, inst, elem_src, i);
         const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
-        element_refs[i] = coerced;
+        ref.* = coerced;
         if (runtime_src == null) {
             if (try sema.resolveMaybeUndefVal(coerced)) |elem_val| {
-                elem.* = elem_val;
+                val.* = try elem_val.intern(dest_elem_ty, mod);
             } else {
                 runtime_src = elem_src;
             }
@@ -29008,10 +29134,10 @@ fn coerceTupleToArray(
         return block.addAggregateInit(dest_ty, element_refs);
     }
 
-    return sema.addConstant(
-        dest_ty,
-        try Value.Tag.aggregate.create(sema.arena, element_vals),
-    );
+    return sema.addConstant(dest_ty, (try mod.intern(.{ .aggregate = .{
+        .ty = dest_ty.toIntern(),
+        .storage = .{ .elems = element_vals },
+    } })).toValue());
 }
 
 /// If the lengths match, coerces element-wise.
@@ -29079,7 +29205,7 @@ fn coerceTupleToStruct(
     @memset(field_refs, .none);
 
     const inst_ty = sema.typeOf(inst);
-    const anon_struct = mod.intern_pool.indexToKey(inst_ty.ip_index).anon_struct_type;
+    const anon_struct = mod.intern_pool.indexToKey(inst_ty.toIntern()).anon_struct_type;
     var runtime_src: ?LazySrcLoc = null;
     for (0..anon_struct.types.len) |field_index_usize| {
         const field_i = @intCast(u32, field_index_usize);
@@ -29105,8 +29231,7 @@ fn coerceTupleToStruct(
         }
         if (runtime_src == null) {
             if (try sema.resolveMaybeUndefVal(coerced)) |field_val| {
-                assert(field_val.ip_index != .none);
-                field_vals[field_index] = field_val.ip_index;
+                field_vals[field_index] = field_val.toIntern();
             } else {
                 runtime_src = field_src;
             }
@@ -29123,7 +29248,7 @@ fn coerceTupleToStruct(
         const field_name = fields.keys()[i];
         const field = fields.values()[i];
         const field_src = inst_src; // TODO better source location
-        if (field.default_val.ip_index == .unreachable_value) {
+        if (field.default_val.toIntern() == .unreachable_value) {
             const template = "missing struct field: {s}";
             const args = .{field_name};
             if (root_msg) |msg| {
@@ -29134,8 +29259,7 @@ fn coerceTupleToStruct(
             continue;
         }
         if (runtime_src == null) {
-            assert(field.default_val.ip_index != .none);
-            field_vals[i] = field.default_val.ip_index;
+            field_vals[i] = field.default_val.toIntern();
         } else {
             field_ref.* = try sema.addConstant(field.ty, field.default_val);
         }
@@ -29152,9 +29276,8 @@ fn coerceTupleToStruct(
         return block.addAggregateInit(struct_ty, field_refs);
     }
 
-    assert(struct_ty.ip_index != .none);
     const struct_val = try mod.intern(.{ .aggregate = .{
-        .ty = struct_ty.ip_index,
+        .ty = struct_ty.toIntern(),
         .storage = .{ .elems = field_vals },
     } });
     errdefer mod.intern_pool.remove(struct_val);
@@ -29170,13 +29293,13 @@ fn coerceTupleToTuple(
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
     const mod = sema.mod;
-    const dest_tuple = mod.intern_pool.indexToKey(tuple_ty.ip_index).anon_struct_type;
+    const dest_tuple = mod.intern_pool.indexToKey(tuple_ty.toIntern()).anon_struct_type;
     const field_vals = try sema.arena.alloc(InternPool.Index, dest_tuple.types.len);
     const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len);
     @memset(field_refs, .none);
 
     const inst_ty = sema.typeOf(inst);
-    const src_tuple = mod.intern_pool.indexToKey(inst_ty.ip_index).anon_struct_type;
+    const src_tuple = mod.intern_pool.indexToKey(inst_ty.toIntern()).anon_struct_type;
     if (src_tuple.types.len > dest_tuple.types.len) return error.NotCoercible;
 
     var runtime_src: ?LazySrcLoc = null;
@@ -29209,7 +29332,7 @@ fn coerceTupleToTuple(
         }
         if (runtime_src == null) {
             if (try sema.resolveMaybeUndefVal(coerced)) |field_val| {
-                field_vals[field_index] = field_val.ip_index;
+                field_vals[field_index] = field_val.toIntern();
             } else {
                 runtime_src = field_src;
             }
@@ -29269,7 +29392,7 @@ fn coerceTupleToTuple(
     return sema.addConstant(
         tuple_ty,
         (try mod.intern(.{ .aggregate = .{
-            .ty = tuple_ty.ip_index,
+            .ty = tuple_ty.toIntern(),
             .storage = .{ .elems = field_vals },
         } })).toValue(),
     );
@@ -29349,7 +29472,7 @@ fn refValue(sema: *Sema, block: *Block, ty: Type, val: Value) !Value {
     try sema.maybeQueueFuncBodyAnalysis(decl);
     try mod.declareDeclDependency(sema.owner_decl_index, decl);
     const result = try mod.intern(.{ .ptr = .{
-        .ty = (try mod.singleConstPtrType(ty)).ip_index,
+        .ty = (try mod.singleConstPtrType(ty)).toIntern(),
         .addr = .{ .decl = decl },
     } });
     return result.toValue();
@@ -29360,8 +29483,8 @@ fn optRefValue(sema: *Sema, block: *Block, ty: Type, opt_val: ?Value) !Value {
     const val = opt_val orelse return Value.null;
     const ptr_val = try sema.refValue(block, ty, val);
     const result = try mod.intern(.{ .opt = .{
-        .ty = (try mod.optionalType((try mod.singleConstPtrType(ty)).ip_index)).ip_index,
-        .val = ptr_val.ip_index,
+        .ty = (try mod.optionalType((try mod.singleConstPtrType(ty)).toIntern())).toIntern(),
+        .val = ptr_val.toIntern(),
     } });
     return result.toValue();
 }
@@ -29382,7 +29505,7 @@ fn analyzeDeclRefInner(sema: *Sema, decl_index: Decl.Index, analyze_fn_body: boo
     const decl = mod.declPtr(decl_index);
     const decl_tv = try decl.typedValue();
     const ptr_ty = try mod.ptrType(.{
-        .elem_type = decl_tv.ty.ip_index,
+        .elem_type = decl_tv.ty.toIntern(),
         .alignment = InternPool.Alignment.fromByteUnits(decl.@"align"),
         .is_const = if (decl.getVariable(mod)) |variable| variable.is_const else false,
         .address_space = decl.@"addrspace",
@@ -29391,7 +29514,7 @@ fn analyzeDeclRefInner(sema: *Sema, decl_index: Decl.Index, analyze_fn_body: boo
         try sema.maybeQueueFuncBodyAnalysis(decl_index);
     }
     return sema.addConstant(ptr_ty, (try mod.intern(.{ .ptr = .{
-        .ty = ptr_ty.ip_index,
+        .ty = ptr_ty.toIntern(),
         .addr = .{ .decl = decl_index },
     } })).toValue());
 }
@@ -29415,13 +29538,10 @@ fn analyzeRef(
     const operand_ty = sema.typeOf(operand);
 
     if (try sema.resolveMaybeUndefVal(operand)) |val| {
-        switch (val.ip_index) {
-            .none => {},
-            else => switch (sema.mod.intern_pool.indexToKey(val.ip_index)) {
-                .extern_func => |extern_func| return sema.analyzeDeclRef(extern_func.decl),
-                .func => |func| return sema.analyzeDeclRef(sema.mod.funcPtr(func.index).owner_decl),
-                else => {},
-            },
+        switch (sema.mod.intern_pool.indexToKey(val.toIntern())) {
+            .extern_func => |extern_func| return sema.analyzeDeclRef(extern_func.decl),
+            .func => |func| return sema.analyzeDeclRef(sema.mod.funcPtr(func.index).owner_decl),
+            else => {},
         }
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
@@ -29617,9 +29737,9 @@ fn analyzeIsNonErrComptimeOnly(
     // exception if the error union error set is known to be empty,
     // we allow the comparison but always make it comptime-known.
     const set_ty = operand_ty.errorUnionSet(mod);
-    switch (set_ty.ip_index) {
+    switch (set_ty.toIntern()) {
         .anyerror_type => {},
-        else => switch (mod.intern_pool.indexToKey(set_ty.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(set_ty.toIntern())) {
             .error_set_type => |error_set_type| {
                 if (error_set_type.names.len == 0) return Air.Inst.Ref.bool_true;
             },
@@ -30027,7 +30147,7 @@ fn analyzeSlice(
             return sema.addConstant(return_ty, (try mod.intern_pool.getCoerced(
                 sema.gpa,
                 try new_ptr_val.intern(new_ptr_ty, mod),
-                return_ty.ip_index,
+                return_ty.toIntern(),
             )).toValue());
         }
 
@@ -30546,8 +30666,8 @@ fn wrapOptional(
 ) !Air.Inst.Ref {
     if (try sema.resolveMaybeUndefVal(inst)) |val| {
         return sema.addConstant(dest_ty, (try sema.mod.intern(.{ .opt = .{
-            .ty = dest_ty.ip_index,
-            .val = val.ip_index,
+            .ty = dest_ty.toIntern(),
+            .val = val.toIntern(),
         } })).toValue());
     }
 
@@ -30567,8 +30687,8 @@ fn wrapErrorUnionPayload(
     const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false });
     if (try sema.resolveMaybeUndefVal(coerced)) |val| {
         return sema.addConstant(dest_ty, (try mod.intern(.{ .error_union = .{
-            .ty = dest_ty.ip_index,
-            .val = .{ .payload = val.ip_index },
+            .ty = dest_ty.toIntern(),
+            .val = .{ .payload = val.toIntern() },
         } })).toValue());
     }
     try sema.requireRuntimeBlock(block, inst_src, null);
@@ -30588,17 +30708,17 @@ fn wrapErrorUnionSet(
     const inst_ty = sema.typeOf(inst);
     const dest_err_set_ty = dest_ty.errorUnionSet(mod);
     if (try sema.resolveMaybeUndefVal(inst)) |val| {
-        switch (dest_err_set_ty.ip_index) {
+        switch (dest_err_set_ty.toIntern()) {
             .anyerror_type => {},
-            else => switch (ip.indexToKey(dest_err_set_ty.ip_index)) {
+            else => switch (ip.indexToKey(dest_err_set_ty.toIntern())) {
                 .error_set_type => |error_set_type| ok: {
-                    const expected_name = mod.intern_pool.indexToKey(val.ip_index).err.name;
+                    const expected_name = mod.intern_pool.indexToKey(val.toIntern()).err.name;
                     if (error_set_type.nameIndex(ip, expected_name) != null) break :ok;
                     return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
                 },
                 .inferred_error_set_type => |ies_index| ok: {
                     const ies = mod.inferredErrorSetPtr(ies_index);
-                    const expected_name = mod.intern_pool.indexToKey(val.ip_index).err.name;
+                    const expected_name = mod.intern_pool.indexToKey(val.toIntern()).err.name;
 
                     // We carefully do this in an order that avoids unnecessarily
                     // resolving the destination error set type.
@@ -31252,34 +31372,31 @@ pub fn resolveFnTypes(sema: *Sema, fn_info: InternPool.Key.FuncType) CompileErro
 /// Make it so that calling hash() and eql() on `val` will not assert due
 /// to a type not having its layout resolved.
 fn resolveLazyValue(sema: *Sema, val: Value) CompileError!void {
-    switch (val.ip_index) {
-        .none => {},
-        else => switch (sema.mod.intern_pool.indexToKey(val.ip_index)) {
-            .int => |int| switch (int.storage) {
-                .u64, .i64, .big_int => {},
-                .lazy_align, .lazy_size => |lazy_ty| try sema.resolveTypeLayout(lazy_ty.toType()),
-            },
-            .ptr => |ptr| {
-                switch (ptr.addr) {
-                    .decl, .mut_decl => {},
-                    .int => |int| try sema.resolveLazyValue(int.toValue()),
-                    .eu_payload, .opt_payload => |base| try sema.resolveLazyValue(base.toValue()),
-                    .comptime_field => |comptime_field| try sema.resolveLazyValue(comptime_field.toValue()),
-                    .elem, .field => |base_index| try sema.resolveLazyValue(base_index.base.toValue()),
-                }
-                if (ptr.len != .none) try sema.resolveLazyValue(ptr.len.toValue());
-            },
-            .aggregate => |aggregate| switch (aggregate.storage) {
-                .bytes => {},
-                .elems => |elems| for (elems) |elem| try sema.resolveLazyValue(elem.toValue()),
-                .repeated_elem => |elem| try sema.resolveLazyValue(elem.toValue()),
-            },
-            .un => |un| {
-                try sema.resolveLazyValue(un.tag.toValue());
-                try sema.resolveLazyValue(un.val.toValue());
-            },
-            else => {},
+    switch (sema.mod.intern_pool.indexToKey(val.toIntern())) {
+        .int => |int| switch (int.storage) {
+            .u64, .i64, .big_int => {},
+            .lazy_align, .lazy_size => |lazy_ty| try sema.resolveTypeLayout(lazy_ty.toType()),
+        },
+        .ptr => |ptr| {
+            switch (ptr.addr) {
+                .decl, .mut_decl => {},
+                .int => |int| try sema.resolveLazyValue(int.toValue()),
+                .eu_payload, .opt_payload => |base| try sema.resolveLazyValue(base.toValue()),
+                .comptime_field => |comptime_field| try sema.resolveLazyValue(comptime_field.toValue()),
+                .elem, .field => |base_index| try sema.resolveLazyValue(base_index.base.toValue()),
+            }
+            if (ptr.len != .none) try sema.resolveLazyValue(ptr.len.toValue());
+        },
+        .aggregate => |aggregate| switch (aggregate.storage) {
+            .bytes => {},
+            .elems => |elems| for (elems) |elem| try sema.resolveLazyValue(elem.toValue()),
+            .repeated_elem => |elem| try sema.resolveLazyValue(elem.toValue()),
         },
+        .un => |un| {
+            try sema.resolveLazyValue(un.tag.toValue());
+            try sema.resolveLazyValue(un.val.toValue());
+        },
+        else => {},
     }
 }
 
@@ -31617,9 +31734,9 @@ fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void {
 pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
     const mod = sema.mod;
 
-    return switch (ty.ip_index) {
+    return switch (ty.toIntern()) {
         .empty_struct_type => false,
-        else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .int_type => false,
             .ptr_type => |ptr_type| {
                 const child_ty = ptr_type.elem_type.toType();
@@ -31776,7 +31893,7 @@ pub fn resolveTypeFully(sema: *Sema, ty: Type) CompileError!void {
             const child_ty = try sema.resolveTypeFields(ty.childType(mod));
             return sema.resolveTypeFully(child_ty);
         },
-        .Struct => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+        .Struct => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .struct_type => return sema.resolveStructFully(ty),
             .anon_struct_type => |tuple| {
                 for (tuple.types) |field_ty| {
@@ -31869,7 +31986,7 @@ fn resolveUnionFully(sema: *Sema, ty: Type) CompileError!void {
 pub fn resolveTypeFields(sema: *Sema, ty: Type) CompileError!Type {
     const mod = sema.mod;
 
-    switch (ty.ip_index) {
+    switch (ty.toIntern()) {
         .var_args_param_type => unreachable,
 
         .none => unreachable,
@@ -31960,7 +32077,7 @@ pub fn resolveTypeFields(sema: *Sema, ty: Type) CompileError!Type {
         .call_modifier_type => return sema.getBuiltinType("CallModifier"),
         .prefetch_options_type => return sema.getBuiltinType("PrefetchOptions"),
 
-        _ => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+        _ => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .struct_type => |struct_type| {
                 const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse return ty;
                 try sema.resolveTypeFieldsStruct(ty, struct_obj);
@@ -32605,7 +32722,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         } else {
             // The provided type is the enum tag type.
             union_obj.tag_ty = provided_ty;
-            const enum_type = switch (mod.intern_pool.indexToKey(union_obj.tag_ty.ip_index)) {
+            const enum_type = switch (mod.intern_pool.indexToKey(union_obj.tag_ty.toIntern())) {
                 .enum_type => |x| x,
                 else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{union_obj.tag_ty.fmt(mod)}),
             };
@@ -32698,7 +32815,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
 
                 break :blk val;
             };
-            enum_field_vals[field_i] = copied_val.ip_index;
+            enum_field_vals[field_i] = copied_val.toIntern();
             const gop = enum_field_vals_map.getOrPutAssumeCapacityContext(copied_val, .{
                 .ty = int_tag_ty,
                 .mod = mod,
@@ -32960,7 +33077,7 @@ fn generateUnionTagTypeSimple(
         .tag_ty = if (enum_field_names.len == 0)
             .noreturn_type
         else
-            (try mod.smallestUnsignedInt(enum_field_names.len - 1)).ip_index,
+            (try mod.smallestUnsignedInt(enum_field_names.len - 1)).toIntern(),
         .names = enum_field_names,
         .values = &.{},
         .tag_mode = .auto,
@@ -33053,9 +33170,9 @@ fn getBuiltinType(sema: *Sema, name: []const u8) CompileError!Type {
 /// TODO assert the return value matches `ty.onePossibleValue`
 pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
     const mod = sema.mod;
-    return switch (ty.ip_index) {
+    return switch (ty.toIntern()) {
         .empty_struct_type => Value.empty_struct,
-        else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .int_type => |int_type| {
                 if (int_type.bits == 0) {
                     return try mod.intValue(ty, 0);
@@ -33074,13 +33191,13 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
 
             inline .array_type, .vector_type => |seq_type| {
                 if (seq_type.len == 0) return (try mod.intern(.{ .aggregate = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .storage = .{ .elems = &.{} },
                 } })).toValue();
                 if (try sema.typeHasOnePossibleValue(seq_type.child.toType())) |opv| {
                     return (try mod.intern(.{ .aggregate = .{
-                        .ty = ty.ip_index,
-                        .storage = .{ .repeated_elem = opv.ip_index },
+                        .ty = ty.toIntern(),
+                        .storage = .{ .repeated_elem = opv.toIntern() },
                     } })).toValue();
                 }
                 return null;
@@ -33169,7 +33286,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 // This TODO is repeated in the redundant implementation of
                 // one-possible-value in type.zig.
                 const empty = try mod.intern(.{ .aggregate = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .storage = .{ .elems = &.{} },
                 } });
                 return empty.toValue();
@@ -33182,7 +33299,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 // In this case the struct has all comptime-known fields and
                 // therefore has one possible value.
                 return (try mod.intern(.{ .aggregate = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .storage = .{ .elems = tuple.values },
                 } })).toValue();
             },
@@ -33208,9 +33325,9 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                 const val_val = (try sema.typeHasOnePossibleValue(only_field.ty)) orelse
                     return null;
                 const only = try mod.intern(.{ .un = .{
-                    .ty = resolved_ty.ip_index,
-                    .tag = tag_val.ip_index,
-                    .val = val_val.ip_index,
+                    .ty = resolved_ty.toIntern(),
+                    .tag = tag_val.toIntern(),
+                    .val = val_val.toIntern(),
                 } });
                 return only.toValue();
             },
@@ -33221,8 +33338,8 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
 
                     if (try sema.typeHasOnePossibleValue(enum_type.tag_ty.toType())) |int_opv| {
                         const only = try mod.intern(.{ .enum_tag = .{
-                            .ty = ty.ip_index,
-                            .int = int_opv.ip_index,
+                            .ty = ty.toIntern(),
+                            .int = int_opv.toIntern(),
                         } });
                         return only.toValue();
                     }
@@ -33234,7 +33351,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                     1 => {
                         if (enum_type.values.len == 0) {
                             const only = try mod.intern(.{ .enum_tag = .{
-                                .ty = ty.ip_index,
+                                .ty = ty.toIntern(),
                                 .int = try mod.intern(.{ .int = .{
                                     .ty = enum_type.tag_ty,
                                     .storage = .{ .u64 = 0 },
@@ -33285,21 +33402,13 @@ pub fn getTmpAir(sema: Sema) Air {
 }
 
 pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref {
-    if (ty.ip_index != .none) {
-        if (@enumToInt(ty.ip_index) < Air.ref_start_index)
-            return @intToEnum(Air.Inst.Ref, @enumToInt(ty.ip_index));
-        try sema.air_instructions.append(sema.gpa, .{
-            .tag = .interned,
-            .data = .{ .interned = ty.ip_index },
-        });
-        return Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
-    } else {
-        try sema.air_instructions.append(sema.gpa, .{
-            .tag = .const_ty,
-            .data = .{ .ty = ty },
-        });
-        return Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
-    }
+    if (@enumToInt(ty.toIntern()) < Air.ref_start_index)
+        return @intToEnum(Air.Inst.Ref, @enumToInt(ty.toIntern()));
+    try sema.air_instructions.append(sema.gpa, .{
+        .tag = .interned,
+        .data = .{ .interned = ty.toIntern() },
+    });
+    return Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
 }
 
 fn addIntUnsigned(sema: *Sema, ty: Type, int: u64) CompileError!Air.Inst.Ref {
@@ -33313,12 +33422,12 @@ fn addConstUndef(sema: *Sema, ty: Type) CompileError!Air.Inst.Ref {
 
 pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref {
     const gpa = sema.gpa;
-    if (val.ip_index != .none and val.ip_index != .null_value) {
-        if (@enumToInt(val.ip_index) < Air.ref_start_index)
-            return @intToEnum(Air.Inst.Ref, @enumToInt(val.ip_index));
+    if (val.ip_index != .none) {
+        if (@enumToInt(val.toIntern()) < Air.ref_start_index)
+            return @intToEnum(Air.Inst.Ref, @enumToInt(val.toIntern()));
         try sema.air_instructions.append(gpa, .{
             .tag = .interned,
-            .data = .{ .interned = val.ip_index },
+            .data = .{ .interned = val.toIntern() },
         });
         const result = Air.indexToRef(@intCast(u32, sema.air_instructions.len - 1));
         // This assertion can be removed when the `ty` parameter is removed from
@@ -33417,7 +33526,7 @@ fn analyzeComptimeAlloc(
 
     try sema.mod.declareDeclDependency(sema.owner_decl_index, decl_index);
     return sema.addConstant(ptr_type, (try sema.mod.intern(.{ .ptr = .{
-        .ty = ptr_type.ip_index,
+        .ty = ptr_type.toIntern(),
         .addr = .{ .mut_decl = .{
             .decl = decl_index,
             .runtime_index = block.runtime_index,
@@ -33589,7 +33698,7 @@ fn usizeCast(sema: *Sema, block: *Block, src: LazySrcLoc, int: u64) CompileError
 /// This logic must be kept in sync with `Type.isPtrLikeOptional`.
 fn typePtrOrOptionalPtrTy(sema: *Sema, ty: Type) !?Type {
     const mod = sema.mod;
-    return switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+    return switch (mod.intern_pool.indexToKey(ty.toIntern())) {
         .ptr_type => |ptr_type| switch (ptr_type.size) {
             .Slice => null,
             .C => ptr_type.elem_type.toType(),
@@ -33624,10 +33733,10 @@ fn typePtrOrOptionalPtrTy(sema: *Sema, ty: Type) !?Type {
 /// elsewhere in value.zig
 pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
     const mod = sema.mod;
-    return switch (ty.ip_index) {
+    return switch (ty.toIntern()) {
         .empty_struct_type => false,
 
-        else => switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .int_type => return false,
             .ptr_type => |ptr_type| {
                 const child_ty = ptr_type.elem_type.toType();
@@ -33873,7 +33982,7 @@ fn anonStructFieldIndex(
     field_src: LazySrcLoc,
 ) !u32 {
     const mod = sema.mod;
-    const anon_struct = mod.intern_pool.indexToKey(struct_ty.ip_index).anon_struct_type;
+    const anon_struct = mod.intern_pool.indexToKey(struct_ty.toIntern()).anon_struct_type;
     for (anon_struct.names, 0..) |name, i| {
         if (mem.eql(u8, mod.intern_pool.stringToSlice(name), field_name)) {
             return @intCast(u32, i);
@@ -33891,14 +34000,17 @@ fn queueFullTypeResolution(sema: *Sema, ty: Type) !void {
 fn intAdd(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) == .Vector) {
-        const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
+        const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(mod));
         const scalar_ty = ty.scalarType(mod);
         for (result_data, 0..) |*scalar, i| {
             const lhs_elem = try lhs.elemValue(mod, i);
             const rhs_elem = try rhs.elemValue(mod, i);
-            scalar.* = try sema.intAddScalar(lhs_elem, rhs_elem, scalar_ty);
+            scalar.* = try (try sema.intAddScalar(lhs_elem, rhs_elem, scalar_ty)).intern(scalar_ty, mod);
         }
-        return Value.Tag.aggregate.create(sema.arena, result_data);
+        return (try mod.intern(.{ .aggregate = .{
+            .ty = ty.toIntern(),
+            .storage = .{ .elems = result_data },
+        } })).toValue();
     }
     return sema.intAddScalar(lhs, rhs, ty);
 }
@@ -33945,14 +34057,17 @@ fn numberAddWrapScalar(
 fn intSub(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) == .Vector) {
-        const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
+        const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(mod));
         const scalar_ty = ty.scalarType(mod);
         for (result_data, 0..) |*scalar, i| {
             const lhs_elem = try lhs.elemValue(sema.mod, i);
             const rhs_elem = try rhs.elemValue(sema.mod, i);
-            scalar.* = try sema.intSubScalar(lhs_elem, rhs_elem, scalar_ty);
+            scalar.* = try (try sema.intSubScalar(lhs_elem, rhs_elem, scalar_ty)).intern(scalar_ty, mod);
         }
-        return Value.Tag.aggregate.create(sema.arena, result_data);
+        return (try mod.intern(.{ .aggregate = .{
+            .ty = ty.toIntern(),
+            .storage = .{ .elems = result_data },
+        } })).toValue();
     }
     return sema.intSubScalar(lhs, rhs, ty);
 }
@@ -34004,18 +34119,26 @@ fn intSubWithOverflow(
 ) !Value.OverflowArithmeticResult {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) == .Vector) {
-        const overflowed_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
-        const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
-        for (result_data, 0..) |*scalar, i| {
+        const vec_len = ty.vectorLen(mod);
+        const overflowed_data = try sema.arena.alloc(InternPool.Index, vec_len);
+        const result_data = try sema.arena.alloc(InternPool.Index, vec_len);
+        const scalar_ty = ty.scalarType(mod);
+        for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
             const lhs_elem = try lhs.elemValue(sema.mod, i);
             const rhs_elem = try rhs.elemValue(sema.mod, i);
-            const of_math_result = try sema.intSubWithOverflowScalar(lhs_elem, rhs_elem, ty.scalarType(mod));
-            overflowed_data[i] = of_math_result.overflow_bit;
-            scalar.* = of_math_result.wrapped_result;
+            const of_math_result = try sema.intSubWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty);
+            of.* = try of_math_result.overflow_bit.intern(Type.bool, mod);
+            scalar.* = try of_math_result.wrapped_result.intern(scalar_ty, mod);
         }
         return Value.OverflowArithmeticResult{
-            .overflow_bit = try Value.Tag.aggregate.create(sema.arena, overflowed_data),
-            .wrapped_result = try Value.Tag.aggregate.create(sema.arena, result_data),
+            .overflow_bit = (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = overflowed_data },
+            } })).toValue(),
+            .wrapped_result = (try mod.intern(.{ .aggregate = .{
+                .ty = (try mod.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue(),
         };
     }
     return sema.intSubWithOverflowScalar(lhs, rhs, ty);
@@ -34057,13 +34180,17 @@ fn floatToInt(
 ) CompileError!Value {
     const mod = sema.mod;
     if (float_ty.zigTypeTag(mod) == .Vector) {
-        const elem_ty = float_ty.childType(mod);
-        const result_data = try sema.arena.alloc(Value, float_ty.vectorLen(mod));
+        const elem_ty = float_ty.scalarType(mod);
+        const result_data = try sema.arena.alloc(InternPool.Index, float_ty.vectorLen(mod));
+        const scalar_ty = int_ty.scalarType(mod);
         for (result_data, 0..) |*scalar, i| {
             const elem_val = try val.elemValue(sema.mod, i);
-            scalar.* = try sema.floatToIntScalar(block, src, elem_val, elem_ty, int_ty.scalarType(mod));
+            scalar.* = try (try sema.floatToIntScalar(block, src, elem_val, elem_ty, int_ty.scalarType(mod))).intern(scalar_ty, mod);
         }
-        return Value.Tag.aggregate.create(sema.arena, result_data);
+        return (try mod.intern(.{ .aggregate = .{
+            .ty = int_ty.toIntern(),
+            .storage = .{ .elems = result_data },
+        } })).toValue();
     }
     return sema.floatToIntScalar(block, src, val, float_ty, int_ty);
 }
@@ -34139,16 +34266,16 @@ fn intFitsInType(
     vector_index: ?*usize,
 ) CompileError!bool {
     const mod = sema.mod;
-    if (ty.ip_index == .comptime_int_type) return true;
+    if (ty.toIntern() == .comptime_int_type) return true;
     const info = ty.intInfo(mod);
-    switch (val.ip_index) {
+    switch (val.toIntern()) {
         .undef,
         .zero,
         .zero_usize,
         .zero_u8,
         => return true,
 
-        else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .variable, .extern_func, .func, .ptr => {
                 const target = mod.getTarget();
                 const ptr_bits = target.ptrBitWidth();
@@ -34219,12 +34346,12 @@ fn intInRange(sema: *Sema, tag_ty: Type, int_val: Value, end: usize) !bool {
 /// Asserts the type is an enum.
 fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool {
     const mod = sema.mod;
-    const enum_type = mod.intern_pool.indexToKey(ty.ip_index).enum_type;
+    const enum_type = mod.intern_pool.indexToKey(ty.toIntern()).enum_type;
     assert(enum_type.tag_mode != .nonexhaustive);
     // The `tagValueIndex` function call below relies on the type being the integer tag type.
     // `getCoerced` assumes the value will fit the new type.
     if (!(try sema.intFitsInType(int, enum_type.tag_ty.toType(), null))) return false;
-    const int_coerced = try mod.intern_pool.getCoerced(sema.gpa, int.ip_index, enum_type.tag_ty);
+    const int_coerced = try mod.intern_pool.getCoerced(sema.gpa, int.toIntern(), enum_type.tag_ty);
 
     return enum_type.tagValueIndex(&mod.intern_pool, int_coerced) != null;
 }
@@ -34237,18 +34364,26 @@ fn intAddWithOverflow(
 ) !Value.OverflowArithmeticResult {
     const mod = sema.mod;
     if (ty.zigTypeTag(mod) == .Vector) {
-        const overflowed_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
-        const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
-        for (result_data, 0..) |*scalar, i| {
+        const vec_len = ty.vectorLen(mod);
+        const overflowed_data = try sema.arena.alloc(InternPool.Index, vec_len);
+        const result_data = try sema.arena.alloc(InternPool.Index, vec_len);
+        const scalar_ty = ty.scalarType(mod);
+        for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
             const lhs_elem = try lhs.elemValue(sema.mod, i);
             const rhs_elem = try rhs.elemValue(sema.mod, i);
-            const of_math_result = try sema.intAddWithOverflowScalar(lhs_elem, rhs_elem, ty.scalarType(mod));
-            overflowed_data[i] = of_math_result.overflow_bit;
-            scalar.* = of_math_result.wrapped_result;
+            const of_math_result = try sema.intAddWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty);
+            of.* = try of_math_result.overflow_bit.intern(Type.bool, mod);
+            scalar.* = try of_math_result.wrapped_result.intern(scalar_ty, mod);
         }
         return Value.OverflowArithmeticResult{
-            .overflow_bit = try Value.Tag.aggregate.create(sema.arena, overflowed_data),
-            .wrapped_result = try Value.Tag.aggregate.create(sema.arena, result_data),
+            .overflow_bit = (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = overflowed_data },
+            } })).toValue(),
+            .wrapped_result = (try mod.intern(.{ .aggregate = .{
+                .ty = (try mod.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue(),
         };
     }
     return sema.intAddWithOverflowScalar(lhs, rhs, ty);
@@ -34340,14 +34475,17 @@ fn compareVector(
 ) !Value {
     const mod = sema.mod;
     assert(ty.zigTypeTag(mod) == .Vector);
-    const result_data = try sema.arena.alloc(Value, ty.vectorLen(mod));
+    const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(mod));
     for (result_data, 0..) |*scalar, i| {
         const lhs_elem = try lhs.elemValue(sema.mod, i);
         const rhs_elem = try rhs.elemValue(sema.mod, i);
         const res_bool = try sema.compareScalar(lhs_elem, op, rhs_elem, ty.scalarType(mod));
-        scalar.* = Value.makeBool(res_bool);
+        scalar.* = try Value.makeBool(res_bool).intern(Type.bool, mod);
     }
-    return Value.Tag.aggregate.create(sema.arena, result_data);
+    return (try mod.intern(.{ .aggregate = .{
+        .ty = (try mod.vectorType(.{ .len = ty.vectorLen(mod), .child = .u1_type })).toIntern(),
+        .storage = .{ .elems = result_data },
+    } })).toValue();
 }
 
 /// Returns the type of a pointer to an element.
src/TypedValue.zig
@@ -103,10 +103,26 @@ pub fn print(
                 return writer.writeAll(" }");
             },
             .bytes => return writer.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),
-            .str_lit => {
-                const str_lit = val.castTag(.str_lit).?.data;
-                const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                return writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)});
+            .repeated => {
+                if (level == 0) {
+                    return writer.writeAll(".{ ... }");
+                }
+                var i: u32 = 0;
+                try writer.writeAll(".{ ");
+                const elem_tv = TypedValue{
+                    .ty = ty.elemType2(mod),
+                    .val = val.castTag(.repeated).?.data,
+                };
+                const len = ty.arrayLen(mod);
+                const max_len = std.math.min(len, max_aggregate_items);
+                while (i < max_len) : (i += 1) {
+                    if (i != 0) try writer.writeAll(", ");
+                    try print(elem_tv, writer, level - 1, mod);
+                }
+                if (len > max_aggregate_items) {
+                    try writer.writeAll(", ...");
+                }
+                return writer.writeAll(" }");
             },
             // TODO these should not appear in this function
             .inferred_alloc => return writer.writeAll("(inferred allocation value)"),
src/value.zig
@@ -37,8 +37,9 @@ pub const Value = struct {
 
         /// A slice of u8 whose memory is managed externally.
         bytes,
-        /// Similar to bytes however it stores an index relative to `Module.string_literal_bytes`.
-        str_lit,
+        /// This value is repeated some number of times. The amount of times to repeat
+        /// is stored externally.
+        repeated,
         /// An instance of a struct, array, or vector.
         /// Each element/field stored as a `Value`.
         /// In the case of sentinel-terminated arrays, the sentinel value *is* stored,
@@ -57,9 +58,9 @@ pub const Value = struct {
 
         pub fn Type(comptime t: Tag) type {
             return switch (t) {
-                .bytes => Payload.Bytes,
+                .repeated => Payload.SubValue,
 
-                .str_lit => Payload.StrLit,
+                .bytes => Payload.Bytes,
 
                 .inferred_alloc => Payload.InferredAlloc,
                 .inferred_alloc_comptime => Payload.InferredAllocComptime,
@@ -171,7 +172,18 @@ pub const Value = struct {
                     .legacy = .{ .ptr_otherwise = &new_payload.base },
                 };
             },
-            .str_lit => return self.copyPayloadShallow(arena, Payload.StrLit),
+            .repeated => {
+                const payload = self.cast(Payload.SubValue).?;
+                const new_payload = try arena.create(Payload.SubValue);
+                new_payload.* = .{
+                    .base = payload.base,
+                    .data = try payload.data.copy(arena),
+                };
+                return Value{
+                    .ip_index = .none,
+                    .legacy = .{ .ptr_otherwise = &new_payload.base },
+                };
+            },
             .aggregate => {
                 const payload = self.castTag(.aggregate).?;
                 const new_payload = try arena.create(Payload.Aggregate);
@@ -187,7 +199,6 @@ pub const Value = struct {
                     .legacy = .{ .ptr_otherwise = &new_payload.base },
                 };
             },
-
             .@"union" => {
                 const tag_and_val = self.castTag(.@"union").?.data;
                 const new_payload = try arena.create(Payload.Union);
@@ -203,7 +214,6 @@ pub const Value = struct {
                     .legacy = .{ .ptr_otherwise = &new_payload.base },
                 };
             },
-
             .inferred_alloc => unreachable,
             .inferred_alloc_comptime => unreachable,
         }
@@ -237,7 +247,7 @@ pub const Value = struct {
     ) !void {
         comptime assert(fmt.len == 0);
         if (start_val.ip_index != .none) {
-            try out_stream.print("(interned: {})", .{start_val.ip_index});
+            try out_stream.print("(interned: {})", .{start_val.toIntern()});
             return;
         }
         var val = start_val;
@@ -249,11 +259,9 @@ pub const Value = struct {
                 return out_stream.writeAll("(union value)");
             },
             .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),
-            .str_lit => {
-                const str_lit = val.castTag(.str_lit).?.data;
-                return out_stream.print("(.str_lit index={d} len={d})", .{
-                    str_lit.index, str_lit.len,
-                });
+            .repeated => {
+                try out_stream.writeAll("(repeated) ");
+                val = val.castTag(.repeated).?.data;
             },
             .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
             .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"),
@@ -274,40 +282,24 @@ pub const Value = struct {
     /// Asserts that the value is representable as an array of bytes.
     /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
     pub fn toAllocatedBytes(val: Value, ty: Type, allocator: Allocator, mod: *Module) ![]u8 {
-        switch (val.ip_index) {
-            .none => switch (val.tag()) {
-                .bytes => {
-                    const bytes = val.castTag(.bytes).?.data;
-                    const adjusted_len = bytes.len - @boolToInt(ty.sentinel(mod) != null);
-                    const adjusted_bytes = bytes[0..adjusted_len];
-                    return allocator.dupe(u8, adjusted_bytes);
-                },
-                .str_lit => {
-                    const str_lit = val.castTag(.str_lit).?.data;
-                    const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                    return allocator.dupe(u8, bytes);
-                },
-                else => return arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
+            .enum_literal => |enum_literal| allocator.dupe(u8, mod.intern_pool.stringToSlice(enum_literal)),
+            .ptr => |ptr| switch (ptr.len) {
+                .none => unreachable,
+                else => arrayToAllocatedBytes(val, ptr.len.toValue().toUnsignedInt(mod), allocator, mod),
             },
-            else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
-                .enum_literal => |enum_literal| allocator.dupe(u8, mod.intern_pool.stringToSlice(enum_literal)),
-                .ptr => |ptr| switch (ptr.len) {
-                    .none => unreachable,
-                    else => arrayToAllocatedBytes(val, ptr.len.toValue().toUnsignedInt(mod), allocator, mod),
-                },
-                .aggregate => |aggregate| switch (aggregate.storage) {
-                    .bytes => |bytes| try allocator.dupe(u8, bytes),
-                    .elems => arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
-                    .repeated_elem => |elem| {
-                        const byte = @intCast(u8, elem.toValue().toUnsignedInt(mod));
-                        const result = try allocator.alloc(u8, @intCast(usize, ty.arrayLen(mod)));
-                        @memset(result, byte);
-                        return result;
-                    },
+            .aggregate => |aggregate| switch (aggregate.storage) {
+                .bytes => |bytes| try allocator.dupe(u8, bytes),
+                .elems => arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
+                .repeated_elem => |elem| {
+                    const byte = @intCast(u8, elem.toValue().toUnsignedInt(mod));
+                    const result = try allocator.alloc(u8, @intCast(usize, ty.arrayLen(mod)));
+                    @memset(result, byte);
+                    return result;
                 },
-                else => unreachable,
             },
-        }
+            else => unreachable,
+        };
     }
 
     fn arrayToAllocatedBytes(val: Value, len: u64, allocator: Allocator, mod: *Module) ![]u8 {
@@ -320,13 +312,13 @@ pub const Value = struct {
     }
 
     pub fn intern(val: Value, ty: Type, mod: *Module) Allocator.Error!InternPool.Index {
-        if (val.ip_index != .none) return mod.intern_pool.getCoerced(mod.gpa, val.ip_index, ty.ip_index);
+        if (val.ip_index != .none) return mod.intern_pool.getCoerced(mod.gpa, val.toIntern(), ty.toIntern());
         switch (val.tag()) {
             .aggregate => {
                 const old_elems = val.castTag(.aggregate).?.data;
                 const new_elems = try mod.gpa.alloc(InternPool.Index, old_elems.len);
                 defer mod.gpa.free(new_elems);
-                const ty_key = mod.intern_pool.indexToKey(ty.ip_index);
+                const ty_key = mod.intern_pool.indexToKey(ty.toIntern());
                 for (new_elems, old_elems, 0..) |*new_elem, old_elem, field_i|
                     new_elem.* = try old_elem.intern(switch (ty_key) {
                         .struct_type => ty.structFieldType(field_i, mod),
@@ -335,14 +327,14 @@ pub const Value = struct {
                         else => unreachable,
                     }, mod);
                 return mod.intern(.{ .aggregate = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .storage = .{ .elems = new_elems },
                 } });
             },
             .@"union" => {
                 const pl = val.castTag(.@"union").?.data;
                 return mod.intern(.{ .un = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
                     .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod), mod),
                 } });
@@ -353,13 +345,15 @@ pub const Value = struct {
 
     pub fn unintern(val: Value, arena: Allocator, mod: *Module) Allocator.Error!Value {
         if (val.ip_index == .none) return val;
-        switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .aggregate => |aggregate| switch (aggregate.storage) {
+                .bytes => |bytes| return Tag.bytes.create(arena, try arena.dupe(u8, bytes)),
                 .elems => |old_elems| {
                     const new_elems = try arena.alloc(Value, old_elems.len);
                     for (new_elems, old_elems) |*new_elem, old_elem| new_elem.* = old_elem.toValue();
                     return Tag.aggregate.create(arena, new_elems);
                 },
+                .repeated_elem => |elem| return Tag.repeated.create(arena, elem.toValue()),
             },
             else => return val,
         }
@@ -372,40 +366,38 @@ pub const Value = struct {
 
     /// Asserts that the value is representable as a type.
     pub fn toType(self: Value) Type {
-        return self.ip_index.toType();
+        return self.toIntern().toType();
     }
 
     pub fn enumToInt(val: Value, ty: Type, mod: *Module) Allocator.Error!Value {
         const ip = &mod.intern_pool;
-        switch (val.ip_index) {
-            else => return switch (ip.indexToKey(ip.typeOf(val.ip_index))) {
-                // Assume it is already an integer and return it directly.
-                .simple_type, .int_type => val,
-                .enum_literal => |enum_literal| {
-                    const field_index = ty.enumFieldIndex(ip.stringToSlice(enum_literal), mod).?;
-                    return switch (ip.indexToKey(ty.ip_index)) {
-                        // Assume it is already an integer and return it directly.
-                        .simple_type, .int_type => val,
-                        .enum_type => |enum_type| if (enum_type.values.len != 0)
-                            enum_type.values[field_index].toValue()
-                        else // Field index and integer values are the same.
-                            mod.intValue(enum_type.tag_ty.toType(), field_index),
-                        else => unreachable,
-                    };
-                },
-                .enum_type => |enum_type| (try ip.getCoerced(
-                    mod.gpa,
-                    val.ip_index,
-                    enum_type.tag_ty,
-                )).toValue(),
-                else => unreachable,
+        return switch (ip.indexToKey(ip.typeOf(val.toIntern()))) {
+            // Assume it is already an integer and return it directly.
+            .simple_type, .int_type => val,
+            .enum_literal => |enum_literal| {
+                const field_index = ty.enumFieldIndex(ip.stringToSlice(enum_literal), mod).?;
+                return switch (ip.indexToKey(ty.toIntern())) {
+                    // Assume it is already an integer and return it directly.
+                    .simple_type, .int_type => val,
+                    .enum_type => |enum_type| if (enum_type.values.len != 0)
+                        enum_type.values[field_index].toValue()
+                    else // Field index and integer values are the same.
+                        mod.intValue(enum_type.tag_ty.toType(), field_index),
+                    else => unreachable,
+                };
             },
-        }
+            .enum_type => |enum_type| (try ip.getCoerced(
+                mod.gpa,
+                val.toIntern(),
+                enum_type.tag_ty,
+            )).toValue(),
+            else => unreachable,
+        };
     }
 
     pub fn tagName(val: Value, mod: *Module) []const u8 {
         const ip = &mod.intern_pool;
-        const enum_tag = switch (ip.indexToKey(val.ip_index)) {
+        const enum_tag = switch (ip.indexToKey(val.toIntern())) {
             .un => |un| ip.indexToKey(un.tag).enum_tag,
             .enum_tag => |x| x,
             .enum_literal => |name| return ip.stringToSlice(name),
@@ -413,7 +405,7 @@ pub const Value = struct {
         };
         const enum_type = ip.indexToKey(enum_tag.ty).enum_type;
         const field_index = field_index: {
-            const field_index = enum_type.tagValueIndex(ip, val.ip_index).?;
+            const field_index = enum_type.tagValueIndex(ip, val.toIntern()).?;
             break :field_index @intCast(u32, field_index);
         };
         const field_name = enum_type.names[field_index];
@@ -432,12 +424,12 @@ pub const Value = struct {
         mod: *Module,
         opt_sema: ?*Sema,
     ) Module.CompileError!BigIntConst {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .bool_false => BigIntMutable.init(&space.limbs, 0).toConst(),
             .bool_true => BigIntMutable.init(&space.limbs, 1).toConst(),
             .undef => unreachable,
             .null_value => BigIntMutable.init(&space.limbs, 0).toConst(),
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .runtime_value => |runtime_value| runtime_value.val.toValue().toBigIntAdvanced(space, mod, opt_sema),
                 .int => |int| switch (int.storage) {
                     .u64, .i64, .big_int => int.storage.toBigInt(space),
@@ -475,18 +467,18 @@ pub const Value = struct {
     }
 
     pub fn getFunctionIndex(val: Value, mod: *Module) Module.Fn.OptionalIndex {
-        return if (val.ip_index != .none) mod.intern_pool.indexToFunc(val.ip_index) else .none;
+        return if (val.ip_index != .none) mod.intern_pool.indexToFunc(val.toIntern()) else .none;
     }
 
     pub fn getExternFunc(val: Value, mod: *Module) ?InternPool.Key.ExternFunc {
-        return if (val.ip_index != .none) switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return if (val.ip_index != .none) switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .extern_func => |extern_func| extern_func,
             else => null,
         } else null;
     }
 
     pub fn getVariable(val: Value, mod: *Module) ?InternPool.Key.Variable {
-        return if (val.ip_index != .none) switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return if (val.ip_index != .none) switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .variable => |variable| variable,
             else => null,
         } else null;
@@ -501,11 +493,11 @@ pub const Value = struct {
     /// If the value fits in a u64, return it, otherwise null.
     /// Asserts not undefined.
     pub fn getUnsignedIntAdvanced(val: Value, mod: *Module, opt_sema: ?*Sema) !?u64 {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .bool_false => 0,
             .bool_true => 1,
             .undef => unreachable,
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .int => |int| switch (int.storage) {
                     .big_int => |big_int| big_int.to(u64) catch null,
                     .u64 => |x| x,
@@ -531,11 +523,11 @@ pub const Value = struct {
 
     /// Asserts the value is an integer and it fits in a i64
     pub fn toSignedInt(val: Value, mod: *Module) i64 {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .bool_false => 0,
             .bool_true => 1,
             .undef => unreachable,
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .int => |int| switch (int.storage) {
                     .big_int => |big_int| big_int.to(i64) catch unreachable,
                     .i64 => |x| x,
@@ -549,7 +541,7 @@ pub const Value = struct {
     }
 
     pub fn toBool(val: Value, _: *const Module) bool {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .bool_true => true,
             .bool_false => false,
             else => unreachable,
@@ -558,7 +550,7 @@ pub const Value = struct {
 
     fn isDeclRef(val: Value, mod: *Module) bool {
         var check = val;
-        while (true) switch (mod.intern_pool.indexToKey(check.ip_index)) {
+        while (true) switch (mod.intern_pool.indexToKey(check.toIntern())) {
             .ptr => |ptr| switch (ptr.addr) {
                 .decl, .mut_decl, .comptime_field => return true,
                 .eu_payload, .opt_payload => |index| check = index.toValue(),
@@ -644,7 +636,7 @@ pub const Value = struct {
             .ErrorSet => {
                 // TODO revisit this when we have the concept of the error tag type
                 const Int = u16;
-                const name = switch (mod.intern_pool.indexToKey(val.ip_index)) {
+                const name = switch (mod.intern_pool.indexToKey(val.toIntern())) {
                     .err => |err| err.name,
                     .error_union => |error_union| error_union.val.err_name,
                     else => unreachable,
@@ -718,7 +710,7 @@ pub const Value = struct {
 
                 if (abi_size == 0) return;
                 if (abi_size <= @sizeOf(u64)) {
-                    const ip_key = mod.intern_pool.indexToKey(int_val.ip_index);
+                    const ip_key = mod.intern_pool.indexToKey(int_val.toIntern());
                     const int: u64 = switch (ip_key.int.storage) {
                         .u64 => |x| x,
                         .i64 => |x| @bitCast(u64, x),
@@ -847,7 +839,7 @@ pub const Value = struct {
                 }
             },
             .Float => return (try mod.intern(.{ .float = .{
-                .ty = ty.ip_index,
+                .ty = ty.toIntern(),
                 .storage = switch (ty.floatBits(target)) {
                     16 => .{ .f16 = @bitCast(f16, std.mem.readInt(u16, buffer[0..2], endian)) },
                     32 => .{ .f32 = @bitCast(f32, std.mem.readInt(u32, buffer[0..4], endian)) },
@@ -860,13 +852,16 @@ pub const Value = struct {
             .Array => {
                 const elem_ty = ty.childType(mod);
                 const elem_size = elem_ty.abiSize(mod);
-                const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen(mod)));
+                const elems = try arena.alloc(InternPool.Index, @intCast(usize, ty.arrayLen(mod)));
                 var offset: usize = 0;
                 for (elems) |*elem| {
-                    elem.* = try readFromMemory(elem_ty, mod, buffer[offset..], arena);
+                    elem.* = try (try readFromMemory(elem_ty, mod, buffer[offset..], arena)).intern(elem_ty, mod);
                     offset += @intCast(usize, elem_size);
                 }
-                return Tag.aggregate.create(arena, elems);
+                return (try mod.intern(.{ .aggregate = .{
+                    .ty = ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue();
             },
             .Vector => {
                 // We use byte_count instead of abi_size here, so that any padding bytes
@@ -878,13 +873,16 @@ pub const Value = struct {
                 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
                 .Extern => {
                     const fields = ty.structFields(mod).values();
-                    const field_vals = try arena.alloc(Value, fields.len);
-                    for (fields, 0..) |field, i| {
+                    const field_vals = try arena.alloc(InternPool.Index, fields.len);
+                    for (field_vals, fields, 0..) |*field_val, field, i| {
                         const off = @intCast(usize, ty.structFieldOffset(i, mod));
-                        const sz = @intCast(usize, ty.structFieldType(i, mod).abiSize(mod));
-                        field_vals[i] = try readFromMemory(field.ty, mod, buffer[off..(off + sz)], arena);
+                        const sz = @intCast(usize, field.ty.abiSize(mod));
+                        field_val.* = try (try readFromMemory(field.ty, mod, buffer[off..(off + sz)], arena)).intern(field.ty, mod);
                     }
-                    return Tag.aggregate.create(arena, field_vals);
+                    return (try mod.intern(.{ .aggregate = .{
+                        .ty = ty.toIntern(),
+                        .storage = .{ .elems = field_vals },
+                    } })).toValue();
                 },
                 .Packed => {
                     const byte_count = (@intCast(usize, ty.bitSize(mod)) + 7) / 8;
@@ -897,7 +895,7 @@ pub const Value = struct {
                 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], endian);
                 const name = mod.error_name_list.items[@intCast(usize, int)];
                 return (try mod.intern(.{ .err = .{
-                    .ty = ty.ip_index,
+                    .ty = ty.toIntern(),
                     .name = mod.intern_pool.getString(name).unwrap().?,
                 } })).toValue();
             },
@@ -961,7 +959,7 @@ pub const Value = struct {
                 }
             },
             .Float => return (try mod.intern(.{ .float = .{
-                .ty = ty.ip_index,
+                .ty = ty.toIntern(),
                 .storage = switch (ty.floatBits(target)) {
                     16 => .{ .f16 = @bitCast(f16, std.mem.readPackedInt(u16, buffer, bit_offset, endian)) },
                     32 => .{ .f32 = @bitCast(f32, std.mem.readPackedInt(u32, buffer, bit_offset, endian)) },
@@ -973,17 +971,20 @@ pub const Value = struct {
             } })).toValue(),
             .Vector => {
                 const elem_ty = ty.childType(mod);
-                const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen(mod)));
+                const elems = try arena.alloc(InternPool.Index, @intCast(usize, ty.arrayLen(mod)));
 
                 var bits: u16 = 0;
                 const elem_bit_size = @intCast(u16, elem_ty.bitSize(mod));
                 for (elems, 0..) |_, i| {
                     // On big-endian systems, LLVM reverses the element order of vectors by default
                     const tgt_elem_i = if (endian == .Big) elems.len - i - 1 else i;
-                    elems[tgt_elem_i] = try readFromPackedMemory(elem_ty, mod, buffer, bit_offset + bits, arena);
+                    elems[tgt_elem_i] = try (try readFromPackedMemory(elem_ty, mod, buffer, bit_offset + bits, arena)).intern(elem_ty, mod);
                     bits += elem_bit_size;
                 }
-                return Tag.aggregate.create(arena, elems);
+                return (try mod.intern(.{ .aggregate = .{
+                    .ty = ty.toIntern(),
+                    .storage = .{ .elems = elems },
+                } })).toValue();
             },
             .Struct => switch (ty.containerLayout(mod)) {
                 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
@@ -991,13 +992,16 @@ pub const Value = struct {
                 .Packed => {
                     var bits: u16 = 0;
                     const fields = ty.structFields(mod).values();
-                    const field_vals = try arena.alloc(Value, fields.len);
+                    const field_vals = try arena.alloc(InternPool.Index, fields.len);
                     for (fields, 0..) |field, i| {
                         const field_bits = @intCast(u16, field.ty.bitSize(mod));
-                        field_vals[i] = try readFromPackedMemory(field.ty, mod, buffer, bit_offset + bits, arena);
+                        field_vals[i] = try (try readFromPackedMemory(field.ty, mod, buffer, bit_offset + bits, arena)).intern(field.ty, mod);
                         bits += field_bits;
                     }
-                    return Tag.aggregate.create(arena, field_vals);
+                    return (try mod.intern(.{ .aggregate = .{
+                        .ty = ty.toIntern(),
+                        .storage = .{ .elems = field_vals },
+                    } })).toValue();
                 },
             },
             .Pointer => {
@@ -1015,7 +1019,7 @@ pub const Value = struct {
 
     /// Asserts that the value is a float or an integer.
     pub fn toFloat(val: Value, comptime T: type, mod: *Module) T {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .int => |int| switch (int.storage) {
                 .big_int => |big_int| @floatCast(T, bigIntToFloat(big_int.limbs, big_int.positive)),
                 inline .u64, .i64 => |x| {
@@ -1119,7 +1123,7 @@ pub const Value = struct {
     pub fn floatCast(self: Value, dest_ty: Type, mod: *Module) !Value {
         const target = mod.getTarget();
         return (try mod.intern(.{ .float = .{
-            .ty = dest_ty.ip_index,
+            .ty = dest_ty.toIntern(),
             .storage = switch (dest_ty.floatBits(target)) {
                 16 => .{ .f16 = self.toFloat(f16, mod) },
                 32 => .{ .f32 = self.toFloat(f32, mod) },
@@ -1133,7 +1137,7 @@ pub const Value = struct {
 
     /// Asserts the value is a float
     pub fn floatHasFraction(self: Value, mod: *const Module) bool {
-        return switch (mod.intern_pool.indexToKey(self.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(self.toIntern())) {
             .float => |float| switch (float.storage) {
                 inline else => |x| @rem(x, 1) != 0,
             },
@@ -1150,10 +1154,10 @@ pub const Value = struct {
         mod: *Module,
         opt_sema: ?*Sema,
     ) Module.CompileError!std.math.Order {
-        return switch (lhs.ip_index) {
+        return switch (lhs.toIntern()) {
             .bool_false => .eq,
             .bool_true => .gt,
-            else => switch (mod.intern_pool.indexToKey(lhs.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(lhs.toIntern())) {
                 .ptr => |ptr| switch (ptr.addr) {
                     .decl, .mut_decl, .comptime_field => .gt,
                     .int => |int| int.toValue().orderAgainstZeroAdvanced(mod, opt_sema),
@@ -1212,8 +1216,8 @@ pub const Value = struct {
             const lhs_tag = lhs.tag();
             const rhs_tag = rhs.tag();
             if (lhs_tag == rhs_tag) {
-                const lhs_storage = mod.intern_pool.indexToKey(lhs.ip_index).float.storage;
-                const rhs_storage = mod.intern_pool.indexToKey(rhs.ip_index).float.storage;
+                const lhs_storage = mod.intern_pool.indexToKey(lhs.toIntern()).float.storage;
+                const rhs_storage = mod.intern_pool.indexToKey(rhs.toIntern()).float.storage;
                 const lhs128: f128 = switch (lhs_storage) {
                     inline else => |x| x,
                 };
@@ -1336,46 +1340,20 @@ pub const Value = struct {
             }
         }
 
-        switch (lhs.ip_index) {
-            .none => switch (lhs.tag()) {
-                .aggregate => {
-                    for (lhs.castTag(.aggregate).?.data) |elem_val| {
-                        if (!(try elem_val.compareAllWithZeroAdvancedExtra(op, mod, opt_sema))) return false;
-                    }
-                    return true;
-                },
-                .str_lit => {
-                    const str_lit = lhs.castTag(.str_lit).?.data;
-                    const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                    for (bytes) |byte| {
-                        if (!std.math.compare(byte, op, 0)) return false;
-                    }
-                    return true;
-                },
-                .bytes => {
-                    const bytes = lhs.castTag(.bytes).?.data;
-                    for (bytes) |byte| {
-                        if (!std.math.compare(byte, op, 0)) return false;
-                    }
-                    return true;
-                },
-                else => {},
+        switch (mod.intern_pool.indexToKey(lhs.toIntern())) {
+            .float => |float| switch (float.storage) {
+                inline else => |x| if (std.math.isNan(x)) return op == .neq,
             },
-            else => switch (mod.intern_pool.indexToKey(lhs.ip_index)) {
-                .float => |float| switch (float.storage) {
-                    inline else => |x| if (std.math.isNan(x)) return op == .neq,
-                },
-                .aggregate => |aggregate| return switch (aggregate.storage) {
-                    .bytes => |bytes| for (bytes) |byte| {
-                        if (!std.math.order(byte, 0).compare(op)) break false;
-                    } else true,
-                    .elems => |elems| for (elems) |elem| {
-                        if (!try elem.toValue().compareAllWithZeroAdvancedExtra(op, mod, opt_sema)) break false;
-                    } else true,
-                    .repeated_elem => |elem| elem.toValue().compareAllWithZeroAdvancedExtra(op, mod, opt_sema),
-                },
-                else => {},
+            .aggregate => |aggregate| return switch (aggregate.storage) {
+                .bytes => |bytes| for (bytes) |byte| {
+                    if (!std.math.order(byte, 0).compare(op)) break false;
+                } else true,
+                .elems => |elems| for (elems) |elem| {
+                    if (!try elem.toValue().compareAllWithZeroAdvancedExtra(op, mod, opt_sema)) break false;
+                } else true,
+                .repeated_elem => |elem| elem.toValue().compareAllWithZeroAdvancedExtra(op, mod, opt_sema),
             },
+            else => {},
         }
         return (try orderAgainstZeroAdvanced(lhs, mod, opt_sema)).compare(op);
     }
@@ -1412,7 +1390,7 @@ pub const Value = struct {
                 const b_field_vals = b.castTag(.aggregate).?.data;
                 assert(a_field_vals.len == b_field_vals.len);
 
-                switch (mod.intern_pool.indexToKey(ty.ip_index)) {
+                switch (mod.intern_pool.indexToKey(ty.toIntern())) {
                     .anon_struct_type => |anon_struct| {
                         assert(anon_struct.types.len == a_field_vals.len);
                         for (anon_struct.types, 0..) |field_ty, i| {
@@ -1577,7 +1555,7 @@ pub const Value = struct {
             // The InternPool data structure hashes based on Key to make interned objects
             // unique. An Index can be treated simply as u32 value for the
             // purpose of Type/Value hashing and equality.
-            std.hash.autoHash(hasher, val.ip_index);
+            std.hash.autoHash(hasher, val.toIntern());
             return;
         }
         const zig_ty_tag = ty.zigTypeTag(mod);
@@ -1663,7 +1641,7 @@ pub const Value = struct {
             // The InternPool data structure hashes based on Key to make interned objects
             // unique. An Index can be treated simply as u32 value for the
             // purpose of Type/Value hashing and equality.
-            std.hash.autoHash(hasher, val.ip_index);
+            std.hash.autoHash(hasher, val.toIntern());
             return;
         }
 
@@ -1703,7 +1681,7 @@ pub const Value = struct {
             },
             .Union => {
                 hasher.update(val.tagName(mod));
-                switch (mod.intern_pool.indexToKey(val.ip_index)) {
+                switch (mod.intern_pool.indexToKey(val.toIntern())) {
                     .un => |un| {
                         const active_field_ty = ty.unionFieldType(un.tag.toValue(), mod);
                         un.val.toValue().hashUncoerced(active_field_ty, hasher, mod);
@@ -1746,7 +1724,7 @@ pub const Value = struct {
     };
 
     pub fn isComptimeMutablePtr(val: Value, mod: *Module) bool {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .ptr => |ptr| switch (ptr.addr) {
                 .mut_decl, .comptime_field => true,
                 .eu_payload, .opt_payload => |base_ptr| base_ptr.toValue().isComptimeMutablePtr(mod),
@@ -1758,8 +1736,8 @@ pub const Value = struct {
     }
 
     pub fn canMutateComptimeVarState(val: Value, mod: *Module) bool {
-        return val.isComptimeMutablePtr(mod) or switch (val.ip_index) {
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return val.isComptimeMutablePtr(mod) or switch (val.toIntern()) {
+            else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .error_union => |error_union| switch (error_union.val) {
                     .err_name => false,
                     .payload => |payload| payload.toValue().canMutateComptimeVarState(mod),
@@ -1785,7 +1763,7 @@ pub const Value = struct {
     /// to a decl, or if it points to some part of a decl (like field_ptr or element_ptr),
     /// this function returns null.
     pub fn pointerDecl(val: Value, mod: *Module) ?Module.Decl.Index {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .variable => |variable| variable.decl,
             .extern_func => |extern_func| extern_func.decl,
             .func => |func| mod.funcPtr(func.index).owner_decl,
@@ -1811,35 +1789,19 @@ pub const Value = struct {
     pub const slice_len_index = 1;
 
     pub fn slicePtr(val: Value, mod: *Module) Value {
-        return mod.intern_pool.slicePtr(val.ip_index).toValue();
+        return mod.intern_pool.slicePtr(val.toIntern()).toValue();
     }
 
     pub fn sliceLen(val: Value, mod: *Module) u64 {
-        return mod.intern_pool.sliceLen(val.ip_index).toValue().toUnsignedInt(mod);
+        return mod.intern_pool.sliceLen(val.toIntern()).toValue().toUnsignedInt(mod);
     }
 
     /// Asserts the value is a single-item pointer to an array, or an array,
     /// or an unknown-length pointer, and returns the element value at the index.
     pub fn elemValue(val: Value, mod: *Module, index: usize) Allocator.Error!Value {
-        switch (val.ip_index) {
+        switch (val.toIntern()) {
             .undef => return Value.undef,
-            .none => switch (val.tag()) {
-                .bytes => {
-                    const byte = val.castTag(.bytes).?.data[index];
-                    return mod.intValue(Type.u8, byte);
-                },
-                .str_lit => {
-                    const str_lit = val.castTag(.str_lit).?.data;
-                    const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                    const byte = bytes[index];
-                    return mod.intValue(Type.u8, byte);
-                },
-
-                .aggregate => return val.castTag(.aggregate).?.data[index],
-
-                else => unreachable,
-            },
-            else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => return switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .ptr => |ptr| switch (ptr.addr) {
                     .decl => |decl| mod.declPtr(decl).val.elemValue(mod, index),
                     .mut_decl => |mut_decl| mod.declPtr(mut_decl.decl).val.elemValue(mod, index),
@@ -1871,26 +1833,26 @@ pub const Value = struct {
     }
 
     pub fn isLazyAlign(val: Value, mod: *Module) bool {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .int => |int| int.storage == .lazy_align,
             else => false,
         };
     }
 
     pub fn isLazySize(val: Value, mod: *Module) bool {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .int => |int| int.storage == .lazy_size,
             else => false,
         };
     }
 
     pub fn isRuntimeValue(val: Value, mod: *Module) bool {
-        return mod.intern_pool.indexToKey(val.ip_index) == .runtime_value;
+        return mod.intern_pool.indexToKey(val.toIntern()) == .runtime_value;
     }
 
     /// Returns true if a Value is backed by a variable
     pub fn isVariable(val: Value, mod: *Module) bool {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .variable => true,
             .ptr => |ptr| switch (ptr.addr) {
                 .decl => |decl_index| {
@@ -1913,7 +1875,7 @@ pub const Value = struct {
     }
 
     pub fn isPtrToThreadLocal(val: Value, mod: *Module) bool {
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .variable => |variable| variable.is_threadlocal,
             .ptr => |ptr| switch (ptr.addr) {
                 .decl => |decl_index| {
@@ -1943,55 +1905,30 @@ pub const Value = struct {
         start: usize,
         end: usize,
     ) error{OutOfMemory}!Value {
-        return switch (val.ip_index) {
-            .none => switch (val.tag()) {
-                .bytes => Tag.bytes.create(arena, val.castTag(.bytes).?.data[start..end]),
-                .str_lit => {
-                    const str_lit = val.castTag(.str_lit).?.data;
-                    return Tag.str_lit.create(arena, .{
-                        .index = @intCast(u32, str_lit.index + start),
-                        .len = @intCast(u32, end - start),
-                    });
-                },
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
+            .ptr => |ptr| switch (ptr.addr) {
+                .decl => |decl| try mod.declPtr(decl).val.sliceArray(mod, arena, start, end),
+                .mut_decl => |mut_decl| try mod.declPtr(mut_decl.decl).val.sliceArray(mod, arena, start, end),
+                .comptime_field => |comptime_field| try comptime_field.toValue().sliceArray(mod, arena, start, end),
+                .elem => |elem| try elem.base.toValue().sliceArray(mod, arena, start + elem.index, end + elem.index),
                 else => unreachable,
             },
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
-                .ptr => |ptr| switch (ptr.addr) {
-                    .decl => |decl| try mod.declPtr(decl).val.sliceArray(mod, arena, start, end),
-                    .mut_decl => |mut_decl| try mod.declPtr(mut_decl.decl).val.sliceArray(mod, arena, start, end),
-                    .comptime_field => |comptime_field| try comptime_field.toValue().sliceArray(mod, arena, start, end),
-                    .elem => |elem| try elem.base.toValue().sliceArray(mod, arena, start + elem.index, end + elem.index),
-                    else => unreachable,
+            .aggregate => |aggregate| (try mod.intern(.{ .aggregate = .{
+                .ty = mod.intern_pool.typeOf(val.toIntern()),
+                .storage = switch (aggregate.storage) {
+                    .bytes => |bytes| .{ .bytes = bytes[start..end] },
+                    .elems => |elems| .{ .elems = elems[start..end] },
+                    .repeated_elem => |elem| .{ .repeated_elem = elem },
                 },
-                .aggregate => |aggregate| (try mod.intern(.{ .aggregate = .{
-                    .ty = mod.intern_pool.typeOf(val.ip_index),
-                    .storage = switch (aggregate.storage) {
-                        .bytes => |bytes| .{ .bytes = bytes[start..end] },
-                        .elems => |elems| .{ .elems = elems[start..end] },
-                        .repeated_elem => |elem| .{ .repeated_elem = elem },
-                    },
-                } })).toValue(),
-                else => unreachable,
-            },
+            } })).toValue(),
+            else => unreachable,
         };
     }
 
     pub fn fieldValue(val: Value, mod: *Module, index: usize) !Value {
-        switch (val.ip_index) {
+        switch (val.toIntern()) {
             .undef => return Value.undef,
-            .none => switch (val.tag()) {
-                .aggregate => {
-                    const field_values = val.castTag(.aggregate).?.data;
-                    return field_values[index];
-                },
-                .@"union" => {
-                    const payload = val.castTag(.@"union").?.data;
-                    // TODO assert the tag is correct
-                    return payload.val;
-                },
-                else => unreachable,
-            },
-            else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => return switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .aggregate => |aggregate| switch (aggregate.storage) {
                     .bytes => |bytes| try mod.intern(.{ .int = .{
                         .ty = .u8_type,
@@ -2000,6 +1937,8 @@ pub const Value = struct {
                     .elems => |elems| elems[index],
                     .repeated_elem => |elem| elem,
                 }.toValue(),
+                // TODO assert the tag is correct
+                .un => |un| un.val.toValue(),
                 else => unreachable,
             },
         }
@@ -2007,7 +1946,7 @@ pub const Value = struct {
 
     pub fn unionTag(val: Value, mod: *Module) Value {
         if (val.ip_index == .none) return val.castTag(.@"union").?.data.tag;
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .undef, .enum_tag => val,
             .un => |un| un.tag.toValue(),
             else => unreachable,
@@ -2022,12 +1961,12 @@ pub const Value = struct {
         mod: *Module,
     ) Allocator.Error!Value {
         const elem_ty = ty.elemType2(mod);
-        const ptr_val = switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        const ptr_val = switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .ptr => |ptr| ptr: {
                 switch (ptr.addr) {
                     .elem => |elem| if (mod.intern_pool.typeOf(elem.base).toType().elemType2(mod).eql(elem_ty, mod))
                         return (try mod.intern(.{ .ptr = .{
-                            .ty = ty.ip_index,
+                            .ty = ty.toIntern(),
                             .addr = .{ .elem = .{
                                 .base = elem.base,
                                 .index = elem.index + index,
@@ -2043,9 +1982,9 @@ pub const Value = struct {
             else => val,
         };
         return (try mod.intern(.{ .ptr = .{
-            .ty = ty.ip_index,
+            .ty = ty.toIntern(),
             .addr = .{ .elem = .{
-                .base = ptr_val.ip_index,
+                .base = ptr_val.toIntern(),
                 .index = index,
             } },
         } })).toValue();
@@ -2053,7 +1992,7 @@ pub const Value = struct {
 
     pub fn isUndef(val: Value, mod: *Module) bool {
         if (val.ip_index == .none) return false;
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .undef => true,
             .simple_value => |v| v == .undefined,
             else => false,
@@ -2070,15 +2009,9 @@ pub const Value = struct {
     /// Returns true if any value contained in `self` is undefined.
     pub fn anyUndef(val: Value, mod: *Module) !bool {
         if (val.ip_index == .none) return false;
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .undef => true,
-            .none => switch (val.tag()) {
-                .aggregate => for (val.castTag(.aggregate).?.data) |field| {
-                    if (try field.anyUndef(mod)) break true;
-                } else false,
-                else => false,
-            },
-            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .undef => true,
                 .simple_value => |v| v == .undefined,
                 .ptr => |ptr| switch (ptr.len) {
@@ -2098,13 +2031,13 @@ pub const Value = struct {
     /// Asserts the value is not undefined and not unreachable.
     /// Integer value 0 is considered null because of C pointers.
     pub fn isNull(val: Value, mod: *Module) bool {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .undef => unreachable,
             .unreachable_value => unreachable,
 
             .null_value => true,
 
-            else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => return switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .int => {
                     var buf: BigIntSpace = undefined;
                     return val.toBigInt(&buf, mod).eqZero();
@@ -2120,7 +2053,7 @@ pub const Value = struct {
     /// something is an error or not because it works without having to figure out the
     /// string.
     pub fn getError(self: Value, mod: *const Module) ?[]const u8 {
-        return mod.intern_pool.stringToSliceUnwrap(switch (mod.intern_pool.indexToKey(self.ip_index)) {
+        return mod.intern_pool.stringToSliceUnwrap(switch (mod.intern_pool.indexToKey(self.toIntern())) {
             .err => |err| err.name.toOptional(),
             .error_union => |error_union| switch (error_union.val) {
                 .err_name => |err_name| err_name.toOptional(),
@@ -2133,12 +2066,12 @@ pub const Value = struct {
     /// Assumes the type is an error union. Returns true if and only if the value is
     /// the error union payload, not an error.
     pub fn errorUnionIsPayload(val: Value, mod: *const Module) bool {
-        return mod.intern_pool.indexToKey(val.ip_index).error_union.val == .payload;
+        return mod.intern_pool.indexToKey(val.toIntern()).error_union.val == .payload;
     }
 
     /// Value of the optional, null if optional has no payload.
     pub fn optionalValue(val: Value, mod: *const Module) ?Value {
-        return switch (mod.intern_pool.indexToKey(val.ip_index).opt.val) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern()).opt.val) {
             .none => null,
             else => |index| index.toValue(),
         };
@@ -2146,14 +2079,9 @@ pub const Value = struct {
 
     /// Valid for all types. Asserts the value is not undefined.
     pub fn isFloat(self: Value, mod: *const Module) bool {
-        return switch (self.ip_index) {
+        return switch (self.toIntern()) {
             .undef => unreachable,
-            .none => switch (self.tag()) {
-                .inferred_alloc => unreachable,
-                .inferred_alloc_comptime => unreachable,
-                else => false,
-            },
-            else => switch (mod.intern_pool.indexToKey(self.ip_index)) {
+            else => switch (mod.intern_pool.indexToKey(self.toIntern())) {
                 .float => true,
                 else => false,
             },
@@ -2169,21 +2097,24 @@ pub const Value = struct {
 
     pub fn intToFloatAdvanced(val: Value, arena: Allocator, int_ty: Type, float_ty: Type, mod: *Module, opt_sema: ?*Sema) !Value {
         if (int_ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, int_ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, int_ty.vectorLen(mod));
             const scalar_ty = float_ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try intToFloatScalar(elem_val, scalar_ty, mod, opt_sema);
+                scalar.* = try (try intToFloatScalar(elem_val, scalar_ty, mod, opt_sema)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intToFloatScalar(val, float_ty, mod, opt_sema);
     }
 
     pub fn intToFloatScalar(val: Value, float_ty: Type, mod: *Module, opt_sema: ?*Sema) !Value {
-        return switch (val.ip_index) {
+        return switch (val.toIntern()) {
             .undef => val,
-            else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+            else => return switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .int => |int| switch (int.storage) {
                     .big_int => |big_int| {
                         const float = bigIntToFloat(big_int.limbs, big_int.positive);
@@ -2217,7 +2148,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = dest_ty.ip_index,
+            .ty = dest_ty.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -2245,14 +2176,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intAddSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod);
+                scalar.* = try (try intAddSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intAddSatScalar(lhs, rhs, ty, arena, mod);
     }
@@ -2292,14 +2226,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intSubSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod);
+                scalar.* = try (try intSubSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intSubSatScalar(lhs, rhs, ty, arena, mod);
     }
@@ -2338,19 +2275,26 @@ pub const Value = struct {
         mod: *Module,
     ) !OverflowArithmeticResult {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const overflowed_data = try arena.alloc(Value, ty.vectorLen(mod));
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const vec_len = ty.vectorLen(mod);
+            const overflowed_data = try arena.alloc(InternPool.Index, vec_len);
+            const result_data = try arena.alloc(InternPool.Index, vec_len);
             const scalar_ty = ty.scalarType(mod);
-            for (result_data, 0..) |*scalar, i| {
+            for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
                 const of_math_result = try intMulWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod);
-                overflowed_data[i] = of_math_result.overflow_bit;
-                scalar.* = of_math_result.wrapped_result;
+                of.* = try of_math_result.overflow_bit.intern(Type.bool, mod);
+                scalar.* = try of_math_result.wrapped_result.intern(scalar_ty, mod);
             }
             return OverflowArithmeticResult{
-                .overflow_bit = try Value.Tag.aggregate.create(arena, overflowed_data),
-                .wrapped_result = try Value.Tag.aggregate.create(arena, result_data),
+                .overflow_bit = (try mod.intern(.{ .aggregate = .{
+                    .ty = ty.toIntern(),
+                    .storage = .{ .elems = overflowed_data },
+                } })).toValue(),
+                .wrapped_result = (try mod.intern(.{ .aggregate = .{
+                    .ty = (try mod.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
+                    .storage = .{ .elems = result_data },
+                } })).toValue(),
             };
         }
         return intMulWithOverflowScalar(lhs, rhs, ty, arena, mod);
@@ -2400,13 +2344,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try numberMulWrapScalar(lhs_elem, rhs_elem, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try numberMulWrapScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return numberMulWrapScalar(lhs, rhs, ty, arena, mod);
     }
@@ -2442,13 +2390,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intMulSatScalar(lhs_elem, rhs_elem, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try intMulSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intMulSatScalar(lhs, rhs, ty, arena, mod);
     }
@@ -2515,12 +2467,16 @@ pub const Value = struct {
     /// operands must be (vectors of) integers; handles undefined scalars.
     pub fn bitwiseNot(val: Value, ty: Type, arena: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try bitwiseNotScalar(elem_val, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try bitwiseNotScalar(elem_val, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return bitwiseNotScalar(val, ty, arena, mod);
     }
@@ -2552,13 +2508,17 @@ pub const Value = struct {
     /// operands must be (vectors of) integers; handles undefined scalars.
     pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try bitwiseAndScalar(lhs_elem, rhs_elem, ty.scalarType(mod), allocator, mod);
+                scalar.* = try (try bitwiseAndScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return bitwiseAndScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2586,13 +2546,17 @@ pub const Value = struct {
     /// operands must be (vectors of) integers; handles undefined scalars.
     pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try bitwiseNandScalar(lhs_elem, rhs_elem, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try bitwiseNandScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return bitwiseNandScalar(lhs, rhs, ty, arena, mod);
     }
@@ -2609,13 +2573,17 @@ pub const Value = struct {
     /// operands must be (vectors of) integers; handles undefined scalars.
     pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try bitwiseOrScalar(lhs_elem, rhs_elem, ty.scalarType(mod), allocator, mod);
+                scalar.* = try (try bitwiseOrScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return bitwiseOrScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2642,14 +2610,17 @@ pub const Value = struct {
     /// operands must be (vectors of) integers; handles undefined scalars.
     pub fn bitwiseXor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try bitwiseXorScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try bitwiseXorScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return bitwiseXorScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2676,14 +2647,17 @@ pub const Value = struct {
 
     pub fn intDiv(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intDivScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try intDivScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intDivScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2715,14 +2689,17 @@ pub const Value = struct {
 
     pub fn intDivFloor(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intDivFloorScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try intDivFloorScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intDivFloorScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2754,14 +2731,17 @@ pub const Value = struct {
 
     pub fn intMod(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intModScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try intModScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intModScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2794,7 +2774,7 @@ pub const Value = struct {
     /// Returns true if the value is a floating point type and is NaN. Returns false otherwise.
     pub fn isNan(val: Value, mod: *const Module) bool {
         if (val.ip_index == .none) return false;
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .float => |float| switch (float.storage) {
                 inline else => |x| std.math.isNan(x),
             },
@@ -2805,7 +2785,7 @@ pub const Value = struct {
     /// Returns true if the value is a floating point type and is infinite. Returns false otherwise.
     pub fn isInf(val: Value, mod: *const Module) bool {
         if (val.ip_index == .none) return false;
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .float => |float| switch (float.storage) {
                 inline else => |x| std.math.isInf(x),
             },
@@ -2815,7 +2795,7 @@ pub const Value = struct {
 
     pub fn isNegativeInf(val: Value, mod: *const Module) bool {
         if (val.ip_index == .none) return false;
-        return switch (mod.intern_pool.indexToKey(val.ip_index)) {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .float => |float| switch (float.storage) {
                 inline else => |x| std.math.isNegativeInf(x),
             },
@@ -2825,13 +2805,17 @@ pub const Value = struct {
 
     pub fn floatRem(lhs: Value, rhs: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatRemScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatRemScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatRemScalar(lhs, rhs, float_type, mod);
     }
@@ -2847,20 +2831,24 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn floatMod(lhs: Value, rhs: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatModScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatModScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatModScalar(lhs, rhs, float_type, mod);
     }
@@ -2876,21 +2864,24 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn intMul(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try intMulScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try intMulScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intMulScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -2918,13 +2909,16 @@ pub const Value = struct {
 
     pub fn intTrunc(val: Value, ty: Type, allocator: Allocator, signedness: std.builtin.Signedness, bits: u16, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try intTruncScalar(elem_val, scalar_ty, allocator, signedness, bits, mod);
+                scalar.* = try (try intTruncScalar(elem_val, scalar_ty, allocator, signedness, bits, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intTruncScalar(val, ty, allocator, signedness, bits, mod);
     }
@@ -2939,14 +2933,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
                 const bits_elem = try bits.elemValue(mod, i);
-                scalar.* = try intTruncScalar(elem_val, scalar_ty, allocator, signedness, @intCast(u16, bits_elem.toUnsignedInt(mod)), mod);
+                scalar.* = try (try intTruncScalar(elem_val, scalar_ty, allocator, signedness, @intCast(u16, bits_elem.toUnsignedInt(mod)), mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return intTruncScalar(val, ty, allocator, signedness, @intCast(u16, bits.toUnsignedInt(mod)), mod);
     }
@@ -2976,14 +2973,17 @@ pub const Value = struct {
 
     pub fn shl(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try shlScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try shlScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return shlScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -3015,18 +3015,26 @@ pub const Value = struct {
         mod: *Module,
     ) !OverflowArithmeticResult {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const overflowed_data = try allocator.alloc(Value, ty.vectorLen(mod));
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
-            for (result_data, 0..) |*scalar, i| {
+            const vec_len = ty.vectorLen(mod);
+            const overflowed_data = try allocator.alloc(InternPool.Index, vec_len);
+            const result_data = try allocator.alloc(InternPool.Index, vec_len);
+            const scalar_ty = ty.scalarType(mod);
+            for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                const of_math_result = try shlWithOverflowScalar(lhs_elem, rhs_elem, ty.scalarType(mod), allocator, mod);
-                overflowed_data[i] = of_math_result.overflow_bit;
-                scalar.* = of_math_result.wrapped_result;
+                const of_math_result = try shlWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                of.* = try of_math_result.overflow_bit.intern(Type.bool, mod);
+                scalar.* = try of_math_result.wrapped_result.intern(scalar_ty, mod);
             }
             return OverflowArithmeticResult{
-                .overflow_bit = try Value.Tag.aggregate.create(allocator, overflowed_data),
-                .wrapped_result = try Value.Tag.aggregate.create(allocator, result_data),
+                .overflow_bit = (try mod.intern(.{ .aggregate = .{
+                    .ty = ty.toIntern(),
+                    .storage = .{ .elems = overflowed_data },
+                } })).toValue(),
+                .wrapped_result = (try mod.intern(.{ .aggregate = .{
+                    .ty = (try mod.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
+                    .storage = .{ .elems = result_data },
+                } })).toValue(),
             };
         }
         return shlWithOverflowScalar(lhs, rhs, ty, allocator, mod);
@@ -3071,13 +3079,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try shlSatScalar(lhs_elem, rhs_elem, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try shlSatScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return shlSatScalar(lhs, rhs, ty, arena, mod);
     }
@@ -3117,13 +3129,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, ty.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, ty.vectorLen(mod));
+            const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try shlTruncScalar(lhs_elem, rhs_elem, ty.scalarType(mod), arena, mod);
+                scalar.* = try (try shlTruncScalar(lhs_elem, rhs_elem, scalar_ty, arena, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return shlTruncScalar(lhs, rhs, ty, arena, mod);
     }
@@ -3143,14 +3159,17 @@ pub const Value = struct {
 
     pub fn shr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
         if (ty.zigTypeTag(mod) == .Vector) {
-            const result_data = try allocator.alloc(Value, ty.vectorLen(mod));
+            const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
             const scalar_ty = ty.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try shrScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod);
+                scalar.* = try (try shrScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(allocator, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = ty.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return shrScalar(lhs, rhs, ty, allocator, mod);
     }
@@ -3193,12 +3212,16 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try floatNegScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatNegScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatNegScalar(val, float_type, mod);
     }
@@ -3218,7 +3241,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3231,13 +3254,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatAddScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatAddScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatAddScalar(lhs, rhs, float_type, mod);
     }
@@ -3258,7 +3285,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3271,13 +3298,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatSubScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatSubScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatSubScalar(lhs, rhs, float_type, mod);
     }
@@ -3298,7 +3329,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3311,13 +3342,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatDivScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatDivScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatDivScalar(lhs, rhs, float_type, mod);
     }
@@ -3338,7 +3373,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3351,13 +3386,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatDivFloorScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatDivFloorScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatDivFloorScalar(lhs, rhs, float_type, mod);
     }
@@ -3378,7 +3417,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3391,13 +3430,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatDivTruncScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatDivTruncScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatDivTruncScalar(lhs, rhs, float_type, mod);
     }
@@ -3418,7 +3461,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3431,13 +3474,17 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const lhs_elem = try lhs.elemValue(mod, i);
                 const rhs_elem = try rhs.elemValue(mod, i);
-                scalar.* = try floatMulScalar(lhs_elem, rhs_elem, float_type.scalarType(mod), mod);
+                scalar.* = try (try floatMulScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floatMulScalar(lhs, rhs, float_type, mod);
     }
@@ -3458,19 +3505,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn sqrt(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try sqrtScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try sqrtScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return sqrtScalar(val, float_type, mod);
     }
@@ -3486,19 +3537,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn sin(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try sinScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try sinScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return sinScalar(val, float_type, mod);
     }
@@ -3514,19 +3569,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn cos(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try cosScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try cosScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return cosScalar(val, float_type, mod);
     }
@@ -3542,19 +3601,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn tan(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try tanScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try tanScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return tanScalar(val, float_type, mod);
     }
@@ -3570,19 +3633,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn exp(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try expScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try expScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return expScalar(val, float_type, mod);
     }
@@ -3598,19 +3665,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn exp2(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try exp2Scalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try exp2Scalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return exp2Scalar(val, float_type, mod);
     }
@@ -3626,19 +3697,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn log(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try logScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try logScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return logScalar(val, float_type, mod);
     }
@@ -3654,19 +3729,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn log2(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try log2Scalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try log2Scalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return log2Scalar(val, float_type, mod);
     }
@@ -3682,19 +3761,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn log10(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try log10Scalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try log10Scalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return log10Scalar(val, float_type, mod);
     }
@@ -3710,19 +3793,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn fabs(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try fabsScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try fabsScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return fabsScalar(val, float_type, mod);
     }
@@ -3738,19 +3825,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn floor(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try floorScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try floorScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return floorScalar(val, float_type, mod);
     }
@@ -3766,19 +3857,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn ceil(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try ceilScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try ceilScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return ceilScalar(val, float_type, mod);
     }
@@ -3794,19 +3889,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn round(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try roundScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try roundScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return roundScalar(val, float_type, mod);
     }
@@ -3822,19 +3921,23 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
 
     pub fn trunc(val: Value, float_type: Type, arena: Allocator, mod: *Module) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const elem_val = try val.elemValue(mod, i);
-                scalar.* = try truncScalar(elem_val, float_type.scalarType(mod), mod);
+                scalar.* = try (try truncScalar(elem_val, scalar_ty, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return truncScalar(val, float_type, mod);
     }
@@ -3850,7 +3953,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3864,20 +3967,18 @@ pub const Value = struct {
         mod: *Module,
     ) !Value {
         if (float_type.zigTypeTag(mod) == .Vector) {
-            const result_data = try arena.alloc(Value, float_type.vectorLen(mod));
+            const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+            const scalar_ty = float_type.scalarType(mod);
             for (result_data, 0..) |*scalar, i| {
                 const mulend1_elem = try mulend1.elemValue(mod, i);
                 const mulend2_elem = try mulend2.elemValue(mod, i);
                 const addend_elem = try addend.elemValue(mod, i);
-                scalar.* = try mulAddScalar(
-                    float_type.scalarType(mod),
-                    mulend1_elem,
-                    mulend2_elem,
-                    addend_elem,
-                    mod,
-                );
+                scalar.* = try (try mulAddScalar(scalar_ty, mulend1_elem, mulend2_elem, addend_elem, mod)).intern(scalar_ty, mod);
             }
-            return Value.Tag.aggregate.create(arena, result_data);
+            return (try mod.intern(.{ .aggregate = .{
+                .ty = float_type.toIntern(),
+                .storage = .{ .elems = result_data },
+            } })).toValue();
         }
         return mulAddScalar(float_type, mulend1, mulend2, addend, mod);
     }
@@ -3899,7 +4000,7 @@ pub const Value = struct {
             else => unreachable,
         };
         return (try mod.intern(.{ .float = .{
-            .ty = float_type.ip_index,
+            .ty = float_type.toIntern(),
             .storage = storage,
         } })).toValue();
     }
@@ -3931,22 +4032,22 @@ pub const Value = struct {
     }
 
     pub fn isGenericPoison(val: Value) bool {
-        return val.ip_index == .generic_poison;
+        return val.toIntern() == .generic_poison;
     }
 
     /// This type is not copyable since it may contain pointers to its inner data.
     pub const Payload = struct {
         tag: Tag,
 
-        pub const Bytes = struct {
+        pub const SubValue = struct {
             base: Payload,
-            /// Includes the sentinel, if any.
-            data: []const u8,
+            data: Value,
         };
 
-        pub const StrLit = struct {
+        pub const Bytes = struct {
             base: Payload,
-            data: Module.StringLiteralContext.Key,
+            /// Includes the sentinel, if any.
+            data: []const u8,
         };
 
         pub const Aggregate = struct {