Commit dfd91abfe1

Jacob Young <jacobly0@users.noreply.github.com>
2023-05-21 05:24:39
InternPool: add more pointer values
1 parent cbf304d
src/codegen/llvm.zig
@@ -3374,9 +3374,15 @@ pub const DeclGen = struct {
                                     val;
                                 break :ptr addrspace_casted_ptr;
                             },
-                            .decl => |decl| try lowerDeclRefValue(dg, tv, decl),
-                            .mut_decl => |mut_decl| try lowerDeclRefValue(dg, tv, mut_decl.decl),
+                            .decl => |decl| try dg.lowerDeclRefValue(tv, decl),
+                            .mut_decl => |mut_decl| try dg.lowerDeclRefValue(tv, mut_decl.decl),
                             .int => |int| dg.lowerIntAsPtr(mod.intern_pool.indexToKey(int).int),
+                            .eu_payload,
+                            .opt_payload,
+                            .elem,
+                            .field,
+                            => try dg.lowerParentPtr(tv.val, tv.ty.ptrInfo(mod).bit_offset % 8 == 0),
+                            .comptime_field => unreachable,
                         };
                         switch (ptr.len) {
                             .none => return ptr_val,
@@ -4091,6 +4097,132 @@ pub const DeclGen = struct {
                 .decl => |decl| dg.lowerParentPtrDecl(ptr_val, decl),
                 .mut_decl => |mut_decl| dg.lowerParentPtrDecl(ptr_val, mut_decl.decl),
                 .int => |int| dg.lowerIntAsPtr(mod.intern_pool.indexToKey(int).int),
+                .eu_payload => |eu_ptr| {
+                    const parent_llvm_ptr = try dg.lowerParentPtr(eu_ptr.toValue(), true);
+
+                    const eu_ty = mod.intern_pool.typeOf(eu_ptr).toType().childType(mod);
+                    const payload_ty = eu_ty.errorUnionPayload(mod);
+                    if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
+                        // In this case, we represent pointer to error union the same as pointer
+                        // to the payload.
+                        return parent_llvm_ptr;
+                    }
+
+                    const payload_offset: u8 = if (payload_ty.abiAlignment(mod) > Type.anyerror.abiSize(mod)) 2 else 1;
+                    const llvm_u32 = dg.context.intType(32);
+                    const indices: [2]*llvm.Value = .{
+                        llvm_u32.constInt(0, .False),
+                        llvm_u32.constInt(payload_offset, .False),
+                    };
+                    const eu_llvm_ty = try dg.lowerType(eu_ty);
+                    return eu_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                },
+                .opt_payload => |opt_ptr| {
+                    const parent_llvm_ptr = try dg.lowerParentPtr(opt_ptr.toValue(), true);
+
+                    const opt_ty = mod.intern_pool.typeOf(opt_ptr).toType().childType(mod);
+                    const payload_ty = opt_ty.optionalChild(mod);
+                    if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod) or
+                        payload_ty.optionalReprIsPayload(mod))
+                    {
+                        // In this case, we represent pointer to optional the same as pointer
+                        // to the payload.
+                        return parent_llvm_ptr;
+                    }
+
+                    const llvm_u32 = dg.context.intType(32);
+                    const indices: [2]*llvm.Value = .{
+                        llvm_u32.constInt(0, .False),
+                        llvm_u32.constInt(0, .False),
+                    };
+                    const opt_llvm_ty = try dg.lowerType(opt_ty);
+                    return opt_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                },
+                .comptime_field => unreachable,
+                .elem => |elem_ptr| {
+                    const parent_llvm_ptr = try dg.lowerParentPtr(elem_ptr.base.toValue(), true);
+
+                    const llvm_usize = try dg.lowerType(Type.usize);
+                    const indices: [1]*llvm.Value = .{
+                        llvm_usize.constInt(elem_ptr.index, .False),
+                    };
+                    const elem_llvm_ty = try dg.lowerType(ptr.ty.toType().childType(mod));
+                    return elem_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                },
+                .field => |field_ptr| {
+                    const parent_llvm_ptr = try dg.lowerParentPtr(field_ptr.base.toValue(), byte_aligned);
+                    const parent_ty = mod.intern_pool.typeOf(field_ptr.base).toType().childType(mod);
+
+                    const field_index = @intCast(u32, field_ptr.index);
+                    const llvm_u32 = dg.context.intType(32);
+                    switch (parent_ty.zigTypeTag(mod)) {
+                        .Union => {
+                            if (parent_ty.containerLayout(mod) == .Packed) {
+                                return parent_llvm_ptr;
+                            }
+
+                            const layout = parent_ty.unionGetLayout(mod);
+                            if (layout.payload_size == 0) {
+                                // In this case a pointer to the union and a pointer to any
+                                // (void) payload is the same.
+                                return parent_llvm_ptr;
+                            }
+                            const llvm_pl_index = if (layout.tag_size == 0)
+                                0
+                            else
+                                @boolToInt(layout.tag_align >= layout.payload_align);
+                            const indices: [2]*llvm.Value = .{
+                                llvm_u32.constInt(0, .False),
+                                llvm_u32.constInt(llvm_pl_index, .False),
+                            };
+                            const parent_llvm_ty = try dg.lowerType(parent_ty);
+                            return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                        },
+                        .Struct => {
+                            if (parent_ty.containerLayout(mod) == .Packed) {
+                                if (!byte_aligned) return parent_llvm_ptr;
+                                const llvm_usize = dg.context.intType(target.cpu.arch.ptrBitWidth());
+                                const base_addr = parent_llvm_ptr.constPtrToInt(llvm_usize);
+                                // count bits of fields before this one
+                                const prev_bits = b: {
+                                    var b: usize = 0;
+                                    for (parent_ty.structFields(mod).values()[0..field_index]) |field| {
+                                        if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
+                                        b += @intCast(usize, field.ty.bitSize(mod));
+                                    }
+                                    break :b b;
+                                };
+                                const byte_offset = llvm_usize.constInt(prev_bits / 8, .False);
+                                const field_addr = base_addr.constAdd(byte_offset);
+                                const final_llvm_ty = dg.context.pointerType(0);
+                                return field_addr.constIntToPtr(final_llvm_ty);
+                            }
+
+                            const parent_llvm_ty = try dg.lowerType(parent_ty);
+                            if (llvmField(parent_ty, field_index, mod)) |llvm_field| {
+                                const indices: [2]*llvm.Value = .{
+                                    llvm_u32.constInt(0, .False),
+                                    llvm_u32.constInt(llvm_field.index, .False),
+                                };
+                                return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                            } else {
+                                const llvm_index = llvm_u32.constInt(@boolToInt(parent_ty.hasRuntimeBitsIgnoreComptime(mod)), .False);
+                                const indices: [1]*llvm.Value = .{llvm_index};
+                                return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                            }
+                        },
+                        .Pointer => {
+                            assert(parent_ty.isSlice(mod));
+                            const indices: [2]*llvm.Value = .{
+                                llvm_u32.constInt(0, .False),
+                                llvm_u32.constInt(field_index, .False),
+                            };
+                            const parent_llvm_ty = try dg.lowerType(parent_ty);
+                            return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
+                        },
+                        else => unreachable,
+                    }
+                },
             },
             else => unreachable,
         };
