Commit 9eda6ccefc

mlugg <mlugg@mlugg.co.uk>
2024-02-01 17:58:52
InternPool: use separate key for slices
This change eliminates some problematic recursive logic in InternPool, and provides a safer API.
1 parent 5a3ae38
src/arch/wasm/CodeGen.zig
@@ -3179,9 +3179,6 @@ fn lowerAnonDeclRef(
 
 fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue {
     const mod = func.bin_file.base.comp.module.?;
-    if (tv.ty.isSlice(mod)) {
-        return WValue{ .memory = try func.bin_file.lowerUnnamedConst(tv, decl_index) };
-    }
 
     const decl = mod.declPtr(decl_index);
     // check if decl is an alias to a function, in which case we
@@ -3335,6 +3332,18 @@ fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue {
             .f64 => |f64_val| return WValue{ .float64 = f64_val },
             else => unreachable,
         },
+        .slice => |slice| {
+            var ptr = ip.indexToKey(slice.ptr).ptr;
+            const owner_decl = while (true) switch (ptr.addr) {
+                .decl => |decl| break decl,
+                .mut_decl => |mut_decl| break mut_decl.decl,
+                .int, .anon_decl => return func.fail("Wasm TODO: lower slice where ptr is not owned by decl", .{}),
+                .opt_payload, .eu_payload => |base| ptr = ip.indexToKey(base).ptr,
+                .elem, .field => |base_index| ptr = ip.indexToKey(base_index.base).ptr,
+                .comptime_field => unreachable,
+            };
+            return .{ .memory = try func.bin_file.lowerUnnamedConst(.{ .ty = ty, .val = val }, owner_decl) };
+        },
         .ptr => |ptr| switch (ptr.addr) {
             .decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0),
             .mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0),
src/codegen/c.zig
@@ -1207,50 +1207,35 @@ pub const DeclGen = struct {
                 try writer.print("{x}", .{try dg.fmtIntLiteral(repr_ty, repr_val, location)});
                 if (!empty) try writer.writeByte(')');
             },
-            .ptr => |ptr| {
-                if (ptr.len != .none) {
-                    if (!location.isInitializer()) {
-                        try writer.writeByte('(');
-                        try dg.renderType(writer, ty);
-                        try writer.writeByte(')');
-                    }
-                    try writer.writeByte('{');
-                }
-                const ptr_location = switch (ptr.len) {
-                    .none => location,
-                    else => initializer_type,
-                };
-                const ptr_ty = switch (ptr.len) {
-                    .none => ty,
-                    else => ty.slicePtrFieldType(mod),
-                };
-                const ptr_val = switch (ptr.len) {
-                    .none => val,
-                    else => val.slicePtr(mod),
-                };
-                switch (ptr.addr) {
-                    .decl => |d| try dg.renderDeclValue(writer, ptr_ty, ptr_val, d, ptr_location),
-                    .mut_decl => |md| try dg.renderDeclValue(writer, ptr_ty, ptr_val, md.decl, ptr_location),
-                    .anon_decl => |decl_val| try dg.renderAnonDeclValue(writer, ptr_ty, ptr_val, decl_val, ptr_location),
-                    .int => |int| {
-                        try writer.writeAll("((");
-                        try dg.renderType(writer, ptr_ty);
-                        try writer.print("){x})", .{
-                            try dg.fmtIntLiteral(Type.usize, Value.fromInterned(int), ptr_location),
-                        });
-                    },
-                    .eu_payload,
-                    .opt_payload,
-                    .elem,
-                    .field,
-                    => try dg.renderParentPtr(writer, ptr_val.ip_index, ptr_location),
-                    .comptime_field => unreachable,
-                }
-                if (ptr.len != .none) {
-                    try writer.writeAll(", ");
-                    try dg.renderValue(writer, Type.usize, Value.fromInterned(ptr.len), initializer_type);
-                    try writer.writeByte('}');
+            .slice => |slice| {
+                if (!location.isInitializer()) {
+                    try writer.writeByte('(');
+                    try dg.renderType(writer, ty);
+                    try writer.writeByte(')');
                 }
+                try writer.writeByte('{');
+                try dg.renderValue(writer, ty.slicePtrFieldType(mod), Value.fromInterned(slice.ptr), initializer_type);
+                try writer.writeAll(", ");
+                try dg.renderValue(writer, Type.usize, Value.fromInterned(slice.len), initializer_type);
+                try writer.writeByte('}');
+            },
+            .ptr => |ptr| switch (ptr.addr) {
+                .decl => |d| try dg.renderDeclValue(writer, ty, val, d, location),
+                .mut_decl => |md| try dg.renderDeclValue(writer, ty, val, md.decl, location),
+                .anon_decl => |decl_val| try dg.renderAnonDeclValue(writer, ty, val, decl_val, location),
+                .int => |int| {
+                    try writer.writeAll("((");
+                    try dg.renderType(writer, ty);
+                    try writer.print("){x})", .{
+                        try dg.fmtIntLiteral(Type.usize, Value.fromInterned(int), location),
+                    });
+                },
+                .eu_payload,
+                .opt_payload,
+                .elem,
+                .field,
+                => try dg.renderParentPtr(writer, val.ip_index, location),
+                .comptime_field => unreachable,
             },
             .opt => |opt| {
                 const payload_ty = ty.optionalChild(mod);
src/codegen/llvm.zig
@@ -3644,6 +3644,7 @@ pub const Object = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -3872,30 +3873,22 @@ pub const Object = struct {
                 128 => try o.builder.fp128Const(val.toFloat(f128, mod)),
                 else => unreachable,
             },
-            .ptr => |ptr| {
-                const ptr_ty = switch (ptr.len) {
-                    .none => ty,
-                    else => ty.slicePtrFieldType(mod),
-                };
-                const ptr_val = switch (ptr.addr) {
-                    .decl => |decl| try o.lowerDeclRefValue(ptr_ty, decl),
-                    .mut_decl => |mut_decl| try o.lowerDeclRefValue(ptr_ty, mut_decl.decl),
-                    .anon_decl => |anon_decl| try o.lowerAnonDeclRef(ptr_ty, anon_decl),
-                    .int => |int| try o.lowerIntAsPtr(int),
-                    .eu_payload,
-                    .opt_payload,
-                    .elem,
-                    .field,
-                    => try o.lowerParentPtr(val),
-                    .comptime_field => unreachable,
-                };
-                switch (ptr.len) {
-                    .none => return ptr_val,
-                    else => return o.builder.structConst(try o.lowerType(ty), &.{
-                        ptr_val, try o.lowerValue(ptr.len),
-                    }),
-                }
+            .ptr => |ptr| return switch (ptr.addr) {
+                .decl => |decl| try o.lowerDeclRefValue(ty, decl),
+                .mut_decl => |mut_decl| try o.lowerDeclRefValue(ty, mut_decl.decl),
+                .anon_decl => |anon_decl| try o.lowerAnonDeclRef(ty, anon_decl),
+                .int => |int| try o.lowerIntAsPtr(int),
+                .eu_payload,
+                .opt_payload,
+                .elem,
+                .field,
+                => try o.lowerParentPtr(val),
+                .comptime_field => unreachable,
             },
+            .slice => |slice| return o.builder.structConst(try o.lowerType(ty), &.{
+                try o.lowerValue(slice.ptr),
+                try o.lowerValue(slice.len),
+            }),
             .opt => |opt| {
                 comptime assert(optional_layout_version == 3);
                 const payload_ty = ty.optionalChild(mod);
src/codegen/spirv.zig
@@ -855,18 +855,12 @@ const DeclGen = struct {
                 const int_ty = ty.intTagType(mod);
                 return try self.constant(int_ty, int_val, repr);
             },
-            .ptr => |ptr| {
-                const ptr_ty = switch (ptr.len) {
-                    .none => ty,
-                    else => ty.slicePtrFieldType(mod),
-                };
-                const ptr_id = try self.constantPtr(ptr_ty, val);
-                if (ptr.len == .none) {
-                    return ptr_id;
-                }
-
-                const len_id = try self.constant(Type.usize, Value.fromInterned(ptr.len), .indirect);
-                return try self.constructStruct(
+            .ptr => return self.constantPtr(ty, val),
+            .slice => |slice| {
+                const ptr_ty = ty.slicePtrFieldType(mod);
+                const ptr_id = try self.constantPtr(ptr_ty, Value.fromInterned(slice.ptr));
+                const len_id = try self.constant(Type.usize, Value.fromInterned(slice.len), .indirect);
+                return self.constructStruct(
                     ty,
                     &.{ ptr_ty, Type.usize },
                     &.{ ptr_id, len_id },
src/codegen.zig
@@ -322,24 +322,24 @@ pub fn generateSymbol(
             },
             .f128 => |f128_val| writeFloat(f128, f128_val, target, endian, try code.addManyAsArray(16)),
         },
-        .ptr => |ptr| {
-            // generate ptr
-            switch (try lowerParentPtr(bin_file, src_loc, switch (ptr.len) {
-                .none => typed_value.val,
-                else => typed_value.val.slicePtr(mod),
-            }.toIntern(), code, debug_output, reloc_info)) {
+        .ptr => switch (try lowerParentPtr(bin_file, src_loc, typed_value.val.toIntern(), code, debug_output, reloc_info)) {
+            .ok => {},
+            .fail => |em| return .{ .fail = em },
+        },
+        .slice => |slice| {
+            switch (try generateSymbol(bin_file, src_loc, .{
+                .ty = typed_value.ty.slicePtrFieldType(mod),
+                .val = Value.fromInterned(slice.ptr),
+            }, code, debug_output, reloc_info)) {
                 .ok => {},
                 .fail => |em| return .{ .fail = em },
             }
-            if (ptr.len != .none) {
-                // generate len
-                switch (try generateSymbol(bin_file, src_loc, .{
-                    .ty = Type.usize,
-                    .val = Value.fromInterned(ptr.len),
-                }, code, debug_output, reloc_info)) {
-                    .ok => {},
-                    .fail => |em| return Result{ .fail = em },
-                }
+            switch (try generateSymbol(bin_file, src_loc, .{
+                .ty = Type.usize,
+                .val = Value.fromInterned(slice.len),
+            }, code, debug_output, reloc_info)) {
+                .ok => {},
+                .fail => |em| return .{ .fail = em },
             }
         },
         .opt => {
@@ -676,7 +676,6 @@ fn lowerParentPtr(
 ) CodeGenError!Result {
     const mod = bin_file.comp.module.?;
     const ptr = mod.intern_pool.indexToKey(parent_ptr).ptr;
-    assert(ptr.len == .none);
     return switch (ptr.addr) {
         .decl => |decl| try lowerDeclRef(bin_file, src_loc, decl, code, debug_output, reloc_info),
         .mut_decl => |md| try lowerDeclRef(bin_file, src_loc, md.decl, code, debug_output, reloc_info),
src/InternPool.zig
@@ -326,6 +326,7 @@ pub const Key = union(enum) {
     empty_enum_value: Index,
     float: Float,
     ptr: Ptr,
+    slice: Slice,
     opt: Opt,
     /// An instance of a struct, array, or vector.
     /// Each element/field stored as an `Index`.
@@ -493,7 +494,7 @@ pub const Key = union(enum) {
                 start: u32,
                 len: u32,
 
-                pub fn get(slice: Slice, ip: *const InternPool) []RuntimeOrder {
+                pub fn get(slice: RuntimeOrder.Slice, ip: *const InternPool) []RuntimeOrder {
                     return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
                 }
             };
@@ -1197,8 +1198,6 @@ pub const Key = union(enum) {
         ty: Index,
         /// The value of the address that the pointer points to.
         addr: Addr,
-        /// This could be `none` if size is not a slice.
-        len: Index = .none,
 
         pub const Addr = union(enum) {
             const Tag = @typeInfo(Addr).Union.tag_type.?;
@@ -1232,6 +1231,15 @@ pub const Key = union(enum) {
         };
     };
 
+    pub const Slice = struct {
+        /// This is the slice type, not the element type.
+        ty: Index,
+        /// The slice's `ptr` field. Must be a many-ptr with the same properties as `ty`.
+        ptr: Index,
+        /// The slice's `len` field. Must be a `usize`.
+        len: Index,
+    };
+
     /// `null` is represented by the `val` field being `none`.
     pub const Opt = extern struct {
         /// This is the optional type; not the payload type.
@@ -1354,12 +1362,14 @@ pub const Key = union(enum) {
                 return hasher.final();
             },
 
+            .slice => |slice| Hash.hash(seed, asBytes(&slice.ty) ++ asBytes(&slice.ptr) ++ asBytes(&slice.len)),
+
             .ptr => |ptr| {
                 // Int-to-ptr pointers are hashed separately than decl-referencing pointers.
                 // This is sound due to pointer provenance rules.
                 const addr: @typeInfo(Key.Ptr.Addr).Union.tag_type.? = ptr.addr;
                 const seed2 = seed + @intFromEnum(addr);
-                const common = asBytes(&ptr.ty) ++ asBytes(&ptr.len);
+                const common = asBytes(&ptr.ty);
                 return switch (ptr.addr) {
                     .decl => |x| Hash.hash(seed2, common ++ asBytes(&x)),
 
@@ -1624,9 +1634,17 @@ pub const Key = union(enum) {
                 return a_ty_info.eql(b_ty_info, ip);
             },
 
+            .slice => |a_info| {
+                const b_info = b.slice;
+                if (a_info.ty != b_info.ty) return false;
+                if (a_info.ptr != b_info.ptr) return false;
+                if (a_info.len != b_info.len) return false;
+                return true;
+            },
+
             .ptr => |a_info| {
                 const b_info = b.ptr;
-                if (a_info.ty != b_info.ty or a_info.len != b_info.len) return false;
+                if (a_info.ty != b_info.ty) return false;
 
                 const AddrTag = @typeInfo(Key.Ptr.Addr).Union.tag_type.?;
                 if (@as(AddrTag, a_info.addr) != @as(AddrTag, b_info.addr)) return false;
@@ -1829,6 +1847,7 @@ pub const Key = union(enum) {
             => .type_type,
 
             inline .ptr,
+            .slice,
             .int,
             .float,
             .opt,
@@ -3983,77 +4002,11 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
         },
         .ptr_slice => {
             const info = ip.extraData(PtrSlice, data);
-            const ptr_item = ip.items.get(@intFromEnum(info.ptr));
-            return .{
-                .ptr = .{
-                    .ty = info.ty,
-                    .addr = switch (ptr_item.tag) {
-                        .ptr_decl => .{
-                            .decl = ip.extraData(PtrDecl, ptr_item.data).decl,
-                        },
-                        .ptr_mut_decl => b: {
-                            const sub_info = ip.extraData(PtrMutDecl, ptr_item.data);
-                            break :b .{ .mut_decl = .{
-                                .decl = sub_info.decl,
-                                .runtime_index = sub_info.runtime_index,
-                            } };
-                        },
-                        .ptr_anon_decl => .{
-                            .anon_decl = .{
-                                .val = ip.extraData(PtrAnonDecl, ptr_item.data).val,
-                                .orig_ty = info.ty,
-                            },
-                        },
-                        .ptr_anon_decl_aligned => b: {
-                            const sub_info = ip.extraData(PtrAnonDeclAligned, ptr_item.data);
-                            break :b .{ .anon_decl = .{
-                                .val = sub_info.val,
-                                .orig_ty = sub_info.orig_ty,
-                            } };
-                        },
-                        .ptr_comptime_field => .{
-                            .comptime_field = ip.extraData(PtrComptimeField, ptr_item.data).field_val,
-                        },
-                        .ptr_int => .{
-                            .int = ip.extraData(PtrBase, ptr_item.data).base,
-                        },
-                        .ptr_eu_payload => .{
-                            .eu_payload = ip.extraData(PtrBase, ptr_item.data).base,
-                        },
-                        .ptr_opt_payload => .{
-                            .opt_payload = ip.extraData(PtrBase, ptr_item.data).base,
-                        },
-                        .ptr_elem => b: {
-                            // Avoid `indexToKey` recursion by asserting the tag encoding.
-                            const sub_info = ip.extraData(PtrBaseIndex, ptr_item.data);
-                            const index_item = ip.items.get(@intFromEnum(sub_info.index));
-                            break :b switch (index_item.tag) {
-                                .int_usize => .{ .elem = .{
-                                    .base = sub_info.base,
-                                    .index = index_item.data,
-                                } },
-                                .int_positive => @panic("TODO"), // implement along with behavior test coverage
-                                else => unreachable,
-                            };
-                        },
-                        .ptr_field => b: {
-                            // Avoid `indexToKey` recursion by asserting the tag encoding.
-                            const sub_info = ip.extraData(PtrBaseIndex, ptr_item.data);
-                            const index_item = ip.items.get(@intFromEnum(sub_info.index));
-                            break :b switch (index_item.tag) {
-                                .int_usize => .{ .field = .{
-                                    .base = sub_info.base,
-                                    .index = index_item.data,
-                                } },
-                                .int_positive => @panic("TODO"), // implement along with behavior test coverage
-                                else => unreachable,
-                            };
-                        },
-                        else => unreachable,
-                    },
-                    .len = info.len,
-                },
-            };
+            return .{ .slice = .{
+                .ty = info.ty,
+                .ptr = info.ptr,
+                .len = info.len,
+            } };
         },
         .int_u8 => .{ .int = .{
             .ty = .u8_type,
@@ -4735,153 +4688,139 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
             });
         },
 
+        .slice => |slice| {
+            assert(ip.indexToKey(slice.ty).ptr_type.flags.size == .Slice);
+            assert(ip.indexToKey(ip.typeOf(slice.ptr)).ptr_type.flags.size == .Many);
+            ip.items.appendAssumeCapacity(.{
+                .tag = .ptr_slice,
+                .data = try ip.addExtra(gpa, PtrSlice{
+                    .ty = slice.ty,
+                    .ptr = slice.ptr,
+                    .len = slice.len,
+                }),
+            });
+        },
+
         .ptr => |ptr| {
             const ptr_type = ip.indexToKey(ptr.ty).ptr_type;
-            switch (ptr.len) {
-                .none => {
-                    assert(ptr_type.flags.size != .Slice);
-                    switch (ptr.addr) {
-                        .decl => |decl| ip.items.appendAssumeCapacity(.{
-                            .tag = .ptr_decl,
-                            .data = try ip.addExtra(gpa, PtrDecl{
-                                .ty = ptr.ty,
-                                .decl = decl,
-                            }),
+            assert(ptr_type.flags.size != .Slice);
+            switch (ptr.addr) {
+                .decl => |decl| ip.items.appendAssumeCapacity(.{
+                    .tag = .ptr_decl,
+                    .data = try ip.addExtra(gpa, PtrDecl{
+                        .ty = ptr.ty,
+                        .decl = decl,
+                    }),
+                }),
+                .mut_decl => |mut_decl| ip.items.appendAssumeCapacity(.{
+                    .tag = .ptr_mut_decl,
+                    .data = try ip.addExtra(gpa, PtrMutDecl{
+                        .ty = ptr.ty,
+                        .decl = mut_decl.decl,
+                        .runtime_index = mut_decl.runtime_index,
+                    }),
+                }),
+                .anon_decl => |anon_decl| ip.items.appendAssumeCapacity(
+                    if (ptrsHaveSameAlignment(ip, ptr.ty, ptr_type, anon_decl.orig_ty)) .{
+                        .tag = .ptr_anon_decl,
+                        .data = try ip.addExtra(gpa, PtrAnonDecl{
+                            .ty = ptr.ty,
+                            .val = anon_decl.val,
                         }),
-                        .mut_decl => |mut_decl| ip.items.appendAssumeCapacity(.{
-                            .tag = .ptr_mut_decl,
-                            .data = try ip.addExtra(gpa, PtrMutDecl{
-                                .ty = ptr.ty,
-                                .decl = mut_decl.decl,
-                                .runtime_index = mut_decl.runtime_index,
-                            }),
+                    } else .{
+                        .tag = .ptr_anon_decl_aligned,
+                        .data = try ip.addExtra(gpa, PtrAnonDeclAligned{
+                            .ty = ptr.ty,
+                            .val = anon_decl.val,
+                            .orig_ty = anon_decl.orig_ty,
                         }),
-                        .anon_decl => |anon_decl| ip.items.appendAssumeCapacity(
-                            if (ptrsHaveSameAlignment(ip, ptr.ty, ptr_type, anon_decl.orig_ty)) .{
-                                .tag = .ptr_anon_decl,
-                                .data = try ip.addExtra(gpa, PtrAnonDecl{
-                                    .ty = ptr.ty,
-                                    .val = anon_decl.val,
-                                }),
-                            } else .{
-                                .tag = .ptr_anon_decl_aligned,
-                                .data = try ip.addExtra(gpa, PtrAnonDeclAligned{
-                                    .ty = ptr.ty,
-                                    .val = anon_decl.val,
-                                    .orig_ty = anon_decl.orig_ty,
-                                }),
-                            },
-                        ),
-                        .comptime_field => |field_val| {
-                            assert(field_val != .none);
-                            ip.items.appendAssumeCapacity(.{
-                                .tag = .ptr_comptime_field,
-                                .data = try ip.addExtra(gpa, PtrComptimeField{
-                                    .ty = ptr.ty,
-                                    .field_val = field_val,
-                                }),
-                            });
+                    },
+                ),
+                .comptime_field => |field_val| {
+                    assert(field_val != .none);
+                    ip.items.appendAssumeCapacity(.{
+                        .tag = .ptr_comptime_field,
+                        .data = try ip.addExtra(gpa, PtrComptimeField{
+                            .ty = ptr.ty,
+                            .field_val = field_val,
+                        }),
+                    });
+                },
+                .int, .eu_payload, .opt_payload => |base| {
+                    switch (ptr.addr) {
+                        .int => assert(ip.typeOf(base) == .usize_type),
+                        .eu_payload => assert(ip.indexToKey(
+                            ip.indexToKey(ip.typeOf(base)).ptr_type.child,
+                        ) == .error_union_type),
+                        .opt_payload => assert(ip.indexToKey(
+                            ip.indexToKey(ip.typeOf(base)).ptr_type.child,
+                        ) == .opt_type),
+                        else => unreachable,
+                    }
+                    ip.items.appendAssumeCapacity(.{
+                        .tag = switch (ptr.addr) {
+                            .int => .ptr_int,
+                            .eu_payload => .ptr_eu_payload,
+                            .opt_payload => .ptr_opt_payload,
+                            else => unreachable,
                         },
-                        .int, .eu_payload, .opt_payload => |base| {
-                            switch (ptr.addr) {
-                                .int => assert(ip.typeOf(base) == .usize_type),
-                                .eu_payload => assert(ip.indexToKey(
-                                    ip.indexToKey(ip.typeOf(base)).ptr_type.child,
-                                ) == .error_union_type),
-                                .opt_payload => assert(ip.indexToKey(
-                                    ip.indexToKey(ip.typeOf(base)).ptr_type.child,
-                                ) == .opt_type),
-                                else => unreachable,
-                            }
-                            ip.items.appendAssumeCapacity(.{
-                                .tag = switch (ptr.addr) {
-                                    .int => .ptr_int,
-                                    .eu_payload => .ptr_eu_payload,
-                                    .opt_payload => .ptr_opt_payload,
-                                    else => unreachable,
+                        .data = try ip.addExtra(gpa, PtrBase{
+                            .ty = ptr.ty,
+                            .base = base,
+                        }),
+                    });
+                },
+                .elem, .field => |base_index| {
+                    const base_ptr_type = ip.indexToKey(ip.typeOf(base_index.base)).ptr_type;
+                    switch (ptr.addr) {
+                        .elem => assert(base_ptr_type.flags.size == .Many),
+                        .field => {
+                            assert(base_ptr_type.flags.size == .One);
+                            switch (ip.indexToKey(base_ptr_type.child)) {
+                                .anon_struct_type => |anon_struct_type| {
+                                    assert(ptr.addr == .field);
+                                    assert(base_index.index < anon_struct_type.types.len);
                                 },
-                                .data = try ip.addExtra(gpa, PtrBase{
-                                    .ty = ptr.ty,
-                                    .base = base,
-                                }),
-                            });
-                        },
-                        .elem, .field => |base_index| {
-                            const base_ptr_type = ip.indexToKey(ip.typeOf(base_index.base)).ptr_type;
-                            switch (ptr.addr) {
-                                .elem => assert(base_ptr_type.flags.size == .Many),
-                                .field => {
-                                    assert(base_ptr_type.flags.size == .One);
-                                    switch (ip.indexToKey(base_ptr_type.child)) {
-                                        .anon_struct_type => |anon_struct_type| {
-                                            assert(ptr.addr == .field);
-                                            assert(base_index.index < anon_struct_type.types.len);
-                                        },
-                                        .struct_type => |struct_type| {
-                                            assert(ptr.addr == .field);
-                                            assert(base_index.index < struct_type.field_types.len);
-                                        },
-                                        .union_type => |union_key| {
-                                            const union_type = ip.loadUnionType(union_key);
-                                            assert(ptr.addr == .field);
-                                            assert(base_index.index < union_type.field_names.len);
-                                        },
-                                        .ptr_type => |slice_type| {
-                                            assert(ptr.addr == .field);
-                                            assert(slice_type.flags.size == .Slice);
-                                            assert(base_index.index < 2);
-                                        },
-                                        else => unreachable,
-                                    }
+                                .struct_type => |struct_type| {
+                                    assert(ptr.addr == .field);
+                                    assert(base_index.index < struct_type.field_types.len);
+                                },
+                                .union_type => |union_key| {
+                                    const union_type = ip.loadUnionType(union_key);
+                                    assert(ptr.addr == .field);
+                                    assert(base_index.index < union_type.field_names.len);
+                                },
+                                .ptr_type => |slice_type| {
+                                    assert(ptr.addr == .field);
+                                    assert(slice_type.flags.size == .Slice);
+                                    assert(base_index.index < 2);
                                 },
                                 else => unreachable,
                             }
-                            _ = ip.map.pop();
-                            const index_index = try ip.get(gpa, .{ .int = .{
-                                .ty = .usize_type,
-                                .storage = .{ .u64 = base_index.index },
-                            } });
-                            assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing);
-                            try ip.items.ensureUnusedCapacity(gpa, 1);
-                            ip.items.appendAssumeCapacity(.{
-                                .tag = switch (ptr.addr) {
-                                    .elem => .ptr_elem,
-                                    .field => .ptr_field,
-                                    else => unreachable,
-                                },
-                                .data = try ip.addExtra(gpa, PtrBaseIndex{
-                                    .ty = ptr.ty,
-                                    .base = base_index.base,
-                                    .index = index_index,
-                                }),
-                            });
                         },
+                        else => unreachable,
                     }
-                },
-                else => {
-                    // TODO: change Key.Ptr for slices to reference the manyptr value
-                    // rather than having an addr field directly. Then we can avoid
-                    // these problematic calls to pop(), get(), and getOrPutAdapted().
-                    assert(ptr_type.flags.size == .Slice);
                     _ = ip.map.pop();
-                    var new_key = key;
-                    new_key.ptr.ty = ip.slicePtrType(ptr.ty);
-                    new_key.ptr.len = .none;
-                    assert(ip.indexToKey(new_key.ptr.ty).ptr_type.flags.size == .Many);
-                    const ptr_index = try ip.get(gpa, new_key);
+                    const index_index = try ip.get(gpa, .{ .int = .{
+                        .ty = .usize_type,
+                        .storage = .{ .u64 = base_index.index },
+                    } });
                     assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing);
                     try ip.items.ensureUnusedCapacity(gpa, 1);
                     ip.items.appendAssumeCapacity(.{
-                        .tag = .ptr_slice,
-                        .data = try ip.addExtra(gpa, PtrSlice{
+                        .tag = switch (ptr.addr) {
+                            .elem => .ptr_elem,
+                            .field => .ptr_field,
+                            else => unreachable,
+                        },
+                        .data = try ip.addExtra(gpa, PtrBaseIndex{
                             .ty = ptr.ty,
-                            .ptr = ptr_index,
-                            .len = ptr.len,
+                            .base = base_index.base,
+                            .index = index_index,
                         }),
                     });
                 },
             }
-            assert(ptr.ty == ip.indexToKey(@as(Index, @enumFromInt(ip.items.len - 1))).ptr.ty);
         },
 
         .opt => |opt| {
@@ -6844,14 +6783,20 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al
                 .val = .none,
             } });
 
-            if (ip.isPointerType(new_ty)) return ip.get(gpa, .{ .ptr = .{
-                .ty = new_ty,
-                .addr = .{ .int = .zero_usize },
-                .len = switch (ip.indexToKey(new_ty).ptr_type.flags.size) {
-                    .One, .Many, .C => .none,
-                    .Slice => try ip.get(gpa, .{ .undef = .usize_type }),
-                },
-            } });
+            if (ip.isPointerType(new_ty)) switch (ip.indexToKey(new_ty).ptr_type.flags.size) {
+                .One, .Many, .C => return ip.get(gpa, .{ .ptr = .{
+                    .ty = new_ty,
+                    .addr = .{ .int = .zero_usize },
+                } }),
+                .Slice => return ip.get(gpa, .{ .slice = .{
+                    .ty = new_ty,
+                    .ptr = try ip.get(gpa, .{ .ptr = .{
+                        .ty = ip.slicePtrType(new_ty),
+                        .addr = .{ .int = .zero_usize },
+                    } }),
+                    .len = try ip.get(gpa, .{ .undef = .usize_type }),
+                } }),
+            };
         },
         else => switch (tags[@intFromEnum(val)]) {
             .func_decl => return getCoercedFuncDecl(ip, gpa, val, new_ty),
@@ -6929,11 +6874,18 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al
             },
             else => {},
         },
-        .ptr => |ptr| if (ip.isPointerType(new_ty))
+        .slice => |slice| if (ip.isPointerType(new_ty) and ip.indexToKey(new_ty).ptr_type.flags.size == .Slice)
+            return ip.get(gpa, .{ .slice = .{
+                .ty = new_ty,
+                .ptr = try ip.getCoerced(gpa, slice.ptr, ip.slicePtrType(new_ty)),
+                .len = slice.len,
+            } })
+        else if (ip.isIntegerType(new_ty))
+            return ip.getCoerced(gpa, slice.ptr, new_ty),
+        .ptr => |ptr| if (ip.isPointerType(new_ty) and ip.indexToKey(new_ty).ptr_type.flags.size != .Slice)
             return ip.get(gpa, .{ .ptr = .{
                 .ty = new_ty,
                 .addr = ptr.addr,
-                .len = ptr.len,
             } })
         else if (ip.isIntegerType(new_ty))
             switch (ptr.addr) {
@@ -6942,14 +6894,20 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al
             },
         .opt => |opt| switch (ip.indexToKey(new_ty)) {
             .ptr_type => |ptr_type| return switch (opt.val) {
-                .none => try ip.get(gpa, .{ .ptr = .{
-                    .ty = new_ty,
-                    .addr = .{ .int = .zero_usize },
-                    .len = switch (ptr_type.flags.size) {
-                        .One, .Many, .C => .none,
-                        .Slice => try ip.get(gpa, .{ .undef = .usize_type }),
-                    },
-                } }),
+                .none => switch (ptr_type.flags.size) {
+                    .One, .Many, .C => try ip.get(gpa, .{ .ptr = .{
+                        .ty = new_ty,
+                        .addr = .{ .int = .zero_usize },
+                    } }),
+                    .Slice => try ip.get(gpa, .{ .slice = .{
+                        .ty = new_ty,
+                        .ptr = try ip.get(gpa, .{ .ptr = .{
+                            .ty = ip.slicePtrType(new_ty),
+                            .addr = .{ .int = .zero_usize },
+                        } }),
+                        .len = try ip.get(gpa, .{ .undef = .usize_type }),
+                    } }),
+                },
                 else => |payload| try ip.getCoerced(gpa, payload, new_ty),
             },
             .opt_type => |child_type| return try ip.get(gpa, .{ .opt = .{
src/Module.zig
@@ -5278,9 +5278,12 @@ pub fn populateTestFunctions(
 
             const test_fn_fields = .{
                 // name
-                try mod.intern(.{ .ptr = .{
+                try mod.intern(.{ .slice = .{
                     .ty = .slice_const_u8_type,
-                    .addr = .{ .decl = test_name_decl_index },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = .manyptr_const_u8_type,
+                        .addr = .{ .decl = test_name_decl_index },
+                    } }),
                     .len = try mod.intern(.{ .int = .{
                         .ty = .usize_type,
                         .storage = .{ .u64 = test_decl_name.len },
@@ -5331,9 +5334,12 @@ pub fn populateTestFunctions(
             },
         });
         const new_val = decl.val;
-        const new_init = try mod.intern(.{ .ptr = .{
+        const new_init = try mod.intern(.{ .slice = .{
             .ty = new_ty.toIntern(),
-            .addr = .{ .decl = array_decl_index },
+            .ptr = try mod.intern(.{ .ptr = .{
+                .ty = new_ty.slicePtrFieldType(mod).toIntern(),
+                .addr = .{ .decl = array_decl_index },
+            } }),
             .len = (try mod.intValue(Type.usize, mod.test_functions.count())).toIntern(),
         } });
         ip.mutateVarInit(decl.val.toIntern(), new_init);
@@ -5423,16 +5429,17 @@ pub fn markReferencedDeclsAlive(mod: *Module, val: Value) Allocator.Error!void {
             .err_name => {},
             .payload => |payload| try mod.markReferencedDeclsAlive(Value.fromInterned(payload)),
         },
-        .ptr => |ptr| {
-            switch (ptr.addr) {
-                .decl => |decl| try mod.markDeclIndexAlive(decl),
-                .anon_decl => {},
-                .mut_decl => |mut_decl| try mod.markDeclIndexAlive(mut_decl.decl),
-                .int, .comptime_field => {},
-                .eu_payload, .opt_payload => |parent| try mod.markReferencedDeclsAlive(Value.fromInterned(parent)),
-                .elem, .field => |base_index| try mod.markReferencedDeclsAlive(Value.fromInterned(base_index.base)),
-            }
-            if (ptr.len != .none) try mod.markReferencedDeclsAlive(Value.fromInterned(ptr.len));
+        .slice => |slice| {
+            try mod.markReferencedDeclsAlive(Value.fromInterned(slice.ptr));
+            try mod.markReferencedDeclsAlive(Value.fromInterned(slice.len));
+        },
+        .ptr => |ptr| switch (ptr.addr) {
+            .decl => |decl| try mod.markDeclIndexAlive(decl),
+            .anon_decl => {},
+            .mut_decl => |mut_decl| try mod.markDeclIndexAlive(mut_decl.decl),
+            .int, .comptime_field => {},
+            .eu_payload, .opt_payload => |parent| try mod.markReferencedDeclsAlive(Value.fromInterned(parent)),
+            .elem, .field => |base_index| try mod.markReferencedDeclsAlive(Value.fromInterned(base_index.base)),
         },
         .opt => |opt| if (opt.val != .none) try mod.markReferencedDeclsAlive(Value.fromInterned(opt.val)),
         .aggregate => |aggregate| for (aggregate.storage.values()) |elem|
src/Sema.zig
@@ -17375,16 +17375,19 @@ fn zirBuiltinSrc(
             .sentinel = .zero_u8,
             .child = .u8_type,
         } });
-        break :v try ip.get(gpa, .{ .ptr = .{
+        break :v try ip.get(gpa, .{ .slice = .{
             .ty = .slice_const_u8_sentinel_0_type,
+            .ptr = try ip.get(gpa, .{ .ptr = .{
+                .ty = .manyptr_const_u8_sentinel_0_type,
+                .addr = .{ .anon_decl = .{
+                    .orig_ty = .slice_const_u8_sentinel_0_type,
+                    .val = try ip.get(gpa, .{ .aggregate = .{
+                        .ty = array_ty,
+                        .storage = .{ .bytes = bytes },
+                    } }),
+                } },
+            } }),
             .len = (try mod.intValue(Type.usize, bytes.len)).toIntern(),
-            .addr = .{ .anon_decl = .{
-                .orig_ty = .slice_const_u8_sentinel_0_type,
-                .val = try ip.get(gpa, .{ .aggregate = .{
-                    .ty = array_ty,
-                    .storage = .{ .bytes = bytes },
-                } }),
-            } },
         } });
     };
 
@@ -17396,16 +17399,19 @@ fn zirBuiltinSrc(
             .sentinel = .zero_u8,
             .child = .u8_type,
         } });
-        break :v try ip.get(gpa, .{ .ptr = .{
+        break :v try ip.get(gpa, .{ .slice = .{
             .ty = .slice_const_u8_sentinel_0_type,
+            .ptr = try ip.get(gpa, .{ .ptr = .{
+                .ty = .manyptr_const_u8_sentinel_0_type,
+                .addr = .{ .anon_decl = .{
+                    .orig_ty = .slice_const_u8_sentinel_0_type,
+                    .val = try ip.get(gpa, .{ .aggregate = .{
+                        .ty = array_ty,
+                        .storage = .{ .bytes = bytes },
+                    } }),
+                } },
+            } }),
             .len = (try mod.intValue(Type.usize, bytes.len)).toIntern(),
-            .addr = .{ .anon_decl = .{
-                .orig_ty = .slice_const_u8_sentinel_0_type,
-                .val = try ip.get(gpa, .{ .aggregate = .{
-                    .ty = array_ty,
-                    .storage = .{ .bytes = bytes },
-                } }),
-            } },
         } });
     };
 
@@ -17517,12 +17523,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .is_const = true,
                     },
                 })).toIntern();
-                break :v try mod.intern(.{ .ptr = .{
+                break :v try mod.intern(.{ .slice = .{
                     .ty = ptr_ty,
-                    .addr = .{ .anon_decl = .{
-                        .orig_ty = ptr_ty,
-                        .val = new_decl_val,
-                    } },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = Type.fromInterned(ptr_ty).slicePtrFieldType(mod).toIntern(),
+                        .addr = .{ .anon_decl = .{
+                            .orig_ty = ptr_ty,
+                            .val = new_decl_val,
+                        } },
+                    } }),
                     .len = (try mod.intValue(Type.usize, param_vals.len)).toIntern(),
                 } });
             };
@@ -17796,12 +17805,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                 .ty = new_decl_ty.toIntern(),
                                 .storage = .{ .bytes = name },
                             } });
-                            break :v try mod.intern(.{ .ptr = .{
+                            break :v try mod.intern(.{ .slice = .{
                                 .ty = .slice_const_u8_sentinel_0_type,
-                                .addr = .{ .anon_decl = .{
-                                    .val = new_decl_val,
-                                    .orig_ty = .slice_const_u8_sentinel_0_type,
-                                } },
+                                .ptr = try mod.intern(.{ .ptr = .{
+                                    .ty = .manyptr_const_u8_sentinel_0_type,
+                                    .addr = .{ .anon_decl = .{
+                                        .val = new_decl_val,
+                                        .orig_ty = .slice_const_u8_sentinel_0_type,
+                                    } },
+                                } }),
                                 .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
                             } });
                         };
@@ -17838,12 +17850,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     .ty = array_errors_ty.toIntern(),
                     .storage = .{ .elems = vals },
                 } });
-                break :v try mod.intern(.{ .ptr = .{
+                break :v try mod.intern(.{ .slice = .{
                     .ty = slice_errors_ty.toIntern(),
-                    .addr = .{ .anon_decl = .{
-                        .orig_ty = slice_errors_ty.toIntern(),
-                        .val = new_decl_val,
-                    } },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = slice_errors_ty.slicePtrFieldType(mod).toIntern(),
+                        .addr = .{ .anon_decl = .{
+                            .orig_ty = slice_errors_ty.toIntern(),
+                            .val = new_decl_val,
+                        } },
+                    } }),
                     .len = (try mod.intValue(Type.usize, vals.len)).toIntern(),
                 } });
             } else .none;
@@ -17925,12 +17940,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .ty = new_decl_ty.toIntern(),
                         .storage = .{ .bytes = name },
                     } });
-                    break :v try mod.intern(.{ .ptr = .{
+                    break :v try mod.intern(.{ .slice = .{
                         .ty = .slice_const_u8_sentinel_0_type,
-                        .addr = .{ .anon_decl = .{
-                            .val = new_decl_val,
-                            .orig_ty = .slice_const_u8_sentinel_0_type,
-                        } },
+                        .ptr = try mod.intern(.{ .ptr = .{
+                            .ty = .manyptr_const_u8_sentinel_0_type,
+                            .addr = .{ .anon_decl = .{
+                                .val = new_decl_val,
+                                .orig_ty = .slice_const_u8_sentinel_0_type,
+                            } },
+                        } }),
                         .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
                     } });
                 };
@@ -17963,12 +17981,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .is_const = true,
                     },
                 })).toIntern();
-                break :v try mod.intern(.{ .ptr = .{
+                break :v try mod.intern(.{ .slice = .{
                     .ty = ptr_ty,
-                    .addr = .{ .anon_decl = .{
-                        .val = new_decl_val,
-                        .orig_ty = ptr_ty,
-                    } },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = Type.fromInterned(ptr_ty).slicePtrFieldType(mod).toIntern(),
+                        .addr = .{ .anon_decl = .{
+                            .val = new_decl_val,
+                            .orig_ty = ptr_ty,
+                        } },
+                    } }),
                     .len = (try mod.intValue(Type.usize, enum_field_vals.len)).toIntern(),
                 } });
             };
@@ -18051,12 +18072,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .ty = new_decl_ty.toIntern(),
                         .storage = .{ .bytes = name },
                     } });
-                    break :v try mod.intern(.{ .ptr = .{
+                    break :v try mod.intern(.{ .slice = .{
                         .ty = .slice_const_u8_sentinel_0_type,
-                        .addr = .{ .anon_decl = .{
-                            .val = new_decl_val,
-                            .orig_ty = .slice_const_u8_sentinel_0_type,
-                        } },
+                        .ptr = try mod.intern(.{ .ptr = .{
+                            .ty = .manyptr_const_u8_sentinel_0_type,
+                            .addr = .{ .anon_decl = .{
+                                .val = new_decl_val,
+                                .orig_ty = .slice_const_u8_sentinel_0_type,
+                            } },
+                        } }),
                         .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
                     } });
                 };
@@ -18097,12 +18121,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .is_const = true,
                     },
                 })).toIntern();
-                break :v try mod.intern(.{ .ptr = .{
+                break :v try mod.intern(.{ .slice = .{
                     .ty = ptr_ty,
-                    .addr = .{ .anon_decl = .{
-                        .orig_ty = ptr_ty,
-                        .val = new_decl_val,
-                    } },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = Type.fromInterned(ptr_ty).slicePtrFieldType(mod).toIntern(),
+                        .addr = .{ .anon_decl = .{
+                            .orig_ty = ptr_ty,
+                            .val = new_decl_val,
+                        } },
+                    } }),
                     .len = (try mod.intValue(Type.usize, union_field_vals.len)).toIntern(),
                 } });
             };
@@ -18199,12 +18226,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                     .ty = new_decl_ty.toIntern(),
                                     .storage = .{ .bytes = bytes },
                                 } });
-                                break :v try mod.intern(.{ .ptr = .{
+                                break :v try mod.intern(.{ .slice = .{
                                     .ty = .slice_const_u8_sentinel_0_type,
-                                    .addr = .{ .anon_decl = .{
-                                        .val = new_decl_val,
-                                        .orig_ty = .slice_const_u8_sentinel_0_type,
-                                    } },
+                                    .ptr = try mod.intern(.{ .ptr = .{
+                                        .ty = .manyptr_const_u8_sentinel_0_type,
+                                        .addr = .{ .anon_decl = .{
+                                            .val = new_decl_val,
+                                            .orig_ty = .slice_const_u8_sentinel_0_type,
+                                        } },
+                                    } }),
                                     .len = (try mod.intValue(Type.usize, bytes.len)).toIntern(),
                                 } });
                             };
@@ -18259,12 +18289,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                             .ty = new_decl_ty.toIntern(),
                             .storage = .{ .bytes = name },
                         } });
-                        break :v try mod.intern(.{ .ptr = .{
+                        break :v try mod.intern(.{ .slice = .{
                             .ty = .slice_const_u8_sentinel_0_type,
-                            .addr = .{ .anon_decl = .{
-                                .val = new_decl_val,
-                                .orig_ty = .slice_const_u8_sentinel_0_type,
-                            } },
+                            .ptr = try mod.intern(.{ .ptr = .{
+                                .ty = .manyptr_const_u8_sentinel_0_type,
+                                .addr = .{ .anon_decl = .{
+                                    .val = new_decl_val,
+                                    .orig_ty = .slice_const_u8_sentinel_0_type,
+                                } },
+                            } }),
                             .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
                         } });
                     };
@@ -18315,12 +18348,15 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         .is_const = true,
                     },
                 })).toIntern();
-                break :v try mod.intern(.{ .ptr = .{
+                break :v try mod.intern(.{ .slice = .{
                     .ty = ptr_ty,
-                    .addr = .{ .anon_decl = .{
-                        .orig_ty = ptr_ty,
-                        .val = new_decl_val,
-                    } },
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = Type.fromInterned(ptr_ty).slicePtrFieldType(mod).toIntern(),
+                        .addr = .{ .anon_decl = .{
+                            .orig_ty = ptr_ty,
+                            .val = new_decl_val,
+                        } },
+                    } }),
                     .len = (try mod.intValue(Type.usize, struct_field_vals.len)).toIntern(),
                 } });
             };
@@ -18453,12 +18489,15 @@ fn typeInfoDecls(
             .is_const = true,
         },
     })).toIntern();
-    return try mod.intern(.{ .ptr = .{
+    return try mod.intern(.{ .slice = .{
         .ty = ptr_ty,
-        .addr = .{ .anon_decl = .{
-            .orig_ty = ptr_ty,
-            .val = new_decl_val,
-        } },
+        .ptr = try mod.intern(.{ .ptr = .{
+            .ty = Type.fromInterned(ptr_ty).slicePtrFieldType(mod).toIntern(),
+            .addr = .{ .anon_decl = .{
+                .orig_ty = ptr_ty,
+                .val = new_decl_val,
+            } },
+        } }),
         .len = (try mod.intValue(Type.usize, decl_vals.items.len)).toIntern(),
     } });
 }
@@ -18498,12 +18537,15 @@ fn typeInfoNamespaceDecls(
                 .ty = new_decl_ty.toIntern(),
                 .storage = .{ .bytes = name },
             } });
-            break :v try mod.intern(.{ .ptr = .{
+            break :v try mod.intern(.{ .slice = .{
                 .ty = .slice_const_u8_sentinel_0_type,
-                .addr = .{ .anon_decl = .{
-                    .orig_ty = .slice_const_u8_sentinel_0_type,
-                    .val = new_decl_val,
-                } },
+                .ptr = try mod.intern(.{ .ptr = .{
+                    .ty = .manyptr_const_u8_sentinel_0_type,
+                    .addr = .{ .anon_decl = .{
+                        .orig_ty = .slice_const_u8_sentinel_0_type,
+                        .val = new_decl_val,
+                    } },
+                } }),
                 .len = (try mod.intValue(Type.usize, name.len)).toIntern(),
             } });
         };
@@ -22738,9 +22780,12 @@ fn ptrCastFull(
             if (dest_info.flags.size == .Slice and src_info.flags.size != .Slice) {
                 if (ptr_val.isUndef(mod)) return mod.undefRef(dest_ty);
                 const arr_len = try mod.intValue(Type.usize, Type.fromInterned(src_info.child).arrayLen(mod));
-                return Air.internedToRef((try mod.intern(.{ .ptr = .{
+                return Air.internedToRef((try mod.intern(.{ .slice = .{
                     .ty = dest_ty.toIntern(),
-                    .addr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr,
+                    .ptr = try mod.intern(.{ .ptr = .{
+                        .ty = dest_ty.slicePtrFieldType(mod).toIntern(),
+                        .addr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr,
+                    } }),
                     .len = arr_len.toIntern(),
                 } })));
             } else {
@@ -28765,21 +28810,24 @@ fn coerceExtra(
                     if (inst_child_ty.structFieldCount(mod) == 0) {
                         // Optional slice is represented with a null pointer so
                         // we use a dummy pointer value with the required alignment.
-                        return Air.internedToRef((try mod.intern(.{ .ptr = .{
+                        return Air.internedToRef((try mod.intern(.{ .slice = .{
                             .ty = dest_ty.toIntern(),
-                            .addr = .{ .int = if (dest_info.flags.alignment != .none)
-                                (try mod.intValue(
-                                    Type.usize,
-                                    dest_info.flags.alignment.toByteUnitsOptional().?,
-                                )).toIntern()
-                            else
-                                try mod.intern_pool.getCoercedInts(
-                                    mod.gpa,
-                                    mod.intern_pool.indexToKey(
-                                        (try Type.fromInterned(dest_info.child).lazyAbiAlignment(mod)).toIntern(),
-                                    ).int,
-                                    .usize_type,
-                                ) },
+                            .ptr = try mod.intern(.{ .ptr = .{
+                                .ty = dest_ty.slicePtrFieldType(mod).toIntern(),
+                                .addr = .{ .int = if (dest_info.flags.alignment != .none)
+                                    (try mod.intValue(
+                                        Type.usize,
+                                        dest_info.flags.alignment.toByteUnitsOptional().?,
+                                    )).toIntern()
+                                else
+                                    try mod.intern_pool.getCoercedInts(
+                                        mod.gpa,
+                                        mod.intern_pool.indexToKey(
+                                            (try Type.fromInterned(dest_info.child).lazyAbiAlignment(mod)).toIntern(),
+                                        ).int,
+                                        .usize_type,
+                                    ) },
+                            } }),
                             .len = (try mod.intValue(Type.usize, 0)).toIntern(),
                         } })));
                     }
@@ -31276,7 +31324,7 @@ fn beginComptimePtrLoad(
                         },
                         Value.slice_len_index => TypedValue{
                             .ty = Type.usize,
-                            .val = Value.fromInterned(ip.indexToKey(try tv.val.intern(tv.ty, mod)).ptr.len),
+                            .val = Value.fromInterned(ip.indexToKey(try tv.val.intern(tv.ty, mod)).slice.len),
                         },
                         else => unreachable,
                     };
@@ -31445,13 +31493,16 @@ fn coerceArrayPtrToSlice(
     if (try sema.resolveValue(inst)) |val| {
         const ptr_array_ty = sema.typeOf(inst);
         const array_ty = ptr_array_ty.childType(mod);
-        const slice_val = try mod.intern(.{ .ptr = .{
+        const slice_val = try mod.intern(.{ .slice = .{
             .ty = dest_ty.toIntern(),
-            .addr = switch (mod.intern_pool.indexToKey(val.toIntern())) {
-                .undef => .{ .int = try mod.intern(.{ .undef = .usize_type }) },
-                .ptr => |ptr| ptr.addr,
-                else => unreachable,
-            },
+            .ptr = try mod.intern(.{ .ptr = .{
+                .ty = dest_ty.slicePtrFieldType(mod).toIntern(),
+                .addr = switch (mod.intern_pool.indexToKey(val.toIntern())) {
+                    .undef => .{ .int = try mod.intern(.{ .undef = .usize_type }) },
+                    .ptr => |ptr| ptr.addr,
+                    else => unreachable,
+                },
+            } }),
             .len = (try mod.intValue(Type.usize, array_ty.arrayLen(mod))).toIntern(),
         } });
         return Air.internedToRef(slice_val);
@@ -35211,51 +35262,43 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
                 (try val.getUnsignedIntAdvanced(mod, sema)).?,
             ),
         },
+        .slice => |slice| {
+            const ptr = try sema.resolveLazyValue(Value.fromInterned(slice.ptr));
+            const len = try sema.resolveLazyValue(Value.fromInterned(slice.len));
+            if (ptr.toIntern() == slice.ptr and len.toIntern() == slice.len) return val;
+            return Value.fromInterned(try mod.intern(.{ .slice = .{
+                .ty = slice.ty,
+                .ptr = ptr.toIntern(),
+                .len = len.toIntern(),
+            } }));
+        },
         .ptr => |ptr| {
-            const resolved_len = switch (ptr.len) {
-                .none => .none,
-                else => (try sema.resolveLazyValue(Value.fromInterned(ptr.len))).toIntern(),
-            };
             switch (ptr.addr) {
-                .decl, .mut_decl, .anon_decl => return if (resolved_len == ptr.len)
-                    val
-                else
-                    Value.fromInterned((try mod.intern(.{ .ptr = .{
-                        .ty = ptr.ty,
-                        .addr = switch (ptr.addr) {
-                            .decl => |decl| .{ .decl = decl },
-                            .mut_decl => |mut_decl| .{ .mut_decl = mut_decl },
-                            .anon_decl => |anon_decl| .{ .anon_decl = anon_decl },
-                            else => unreachable,
-                        },
-                        .len = resolved_len,
-                    } }))),
+                .decl, .mut_decl, .anon_decl => return val,
                 .comptime_field => |field_val| {
                     const resolved_field_val =
                         (try sema.resolveLazyValue(Value.fromInterned(field_val))).toIntern();
-                    return if (resolved_field_val == field_val and resolved_len == ptr.len)
+                    return if (resolved_field_val == field_val)
                         val
                     else
                         Value.fromInterned((try mod.intern(.{ .ptr = .{
                             .ty = ptr.ty,
                             .addr = .{ .comptime_field = resolved_field_val },
-                            .len = resolved_len,
                         } })));
                 },
                 .int => |int| {
                     const resolved_int = (try sema.resolveLazyValue(Value.fromInterned(int))).toIntern();
-                    return if (resolved_int == int and resolved_len == ptr.len)
+                    return if (resolved_int == int)
                         val
                     else
                         Value.fromInterned((try mod.intern(.{ .ptr = .{
                             .ty = ptr.ty,
                             .addr = .{ .int = resolved_int },
-                            .len = resolved_len,
                         } })));
                 },
                 .eu_payload, .opt_payload => |base| {
                     const resolved_base = (try sema.resolveLazyValue(Value.fromInterned(base))).toIntern();
-                    return if (resolved_base == base and resolved_len == ptr.len)
+                    return if (resolved_base == base)
                         val
                     else
                         Value.fromInterned((try mod.intern(.{ .ptr = .{
@@ -35265,12 +35308,11 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
                                 .opt_payload => .{ .opt_payload = resolved_base },
                                 else => unreachable,
                             },
-                            .len = ptr.len,
                         } })));
                 },
                 .elem, .field => |base_index| {
                     const resolved_base = (try sema.resolveLazyValue(Value.fromInterned(base_index.base))).toIntern();
-                    return if (resolved_base == base_index.base and resolved_len == ptr.len)
+                    return if (resolved_base == base_index.base)
                         val
                     else
                         Value.fromInterned((try mod.intern(.{ .ptr = .{
@@ -35286,7 +35328,6 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
                                 } },
                                 else => unreachable,
                             },
-                            .len = ptr.len,
                         } })));
                 },
             }
src/type.zig
@@ -426,6 +426,7 @@ pub const Type = struct {
             .empty_enum_value,
             .float,
             .ptr,
+            .slice,
             .opt,
             .aggregate,
             .un,
@@ -651,6 +652,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -758,6 +760,7 @@ pub const Type = struct {
             .empty_enum_value,
             .float,
             .ptr,
+            .slice,
             .opt,
             .aggregate,
             .un,
@@ -1073,6 +1076,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -1434,6 +1438,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -1660,6 +1665,7 @@ pub const Type = struct {
             .empty_enum_value,
             .float,
             .ptr,
+            .slice,
             .opt,
             .aggregate,
             .un,
@@ -2195,6 +2201,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -2538,6 +2545,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
@@ -2731,6 +2739,7 @@ pub const Type = struct {
                 .empty_enum_value,
                 .float,
                 .ptr,
+                .slice,
                 .opt,
                 .aggregate,
                 .un,
src/TypedValue.zig
@@ -264,52 +264,56 @@ pub fn print(
             .float => |float| switch (float.storage) {
                 inline else => |x| return writer.print("{d}", .{@as(f64, @floatCast(x))}),
             },
-            .ptr => |ptr| {
-                if (ptr.addr == .int) {
-                    switch (ip.indexToKey(ptr.addr.int)) {
-                        .int => |i| switch (i.storage) {
-                            inline else => |addr| return writer.print("{x:0>8}", .{addr}),
-                        },
-                        .undef => return writer.writeAll("undefined"),
-                        else => unreachable,
-                    }
+            .slice => |slice| {
+                const ptr_ty = switch (ip.indexToKey(slice.ptr)) {
+                    .ptr => |ptr| ty: {
+                        if (ptr.addr == .int) return print(.{
+                            .ty = Type.fromInterned(ptr.ty),
+                            .val = Value.fromInterned(slice.ptr),
+                        }, writer, level - 1, mod);
+                        break :ty ip.indexToKey(ptr.ty).ptr_type;
+                    },
+                    .undef => |ptr_ty| ip.indexToKey(ptr_ty).ptr_type,
+                    else => unreachable,
+                };
+                if (level == 0) {
+                    return writer.writeAll(".{ ... }");
                 }
-
-                const ptr_ty = ip.indexToKey(ty.toIntern()).ptr_type;
-                if (ptr_ty.flags.size == .Slice) {
-                    if (level == 0) {
-                        return writer.writeAll(".{ ... }");
-                    }
-                    const elem_ty = Type.fromInterned(ptr_ty.child);
-                    const len = Value.fromInterned(ptr.len).toUnsignedInt(mod);
-                    if (elem_ty.eql(Type.u8, mod)) str: {
-                        const max_len = @min(len, max_string_len);
-                        var buf: [max_string_len]u8 = undefined;
-                        for (buf[0..max_len], 0..) |*c, i| {
-                            const maybe_elem = try val.maybeElemValue(mod, i);
-                            const elem = maybe_elem orelse return writer.writeAll(".{ (reinterpreted data) }");
-                            if (elem.isUndef(mod)) break :str;
-                            c.* = @as(u8, @intCast(elem.toUnsignedInt(mod)));
-                        }
-                        const truncated = if (len > max_string_len) " (truncated)" else "";
-                        return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated });
-                    }
-                    try writer.writeAll(".{ ");
-                    const max_len = @min(len, max_aggregate_items);
-                    for (0..max_len) |i| {
-                        if (i != 0) try writer.writeAll(", ");
+                const elem_ty = Type.fromInterned(ptr_ty.child);
+                const len = Value.fromInterned(slice.len).toUnsignedInt(mod);
+                if (elem_ty.eql(Type.u8, mod)) str: {
+                    const max_len = @min(len, max_string_len);
+                    var buf: [max_string_len]u8 = undefined;
+                    for (buf[0..max_len], 0..) |*c, i| {
                         const maybe_elem = try val.maybeElemValue(mod, i);
-                        const elem = maybe_elem orelse return writer.writeAll("(reinterpreted data) }");
-                        try print(.{
-                            .ty = elem_ty,
-                            .val = elem,
-                        }, writer, level - 1, mod);
-                    }
-                    if (len > max_aggregate_items) {
-                        try writer.writeAll(", ...");
+                        const elem = maybe_elem orelse return writer.writeAll(".{ (reinterpreted data) }");
+                        if (elem.isUndef(mod)) break :str;
+                        c.* = @as(u8, @intCast(elem.toUnsignedInt(mod)));
                     }
-                    return writer.writeAll(" }");
+                    const truncated = if (len > max_string_len) " (truncated)" else "";
+                    return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated });
+                }
+                try writer.writeAll(".{ ");
+                const max_len = @min(len, max_aggregate_items);
+                for (0..max_len) |i| {
+                    if (i != 0) try writer.writeAll(", ");
+                    const maybe_elem = try val.maybeElemValue(mod, i);
+                    const elem = maybe_elem orelse return writer.writeAll("(reinterpreted data) }");
+                    try print(.{
+                        .ty = elem_ty,
+                        .val = elem,
+                    }, writer, level - 1, mod);
                 }
+                if (len > max_aggregate_items) {
+                    try writer.writeAll(", ...");
+                }
+                return writer.writeAll(" }");
+            },
+            .ptr => |ptr| {
+                if (ptr.addr == .int) {}
+
+                const ptr_ty = ip.indexToKey(ty.toIntern()).ptr_type;
+                if (ptr_ty.flags.size == .Slice) {}
 
                 switch (ptr.addr) {
                     .decl => |decl_index| {
src/value.zig
@@ -194,10 +194,7 @@ pub const Value = struct {
         const ip = &mod.intern_pool;
         return switch (mod.intern_pool.indexToKey(val.toIntern())) {
             .enum_literal => |enum_literal| enum_literal,
-            .ptr => |ptr| switch (ptr.len) {
-                .none => unreachable,
-                else => try arrayToIpString(val, Value.fromInterned(ptr.len).toUnsignedInt(mod), mod),
-            },
+            .slice => |slice| try arrayToIpString(val, Value.fromInterned(slice.len).toUnsignedInt(mod), mod),
             .aggregate => |aggregate| switch (aggregate.storage) {
                 .bytes => |bytes| try ip.getOrPutString(mod.gpa, bytes),
                 .elems => try arrayToIpString(val, ty.arrayLen(mod), mod),
@@ -217,10 +214,7 @@ pub const Value = struct {
     pub fn toAllocatedBytes(val: Value, ty: Type, allocator: Allocator, mod: *Module) ![]u8 {
         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 => try arrayToAllocatedBytes(val, Value.fromInterned(ptr.len).toUnsignedInt(mod), allocator, mod),
-            },
+            .slice => |slice| try arrayToAllocatedBytes(val, Value.fromInterned(slice.len).toUnsignedInt(mod), allocator, mod),
             .aggregate => |aggregate| switch (aggregate.storage) {
                 .bytes => |bytes| try allocator.dupe(u8, bytes),
                 .elems => try arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
@@ -286,12 +280,11 @@ pub const Value = struct {
             },
             .slice => {
                 const pl = val.castTag(.slice).?.data;
-                const ptr = try pl.ptr.intern(ty.slicePtrFieldType(mod), mod);
-                var ptr_key = ip.indexToKey(ptr).ptr;
-                assert(ptr_key.len == .none);
-                ptr_key.ty = ty.toIntern();
-                ptr_key.len = try pl.len.intern(Type.usize, mod);
-                return mod.intern(.{ .ptr = ptr_key });
+                return mod.intern(.{ .slice = .{
+                    .ty = ty.toIntern(),
+                    .len = try pl.len.intern(Type.usize, mod),
+                    .ptr = try pl.ptr.intern(ty.slicePtrFieldType(mod), mod),
+                } });
             },
             .bytes => {
                 const pl = val.castTag(.bytes).?.data;
@@ -374,6 +367,7 @@ pub const Value = struct {
             .enum_tag,
             .empty_enum_value,
             .float,
+            .ptr,
             => val,
 
             .error_union => |error_union| switch (error_union.val) {
@@ -381,13 +375,10 @@ pub const Value = struct {
                 .payload => |payload| Tag.eu_payload.create(arena, Value.fromInterned(payload)),
             },
 
-            .ptr => |ptr| switch (ptr.len) {
-                .none => val,
-                else => |len| Tag.slice.create(arena, .{
-                    .ptr = val.slicePtr(mod),
-                    .len = Value.fromInterned(len),
-                }),
-            },
+            .slice => |slice| Tag.slice.create(arena, .{
+                .ptr = Value.fromInterned(slice.ptr),
+                .len = Value.fromInterned(slice.len),
+            }),
 
             .opt => |opt| switch (opt.val) {
                 .none => val,
@@ -1538,6 +1529,7 @@ pub const Value = struct {
 
     pub fn isComptimeMutablePtr(val: Value, mod: *Module) bool {
         return switch (mod.intern_pool.indexToKey(val.toIntern())) {
+            .slice => |slice| return Value.fromInterned(slice.ptr).isComptimeMutablePtr(mod),
             .ptr => |ptr| switch (ptr.addr) {
                 .mut_decl, .comptime_field => true,
                 .eu_payload, .opt_payload => |base_ptr| Value.fromInterned(base_ptr).isComptimeMutablePtr(mod),
@@ -1600,9 +1592,8 @@ pub const Value = struct {
 
     pub fn sliceLen(val: Value, mod: *Module) u64 {
         const ip = &mod.intern_pool;
-        const ptr = ip.indexToKey(val.toIntern()).ptr;
-        return switch (ptr.len) {
-            .none => switch (ip.indexToKey(switch (ptr.addr) {
+        return switch (ip.indexToKey(val.toIntern())) {
+            .ptr => |ptr| switch (ip.indexToKey(switch (ptr.addr) {
                 .decl => |decl| mod.declPtr(decl).ty.toIntern(),
                 .mut_decl => |mut_decl| mod.declPtr(mut_decl.decl).ty.toIntern(),
                 .anon_decl => |anon_decl| ip.typeOf(anon_decl.val),
@@ -1612,7 +1603,8 @@ pub const Value = struct {
                 .array_type => |array_type| array_type.len,
                 else => 1,
             },
-            else => Value.fromInterned(ptr.len).toUnsignedInt(mod),
+            .slice => |slice| Value.fromInterned(slice.len).toUnsignedInt(mod),
+            else => unreachable,
         };
     }
 
@@ -1636,6 +1628,7 @@ pub const Value = struct {
                 .undef => |ty| Value.fromInterned((try mod.intern(.{
                     .undef = Type.fromInterned(ty).elemType2(mod).toIntern(),
                 }))),
+                .slice => |slice| return Value.fromInterned(slice.ptr).maybeElemValue(mod, index),
                 .ptr => |ptr| switch (ptr.addr) {
                     .decl => |decl| mod.declPtr(decl).val.maybeElemValue(mod, index),
                     .anon_decl => |anon_decl| Value.fromInterned(anon_decl.val).maybeElemValue(mod, index),
@@ -1800,25 +1793,23 @@ pub const Value = struct {
     ) Allocator.Error!Value {
         const elem_ty = elem_ptr_ty.childType(mod);
         const ptr_val = switch (mod.intern_pool.indexToKey(val.toIntern())) {
-            .ptr => |ptr| ptr: {
-                switch (ptr.addr) {
-                    .elem => |elem| if (Type.fromInterned(mod.intern_pool.typeOf(elem.base)).elemType2(mod).eql(elem_ty, mod))
-                        return Value.fromInterned((try mod.intern(.{ .ptr = .{
-                            .ty = elem_ptr_ty.toIntern(),
-                            .addr = .{ .elem = .{
-                                .base = elem.base,
-                                .index = elem.index + index,
-                            } },
-                        } }))),
-                    else => {},
-                }
-                break :ptr switch (ptr.len) {
-                    .none => val,
-                    else => val.slicePtr(mod),
-                };
-            },
+            .slice => |slice| Value.fromInterned(slice.ptr),
             else => val,
         };
+        switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) {
+            .ptr => |ptr| switch (ptr.addr) {
+                .elem => |elem| if (Type.fromInterned(mod.intern_pool.typeOf(elem.base)).elemType2(mod).eql(elem_ty, mod))
+                    return Value.fromInterned((try mod.intern(.{ .ptr = .{
+                        .ty = elem_ptr_ty.toIntern(),
+                        .addr = .{ .elem = .{
+                            .base = elem.base,
+                            .index = elem.index + index,
+                        } },
+                    } }))),
+                else => {},
+            },
+            else => {},
+        }
         var ptr_ty_key = mod.intern_pool.indexToKey(elem_ptr_ty.toIntern()).ptr_type;
         assert(ptr_ty_key.flags.size != .Slice);
         ptr_ty_key.flags.size = .Many;
@@ -1850,12 +1841,9 @@ pub const Value = struct {
             else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
                 .undef => true,
                 .simple_value => |v| v == .undefined,
-                .ptr => |ptr| switch (ptr.len) {
-                    .none => false,
-                    else => for (0..@as(usize, @intCast(Value.fromInterned(ptr.len).toUnsignedInt(mod)))) |index| {
-                        if (try (try val.elemValue(mod, index)).anyUndef(mod)) break true;
-                    } else false,
-                },
+                .slice => |slice| for (0..@intCast(Value.fromInterned(slice.len).toUnsignedInt(mod))) |idx| {
+                    if (try (try val.elemValue(mod, idx)).anyUndef(mod)) break true;
+                } else false,
                 .aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {
                     const elem = mod.intern_pool.indexToKey(val.toIntern()).aggregate.storage.values()[i];
                     if (try anyUndef(Value.fromInterned(elem), mod)) break true;