Commit 5ec6e3036b

mlugg <mlugg@mlugg.co.uk>
2024-03-26 00:08:59
Sema: introduce separate `MutableValue` representation for comptime-mutable memory
Perhaps someday, we will make Sema operate on mutable values more generally. For now, it makes sense to split out this representation, since it is only used in comptime pointer accesses. There are some currently unused methods on `MutableValue` which will be used once I rewrite the comptime pointer access logic to be less terrible. The commit following this one will - at long last - delete the legacy Value representation
1 parent c6f3e9d
src/Module.zig
@@ -3598,7 +3598,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
 
     const old_has_tv = decl.has_tv;
     // The following values are ignored if `!old_has_tv`
-    const old_ty = decl.typeOf(mod);
+    const old_ty = if (old_has_tv) decl.typeOf(mod) else undefined;
     const old_val = decl.val;
     const old_align = decl.alignment;
     const old_linksection = decl.@"linksection";
src/mutable_value.zig
@@ -0,0 +1,508 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const Zcu = @import("Module.zig");
+const InternPool = @import("InternPool.zig");
+const Type = @import("type.zig").Type;
+const Value = @import("Value.zig");
+
+/// We use a tagged union here because while it wastes a few bytes for some tags, having a fixed
+/// size for the type makes the common `aggregate` representation more efficient.
+/// For aggregates, the sentinel value, if any, *is* stored.
+pub const MutableValue = union(enum) {
+    /// An interned value.
+    interned: InternPool.Index,
+    /// An error union value which is a payload (not an error).
+    eu_payload: SubValue,
+    /// An optional value which is a payload (not `null`).
+    opt_payload: SubValue,
+    /// An aggregate consisting of a single repeated value.
+    repeated: SubValue,
+    /// An aggregate of `u8` consisting of "plain" bytes (no lazy or undefined elements).
+    bytes: Bytes,
+    /// An aggregate with arbitrary sub-values.
+    aggregate: Aggregate,
+    /// A slice, containing a pointer and length.
+    slice: Slice,
+    /// An instance of a union.
+    un: Union,
+
+    pub const SubValue = struct {
+        ty: InternPool.Index,
+        child: *MutableValue,
+    };
+    pub const Bytes = struct {
+        ty: InternPool.Index,
+        data: []u8,
+    };
+    pub const Aggregate = struct {
+        ty: InternPool.Index,
+        elems: []MutableValue,
+    };
+    pub const Slice = struct {
+        ty: InternPool.Index,
+        /// Must have the appropriate many-ptr type.
+        /// TODO: we want this to be an `InternPool.Index`, but `Sema.beginComptimePtrMutation` doesn't support it.
+        ptr: *MutableValue,
+        /// Must be of type `usize`.
+        /// TODO: we want this to be an `InternPool.Index`, but `Sema.beginComptimePtrMutation` doesn't support it.
+        len: *MutableValue,
+    };
+    pub const Union = struct {
+        ty: InternPool.Index,
+        tag: InternPool.Index,
+        payload: *MutableValue,
+    };
+
+    pub fn intern(mv: MutableValue, zcu: *Zcu, arena: Allocator) Allocator.Error!InternPool.Index {
+        const ip = &zcu.intern_pool;
+        const gpa = zcu.gpa;
+        return switch (mv) {
+            .interned => |ip_index| ip_index,
+            .eu_payload => |sv| try ip.get(gpa, .{ .error_union = .{
+                .ty = sv.ty,
+                .val = .{ .payload = try sv.child.intern(zcu, arena) },
+            } }),
+            .opt_payload => |sv| try ip.get(gpa, .{ .opt = .{
+                .ty = sv.ty,
+                .val = try sv.child.intern(zcu, arena),
+            } }),
+            .repeated => |sv| try ip.get(gpa, .{ .aggregate = .{
+                .ty = sv.ty,
+                .storage = .{ .repeated_elem = try sv.child.intern(zcu, arena) },
+            } }),
+            .bytes => |b| try ip.get(gpa, .{ .aggregate = .{
+                .ty = b.ty,
+                .storage = .{ .bytes = b.data },
+            } }),
+            .aggregate => |a| {
+                const elems = try arena.alloc(InternPool.Index, a.elems.len);
+                for (a.elems, elems) |mut_elem, *interned_elem| {
+                    interned_elem.* = try mut_elem.intern(zcu, arena);
+                }
+                return ip.get(gpa, .{ .aggregate = .{
+                    .ty = a.ty,
+                    .storage = .{ .elems = elems },
+                } });
+            },
+            .slice => |s| try ip.get(gpa, .{ .slice = .{
+                .ty = s.ty,
+                .ptr = try s.ptr.intern(zcu, arena),
+                .len = try s.len.intern(zcu, arena),
+            } }),
+            .un => |u| try ip.get(gpa, .{ .un = .{
+                .ty = u.ty,
+                .tag = u.tag,
+                .val = try u.payload.intern(zcu, arena),
+            } }),
+        };
+    }
+
+    /// Un-interns the top level of this `MutableValue`, if applicable.
+    /// * Non-error error unions use `eu_payload`
+    /// * Non-null optionals use `eu_payload
+    /// * Slices use `slice`
+    /// * Unions use `un`
+    /// * Aggregates use `repeated` or `bytes` or `aggregate`
+    /// If `!allow_bytes`, the `bytes` representation will not be used.
+    /// If `!allow_repeated`, the `repeated` representation will not be used.
+    pub fn unintern(
+        mv: *MutableValue,
+        zcu: *Zcu,
+        arena: Allocator,
+        allow_bytes: bool,
+        allow_repeated: bool,
+    ) Allocator.Error!void {
+        const ip = &zcu.intern_pool;
+        const gpa = zcu.gpa;
+        switch (mv.*) {
+            .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
+                .opt => |opt| if (opt.val != .none) {
+                    const mut_payload = try arena.create(MutableValue);
+                    mut_payload.* = .{ .interned = opt.val };
+                    mv.* = .{ .opt_payload = .{
+                        .ty = opt.ty,
+                        .child = mut_payload,
+                    } };
+                },
+                .error_union => |eu| switch (eu.val) {
+                    .err_name => {},
+                    .payload => |payload| {
+                        const mut_payload = try arena.create(MutableValue);
+                        mut_payload.* = .{ .interned = payload };
+                        mv.* = .{ .eu_payload = .{
+                            .ty = eu.ty,
+                            .child = mut_payload,
+                        } };
+                    },
+                },
+                .slice => |slice| {
+                    const ptr = try arena.create(MutableValue);
+                    const len = try arena.create(MutableValue);
+                    ptr.* = .{ .interned = slice.ptr };
+                    len.* = .{ .interned = slice.len };
+                    mv.* = .{ .slice = .{
+                        .ty = slice.ty,
+                        .ptr = ptr,
+                        .len = len,
+                    } };
+                },
+                .un => |un| {
+                    const payload = try arena.create(MutableValue);
+                    payload.* = .{ .interned = un.val };
+                    mv.* = .{ .un = .{
+                        .ty = un.ty,
+                        .tag = un.tag,
+                        .payload = payload,
+                    } };
+                },
+                .aggregate => |agg| switch (agg.storage) {
+                    .bytes => |bytes| {
+                        assert(bytes.len == ip.aggregateTypeLenIncludingSentinel(agg.ty));
+                        assert(ip.childType(agg.ty) == .u8_type);
+                        if (allow_bytes) {
+                            const arena_bytes = try arena.alloc(u8, bytes.len);
+                            @memcpy(arena_bytes, bytes);
+                            mv.* = .{ .bytes = .{
+                                .ty = agg.ty,
+                                .data = arena_bytes,
+                            } };
+                        } else {
+                            const mut_elems = try arena.alloc(MutableValue, bytes.len);
+                            for (bytes, mut_elems) |b, *mut_elem| {
+                                mut_elem.* = .{ .interned = try ip.get(gpa, .{ .int = .{
+                                    .ty = .u8_type,
+                                    .storage = .{ .u64 = b },
+                                } }) };
+                            }
+                            mv.* = .{ .aggregate = .{
+                                .ty = agg.ty,
+                                .elems = mut_elems,
+                            } };
+                        }
+                    },
+                    .elems => |elems| {
+                        assert(elems.len == ip.aggregateTypeLenIncludingSentinel(agg.ty));
+                        const mut_elems = try arena.alloc(MutableValue, elems.len);
+                        for (elems, mut_elems) |interned_elem, *mut_elem| {
+                            mut_elem.* = .{ .interned = interned_elem };
+                        }
+                        mv.* = .{ .aggregate = .{
+                            .ty = agg.ty,
+                            .elems = mut_elems,
+                        } };
+                    },
+                    .repeated_elem => |val| {
+                        if (allow_repeated) {
+                            const repeated_val = try arena.create(MutableValue);
+                            repeated_val.* = .{ .interned = val };
+                            mv.* = .{ .repeated = .{
+                                .ty = agg.ty,
+                                .child = repeated_val,
+                            } };
+                        } else {
+                            const len = ip.aggregateTypeLenIncludingSentinel(agg.ty);
+                            const mut_elems = try arena.alloc(MutableValue, @intCast(len));
+                            @memset(mut_elems, .{ .interned = val });
+                            mv.* = .{ .aggregate = .{
+                                .ty = agg.ty,
+                                .elems = mut_elems,
+                            } };
+                        }
+                    },
+                },
+                .undef => |ty_ip| switch (Type.fromInterned(ty_ip).zigTypeTag(zcu)) {
+                    .Struct, .Array, .Vector => |type_tag| {
+                        const ty = Type.fromInterned(ty_ip);
+                        const opt_sent = ty.sentinel(zcu);
+                        if (type_tag == .Struct or opt_sent != null or !allow_repeated) {
+                            const len_no_sent = ip.aggregateTypeLen(ty_ip);
+                            const elems = try arena.alloc(MutableValue, @intCast(len_no_sent + @intFromBool(opt_sent != null)));
+                            switch (type_tag) {
+                                .Array, .Vector => {
+                                    const elem_ty = ip.childType(ty_ip);
+                                    const undef_elem = try ip.get(gpa, .{ .undef = elem_ty });
+                                    @memset(elems[0..@intCast(len_no_sent)], .{ .interned = undef_elem });
+                                },
+                                .Struct => for (elems[0..@intCast(len_no_sent)], 0..) |*mut_elem, i| {
+                                    const field_ty = ty.structFieldType(i, zcu).toIntern();
+                                    mut_elem.* = .{ .interned = try ip.get(gpa, .{ .undef = field_ty }) };
+                                },
+                                else => unreachable,
+                            }
+                            if (opt_sent) |s| elems[@intCast(len_no_sent)] = .{ .interned = s.toIntern() };
+                            mv.* = .{ .aggregate = .{
+                                .ty = ty_ip,
+                                .elems = elems,
+                            } };
+                        } else {
+                            const repeated_val = try arena.create(MutableValue);
+                            repeated_val.* = .{
+                                .interned = try ip.get(gpa, .{ .undef = ip.childType(ty_ip) }),
+                            };
+                            mv.* = .{ .repeated = .{
+                                .ty = ty_ip,
+                                .child = repeated_val,
+                            } };
+                        }
+                    },
+                    .Union => {
+                        const payload = try arena.create(MutableValue);
+                        // HACKHACK: this logic is silly, but Sema detects it and reverts the change where needed.
+                        // See comment at the top of `Sema.beginComptimePtrMutationInner`.
+                        payload.* = .{ .interned = .undef };
+                        mv.* = .{ .un = .{
+                            .ty = ty_ip,
+                            .tag = .none,
+                            .payload = payload,
+                        } };
+                    },
+                    .Pointer => {
+                        const ptr_ty = ip.indexToKey(ty_ip).ptr_type;
+                        if (ptr_ty.flags.size != .Slice) return;
+                        const ptr = try arena.create(MutableValue);
+                        const len = try arena.create(MutableValue);
+                        ptr.* = .{ .interned = try ip.get(gpa, .{ .undef = ip.slicePtrType(ty_ip) }) };
+                        len.* = .{ .interned = try ip.get(gpa, .{ .undef = .usize_type }) };
+                        mv.* = .{ .slice = .{
+                            .ty = ty_ip,
+                            .ptr = ptr,
+                            .len = len,
+                        } };
+                    },
+                    else => {},
+                },
+                else => {},
+            },
+            .bytes => |bytes| if (!allow_bytes) {
+                const elems = try arena.alloc(MutableValue, bytes.data.len);
+                for (bytes.data, elems) |byte, *interned_byte| {
+                    interned_byte.* = .{ .interned = try ip.get(gpa, .{ .int = .{
+                        .ty = .u8_type,
+                        .storage = .{ .u64 = byte },
+                    } }) };
+                }
+                mv.* = .{ .aggregate = .{
+                    .ty = bytes.ty,
+                    .elems = elems,
+                } };
+            },
+            else => {},
+        }
+    }
+
+    /// Get a pointer to the `MutableValue` associated with a field/element.
+    /// The returned pointer can be safety mutated through to modify the field value.
+    /// The returned pointer is valid until the representation of `mv` changes.
+    /// This function does *not* support accessing the ptr/len field of slices.
+    pub fn elem(
+        mv: *MutableValue,
+        zcu: *Zcu,
+        arena: Allocator,
+        field_idx: usize,
+    ) Allocator.Error!*MutableValue {
+        const ip = &zcu.intern_pool;
+        const gpa = zcu.gpa;
+        // Convert to the `aggregate` representation.
+        switch (mv) {
+            .eu_payload, .opt_payload, .slice, .un => unreachable,
+            .interned => {
+                try mv.unintern(zcu, arena, false, false);
+            },
+            .bytes => |bytes| {
+                const elems = try arena.alloc(MutableValue, bytes.data.len);
+                for (bytes.data, elems) |byte, interned_byte| {
+                    interned_byte.* = try ip.get(gpa, .{ .int = .{
+                        .ty = .u8_type,
+                        .storage = .{ .u64 = byte },
+                    } });
+                }
+                mv.* = .{ .aggregate = .{
+                    .ty = bytes.ty,
+                    .elems = elems,
+                } };
+            },
+            .repeated => |repeated| {
+                const len = ip.aggregateTypeLenIncludingSentinel(repeated.ty);
+                const elems = try arena.alloc(MutableValue, @intCast(len));
+                @memset(elems, repeated.child.*);
+                mv.* = .{ .aggregate = .{
+                    .ty = repeated.ty,
+                    .elems = elems,
+                } };
+            },
+            .aggregate => {},
+        }
+        return &mv.aggregate.elems[field_idx];
+    }
+
+    /// Modify a single field of a `MutableValue` which represents an aggregate or slice, leaving others
+    /// untouched. When an entire field must be modified, this should be used in preference to `elemPtr`
+    /// to allow for an optimal representation.
+    /// For slices, uses `Value.slice_ptr_index` and `Value.slice_len_index`.
+    pub fn setElem(
+        mv: *MutableValue,
+        zcu: *Zcu,
+        arena: Allocator,
+        field_idx: usize,
+        field_val: MutableValue,
+    ) Allocator.Error!void {
+        const ip = &zcu.intern_pool;
+        const is_trivial_int = field_val.isTrivialInt(zcu);
+        try mv.unintern(arena, is_trivial_int, true);
+        switch (mv) {
+            .interned,
+            .eu_payload,
+            .opt_payload,
+            .un,
+            => unreachable,
+            .slice => |*s| switch (field_idx) {
+                Value.slice_ptr_index => s.ptr = field_val,
+                Value.slice_len_index => s.len = field_val,
+            },
+            .bytes => |b| {
+                assert(is_trivial_int);
+                assert(field_val.typeOf() == Type.u8);
+                b.data[field_idx] = Value.fromInterned(field_val.interned).toUnsignedInt(zcu);
+            },
+            .repeated => |r| {
+                if (field_val.eqlTrivial(r.child.*)) return;
+                // We must switch to either the `aggregate` or the `bytes` representation.
+                const len_inc_sent = ip.aggregateTypeLenIncludingSentinel(r.ty);
+                if (ip.zigTypeTag(r.ty) != .Struct and
+                    is_trivial_int and
+                    Type.fromInterned(r.ty).childType(zcu) == .u8_type and
+                    r.child.isTrivialInt(zcu))
+                {
+                    // We can use the `bytes` representation.
+                    const bytes = try arena.alloc(u8, @intCast(len_inc_sent));
+                    const repeated_byte = Value.fromInterned(r.child.interned).getUnsignedInt(zcu);
+                    @memset(bytes, repeated_byte);
+                    bytes[field_idx] = Value.fromInterned(field_val.interned).getUnsignedInt(zcu);
+                    mv.* = .{ .bytes = .{
+                        .ty = r.ty,
+                        .data = bytes,
+                    } };
+                } else {
+                    // We must use the `aggregate` representation.
+                    const mut_elems = try arena.alloc(u8, @intCast(len_inc_sent));
+                    @memset(mut_elems, r.child.*);
+                    mut_elems[field_idx] = field_val;
+                    mv.* = .{ .aggregate = .{
+                        .ty = r.ty,
+                        .elems = mut_elems,
+                    } };
+                }
+            },
+            .aggregate => |a| {
+                a.elems[field_idx] = field_val;
+                const is_struct = ip.zigTypeTag(a.ty) == .Struct;
+                // Attempt to switch to a more efficient representation.
+                const is_repeated = for (a.elems) |e| {
+                    if (!e.eqlTrivial(field_val)) break false;
+                } else true;
+                if (is_repeated) {
+                    // Switch to `repeated` repr
+                    const mut_repeated = try arena.create(MutableValue);
+                    mut_repeated.* = field_val;
+                    mv.* = .{ .repeated = .{
+                        .ty = a.ty,
+                        .child = mut_repeated,
+                    } };
+                } else if (!is_struct and is_trivial_int and Type.fromInterned(a.ty).childType(zcu).toIntern() == .u8_type) {
+                    // See if we can switch to `bytes` repr
+                    for (a.elems) |e| {
+                        switch (e) {
+                            else => break,
+                            .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
+                                else => break,
+                                .int => |int| switch (int.storage) {
+                                    .u64, .i64, .big_int => {},
+                                    .lazy_align, .lazy_size => break,
+                                },
+                            },
+                        }
+                    } else {
+                        const bytes = try arena.alloc(u8, a.elems.len);
+                        for (a.elems, bytes) |elem_val, *b| {
+                            b.* = Value.fromInterned(elem_val.interned).toUnsignedInt(zcu);
+                        }
+                        mv.* = .{ .bytes = .{
+                            .ty = a.ty,
+                            .data = bytes,
+                        } };
+                    }
+                }
+            },
+        }
+    }
+
+    /// Get the value of a single field of a `MutableValue` which represents an aggregate or slice.
+    /// For slices, uses `Value.slice_ptr_index` and `Value.slice_len_index`.
+    pub fn getElem(
+        mv: MutableValue,
+        zcu: *Zcu,
+        field_idx: usize,
+    ) Allocator.Error!MutableValue {
+        return switch (mv) {
+            .eu_payload,
+            .opt_payload,
+            => unreachable,
+            .interned => |ip_index| {
+                const ty = Type.fromInterned(zcu.intern_pool.typeOf(ip_index));
+                switch (ty.zigTypeTag(zcu)) {
+                    .Array, .Vector => return .{ .interned = (try Value.fromInterned(ip_index).elemValue(zcu, field_idx)).toIntern() },
+                    .Struct, .Union => return .{ .interned = (try Value.fromInterned(ip_index).fieldValue(zcu, field_idx)).toIntern() },
+                    .Pointer => {
+                        assert(ty.isSlice(zcu));
+                        return switch (field_idx) {
+                            Value.slice_ptr_index => .{ .interned = Value.fromInterned(ip_index).slicePtr(zcu).toIntern() },
+                            Value.slice_len_index => .{ .interned = switch (zcu.intern_pool.indexToKey(ip_index)) {
+                                .undef => try zcu.intern(.{ .undef = .usize_type }),
+                                .slice => |s| s.len,
+                                else => unreachable,
+                            } },
+                            else => unreachable,
+                        };
+                    },
+                    else => unreachable,
+                }
+            },
+            .un => |un| {
+                // TODO assert the tag is correct
+                return un.payload.*;
+            },
+            .slice => |s| switch (field_idx) {
+                Value.slice_ptr_index => s.ptr.*,
+                Value.slice_len_index => s.len.*,
+                else => unreachable,
+            },
+            .bytes => |b| .{ .interned = try zcu.intern(.{ .int = .{
+                .ty = .u8_type,
+                .storage = .{ .u64 = b.data[field_idx] },
+            } }) },
+            .repeated => |r| r.child.*,
+            .aggregate => |a| a.elems[field_idx],
+        };
+    }
+
+    fn isTrivialInt(mv: MutableValue, zcu: *Zcu) bool {
+        return switch (mv) {
+            else => false,
+            .interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) {
+                else => false,
+                .int => |int| switch (int.storage) {
+                    .u64, .i64, .big_int => true,
+                    .lazy_align, .lazy_size => false,
+                },
+            },
+        };
+    }
+
+    pub fn typeOf(mv: MutableValue, zcu: *Zcu) Type {
+        return switch (mv) {
+            .interned => |ip_index| Type.fromInterned(zcu.intern_pool.typeOf(ip_index)),
+            inline else => |x| Type.fromInterned(x.ty),
+        };
+    }
+};
src/Sema.zig
@@ -139,8 +139,7 @@ const MaybeComptimeAlloc = struct {
 };
 
 const ComptimeAlloc = struct {
-    ty: Type,
-    val: Value,
+    val: MutableValue,
     is_const: bool,
     /// `.none` indicates that the alignment is the natural alignment of `val`.
     alignment: Alignment,
@@ -153,8 +152,7 @@ const ComptimeAlloc = struct {
 fn newComptimeAlloc(sema: *Sema, block: *Block, ty: Type, alignment: Alignment) !ComptimeAllocIndex {
     const idx = sema.comptime_allocs.items.len;
     try sema.comptime_allocs.append(sema.gpa, .{
-        .ty = ty,
-        .val = Value.fromInterned(try sema.mod.intern(.{ .undef = ty.toIntern() })),
+        .val = .{ .interned = try sema.mod.intern(.{ .undef = ty.toIntern() }) },
         .is_const = false,
         .alignment = alignment,
         .runtime_index = block.runtime_index,
@@ -175,6 +173,7 @@ const log = std.log.scoped(.sema);
 
 const Sema = @This();
 const Value = @import("Value.zig");
+const MutableValue = @import("mutable_value.zig").MutableValue;
 const Type = @import("type.zig").Type;
 const TypedValue = @import("TypedValue.zig");
 const Air = @import("Air.zig");
@@ -3762,10 +3761,10 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
         if (!sema.isComptimeMutablePtr(ptr_val)) break :already_ct;
         const alloc_index = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr.comptime_alloc;
         const ct_alloc = sema.getComptimeAlloc(alloc_index);
-        const interned = try ct_alloc.val.intern(ct_alloc.ty, mod);
+        const interned = try ct_alloc.val.intern(mod, sema.arena);
         if (Value.fromInterned(interned).canMutateComptimeVarState(mod)) {
             // Preserve the comptime alloc, just make the pointer const.
-            ct_alloc.val = Value.fromInterned(interned);
+            ct_alloc.val = .{ .interned = interned };
             ct_alloc.is_const = true;
             return sema.makePtrConst(block, alloc);
         } else {
@@ -4030,7 +4029,7 @@ fn finishResolveComptimeKnownAllocPtr(
         const alloc_index = existing_comptime_alloc orelse a: {
             const idx = try sema.newComptimeAlloc(block, alloc_ty.childType(zcu), alloc_ty.ptrAlignment(zcu));
             const alloc = sema.getComptimeAlloc(idx);
-            alloc.val = Value.fromInterned(result_val);
+            alloc.val = .{ .interned = result_val };
             break :a idx;
         };
         sema.getComptimeAlloc(alloc_index).is_const = true;
@@ -4193,7 +4192,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
                 .anon_decl => |a| a.val,
                 .comptime_alloc => |i| val: {
                     const alloc = sema.getComptimeAlloc(i);
-                    break :val try alloc.val.intern(alloc.ty, mod);
+                    break :val try alloc.val.intern(mod, sema.arena);
                 },
                 else => unreachable,
             };
@@ -5597,7 +5596,7 @@ fn storeToInferredAllocComptime(
         } });
     } else {
         const alloc_index = try sema.newComptimeAlloc(block, operand_ty, iac.alignment);
-        sema.getComptimeAlloc(alloc_index).val = operand_val;
+        sema.getComptimeAlloc(alloc_index).val = .{ .interned = operand_val.toIntern() };
         iac.ptr = try zcu.intern(.{ .ptr = .{
             .ty = alloc_ty.toIntern(),
             .addr = .{ .comptime_alloc = alloc_index },
@@ -30655,21 +30654,22 @@ fn storePtrVal(
         .opv => {},
         .direct => |val_ptr| {
             if (mut_kit.root == .comptime_field) {
-                val_ptr.* = Value.fromInterned((try val_ptr.intern(operand_ty, mod)));
-                if (!operand_val.eql(val_ptr.*, operand_ty, mod)) {
+                val_ptr.* = .{ .interned = try val_ptr.intern(mod, sema.arena) };
+                if (operand_val.toIntern() != val_ptr.interned) {
                     // TODO use failWithInvalidComptimeFieldStore
                     return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{});
                 }
                 return;
             }
-            val_ptr.* = Value.fromInterned((try operand_val.intern(operand_ty, mod)));
+            val_ptr.* = .{ .interned = operand_val.toIntern() };
         },
         .reinterpret => |reinterpret| {
             try sema.resolveTypeLayout(mut_kit.ty);
             const abi_size = try sema.usizeCast(block, src, mut_kit.ty.abiSize(mod));
             const buffer = try sema.gpa.alloc(u8, abi_size);
             defer sema.gpa.free(buffer);
-            reinterpret.val_ptr.*.writeToMemory(mut_kit.ty, mod, buffer) catch |err| switch (err) {
+            const interned_old = Value.fromInterned(try reinterpret.val_ptr.intern(mod, sema.arena));
+            interned_old.writeToMemory(mut_kit.ty, mod, buffer) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
                 error.ReinterpretDeclRef => unreachable,
                 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
@@ -30693,7 +30693,7 @@ fn storePtrVal(
                 error.IllDefinedMemoryLayout => unreachable,
                 error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
             };
-            reinterpret.val_ptr.* = Value.fromInterned((try val.intern(mut_kit.ty, mod)));
+            reinterpret.val_ptr.* = .{ .interned = val.toIntern() };
         },
         .bad_decl_ty, .bad_ptr_ty => {
             // TODO show the decl declaration site in a note and explain whether the decl
@@ -30718,11 +30718,11 @@ const ComptimePtrMutationKit = struct {
         opv,
         /// The pointer type matches the actual comptime Value so a direct
         /// modification is possible.
-        direct: *Value,
+        direct: *MutableValue,
         /// The largest parent Value containing pointee and having a well-defined memory layout.
         /// This is used for bitcasting, if direct dereferencing failed.
         reinterpret: struct {
-            val_ptr: *Value,
+            val_ptr: *MutableValue,
             byte_offset: usize,
             /// If set, write the operand to packed memory
             write_packed: bool = false,
@@ -30754,15 +30754,15 @@ fn beginComptimePtrMutation(
         .decl, .anon_decl, .int => unreachable, // isComptimeMutablePtr has been checked already
         .comptime_alloc => |alloc_index| {
             const alloc = sema.getComptimeAlloc(alloc_index);
-            return sema.beginComptimePtrMutationInner(block, src, alloc.ty, &alloc.val, ptr_elem_ty, .{ .alloc = alloc_index });
+            return sema.beginComptimePtrMutationInner(block, src, alloc.val.typeOf(mod), &alloc.val, ptr_elem_ty, .{ .alloc = alloc_index });
         },
         .comptime_field => |comptime_field| {
-            const duped = try sema.arena.create(Value);
-            duped.* = Value.fromInterned(comptime_field);
+            const duped = try sema.arena.create(MutableValue);
+            duped.* = .{ .interned = comptime_field };
             return sema.beginComptimePtrMutationInner(
                 block,
                 src,
-                Type.fromInterned(mod.intern_pool.typeOf(comptime_field)),
+                duped.typeOf(mod),
                 duped,
                 ptr_elem_ty,
                 .comptime_field,
@@ -30775,36 +30775,28 @@ fn beginComptimePtrMutation(
                 .opv => unreachable,
                 .direct => |val_ptr| {
                     const payload_ty = parent.ty.errorUnionPayload(mod);
-                    if (val_ptr.ip_index == .none and val_ptr.tag() == .eu_payload) {
-                        return ComptimePtrMutationKit{
-                            .root = parent.root,
-                            .pointee = .{ .direct = &val_ptr.castTag(.eu_payload).?.data },
-                            .ty = payload_ty,
-                        };
-                    } else {
+                    try val_ptr.unintern(mod, sema.arena, false, false);
+                    if (val_ptr.* == .interned) {
                         // 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 payload = try sema.arena.create(Value.Payload.SubValue);
-                        payload.* = .{
-                            .base = .{ .tag = .eu_payload },
-                            .data = Value.fromInterned((try mod.intern(.{ .undef = payload_ty.toIntern() }))),
-                        };
-
-                        val_ptr.* = Value.initPayload(&payload.base);
-
-                        return ComptimePtrMutationKit{
-                            .root = parent.root,
-                            .pointee = .{ .direct = &payload.data },
-                            .ty = payload_ty,
-                        };
+                        // representation of the error union to `eu_payload`.
+                        const child = try sema.arena.create(MutableValue);
+                        child.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) };
+                        val_ptr.* = .{ .eu_payload = .{
+                            .ty = parent.ty.toIntern(),
+                            .child = child,
+                        } };
                     }
+                    return .{
+                        .root = parent.root,
+                        .pointee = .{ .direct = val_ptr.eu_payload.child },
+                        .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{
+                .reinterpret => return .{
                     .root = parent.root,
                     .pointee = .bad_ptr_ty,
                     .ty = eu_ty,
@@ -30818,46 +30810,28 @@ fn beginComptimePtrMutation(
                 .opv => unreachable,
                 .direct => |val_ptr| {
                     const payload_ty = parent.ty.optionalChild(mod);
-                    switch (val_ptr.ip_index) {
-                        .none => return ComptimePtrMutationKit{
-                            .root = parent.root,
-                            .pointee = .{ .direct = &val_ptr.castTag(.opt_payload).?.data },
-                            .ty = payload_ty,
-                        },
-                        else => {
-                            const payload_val = switch (mod.intern_pool.indexToKey(val_ptr.ip_index)) {
-                                .undef => try mod.intern(.{ .undef = payload_ty.toIntern() }),
-                                .opt => |opt| switch (opt.val) {
-                                    .none => try mod.intern(.{ .undef = payload_ty.toIntern() }),
-                                    else => |payload| payload,
-                                },
-                                else => unreachable,
-                            };
-
-                            // 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 payload = try sema.arena.create(Value.Payload.SubValue);
-                            payload.* = .{
-                                .base = .{ .tag = .opt_payload },
-                                .data = Value.fromInterned(payload_val),
-                            };
-
-                            val_ptr.* = Value.initPayload(&payload.base);
-
-                            return ComptimePtrMutationKit{
-                                .root = parent.root,
-                                .pointee = .{ .direct = &payload.data },
-                                .ty = payload_ty,
-                            };
-                        },
+                    try val_ptr.unintern(mod, sema.arena, false, false);
+                    if (val_ptr.* == .interned) {
+                        // 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 to `opt_payload`.
+                        const child = try sema.arena.create(MutableValue);
+                        child.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) };
+                        val_ptr.* = .{ .opt_payload = .{
+                            .ty = parent.ty.toIntern(),
+                            .child = child,
+                        } };
                     }
+                    return .{
+                        .root = parent.root,
+                        .pointee = .{ .direct = val_ptr.opt_payload.child },
+                        .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{
+                .reinterpret => return .{
                     .root = parent.root,
                     .pointee = .bad_ptr_ty,
                     .ty = opt_ty,
@@ -30916,106 +30890,28 @@ fn beginComptimePtrMutation(
                             };
                         }
 
-                        switch (val_ptr.ip_index) {
-                            .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 = sema.arena;
-
-                                    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(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[@intCast(elem_ptr.index)],
-                                        ptr_elem_ty,
-                                        parent.root,
-                                    );
-                                },
-                                .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 = sema.arena;
-
-                                    const repeated_val = try val_ptr.castTag(.repeated).?.data.intern(parent.ty.childType(mod), 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.fromInterned(repeated_val));
-
-                                    val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
-
-                                    return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        elem_ty,
-                                        &elems[@intCast(elem_ptr.index)],
-                                        ptr_elem_ty,
-                                        parent.root,
-                                    );
-                                },
-
-                                .aggregate => return beginComptimePtrMutationInner(
-                                    sema,
-                                    block,
-                                    src,
-                                    elem_ty,
-                                    &val_ptr.castTag(.aggregate).?.data[@intCast(elem_ptr.index)],
-                                    ptr_elem_ty,
-                                    parent.root,
-                                ),
+                        try val_ptr.unintern(mod, sema.arena, false, false);
+
+                        const aggregate = switch (val_ptr.*) {
+                            .interned,
+                            .bytes,
+                            .repeated,
+                            .eu_payload,
+                            .opt_payload,
+                            .slice,
+                            .un,
+                            => unreachable,
+                            .aggregate => |*a| a,
+                        };
 
-                                else => unreachable,
-                            },
-                            else => switch (mod.intern_pool.indexToKey(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 = sema.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);
-                                    @memset(elems, Value.fromInterned((try mod.intern(.{ .undef = elem_ty.toIntern() }))));
-
-                                    val_ptr.* = try Value.Tag.aggregate.create(arena, elems);
-
-                                    return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        elem_ty,
-                                        &elems[@intCast(elem_ptr.index)],
-                                        ptr_elem_ty,
-                                        parent.root,
-                                    );
-                                },
-                                else => unreachable,
-                            },
-                        }
+                        return sema.beginComptimePtrMutationInner(
+                            block,
+                            src,
+                            elem_ty,
+                            &aggregate.elems[@intCast(elem_ptr.index)],
+                            ptr_elem_ty,
+                            parent.root,
+                        );
                     },
                     else => {
                         if (elem_ptr.index != 0) {
@@ -31039,7 +30935,7 @@ fn beginComptimePtrMutation(
                     if (!base_elem_ty.hasWellDefinedLayout(mod)) {
                         // Even though the parent value type has well-defined memory layout, our
                         // pointer type does not.
-                        return ComptimePtrMutationKit{
+                        return .{
                             .root = parent.root,
                             .pointee = .bad_ptr_ty,
                             .ty = base_elem_ty,
@@ -31049,7 +30945,7 @@ fn beginComptimePtrMutation(
                     const elem_abi_size_u64 = try sema.typeAbiSize(base_elem_ty);
                     const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
                     const elem_idx = try sema.usizeCast(block, src, elem_ptr.index);
-                    return ComptimePtrMutationKit{
+                    return .{
                         .root = parent.root,
                         .pointee = .{ .reinterpret = .{
                             .val_ptr = reinterpret.val_ptr,
@@ -31068,56 +30964,68 @@ fn beginComptimePtrMutation(
             var parent = try sema.beginComptimePtrMutation(block, src, Value.fromInterned(field_ptr.base), base_child_ty);
             switch (parent.pointee) {
                 .opv => unreachable,
-                .direct => |val_ptr| switch (val_ptr.ip_index) {
-                    .empty_struct => {
-                        const duped = try sema.arena.create(Value);
-                        duped.* = val_ptr.*;
-                        return beginComptimePtrMutationInner(
-                            sema,
-                            block,
-                            src,
-                            parent.ty.structFieldType(field_index, mod),
-                            duped,
-                            ptr_elem_ty,
-                            parent.root,
-                        );
-                    },
-                    .none => switch (val_ptr.tag()) {
-                        .aggregate => return beginComptimePtrMutationInner(
-                            sema,
+                .direct => |val_ptr| {
+                    try val_ptr.unintern(mod, sema.arena, false, false);
+                    switch (val_ptr.*) {
+                        .interned,
+                        .eu_payload,
+                        .opt_payload,
+                        .repeated,
+                        .bytes,
+                        => unreachable,
+                        .aggregate => |*a| return sema.beginComptimePtrMutationInner(
                             block,
                             src,
                             parent.ty.structFieldType(field_index, mod),
-                            &val_ptr.castTag(.aggregate).?.data[field_index],
+                            &a.elems[field_index],
                             ptr_elem_ty,
                             parent.root,
                         ),
-                        .repeated => {
-                            const arena = sema.arena;
-
-                            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,
+                        .slice => |*s| switch (field_index) {
+                            Value.slice_ptr_index => return sema.beginComptimePtrMutationInner(
                                 block,
                                 src,
-                                parent.ty.structFieldType(field_index, mod),
-                                &elems[field_index],
+                                parent.ty.slicePtrFieldType(mod),
+                                s.ptr,
                                 ptr_elem_ty,
                                 parent.root,
-                            );
+                            ),
+                            Value.slice_len_index => return sema.beginComptimePtrMutationInner(
+                                block,
+                                src,
+                                Type.usize,
+                                s.len,
+                                ptr_elem_ty,
+                                parent.root,
+                            ),
+                            else => unreachable,
                         },
-                        .@"union" => {
-                            const payload = &val_ptr.castTag(.@"union").?.data;
+                        .un => |*un| {
                             const layout = base_child_ty.containerLayout(mod);
 
                             const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
                             const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
-                            if (layout == .auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
+                            if (un.tag == .none and un.payload.* == .interned and un.payload.interned == .undef) {
+                                // A union has been initialized to undefined at comptime and now we
+                                // are for the first time setting the payload. We must change the
+                                // tag implicitly.
+                                const payload_ty = parent.ty.structFieldType(field_index, mod);
+                                un.tag = hypothetical_tag.toIntern();
+                                un.payload.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) };
+                                return beginComptimePtrMutationInner(
+                                    sema,
+                                    block,
+                                    src,
+                                    payload_ty,
+                                    un.payload,
+                                    ptr_elem_ty,
+                                    parent.root,
+                                );
+                            }
+
+                            if (layout == .auto or hypothetical_tag.toIntern() == un.tag) {
                                 // We need to set the active field of the union.
-                                payload.tag = hypothetical_tag;
+                                un.tag = hypothetical_tag.toIntern();
 
                                 const field_ty = parent.ty.structFieldType(field_index, mod);
                                 return beginComptimePtrMutationInner(
@@ -31125,7 +31033,7 @@ fn beginComptimePtrMutation(
                                     block,
                                     src,
                                     field_ty,
-                                    &payload.val,
+                                    un.payload,
                                     ptr_elem_ty,
                                     parent.root,
                                 );
@@ -31133,11 +31041,10 @@ fn beginComptimePtrMutation(
                                 // Writing to a different field (a different or unknown tag is active) requires reinterpreting
                                 // memory of the entire union, which requires knowing its abiSize.
                                 try sema.resolveTypeLayout(parent.ty);
-
                                 // This union value no longer has a well-defined tag type.
                                 // The reinterpretation will read it back out as .none.
-                                payload.val = try payload.val.unintern(sema.arena, mod);
-                                return ComptimePtrMutationKit{
+                                try un.payload.unintern(mod, sema.arena, false, false);
+                                return .{
                                     .root = parent.root,
                                     .pointee = .{ .reinterpret = .{
                                         .val_ptr = val_ptr,
@@ -31148,119 +31055,12 @@ fn beginComptimePtrMutation(
                                 };
                             }
                         },
-                        .slice => switch (field_index) {
-                            Value.slice_ptr_index => return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                parent.ty.slicePtrFieldType(mod),
-                                &val_ptr.castTag(.slice).?.data.ptr,
-                                ptr_elem_ty,
-                                parent.root,
-                            ),
-
-                            Value.slice_len_index => return beginComptimePtrMutationInner(
-                                sema,
-                                block,
-                                src,
-                                Type.usize,
-                                &val_ptr.castTag(.slice).?.data.len,
-                                ptr_elem_ty,
-                                parent.root,
-                            ),
-
-                            else => unreachable,
-                        },
-                        else => unreachable,
-                    },
-                    else => switch (mod.intern_pool.indexToKey(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 = sema.arena;
-
-                            switch (parent.ty.zigTypeTag(mod)) {
-                                .Struct => {
-                                    const fields = try arena.alloc(Value, parent.ty.structFieldCount(mod));
-                                    for (fields, 0..) |*field, i| field.* = Value.fromInterned((try mod.intern(.{
-                                        .undef = parent.ty.structFieldType(i, mod).toIntern(),
-                                    })));
-
-                                    val_ptr.* = try Value.Tag.aggregate.create(arena, fields);
-
-                                    return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        parent.ty.structFieldType(field_index, mod),
-                                        &fields[field_index],
-                                        ptr_elem_ty,
-                                        parent.root,
-                                    );
-                                },
-                                .Union => {
-                                    const payload = try arena.create(Value.Payload.Union);
-                                    const tag_ty = parent.ty.unionTagTypeHypothetical(mod);
-                                    const payload_ty = parent.ty.structFieldType(field_index, mod);
-                                    payload.* = .{ .data = .{
-                                        .tag = try mod.enumValueFieldIndex(tag_ty, field_index),
-                                        .val = Value.fromInterned((try mod.intern(.{ .undef = payload_ty.toIntern() }))),
-                                    } };
-
-                                    val_ptr.* = Value.initPayload(&payload.base);
-
-                                    return beginComptimePtrMutationInner(
-                                        sema,
-                                        block,
-                                        src,
-                                        payload_ty,
-                                        &payload.data.val,
-                                        ptr_elem_ty,
-                                        parent.root,
-                                    );
-                                },
-                                .Pointer => {
-                                    assert(parent.ty.isSlice(mod));
-                                    const ptr_ty = parent.ty.slicePtrFieldType(mod);
-                                    val_ptr.* = try Value.Tag.slice.create(arena, .{
-                                        .ptr = Value.fromInterned((try mod.intern(.{ .undef = ptr_ty.toIntern() }))),
-                                        .len = Value.fromInterned((try mod.intern(.{ .undef = .usize_type }))),
-                                    });
-
-                                    switch (field_index) {
-                                        Value.slice_ptr_index => return beginComptimePtrMutationInner(
-                                            sema,
-                                            block,
-                                            src,
-                                            ptr_ty,
-                                            &val_ptr.castTag(.slice).?.data.ptr,
-                                            ptr_elem_ty,
-                                            parent.root,
-                                        ),
-                                        Value.slice_len_index => return beginComptimePtrMutationInner(
-                                            sema,
-                                            block,
-                                            src,
-                                            Type.usize,
-                                            &val_ptr.castTag(.slice).?.data.len,
-                                            ptr_elem_ty,
-                                            parent.root,
-                                        ),
-
-                                        else => unreachable,
-                                    }
-                                },
-                                else => unreachable,
-                            }
-                        },
-                        else => unreachable,
-                    },
+                    }
                 },
                 .reinterpret => |reinterpret| {
                     const field_offset_u64 = base_child_ty.structFieldOffset(field_index, mod);
                     const field_offset = try sema.usizeCast(block, src, field_offset_u64);
-                    return ComptimePtrMutationKit{
+                    return .{
                         .root = parent.root,
                         .pointee = .{ .reinterpret = .{
                             .val_ptr = reinterpret.val_ptr,
@@ -31280,7 +31080,7 @@ fn beginComptimePtrMutationInner(
     block: *Block,
     src: LazySrcLoc,
     decl_ty: Type,
-    decl_val: *Value,
+    decl_val: *MutableValue,
     ptr_elem_ty: Type,
     root: ComptimePtrMutationKit.Root,
 ) CompileError!ComptimePtrMutationKit {
@@ -31288,7 +31088,13 @@ fn beginComptimePtrMutationInner(
     const target = mod.getTarget();
     const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok;
 
-    decl_val.* = try decl_val.unintern(sema.arena, mod);
+    const old_decl_val = decl_val.*;
+    try decl_val.unintern(mod, sema.arena, false, false);
+    if (decl_val.* == .un and decl_val.un.tag == .none and decl_val.un.payload.* == .interned and decl_val.un.payload.interned == .undef) {
+        // HACKHACK: undefined union - re-intern it for now
+        // `unintern` probably should just leave these as is, but I'm leaving it until I rewrite comptime pointer access.
+        decl_val.* = old_decl_val;
+    }
 
     if (coerce_ok) {
         return ComptimePtrMutationKit{
@@ -31334,21 +31140,16 @@ fn beginComptimePtrMutationInner(
     };
 }
 
-const TypedValueAndOffset = struct {
-    tv: TypedValue,
-    byte_offset: usize,
-};
-
 const ComptimePtrLoadKit = struct {
     /// The Value and Type corresponding to the pointee of the provided pointer.
     /// If a direct dereference is not possible, this is null.
-    pointee: ?TypedValue,
+    pointee: ?MutableValue,
     /// The largest parent Value containing `pointee` and having a well-defined memory layout.
     /// This is used for bitcasting, if direct dereferencing failed (i.e. `pointee` is null).
-    parent: ?TypedValueAndOffset,
-    /// Whether the `pointee` could be mutated by further
-    /// semantic analysis and a copy must be performed.
-    is_mutable: bool,
+    parent: ?struct {
+        val: MutableValue,
+        byte_offset: usize,
+    },
     /// If the root decl could not be used as `parent`, this is the type that
     /// caused that by not having a well-defined layout
     ty_without_well_defined_layout: ?Type,
@@ -31375,53 +31176,41 @@ fn beginComptimePtrLoad(
         .ptr => |ptr| switch (ptr.addr) {
             .decl => |decl_index| blk: {
                 const decl = mod.declPtr(decl_index);
-                const decl_tv = try decl.typedValue(mod);
                 try sema.declareDependency(.{ .decl_val = decl_index });
                 if (decl.val.getVariable(mod) != null) return error.RuntimeLoad;
-
+                const decl_val: MutableValue = .{ .interned = decl.val.toIntern() };
                 const layout_defined = decl.typeOf(mod).hasWellDefinedLayout(mod);
                 break :blk ComptimePtrLoadKit{
-                    .parent = if (layout_defined) .{ .tv = decl_tv, .byte_offset = 0 } else null,
-                    .pointee = decl_tv,
-                    .is_mutable = false,
+                    .parent = if (layout_defined) .{ .val = decl_val, .byte_offset = 0 } else null,
+                    .pointee = decl_val,
                     .ty_without_well_defined_layout = if (!layout_defined) decl.typeOf(mod) else null,
                 };
             },
             .comptime_alloc => |alloc_index| kit: {
                 const alloc = sema.getComptimeAlloc(alloc_index);
-                const alloc_tv: TypedValue = .{
-                    .ty = alloc.ty,
-                    .val = alloc.val,
-                };
-                const layout_defined = alloc.ty.hasWellDefinedLayout(mod);
+                const alloc_ty = alloc.val.typeOf(mod);
+                const layout_defined = alloc_ty.hasWellDefinedLayout(mod);
                 break :kit .{
-                    .parent = if (layout_defined) .{ .tv = alloc_tv, .byte_offset = 0 } else null,
-                    .pointee = alloc_tv,
-                    .is_mutable = true,
-                    .ty_without_well_defined_layout = if (!layout_defined) alloc.ty else null,
+                    .parent = if (layout_defined) .{ .val = alloc.val, .byte_offset = 0 } else null,
+                    .pointee = alloc.val,
+                    .ty_without_well_defined_layout = if (!layout_defined) alloc_ty else null,
                 };
             },
             .anon_decl => |anon_decl| blk: {
                 const decl_val = anon_decl.val;
                 if (Value.fromInterned(decl_val).getVariable(mod) != null) return error.RuntimeLoad;
                 const decl_ty = Type.fromInterned(ip.typeOf(decl_val));
-                const decl_tv: TypedValue = .{ .ty = decl_ty, .val = Value.fromInterned(decl_val) };
+                const decl_mv: MutableValue = .{ .interned = decl_val };
                 const layout_defined = decl_ty.hasWellDefinedLayout(mod);
                 break :blk ComptimePtrLoadKit{
-                    .parent = if (layout_defined) .{ .tv = decl_tv, .byte_offset = 0 } else null,
-                    .pointee = decl_tv,
-                    .is_mutable = false,
+                    .parent = if (layout_defined) .{ .val = decl_mv, .byte_offset = 0 } else null,
+                    .pointee = decl_mv,
                     .ty_without_well_defined_layout = if (!layout_defined) decl_ty else null,
                 };
             },
             .int => return error.RuntimeLoad,
             .eu_payload, .opt_payload => |container_ptr| blk: {
                 const container_ty = Type.fromInterned(ip.typeOf(container_ptr)).childType(mod);
-                const payload_ty = switch (ptr.addr) {
-                    .eu_payload => container_ty.errorUnionPayload(mod),
-                    .opt_payload => container_ty.optionalChild(mod),
-                    else => unreachable,
-                };
                 var deref = try sema.beginComptimePtrLoad(block, src, Value.fromInterned(container_ptr), container_ty);
 
                 // eu_payload and opt_payload never have a well-defined layout
@@ -31430,15 +31219,14 @@ fn beginComptimePtrLoad(
                     deref.ty_without_well_defined_layout = container_ty;
                 }
 
-                if (deref.pointee) |*tv| {
+                if (deref.pointee) |pointee| {
+                    const pointee_ty = pointee.typeOf(mod);
                     const coerce_in_mem_ok =
-                        (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;
+                        (try sema.coerceInMemoryAllowed(block, container_ty, pointee_ty, false, target, src, src)) == .ok or
+                        (try sema.coerceInMemoryAllowed(block, pointee_ty, container_ty, false, target, src, src)) == .ok;
                     if (coerce_in_mem_ok) {
-                        const payload_val = switch (tv.val.ip_index) {
-                            .none => tv.val.cast(Value.Payload.SubValue).?.data,
-                            .null_value => return sema.fail(block, src, "attempt to use null value", .{}),
-                            else => Value.fromInterned(switch (ip.indexToKey(tv.val.toIntern())) {
+                        deref.pointee = switch (pointee) {
+                            .interned => |ip_index| .{ .interned = switch (ip.indexToKey(ip_index)) {
                                 .error_union => |error_union| switch (error_union.val) {
                                     .err_name => |err_name| return sema.fail(
                                         block,
@@ -31453,23 +31241,20 @@ fn beginComptimePtrLoad(
                                     else => |payload| payload,
                                 },
                                 else => unreachable,
-                            }),
+                            } },
+                            .eu_payload, .opt_payload => |p| p.child.*,
+                            else => unreachable,
                         };
-                        tv.* = TypedValue{ .ty = payload_ty, .val = payload_val };
                         break :blk deref;
                     }
                 }
                 deref.pointee = null;
                 break :blk deref;
             },
-            .comptime_field => |comptime_field| blk: {
-                const field_ty = Type.fromInterned(ip.typeOf(comptime_field));
-                break :blk ComptimePtrLoadKit{
-                    .parent = null,
-                    .pointee = .{ .ty = field_ty, .val = Value.fromInterned(comptime_field) },
-                    .is_mutable = false,
-                    .ty_without_well_defined_layout = field_ty,
-                };
+            .comptime_field => |field_val| .{
+                .parent = null,
+                .pointee = .{ .interned = field_val },
+                .ty_without_well_defined_layout = Type.fromInterned(ip.typeOf(field_val)),
             },
             .elem => |elem_ptr| blk: {
                 const elem_ty = Type.fromInterned(ip.typeOf(elem_ptr.base)).elemType2(mod);
@@ -31502,30 +31287,37 @@ fn beginComptimePtrLoad(
 
                 // If we're loading an elem that was derived from a different type
                 // than the true type of the underlying decl, we cannot deref directly
-                const ty_matches = if (deref.pointee != null and deref.pointee.?.ty.isArrayOrVector(mod)) x: {
-                    const deref_elem_ty = deref.pointee.?.ty.childType(mod);
-                    break :x (try sema.coerceInMemoryAllowed(block, deref_elem_ty, elem_ty, false, target, src, src)) == .ok or
-                        (try sema.coerceInMemoryAllowed(block, elem_ty, deref_elem_ty, false, target, src, src)) == .ok;
+                const ty_matches = if (deref.pointee) |pointee| match: {
+                    const ty = pointee.typeOf(mod);
+                    if (!ty.isArrayOrVector(mod)) break :match false;
+                    const deref_elem_ty = ty.childType(mod);
+                    if ((try sema.coerceInMemoryAllowed(block, deref_elem_ty, elem_ty, false, target, src, src)) == .ok) break :match true;
+                    if ((try sema.coerceInMemoryAllowed(block, elem_ty, deref_elem_ty, false, target, src, src)) == .ok) break :match true;
+                    break :match false;
                 } else false;
                 if (!ty_matches) {
                     deref.pointee = null;
                     break :blk deref;
                 }
 
-                var array_tv = deref.pointee.?;
-                const check_len = array_tv.ty.arrayLenIncludingSentinel(mod);
+                var array_val = deref.pointee.?;
+                const check_len = array_val.typeOf(mod).arrayLenIncludingSentinel(mod);
                 if (maybe_array_ty) |load_ty| {
                     // It's possible that we're loading a [N]T, in which case we'd like to slice
                     // the pointee array directly from our parent array.
                     if (load_ty.isArrayOrVector(mod) and load_ty.childType(mod).eql(elem_ty, mod)) {
                         const len = try sema.usizeCast(block, src, load_ty.arrayLenIncludingSentinel(mod));
                         const elem_idx = try sema.usizeCast(block, src, elem_ptr.index);
-                        deref.pointee = if (elem_ptr.index + len <= check_len) TypedValue{
-                            .ty = try mod.arrayType(.{
-                                .len = len,
-                                .child = elem_ty.toIntern(),
-                            }),
-                            .val = try array_tv.val.sliceArray(sema, elem_idx, elem_idx + len),
+                        deref.pointee = if (elem_ptr.index + len <= check_len) switch (array_val) {
+                            .aggregate => |a| .{ .aggregate = .{
+                                .ty = (try mod.arrayType(.{ .len = len, .child = elem_ty.toIntern() })).toIntern(),
+                                .elems = a.elems[elem_idx..][0..len],
+                            } },
+                            else => .{
+                                .interned = (try (Value.fromInterned(
+                                    try array_val.intern(mod, sema.arena),
+                                ).sliceArray(sema, elem_idx, elem_idx + len))).toIntern(),
+                            },
                         } else null;
                         break :blk deref;
                     }
@@ -31536,18 +31328,12 @@ fn beginComptimePtrLoad(
                     break :blk deref;
                 }
                 if (elem_ptr.index == check_len - 1) {
-                    if (array_tv.ty.sentinel(mod)) |sent| {
-                        deref.pointee = TypedValue{
-                            .ty = elem_ty,
-                            .val = sent,
-                        };
+                    if (array_val.typeOf(mod).sentinel(mod)) |sent| {
+                        deref.pointee = .{ .interned = sent.toIntern() };
                         break :blk deref;
                     }
                 }
-                deref.pointee = TypedValue{
-                    .ty = elem_ty,
-                    .val = try array_tv.val.elemValue(mod, @intCast(elem_ptr.index)),
-                };
+                deref.pointee = try array_val.getElem(mod, @intCast(elem_ptr.index));
                 break :blk deref;
             },
             .field => |field_ptr| blk: {
@@ -31571,37 +31357,17 @@ fn beginComptimePtrLoad(
                     deref.ty_without_well_defined_layout = container_ty;
                 }
 
-                const tv = deref.pointee orelse {
-                    deref.pointee = null;
-                    break :blk deref;
-                };
+                const pointee = deref.pointee orelse break :blk deref;
+                const pointee_ty = pointee.typeOf(mod);
                 const coerce_in_mem_ok =
-                    (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;
+                    (try sema.coerceInMemoryAllowed(block, container_ty, pointee_ty, false, target, src, src)) == .ok or
+                    (try sema.coerceInMemoryAllowed(block, pointee_ty, container_ty, false, target, src, src)) == .ok;
                 if (!coerce_in_mem_ok) {
                     deref.pointee = null;
                     break :blk deref;
                 }
 
-                if (container_ty.isSlice(mod)) {
-                    deref.pointee = switch (field_index) {
-                        Value.slice_ptr_index => TypedValue{
-                            .ty = container_ty.slicePtrFieldType(mod),
-                            .val = tv.val.slicePtr(mod),
-                        },
-                        Value.slice_len_index => TypedValue{
-                            .ty = Type.usize,
-                            .val = Value.fromInterned(ip.indexToKey(try tv.val.intern(tv.ty, mod)).slice.len),
-                        },
-                        else => unreachable,
-                    };
-                } else {
-                    const field_ty = container_ty.structFieldType(field_index, mod);
-                    deref.pointee = TypedValue{
-                        .ty = field_ty,
-                        .val = try tv.val.fieldValue(mod, field_index),
-                    };
-                }
+                deref.pointee = try pointee.getElem(mod, field_index);
                 break :blk deref;
             },
         },
@@ -31612,9 +31378,9 @@ fn beginComptimePtrLoad(
         else => unreachable,
     };
 
-    if (deref.pointee) |tv| {
-        if (deref.parent == null and tv.ty.hasWellDefinedLayout(mod)) {
-            deref.parent = .{ .tv = tv, .byte_offset = 0 };
+    if (deref.pointee) |val| {
+        if (deref.parent == null and val.typeOf(mod).hasWellDefinedLayout(mod)) {
+            deref.parent = .{ .val = val, .byte_offset = 0 };
         }
     }
     return deref;
@@ -38289,17 +38055,18 @@ fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value
         else => |e| return e,
     };
 
-    if (deref.pointee) |tv| {
+    if (deref.pointee) |pointee| {
+        const uncoerced_val = Value.fromInterned(try pointee.intern(mod, sema.arena));
+        const ty = Type.fromInterned(mod.intern_pool.typeOf(uncoerced_val.toIntern()));
         const coerce_in_mem_ok =
-            (try sema.coerceInMemoryAllowed(block, load_ty, tv.ty, false, target, src, src)) == .ok or
-            (try sema.coerceInMemoryAllowed(block, tv.ty, load_ty, false, target, src, src)) == .ok;
+            (try sema.coerceInMemoryAllowed(block, load_ty, ty, false, target, src, src)) == .ok or
+            (try sema.coerceInMemoryAllowed(block, ty, load_ty, false, target, src, src)) == .ok;
         if (coerce_in_mem_ok) {
             // We have a Value that lines up in virtual memory exactly with what we want to load,
             // and it is in-memory coercible to load_ty. It may be returned without modifications.
             // Move mutable decl values to the InternPool and assert other decls are already in
             // the InternPool.
-            const uncoerced_val = if (deref.is_mutable) try tv.val.intern(tv.ty, mod) else tv.val.toIntern();
-            const coerced_val = try mod.getCoerced(Value.fromInterned(uncoerced_val), load_ty);
+            const coerced_val = try mod.getCoerced(uncoerced_val, load_ty);
             return .{ .val = coerced_val };
         }
     }
@@ -38313,21 +38080,35 @@ fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value
     const load_sz = try sema.typeAbiSize(load_ty);
 
     // Try the smaller bit-cast first, since that's more efficient than using the larger `parent`
-    if (deref.pointee) |tv| if (load_sz <= try sema.typeAbiSize(tv.ty))
-        return DerefResult{ .val = (try sema.bitCastVal(block, src, tv.val, tv.ty, load_ty, 0)) orelse return .runtime_load };
+    if (deref.pointee) |pointee| {
+        const val_ip_index = try pointee.intern(mod, sema.arena);
+        const val = Value.fromInterned(val_ip_index);
+        const ty = Type.fromInterned(mod.intern_pool.typeOf(val_ip_index));
+        if (load_sz <= try sema.typeAbiSize(ty)) {
+            return .{ .val = (try sema.bitCastVal(block, src, val, ty, load_ty, 0)) orelse return .runtime_load };
+        }
+    }
 
     // If that fails, try to bit-cast from the largest parent value with a well-defined layout
-    if (deref.parent) |parent| if (load_sz + parent.byte_offset <= try sema.typeAbiSize(parent.tv.ty))
-        return DerefResult{ .val = (try sema.bitCastVal(block, src, parent.tv.val, parent.tv.ty, load_ty, parent.byte_offset)) orelse return .runtime_load };
+    if (deref.parent) |parent| {
+        const parent_ip_index = try parent.val.intern(mod, sema.arena);
+        const parent_val = Value.fromInterned(parent_ip_index);
+        const parent_ty = Type.fromInterned(mod.intern_pool.typeOf(parent_ip_index));
+        if (load_sz + parent.byte_offset <= try sema.typeAbiSize(parent_ty)) {
+            return .{ .val = (try sema.bitCastVal(block, src, parent_val, parent_ty, load_ty, parent.byte_offset)) orelse return .runtime_load };
+        }
+    }
 
     if (deref.ty_without_well_defined_layout) |bad_ty| {
         // We got no parent for bit-casting, or the parent we got was too small. Either way, the problem
         // is that some type we encountered when de-referencing does not have a well-defined layout.
-        return DerefResult{ .needed_well_defined = bad_ty };
+        return .{ .needed_well_defined = bad_ty };
     } else {
         // If all encountered types had well-defined layouts, the parent is the root decl and it just
         // wasn't big enough for the load.
-        return DerefResult{ .out_of_bounds = deref.parent.?.tv.ty };
+        const parent_ip_index = try deref.parent.?.val.intern(mod, sema.arena);
+        const parent_ty = Type.fromInterned(mod.intern_pool.typeOf(parent_ip_index));
+        return .{ .out_of_bounds = parent_ty };
     }
 }
 
src/Value.zig
@@ -1627,7 +1627,9 @@ pub fn maybeElemValueFull(val: Value, sema: ?*Sema, mod: *Module, index: usize)
             .ptr => |ptr| switch (ptr.addr) {
                 .decl => |decl| mod.declPtr(decl).val.maybeElemValueFull(sema, mod, index),
                 .anon_decl => |anon_decl| Value.fromInterned(anon_decl.val).maybeElemValueFull(sema, mod, index),
-                .comptime_alloc => |idx| if (sema) |s| s.getComptimeAlloc(idx).val.maybeElemValueFull(sema, mod, index) else null,
+                .comptime_alloc => |idx| if (sema) |s| Value.fromInterned(
+                    try s.getComptimeAlloc(idx).val.intern(mod, s.arena),
+                ).maybeElemValueFull(sema, mod, index) else null,
                 .int, .eu_payload => null,
                 .opt_payload => |base| Value.fromInterned(base).maybeElemValueFull(sema, mod, index),
                 .comptime_field => |field_val| Value.fromInterned(field_val).maybeElemValueFull(sema, mod, index),
@@ -1697,7 +1699,9 @@ pub fn sliceArray(
         else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .ptr => |ptr| switch (ptr.addr) {
                 .decl => |decl| try mod.declPtr(decl).val.sliceArray(sema, start, end),
-                .comptime_alloc => |idx| sema.getComptimeAlloc(idx).val.sliceArray(sema, start, end),
+                .comptime_alloc => |idx| try Value.fromInterned(
+                    try sema.getComptimeAlloc(idx).val.intern(mod, sema.arena),
+                ).sliceArray(sema, start, end),
                 .comptime_field => |comptime_field| Value.fromInterned(comptime_field)
                     .sliceArray(sema, start, end),
                 .elem => |elem| Value.fromInterned(elem.base)