src/Air.zig
@@ -1292,7 +1292,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index, ip: InternPool) Type {
         .try_ptr,
         => return air.getRefType(datas[inst].ty_pl.ty),
 
-        .interned => return ip.indexToKey(datas[inst].interned).typeOf().toType(),
+        .interned => return ip.typeOf(datas[inst].interned).toType(),
 
         .not,
         .bitcast,
src/InternPool.zig
@@ -510,6 +510,16 @@ pub const Key = union(enum) {
                 runtime_index: RuntimeIndex,
             },
             int: Index,
+            eu_payload: Index,
+            opt_payload: Index,
+            comptime_field: Index,
+            elem: BaseIndex,
+            field: BaseIndex,
+
+            pub const BaseIndex = struct {
+                base: Index,
+                index: u64,
+            };
         };
     };
 
@@ -599,6 +609,7 @@ pub const Key = union(enum) {
 
             .ptr => |ptr| {
                 std.hash.autoHash(hasher, ptr.ty);
+                std.hash.autoHash(hasher, ptr.len);
                 // Int-to-ptr pointers are hashed separately than decl-referencing pointers.
                 // This is sound due to pointer provenance rules.
                 std.hash.autoHash(hasher, @as(@typeInfo(Key.Ptr.Addr).Union.tag_type.?, ptr.addr));
@@ -607,6 +618,11 @@ pub const Key = union(enum) {
                     .decl => |decl| std.hash.autoHash(hasher, decl),
                     .mut_decl => |mut_decl| std.hash.autoHash(hasher, mut_decl),
                     .int => |int| std.hash.autoHash(hasher, int),
+                    .eu_payload => |eu_payload| std.hash.autoHash(hasher, eu_payload),
+                    .opt_payload => |opt_payload| std.hash.autoHash(hasher, opt_payload),
+                    .comptime_field => |comptime_field| std.hash.autoHash(hasher, comptime_field),
+                    .elem => |elem| std.hash.autoHash(hasher, elem),
+                    .field => |field| std.hash.autoHash(hasher, field),
                 }
             },
 
@@ -719,7 +735,7 @@ pub const Key = union(enum) {
 
             .ptr => |a_info| {
                 const b_info = b.ptr;
-                if (a_info.ty != b_info.ty) return false;
+                if (a_info.ty != b_info.ty or a_info.len != b_info.len) return false;
 
                 const AddrTag = @typeInfo(Key.Ptr.Addr).Union.tag_type.?;
                 if (@as(AddrTag, a_info.addr) != @as(AddrTag, b_info.addr)) return false;
@@ -729,6 +745,11 @@ pub const Key = union(enum) {
                     .decl => |a_decl| a_decl == b_info.addr.decl,
                     .mut_decl => |a_mut_decl| std.meta.eql(a_mut_decl, b_info.addr.mut_decl),
                     .int => |a_int| a_int == b_info.addr.int,
+                    .eu_payload => |a_eu_payload| a_eu_payload == b_info.addr.eu_payload,
+                    .opt_payload => |a_opt_payload| a_opt_payload == b_info.addr.opt_payload,
+                    .comptime_field => |a_comptime_field| a_comptime_field == b_info.addr.comptime_field,
+                    .elem => |a_elem| std.meta.eql(a_elem, b_info.addr.elem),
+                    .field => |a_field| std.meta.eql(a_field, b_info.addr.field),
                 };
             },
 
@@ -1375,6 +1396,26 @@ pub const Tag = enum(u8) {
     /// Only pointer types are allowed to have this encoding. Optional types must use
     /// `opt_payload` or `opt_null`.
     ptr_int,
+    /// A pointer to the payload of an error union.
+    /// data is Index of a pointer value to the error union.
+    /// In order to use this encoding, one must ensure that the `InternPool`
+    /// already contains the payload pointer type corresponding to this payload.
+    ptr_eu_payload,
+    /// A pointer to the payload of an optional.
+    /// data is Index of a pointer value to the optional.
+    /// In order to use this encoding, one must ensure that the `InternPool`
+    /// already contains the payload pointer type corresponding to this payload.
+    ptr_opt_payload,
+    /// data is extra index of PtrComptimeField, which contains the pointer type and field value.
+    ptr_comptime_field,
+    /// A pointer to an array element.
+    /// data is extra index of PtrBaseIndex, which contains the base array and element index.
+    /// In order to use this encoding, one must ensure that the `InternPool`
+    /// already contains the elem pointer type corresponding to this payload.
+    ptr_elem,
+    /// A pointer to a container field.
+    /// data is extra index of PtrBaseIndex, which contains the base container and field index.
+    ptr_field,
     /// A slice.
     /// data is extra index of PtrSlice, which contains the ptr and len values
     /// In order to use this encoding, one must ensure that the `InternPool`
@@ -1753,6 +1794,17 @@ pub const PtrInt = struct {
     addr: Index,
 };
 
+pub const PtrComptimeField = struct {
+    ty: Index,
+    field_val: Index,
+};
+
+pub const PtrBaseIndex = struct {
+    ty: Index,
+    base: Index,
+    index: Index,
+};
+
 pub const PtrSlice = struct {
     ptr: Index,
     len: Index,
@@ -1956,10 +2008,10 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
         },
 
         .type_slice => {
-            const ptr_ty_index = @intToEnum(Index, data);
-            var result = indexToKey(ip, ptr_ty_index);
-            result.ptr_type.size = .Slice;
-            return result;
+            const ptr_type_index = @intToEnum(Index, data);
+            var result = indexToKey(ip, ptr_type_index).ptr_type;
+            result.size = .Slice;
+            return .{ .ptr_type = result };
         },
 
         .type_optional => .{ .opt_type = @intToEnum(Index, data) },
@@ -2063,7 +2115,7 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
             // The existence of `opt_payload` guarantees that the optional type will be
             // stored in the `InternPool`.
             const opt_ty = ip.getAssumeExists(.{
-                .opt_type = indexToKey(ip, payload_val).typeOf(),
+                .opt_type = ip.typeOf(payload_val),
             });
             return .{ .opt = .{
                 .ty = opt_ty,
@@ -2108,14 +2160,59 @@ pub fn indexToKey(ip: InternPool, index: Index) Key {
                 .addr = .{ .int = info.addr },
             } };
         },
+        .ptr_eu_payload => {
+            const ptr_eu_index = @intToEnum(Index, data);
+            var ptr_type = ip.indexToKey(ip.typeOf(ptr_eu_index)).ptr_type;
+            ptr_type.elem_type = ip.indexToKey(ptr_type.elem_type).error_union_type.payload_type;
+            return .{ .ptr = .{
+                .ty = ip.getAssumeExists(.{ .ptr_type = ptr_type }),
+                .addr = .{ .eu_payload = ptr_eu_index },
+            } };
+        },
+        .ptr_opt_payload => {
+            const ptr_opt_index = @intToEnum(Index, data);
+            var ptr_type = ip.indexToKey(ip.typeOf(ptr_opt_index)).ptr_type;
+            ptr_type.elem_type = ip.indexToKey(ptr_type.elem_type).opt_type;
+            return .{ .ptr = .{
+                .ty = ip.getAssumeExists(.{ .ptr_type = ptr_type }),
+                .addr = .{ .opt_payload = ptr_opt_index },
+            } };
+        },
+        .ptr_comptime_field => {
+            const info = ip.extraData(PtrComptimeField, data);
+            return .{ .ptr = .{
+                .ty = info.ty,
+                .addr = .{ .comptime_field = info.field_val },
+            } };
+        },
+        .ptr_elem => {
+            const info = ip.extraData(PtrBaseIndex, data);
+            return .{ .ptr = .{
+                .ty = info.ty,
+                .addr = .{ .elem = .{
+                    .base = info.base,
+                    .index = ip.indexToKey(info.index).int.storage.u64,
+                } },
+            } };
+        },
+        .ptr_field => {
+            const info = ip.extraData(PtrBaseIndex, data);
+            return .{ .ptr = .{
+                .ty = info.ty,
+                .addr = .{ .field = .{
+                    .base = info.base,
+                    .index = ip.indexToKey(info.index).int.storage.u64,
+                } },
+            } };
+        },
         .ptr_slice => {
             const info = ip.extraData(PtrSlice, data);
             const ptr = ip.indexToKey(info.ptr).ptr;
-            var ptr_ty = ip.indexToKey(ptr.ty);
-            assert(ptr_ty.ptr_type.size == .Many);
-            ptr_ty.ptr_type.size = .Slice;
+            var ptr_type = ip.indexToKey(ptr.ty).ptr_type;
+            assert(ptr_type.size == .Many);
+            ptr_type.size = .Slice;
             return .{ .ptr = .{
-                .ty = ip.getAssumeExists(ptr_ty),
+                .ty = ip.getAssumeExists(.{ .ptr_type = ptr_type }),
                 .addr = ptr.addr,
                 .len = info.len,
             } };
@@ -2301,9 +2398,7 @@ fn indexToKeyBigInt(ip: InternPool, limb_index: u32, positive: bool) Key {
 pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
     const adapter: KeyAdapter = .{ .intern_pool = ip };
     const gop = try ip.map.getOrPutAdapted(gpa, key, adapter);
-    if (gop.found_existing) {
-        return @intToEnum(Index, gop.index);
-    }
+    if (gop.found_existing) return @intToEnum(Index, gop.index);
     try ip.items.ensureUnusedCapacity(gpa, 1);
     switch (key) {
         .int_type => |int_type| {
@@ -2322,11 +2417,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
             if (ptr_type.size == .Slice) {
                 var new_key = key;
                 new_key.ptr_type.size = .Many;
-                const ptr_ty_index = try get(ip, gpa, new_key);
+                const ptr_type_index = try get(ip, gpa, new_key);
                 try ip.items.ensureUnusedCapacity(gpa, 1);
                 ip.items.appendAssumeCapacity(.{
                     .tag = .type_slice,
-                    .data = @enumToInt(ptr_ty_index),
+                    .data = @enumToInt(ptr_type_index),
                 });
                 return @intToEnum(Index, ip.items.len - 1);
             }
@@ -2584,64 +2679,98 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
 
         .extern_func => @panic("TODO"),
 
-        .ptr => |ptr| switch (ptr.len) {
-            .none => {
-                assert(ip.indexToKey(ptr.ty).ptr_type.size != .Slice);
-                switch (ptr.addr) {
-                    .@"var" => |@"var"| ip.items.appendAssumeCapacity(.{
-                        .tag = .ptr_var,
-                        .data = try ip.addExtra(gpa, PtrVar{
-                            .ty = ptr.ty,
-                            .init = @"var".init,
-                            .owner_decl = @"var".owner_decl,
-                            .lib_name = @"var".lib_name,
-                            .flags = .{
-                                .is_const = @"var".is_const,
-                                .is_threadlocal = @"var".is_threadlocal,
-                                .is_weak_linkage = @"var".is_weak_linkage,
-                            },
+        .ptr => |ptr| {
+            const ptr_type = ip.indexToKey(ptr.ty).ptr_type;
+            switch (ptr.len) {
+                .none => {
+                    assert(ptr_type.size != .Slice);
+                    switch (ptr.addr) {
+                        .@"var" => |@"var"| ip.items.appendAssumeCapacity(.{
+                            .tag = .ptr_var,
+                            .data = try ip.addExtra(gpa, PtrVar{
+                                .ty = ptr.ty,
+                                .init = @"var".init,
+                                .owner_decl = @"var".owner_decl,
+                                .lib_name = @"var".lib_name,
+                                .flags = .{
+                                    .is_const = @"var".is_const,
+                                    .is_threadlocal = @"var".is_threadlocal,
+                                    .is_weak_linkage = @"var".is_weak_linkage,
+                                },
+                            }),
                         }),
-                    }),
-                    .decl => |decl| ip.items.appendAssumeCapacity(.{
-                        .tag = .ptr_decl,
-                        .data = try ip.addExtra(gpa, PtrDecl{
-                            .ty = ptr.ty,
-                            .decl = decl,
+                        .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,
+                        .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,
+                            }),
                         }),
-                    }),
-                    .int => |int| ip.items.appendAssumeCapacity(.{
-                        .tag = .ptr_int,
-                        .data = try ip.addExtra(gpa, PtrInt{
-                            .ty = ptr.ty,
-                            .addr = int,
+                        .int => |int| ip.items.appendAssumeCapacity(.{
+                            .tag = .ptr_int,
+                            .data = try ip.addExtra(gpa, PtrInt{
+                                .ty = ptr.ty,
+                                .addr = int,
+                            }),
                         }),
-                    }),
-                }
-            },
-            else => {
-                assert(ip.indexToKey(ptr.ty).ptr_type.size == .Slice);
-                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.size == .Many);
-                const ptr_index = try get(ip, gpa, new_key);
-                try ip.items.ensureUnusedCapacity(gpa, 1);
-                ip.items.appendAssumeCapacity(.{
-                    .tag = .ptr_slice,
-                    .data = try ip.addExtra(gpa, PtrSlice{
-                        .ptr = ptr_index,
-                        .len = ptr.len,
-                    }),
-                });
-            },
+                        .eu_payload, .opt_payload => |data| ip.items.appendAssumeCapacity(.{
+                            .tag = switch (ptr.addr) {
+                                .eu_payload => .ptr_eu_payload,
+                                .opt_payload => .ptr_opt_payload,
+                                else => unreachable,
+                            },
+                            .data = @enumToInt(data),
+                        }),
+                        .comptime_field => |field_val| ip.items.appendAssumeCapacity(.{
+                            .tag = .ptr_comptime_field,
+                            .data = try ip.addExtra(gpa, PtrComptimeField{
+                                .ty = ptr.ty,
+                                .field_val = field_val,
+                            }),
+                        }),
+                        .elem, .field => |base_index| {
+                            const index_index = try get(ip, gpa, .{ .int = .{
+                                .ty = .usize_type,
+                                .storage = .{ .u64 = base_index.index },
+                            } });
+                            try ip.items.ensureUnusedCapacity(gpa, 1);
+                            ip.items.appendAssumeCapacity(.{
+                                .tag = .ptr_elem,
+                                .data = try ip.addExtra(gpa, PtrBaseIndex{
+                                    .ty = ptr.ty,
+                                    .base = base_index.base,
+                                    .index = index_index,
+                                }),
+                            });
+                        },
+                    }
+                },
+                else => {
+                    assert(ptr_type.size == .Slice);
+                    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.size == .Many);
+                    const ptr_index = try get(ip, gpa, new_key);
+                    try ip.items.ensureUnusedCapacity(gpa, 1);
+                    ip.items.appendAssumeCapacity(.{
+                        .tag = .ptr_slice,
+                        .data = try ip.addExtra(gpa, PtrSlice{
+                            .ptr = ptr_index,
+                            .len = ptr.len,
+                        }),
+                    });
+                },
+            }
+            assert(ptr.ty == ip.indexToKey(@intToEnum(Index, ip.items.len - 1)).ptr.ty);
         },
 
         .opt => |opt| {
@@ -3683,6 +3812,11 @@ fn dumpFallible(ip: InternPool, arena: Allocator) anyerror!void {
             .ptr_decl => @sizeOf(PtrDecl),
             .ptr_mut_decl => @sizeOf(PtrMutDecl),
             .ptr_int => @sizeOf(PtrInt),
+            .ptr_eu_payload => 0,
+            .ptr_opt_payload => 0,
+            .ptr_comptime_field => @sizeOf(PtrComptimeField),
+            .ptr_elem => @sizeOf(PtrBaseIndex),
+            .ptr_field => @sizeOf(PtrBaseIndex),
             .ptr_slice => @sizeOf(PtrSlice),
             .opt_null => 0,
             .opt_payload => 0,
@@ -3757,6 +3891,10 @@ pub fn unionPtr(ip: *InternPool, index: Module.Union.Index) *Module.Union {
     return ip.allocated_unions.at(@enumToInt(index));
 }
 
+pub fn unionPtrConst(ip: InternPool, index: Module.Union.Index) *const Module.Union {
+    return ip.allocated_unions.at(@enumToInt(index));
+}
+
 pub fn inferredErrorSetPtr(ip: *InternPool, index: Module.Fn.InferredErrorSet.Index) *Module.Fn.InferredErrorSet {
     return ip.allocated_inferred_error_sets.at(@enumToInt(index));
 }
src/Module.zig
@@ -6783,7 +6783,7 @@ pub fn intType(mod: *Module, signedness: std.builtin.Signedness, bits: u16) Allo
 
 pub fn arrayType(mod: *Module, info: InternPool.Key.ArrayType) Allocator.Error!Type {
     if (std.debug.runtime_safety and info.sentinel != .none) {
-        const sent_ty = mod.intern_pool.indexToKey(info.sentinel).typeOf();
+        const sent_ty = mod.intern_pool.typeOf(info.sentinel);
         assert(sent_ty == info.child);
     }
     const i = try intern(mod, .{ .array_type = info });
@@ -6802,7 +6802,7 @@ pub fn optionalType(mod: *Module, child_type: InternPool.Index) Allocator.Error!
 
 pub fn ptrType(mod: *Module, info: InternPool.Key.PtrType) Allocator.Error!Type {
     if (std.debug.runtime_safety and info.sentinel != .none) {
-        const sent_ty = mod.intern_pool.indexToKey(info.sentinel).typeOf();
+        const sent_ty = mod.intern_pool.typeOf(info.sentinel);
         assert(sent_ty == info.elem_type);
     }
     const i = try intern(mod, .{ .ptr_type = info });
src/print_zir.zig
@@ -1192,7 +1192,7 @@ const Writer = struct {
             .field => {
                 const field_name = self.code.nullTerminatedString(extra.data.field_name_start);
                 try self.writeInstRef(stream, extra.data.obj_ptr);
-                try stream.print(", {}", .{std.zig.fmtId(field_name)});
+                try stream.print(", \"{}\"", .{std.zig.fmtEscapes(field_name)});
             },
         }
         try stream.writeAll(", [");
src/Sema.zig
@@ -28473,6 +28473,178 @@ fn beginComptimePtrLoad(
                         .ty_without_well_defined_layout = if (!layout_defined) decl.ty else null,
                     };
                 },
+                .eu_payload, .opt_payload => |container_ptr| blk: {
+                    const container_ty = mod.intern_pool.typeOf(container_ptr).toType().childType(mod);
+                    const payload_ty = ptr.ty.toType().childType(mod);
+                    var deref = try sema.beginComptimePtrLoad(block, src, container_ptr.toValue(), container_ty);
+
+                    // eu_payload_ptr and opt_payload_ptr never have a well-defined layout
+                    if (deref.parent != null) {
+                        deref.parent = null;
+                        deref.ty_without_well_defined_layout = container_ty;
+                    }
+
+                    if (deref.pointee) |*tv| {
+                        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;
+                        if (coerce_in_mem_ok) {
+                            const payload_val = switch (ptr_val.tag()) {
+                                .eu_payload_ptr => if (tv.val.castTag(.eu_payload)) |some| some.data else {
+                                    return sema.fail(block, src, "attempt to unwrap error: {s}", .{tv.val.castTag(.@"error").?.data.name});
+                                },
+                                .opt_payload_ptr => if (tv.val.castTag(.opt_payload)) |some| some.data else opt: {
+                                    if (tv.val.isNull(mod)) return sema.fail(block, src, "attempt to use null value", .{});
+                                    break :opt tv.val;
+                                },
+                                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 = mod.intern_pool.typeOf(comptime_field).toType();
+                    break :blk ComptimePtrLoadKit{
+                        .parent = null,
+                        .pointee = .{ .ty = field_ty, .val = comptime_field.toValue() },
+                        .is_mutable = false,
+                        .ty_without_well_defined_layout = field_ty,
+                    };
+                },
+                .elem => |elem_ptr| blk: {
+                    const elem_ty = ptr.ty.toType().childType(mod);
+                    var deref = try sema.beginComptimePtrLoad(block, src, elem_ptr.base.toValue(), null);
+
+                    // This code assumes that elem_ptrs have been "flattened" in order for direct dereference
+                    // to succeed, meaning that elem ptrs of the same elem_ty are coalesced. Here we check that
+                    // our parent is not an elem_ptr with the same elem_ty, since that would be "unflattened"
+                    switch (mod.intern_pool.indexToKey(elem_ptr.base)) {
+                        .ptr => |base_ptr| switch (base_ptr.addr) {
+                            .elem => |base_elem| assert(!mod.intern_pool.typeOf(base_elem.base).toType().elemType2(mod).eql(elem_ty, mod)),
+                            else => {},
+                        },
+                        else => {},
+                    }
+
+                    if (elem_ptr.index != 0) {
+                        if (elem_ty.hasWellDefinedLayout(mod)) {
+                            if (deref.parent) |*parent| {
+                                // Update the byte offset (in-place)
+                                const elem_size = try sema.typeAbiSize(elem_ty);
+                                const offset = parent.byte_offset + elem_size * elem_ptr.index;
+                                parent.byte_offset = try sema.usizeCast(block, src, offset);
+                            }
+                        } else {
+                            deref.parent = null;
+                            deref.ty_without_well_defined_layout = elem_ty;
+                        }
+                    }
+
+                    // 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;
+                    } else false;
+                    if (!ty_matches) {
+                        deref.pointee = null;
+                        break :blk deref;
+                    }
+
+                    var array_tv = deref.pointee.?;
+                    const check_len = array_tv.ty.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 N = try sema.usizeCast(block, src, load_ty.arrayLenIncludingSentinel(mod));
+                            deref.pointee = if (elem_ptr.index + N <= check_len) TypedValue{
+                                .ty = try Type.array(sema.arena, N, null, elem_ty, mod),
+                                .val = try array_tv.val.sliceArray(mod, sema.arena, elem_ptr.index, elem_ptr.index + N),
+                            } else null;
+                            break :blk deref;
+                        }
+                    }
+
+                    if (elem_ptr.index >= check_len) {
+                        deref.pointee = null;
+                        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,
+                            };
+                            break :blk deref;
+                        }
+                    }
+                    deref.pointee = TypedValue{
+                        .ty = elem_ty,
+                        .val = try array_tv.val.elemValue(mod, elem_ptr.index),
+                    };
+                    break :blk deref;
+                },
+                .field => |field_ptr| blk: {
+                    const field_index = @intCast(u32, field_ptr.index);
+                    const container_ty = mod.intern_pool.typeOf(field_ptr.base).toType().childType(mod);
+                    var deref = try sema.beginComptimePtrLoad(block, src, field_ptr.base.toValue(), container_ty);
+
+                    if (container_ty.hasWellDefinedLayout(mod)) {
+                        const struct_obj = mod.typeToStruct(container_ty);
+                        if (struct_obj != null and struct_obj.?.layout == .Packed) {
+                            // packed structs are not byte addressable
+                            deref.parent = null;
+                        } else if (deref.parent) |*parent| {
+                            // Update the byte offset (in-place)
+                            try sema.resolveTypeLayout(container_ty);
+                            const field_offset = container_ty.structFieldOffset(field_index, mod);
+                            parent.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + field_offset);
+                        }
+                    } else {
+                        deref.parent = null;
+                        deref.ty_without_well_defined_layout = container_ty;
+                    }
+
+                    const tv = deref.pointee orelse {
+                        deref.pointee = null;
+                        break :blk deref;
+                    };
+                    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;
+                    if (!coerce_in_mem_ok) {
+                        deref.pointee = null;
+                        break :blk deref;
+                    }
+
+                    if (container_ty.isSlice(mod)) {
+                        const slice_val = tv.val.castTag(.slice).?.data;
+                        deref.pointee = switch (field_index) {
+                            Value.Payload.Slice.ptr_index => TypedValue{
+                                .ty = container_ty.slicePtrFieldType(mod),
+                                .val = slice_val.ptr,
+                            },
+                            Value.Payload.Slice.len_index => TypedValue{
+                                .ty = Type.usize,
+                                .val = slice_val.len,
+                            },
+                            else => unreachable,
+                        };
+                    } else {
+                        const field_ty = container_ty.structFieldType(field_index, mod);
+                        deref.pointee = TypedValue{
+                            .ty = field_ty,
+                            .val = try tv.val.fieldValue(tv.ty, mod, field_index),
+                        };
+                    }
+                    break :blk deref;
+                },
             },
             else => unreachable,
         },
@@ -28559,11 +28731,12 @@ fn coerceArrayPtrToSlice(
     if (try sema.resolveMaybeUndefVal(inst)) |val| {
         const ptr_array_ty = sema.typeOf(inst);
         const array_ty = ptr_array_ty.childType(mod);
-        const slice_val = try Value.Tag.slice.create(sema.arena, .{
-            .ptr = val,
-            .len = try mod.intValue(Type.usize, array_ty.arrayLen(mod)),
-        });
-        return sema.addConstant(dest_ty, slice_val);
+        const slice_val = try mod.intern(.{ .ptr = .{
+            .ty = dest_ty.ip_index,
+            .addr = mod.intern_pool.indexToKey(val.ip_index).ptr.addr,
+            .len = (try mod.intValue(Type.usize, array_ty.arrayLen(mod))).ip_index,
+        } });
+        return sema.addConstant(dest_ty, slice_val.toValue());
     }
     try sema.requireRuntimeBlock(block, inst_src, null);
     return block.addTyOp(.array_to_slice, dest_ty, inst);
@@ -29769,6 +29942,7 @@ fn analyzeSlice(
 
     const start = try sema.coerce(block, Type.usize, uncasted_start, start_src);
     const new_ptr = try sema.analyzePtrArithmetic(block, src, ptr, start, .ptr_add, ptr_src, start_src);
+    const new_ptr_ty = sema.typeOf(new_ptr);
 
     // true if and only if the end index of the slice, implicitly or explicitly, equals
     // the length of the underlying object being sliced. we might learn the length of the
@@ -29914,7 +30088,7 @@ fn analyzeSlice(
                 const end_int = end_val.getUnsignedInt(mod).?;
                 const sentinel_index = try sema.usizeCast(block, end_src, end_int - start_int);
 
-                const elem_ptr = try ptr_val.elemPtr(sema.typeOf(new_ptr), sema.arena, sentinel_index, sema.mod);
+                const elem_ptr = try ptr_val.elemPtr(new_ptr_ty, sema.arena, sentinel_index, sema.mod);
                 const res = try sema.pointerDerefExtra(block, src, elem_ptr, elem_ty, false);
                 const actual_sentinel = switch (res) {
                     .runtime_load => break :sentinel_check,
@@ -29960,7 +30134,7 @@ fn analyzeSlice(
         try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src, false);
     const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);
 
-    const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo(mod);
+    const new_ptr_ty_info = new_ptr_ty.ptrInfo(mod);
     const new_allowzero = new_ptr_ty_info.@"allowzero" and sema.typeOf(ptr).ptrSize(mod) != .C;
 
     if (opt_new_len_val) |new_len_val| {
@@ -30009,7 +30183,11 @@ fn analyzeSlice(
         };
 
         if (!new_ptr_val.isUndef(mod)) {
-            return sema.addConstant(return_ty, new_ptr_val);
+            return sema.addConstant(return_ty, (try mod.intern_pool.getCoerced(
+                mod.gpa,
+                try new_ptr_val.intern(new_ptr_ty, mod),
+                return_ty.ip_index,
+            )).toValue());
         }
 
         // Special case: @as([]i32, undefined)[x..x]
src/value.zig
@@ -559,37 +559,46 @@ pub const Value = struct {
     /// Asserts that the value is representable as an array of bytes.
     /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
     pub fn toAllocatedBytes(val: Value, ty: Type, allocator: Allocator, mod: *Module) ![]u8 {
-        switch (val.tag()) {
-            .bytes => {
-                const bytes = val.castTag(.bytes).?.data;
-                const adjusted_len = bytes.len - @boolToInt(ty.sentinel(mod) != null);
-                const adjusted_bytes = bytes[0..adjusted_len];
-                return allocator.dupe(u8, adjusted_bytes);
-            },
-            .str_lit => {
-                const str_lit = val.castTag(.str_lit).?.data;
-                const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
-                return allocator.dupe(u8, bytes);
-            },
-            .enum_literal => return allocator.dupe(u8, val.castTag(.enum_literal).?.data),
-            .repeated => {
-                const byte = @intCast(u8, val.castTag(.repeated).?.data.toUnsignedInt(mod));
-                const result = try allocator.alloc(u8, @intCast(usize, ty.arrayLen(mod)));
-                @memset(result, byte);
-                return result;
-            },
-            .decl_ref => {
-                const decl_index = val.castTag(.decl_ref).?.data;
-                const decl = mod.declPtr(decl_index);
-                const decl_val = try decl.value();
-                return decl_val.toAllocatedBytes(decl.ty, allocator, mod);
+        switch (val.ip_index) {
+            .none => switch (val.tag()) {
+                .bytes => {
+                    const bytes = val.castTag(.bytes).?.data;
+                    const adjusted_len = bytes.len - @boolToInt(ty.sentinel(mod) != null);
+                    const adjusted_bytes = bytes[0..adjusted_len];
+                    return allocator.dupe(u8, adjusted_bytes);
+                },
+                .str_lit => {
+                    const str_lit = val.castTag(.str_lit).?.data;
+                    const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
+                    return allocator.dupe(u8, bytes);
+                },
+                .enum_literal => return allocator.dupe(u8, val.castTag(.enum_literal).?.data),
+                .repeated => {
+                    const byte = @intCast(u8, val.castTag(.repeated).?.data.toUnsignedInt(mod));
+                    const result = try allocator.alloc(u8, @intCast(usize, ty.arrayLen(mod)));
+                    @memset(result, byte);
+                    return result;
+                },
+                .decl_ref => {
+                    const decl_index = val.castTag(.decl_ref).?.data;
+                    const decl = mod.declPtr(decl_index);
+                    const decl_val = try decl.value();
+                    return decl_val.toAllocatedBytes(decl.ty, allocator, mod);
+                },
+                .the_only_possible_value => return &[_]u8{},
+                .slice => {
+                    const slice = val.castTag(.slice).?.data;
+                    return arrayToAllocatedBytes(slice.ptr, slice.len.toUnsignedInt(mod), allocator, mod);
+                },
+                else => return arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
             },
-            .the_only_possible_value => return &[_]u8{},
-            .slice => {
-                const slice = val.castTag(.slice).?.data;
-                return arrayToAllocatedBytes(slice.ptr, slice.len.toUnsignedInt(mod), allocator, mod);
+            else => switch (mod.intern_pool.indexToKey(val.ip_index)) {
+                .ptr => |ptr| switch (ptr.len) {
+                    .none => unreachable,
+                    else => return arrayToAllocatedBytes(val, ptr.len.toValue().toUnsignedInt(mod), allocator, mod),
+                },
+                else => unreachable,
             },
-            else => return arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
         }
     }
 
@@ -605,6 +614,16 @@ pub const Value = struct {
     pub fn intern(val: Value, ty: Type, mod: *Module) Allocator.Error!InternPool.Index {
         if (val.ip_index != .none) return mod.intern_pool.getCoerced(mod.gpa, val.ip_index, ty.ip_index);
         switch (val.tag()) {
+            .elem_ptr => {
+                const pl = val.castTag(.elem_ptr).?.data;
+                return mod.intern(.{ .ptr = .{
+                    .ty = ty.ip_index,
+                    .addr = .{ .elem = .{
+                        .base = pl.array_ptr.ip_index,
+                        .index = pl.index,
+                    } },
+                } });
+            },
             .slice => {
                 const pl = val.castTag(.slice).?.data;
                 const ptr = try pl.ptr.intern(ty.slicePtrFieldType(mod), mod);
@@ -2601,7 +2620,10 @@ pub const Value = struct {
                     .@"var" => unreachable,
                     .decl => |decl| mod.declPtr(decl).val.elemValue(mod, index),
                     .mut_decl => |mut_decl| mod.declPtr(mut_decl.decl).val.elemValue(mod, index),
-                    .int => unreachable,
+                    .int, .eu_payload, .opt_payload => unreachable,
+                    .comptime_field => |field_val| field_val.toValue().elemValue(mod, index),
+                    .elem => |elem| elem.base.toValue().elemValue(mod, index + elem.index),
+                    .field => unreachable,
                 },
                 .aggregate => |aggregate| switch (aggregate.storage) {
                     .elems => |elems| elems[index].toValue(),