diff --git a/lib/compiler/resinator/ico.zig b/lib/compiler/resinator/ico.zig index 310db6ece289..e90325544e31 100644 --- a/lib/compiler/resinator/ico.zig +++ b/lib/compiler/resinator/ico.zig @@ -232,7 +232,7 @@ test "icon data size too small" { try std.testing.expectError(error.ImpossibleDataSize, read(std.testing.allocator, fbs.reader(), data.len)); } -pub const ImageFormat = enum { +pub const ImageFormat = enum(u2) { dib, png, riff, diff --git a/lib/std/net.zig b/lib/std/net.zig index b12fb1932d8f..ddbb41470531 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -271,13 +271,16 @@ pub const Ip4Address = extern struct { sa: posix.sockaddr.in, pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address { - var result = Ip4Address{ + var result: Ip4Address = .{ .sa = .{ .port = mem.nativeToBig(u16, port), .addr = undefined, }, }; const out_ptr = mem.asBytes(&result.sa.addr); + if (@inComptime()) { + @memset(out_ptr, 0); // TODO: #19634 + } var x: u8 = 0; var index: u8 = 0; @@ -389,6 +392,9 @@ pub const Ip6Address = extern struct { .addr = undefined, }, }; + if (@inComptime()) { + @memset(std.mem.asBytes(&result.sa.addr), 0); // TODO: #19634 + } var ip_slice: *[16]u8 = result.sa.addr[0..]; var tail: [16]u8 = undefined; @@ -507,6 +513,9 @@ pub const Ip6Address = extern struct { .addr = undefined, }, }; + if (@inComptime()) { + @memset(std.mem.asBytes(&result.sa.addr), 0); // TODO: #19634 + } var ip_slice: *[16]u8 = result.sa.addr[0..]; var tail: [16]u8 = undefined; diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index 02c721e7cffb..d76df4d04e24 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -214,6 +214,10 @@ pub fn PackedIntArrayEndian(comptime Int: type, comptime endian: Endian, comptim /// or, more likely, an array literal. pub fn init(ints: [int_count]Int) Self { var self: Self = undefined; + if (@inComptime()) { + // TODO: #19634 + @memset(&self.bytes, 0xAA); + } for (ints, 0..) |int, i| self.set(i, int); return self; } @@ -221,6 +225,10 @@ pub fn PackedIntArrayEndian(comptime Int: type, comptime endian: Endian, comptim /// Initialize all entries of a packed array to the same value. pub fn initAllTo(int: Int) Self { var self: Self = undefined; + if (@inComptime()) { + // TODO: #19634 + @memset(&self.bytes, 0xAA); + } self.setAll(int); return self; } diff --git a/src/InternPool.zig b/src/InternPool.zig index 15dba62e075e..f2495773cb26 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -565,7 +565,7 @@ pub const OptionalNullTerminatedString = enum(u32) { /// * decl val (so that we can analyze the value lazily) /// * decl ref (so that we can analyze the reference lazily) pub const CaptureValue = packed struct(u32) { - tag: enum { @"comptime", runtime, decl_val, decl_ref }, + tag: enum(u2) { @"comptime", runtime, decl_val, decl_ref }, idx: u30, pub fn wrap(val: Unwrapped) CaptureValue { @@ -1026,22 +1026,76 @@ pub const Key = union(enum) { pub const Ptr = struct { /// This is the pointer type, not the element type. ty: Index, - /// The value of the address that the pointer points to. - addr: Addr, + /// The base address which this pointer is offset from. + base_addr: BaseAddr, + /// The offset of this pointer from `base_addr` in bytes. + byte_offset: u64, - pub const Addr = union(enum) { - const Tag = @typeInfo(Addr).Union.tag_type.?; + pub const BaseAddr = union(enum) { + const Tag = @typeInfo(BaseAddr).Union.tag_type.?; + /// Points to the value of a single `Decl`, which may be constant or a `variable`. decl: DeclIndex, + + /// Points to the value of a single comptime alloc stored in `Sema`. comptime_alloc: ComptimeAllocIndex, + + /// Points to a single unnamed constant value. anon_decl: AnonDecl, + + /// Points to a comptime field of a struct. Index is the field's value. + /// + /// TODO: this exists because these fields are semantically mutable. We + /// should probably change the language so that this isn't the case. comptime_field: Index, - int: Index, + + /// A pointer with a fixed integer address, usually from `@ptrFromInt`. + /// + /// The address is stored entirely by `byte_offset`, which will be positive + /// and in-range of a `usize`. The base address is, for all intents and purposes, 0. + int, + + /// A pointer to the payload of an error union. Index is the error union pointer. + /// To ensure a canonical representation, the type of the base pointer must: + /// * be a one-pointer + /// * be `const`, `volatile` and `allowzero` + /// * have alignment 1 + /// * have the same address space as this pointer + /// * have a host size, bit offset, and vector index of 0 + /// See `Value.canonicalizeBasePtr` which enforces these properties. eu_payload: Index, + + /// A pointer to the payload of a non-pointer-like optional. Index is the + /// optional pointer. To ensure a canonical representation, the base + /// pointer is subject to the same restrictions as in `eu_payload`. opt_payload: Index, - elem: BaseIndex, + + /// A pointer to a field of a slice, or of an auto-layout struct or union. Slice fields + /// are referenced according to `Value.slice_ptr_index` and `Value.slice_len_index`. + /// Base is the aggregate pointer, which is subject to the same restrictions as + /// in `eu_payload`. field: BaseIndex, + /// A pointer to an element of a comptime-only array. Base is the + /// many-pointer we are indexing into. It is subject to the same restrictions + /// as in `eu_payload`, except it must be a many-pointer rather than a one-pointer. + /// + /// The element type of the base pointer must NOT be an array. Additionally, the + /// base pointer is guaranteed to not be an `arr_elem` into a pointer with the + /// same child type. Thus, since there are no two comptime-only types which are + /// IMC to one another, the only case where the base pointer may also be an + /// `arr_elem` is when this pointer is semantically invalid (e.g. it reinterprets + /// a `type` as a `comptime_int`). These restrictions are in place to ensure + /// a canonical representation. + /// + /// This kind of base address differs from others in that it may refer to any + /// sequence of values; for instance, an `arr_elem` at index 2 may refer to + /// any number of elements starting from index 2. + /// + /// Index must not be 0. To refer to the element at index 0, simply reinterpret + /// the aggregate pointer. + arr_elem: BaseIndex, + pub const MutDecl = struct { decl: DeclIndex, runtime_index: RuntimeIndex, @@ -1222,10 +1276,11 @@ pub const Key = union(enum) { .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); - return switch (ptr.addr) { + const addr_tag: Key.Ptr.BaseAddr.Tag = ptr.base_addr; + const seed2 = seed + @intFromEnum(addr_tag); + const big_offset: i128 = ptr.byte_offset; + const common = asBytes(&ptr.ty) ++ asBytes(&big_offset); + return switch (ptr.base_addr) { inline .decl, .comptime_alloc, .anon_decl, @@ -1235,7 +1290,7 @@ pub const Key = union(enum) { .comptime_field, => |x| Hash.hash(seed2, common ++ asBytes(&x)), - .elem, .field => |x| Hash.hash( + .arr_elem, .field => |x| Hash.hash( seed2, common ++ asBytes(&x.base) ++ asBytes(&x.index), ), @@ -1494,21 +1549,21 @@ pub const Key = union(enum) { .ptr => |a_info| { const b_info = b.ptr; 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; - - return switch (a_info.addr) { - .decl => |a_decl| a_decl == b_info.addr.decl, - .comptime_alloc => |a_alloc| a_alloc == b_info.addr.comptime_alloc, - .anon_decl => |ad| ad.val == b_info.addr.anon_decl.val and - ad.orig_ty == b_info.addr.anon_decl.orig_ty, - .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), + if (a_info.byte_offset != b_info.byte_offset) return false; + + if (@as(Key.Ptr.BaseAddr.Tag, a_info.base_addr) != @as(Key.Ptr.BaseAddr.Tag, b_info.base_addr)) return false; + + return switch (a_info.base_addr) { + .decl => |a_decl| a_decl == b_info.base_addr.decl, + .comptime_alloc => |a_alloc| a_alloc == b_info.base_addr.comptime_alloc, + .anon_decl => |ad| ad.val == b_info.base_addr.anon_decl.val and + ad.orig_ty == b_info.base_addr.anon_decl.orig_ty, + .int => true, + .eu_payload => |a_eu_payload| a_eu_payload == b_info.base_addr.eu_payload, + .opt_payload => |a_opt_payload| a_opt_payload == b_info.base_addr.opt_payload, + .comptime_field => |a_comptime_field| a_comptime_field == b_info.base_addr.comptime_field, + .arr_elem => |a_elem| std.meta.eql(a_elem, b_info.base_addr.arr_elem), + .field => |a_field| std.meta.eql(a_field, b_info.base_addr.field), }; }, @@ -2836,7 +2891,7 @@ pub const Index = enum(u32) { ptr_anon_decl: struct { data: *PtrAnonDecl }, ptr_anon_decl_aligned: struct { data: *PtrAnonDeclAligned }, ptr_comptime_field: struct { data: *PtrComptimeField }, - ptr_int: struct { data: *PtrBase }, + ptr_int: struct { data: *PtrInt }, ptr_eu_payload: struct { data: *PtrBase }, ptr_opt_payload: struct { data: *PtrBase }, ptr_elem: struct { data: *PtrBaseIndex }, @@ -3304,7 +3359,7 @@ pub const Tag = enum(u8) { /// data is extra index of `PtrComptimeField`, which contains the pointer type and field value. ptr_comptime_field, /// A pointer with an integer value. - /// data is extra index of `PtrBase`, which contains the type and address. + /// data is extra index of `PtrInt`, which contains the type and address (byte offset from 0). /// Only pointer types are allowed to have this encoding. Optional types must use /// `opt_payload` or `opt_null`. ptr_int, @@ -3497,7 +3552,7 @@ pub const Tag = enum(u8) { .ptr_anon_decl => PtrAnonDecl, .ptr_anon_decl_aligned => PtrAnonDeclAligned, .ptr_comptime_field => PtrComptimeField, - .ptr_int => PtrBase, + .ptr_int => PtrInt, .ptr_eu_payload => PtrBase, .ptr_opt_payload => PtrBase, .ptr_elem => PtrBaseIndex, @@ -4153,11 +4208,37 @@ pub const PackedU64 = packed struct(u64) { pub const PtrDecl = struct { ty: Index, decl: DeclIndex, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, decl: DeclIndex, byte_offset: u64) @This() { + return .{ + .ty = ty, + .decl = decl, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrAnonDecl = struct { ty: Index, val: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, val: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .val = val, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrAnonDeclAligned = struct { @@ -4165,27 +4246,110 @@ pub const PtrAnonDeclAligned = struct { val: Index, /// Must be nonequal to `ty`. Only the alignment from this value is important. orig_ty: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, val: Index, orig_ty: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .val = val, + .orig_ty = orig_ty, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrComptimeAlloc = struct { ty: Index, index: ComptimeAllocIndex, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, index: ComptimeAllocIndex, byte_offset: u64) @This() { + return .{ + .ty = ty, + .index = index, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrComptimeField = struct { ty: Index, field_val: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, field_val: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .field_val = field_val, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrBase = struct { ty: Index, base: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, base: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .base = base, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrBaseIndex = struct { ty: Index, base: Index, index: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, base: Index, index: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .base = base, + .index = index, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } +}; + +pub const PtrInt = struct { + ty: Index, + byte_offset_a: u32, + byte_offset_b: u32, + fn init(ty: Index, byte_offset: u64) @This() { + return .{ + .ty = ty, + .byte_offset_a = @intCast(byte_offset >> 32), + .byte_offset_b = @truncate(byte_offset), + }; + } + fn byteOffset(data: @This()) u64 { + return @as(u64, data.byte_offset_a) << 32 | data.byte_offset_b; + } }; pub const PtrSlice = struct { @@ -4569,78 +4733,55 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { }, .ptr_decl => { const info = ip.extraData(PtrDecl, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .decl = info.decl }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .decl = info.decl }, .byte_offset = info.byteOffset() } }; }, .ptr_comptime_alloc => { const info = ip.extraData(PtrComptimeAlloc, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .comptime_alloc = info.index }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .comptime_alloc = info.index }, .byte_offset = info.byteOffset() } }; }, .ptr_anon_decl => { const info = ip.extraData(PtrAnonDecl, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .anon_decl = .{ - .val = info.val, - .orig_ty = info.ty, - } }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .anon_decl = .{ + .val = info.val, + .orig_ty = info.ty, + } }, .byte_offset = info.byteOffset() } }; }, .ptr_anon_decl_aligned => { const info = ip.extraData(PtrAnonDeclAligned, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .anon_decl = .{ - .val = info.val, - .orig_ty = info.orig_ty, - } }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .anon_decl = .{ + .val = info.val, + .orig_ty = info.orig_ty, + } }, .byte_offset = info.byteOffset() } }; }, .ptr_comptime_field => { const info = ip.extraData(PtrComptimeField, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .comptime_field = info.field_val }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .comptime_field = info.field_val }, .byte_offset = info.byteOffset() } }; }, .ptr_int => { - const info = ip.extraData(PtrBase, data); + const info = ip.extraData(PtrInt, data); return .{ .ptr = .{ .ty = info.ty, - .addr = .{ .int = info.base }, + .base_addr = .int, + .byte_offset = info.byteOffset(), } }; }, .ptr_eu_payload => { const info = ip.extraData(PtrBase, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .eu_payload = info.base }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .eu_payload = info.base }, .byte_offset = info.byteOffset() } }; }, .ptr_opt_payload => { const info = ip.extraData(PtrBase, data); - return .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .opt_payload = info.base }, - } }; + return .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .opt_payload = info.base }, .byte_offset = info.byteOffset() } }; }, .ptr_elem => { // Avoid `indexToKey` recursion by asserting the tag encoding. const info = ip.extraData(PtrBaseIndex, data); const index_item = ip.items.get(@intFromEnum(info.index)); return switch (index_item.tag) { - .int_usize => .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .elem = .{ - .base = info.base, - .index = index_item.data, - } }, - } }, + .int_usize => .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .arr_elem = .{ + .base = info.base, + .index = index_item.data, + } }, .byte_offset = info.byteOffset() } }, .int_positive => @panic("TODO"), // implement along with behavior test coverage else => unreachable, }; @@ -4650,13 +4791,10 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { const info = ip.extraData(PtrBaseIndex, data); const index_item = ip.items.get(@intFromEnum(info.index)); return switch (index_item.tag) { - .int_usize => .{ .ptr = .{ - .ty = info.ty, - .addr = .{ .field = .{ - .base = info.base, - .index = index_item.data, - } }, - } }, + .int_usize => .{ .ptr = .{ .ty = info.ty, .base_addr = .{ .field = .{ + .base = info.base, + .index = index_item.data, + } }, .byte_offset = info.byteOffset() } }, .int_positive => @panic("TODO"), // implement along with behavior test coverage else => unreachable, }; @@ -5211,57 +5349,40 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .ptr => |ptr| { const ptr_type = ip.indexToKey(ptr.ty).ptr_type; assert(ptr_type.flags.size != .Slice); - ip.items.appendAssumeCapacity(switch (ptr.addr) { + ip.items.appendAssumeCapacity(switch (ptr.base_addr) { .decl => |decl| .{ .tag = .ptr_decl, - .data = try ip.addExtra(gpa, PtrDecl{ - .ty = ptr.ty, - .decl = decl, - }), + .data = try ip.addExtra(gpa, PtrDecl.init(ptr.ty, decl, ptr.byte_offset)), }, .comptime_alloc => |alloc_index| .{ .tag = .ptr_comptime_alloc, - .data = try ip.addExtra(gpa, PtrComptimeAlloc{ - .ty = ptr.ty, - .index = alloc_index, - }), + .data = try ip.addExtra(gpa, PtrComptimeAlloc.init(ptr.ty, alloc_index, ptr.byte_offset)), }, .anon_decl => |anon_decl| if (ptrsHaveSameAlignment(ip, ptr.ty, ptr_type, anon_decl.orig_ty)) item: { if (ptr.ty != anon_decl.orig_ty) { _ = ip.map.pop(); var new_key = key; - new_key.ptr.addr.anon_decl.orig_ty = ptr.ty; + new_key.ptr.base_addr.anon_decl.orig_ty = ptr.ty; const new_gop = try ip.map.getOrPutAdapted(gpa, new_key, adapter); if (new_gop.found_existing) return @enumFromInt(new_gop.index); } break :item .{ .tag = .ptr_anon_decl, - .data = try ip.addExtra(gpa, PtrAnonDecl{ - .ty = ptr.ty, - .val = anon_decl.val, - }), + .data = try ip.addExtra(gpa, PtrAnonDecl.init(ptr.ty, anon_decl.val, ptr.byte_offset)), }; } 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, - }), + .data = try ip.addExtra(gpa, PtrAnonDeclAligned.init(ptr.ty, anon_decl.val, anon_decl.orig_ty, ptr.byte_offset)), }, .comptime_field => |field_val| item: { assert(field_val != .none); break :item .{ .tag = .ptr_comptime_field, - .data = try ip.addExtra(gpa, PtrComptimeField{ - .ty = ptr.ty, - .field_val = field_val, - }), + .data = try ip.addExtra(gpa, PtrComptimeField.init(ptr.ty, field_val, ptr.byte_offset)), }; }, - .int, .eu_payload, .opt_payload => |base| item: { - switch (ptr.addr) { - .int => assert(ip.typeOf(base) == .usize_type), + .eu_payload, .opt_payload => |base| item: { + switch (ptr.base_addr) { .eu_payload => assert(ip.indexToKey( ip.indexToKey(ip.typeOf(base)).ptr_type.child, ) == .error_union_type), @@ -5271,40 +5392,40 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { else => unreachable, } break :item .{ - .tag = switch (ptr.addr) { - .int => .ptr_int, + .tag = switch (ptr.base_addr) { .eu_payload => .ptr_eu_payload, .opt_payload => .ptr_opt_payload, else => unreachable, }, - .data = try ip.addExtra(gpa, PtrBase{ - .ty = ptr.ty, - .base = base, - }), + .data = try ip.addExtra(gpa, PtrBase.init(ptr.ty, base, ptr.byte_offset)), }; }, - .elem, .field => |base_index| item: { + .int => .{ + .tag = .ptr_int, + .data = try ip.addExtra(gpa, PtrInt.init(ptr.ty, ptr.byte_offset)), + }, + .arr_elem, .field => |base_index| item: { const base_ptr_type = ip.indexToKey(ip.typeOf(base_index.base)).ptr_type; - switch (ptr.addr) { - .elem => assert(base_ptr_type.flags.size == .Many), + switch (ptr.base_addr) { + .arr_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(ptr.base_addr == .field); assert(base_index.index < anon_struct_type.types.len); }, .struct_type => { - assert(ptr.addr == .field); + assert(ptr.base_addr == .field); assert(base_index.index < ip.loadStructType(base_ptr_type.child).field_types.len); }, .union_type => { const union_type = ip.loadUnionType(base_ptr_type.child); - assert(ptr.addr == .field); + assert(ptr.base_addr == .field); assert(base_index.index < union_type.field_types.len); }, .ptr_type => |slice_type| { - assert(ptr.addr == .field); + assert(ptr.base_addr == .field); assert(slice_type.flags.size == .Slice); assert(base_index.index < 2); }, @@ -5321,16 +5442,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing); try ip.items.ensureUnusedCapacity(gpa, 1); break :item .{ - .tag = switch (ptr.addr) { - .elem => .ptr_elem, + .tag = switch (ptr.base_addr) { + .arr_elem => .ptr_elem, .field => .ptr_field, else => unreachable, }, - .data = try ip.addExtra(gpa, PtrBaseIndex{ - .ty = ptr.ty, - .base = base_index.base, - .index = index_index, - }), + .data = try ip.addExtra(gpa, PtrBaseIndex.init(ptr.ty, base_index.base, index_index, ptr.byte_offset)), }; }, }); @@ -7584,13 +7701,15 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al 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 }, + .base_addr = .int, + .byte_offset = 0, } }), .Slice => return ip.get(gpa, .{ .slice = .{ .ty = new_ty, .ptr = try ip.get(gpa, .{ .ptr = .{ .ty = ip.slicePtrType(new_ty), - .addr = .{ .int = .zero_usize }, + .base_addr = .int, + .byte_offset = 0, } }), .len = try ip.get(gpa, .{ .undef = .usize_type }), } }), @@ -7630,10 +7749,15 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al .ty = new_ty, .int = try ip.getCoerced(gpa, val, ip.loadEnumType(new_ty).tag_ty), } }), - .ptr_type => return ip.get(gpa, .{ .ptr = .{ - .ty = new_ty, - .addr = .{ .int = try ip.getCoerced(gpa, val, .usize_type) }, - } }), + .ptr_type => switch (int.storage) { + inline .u64, .i64 => |int_val| return ip.get(gpa, .{ .ptr = .{ + .ty = new_ty, + .base_addr = .int, + .byte_offset = @intCast(int_val), + } }), + .big_int => unreachable, // must be a usize + .lazy_align, .lazy_size => {}, + }, else => if (ip.isIntegerType(new_ty)) return getCoercedInts(ip, gpa, int, new_ty), }, @@ -7684,11 +7808,15 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al .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, + .base_addr = ptr.base_addr, + .byte_offset = ptr.byte_offset, } }) else if (ip.isIntegerType(new_ty)) - switch (ptr.addr) { - .int => |int| return ip.getCoerced(gpa, int, new_ty), + switch (ptr.base_addr) { + .int => return ip.get(gpa, .{ .int = .{ + .ty = .usize_type, + .storage = .{ .u64 = @intCast(ptr.byte_offset) }, + } }), else => {}, }, .opt => |opt| switch (ip.indexToKey(new_ty)) { @@ -7696,13 +7824,15 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al .none => switch (ptr_type.flags.size) { .One, .Many, .C => try ip.get(gpa, .{ .ptr = .{ .ty = new_ty, - .addr = .{ .int = .zero_usize }, + .base_addr = .int, + .byte_offset = 0, } }), .Slice => try ip.get(gpa, .{ .slice = .{ .ty = new_ty, .ptr = try ip.get(gpa, .{ .ptr = .{ .ty = ip.slicePtrType(new_ty), - .addr = .{ .int = .zero_usize }, + .base_addr = .int, + .byte_offset = 0, } }), .len = try ip.get(gpa, .{ .undef = .usize_type }), } }), @@ -8181,7 +8311,7 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { .ptr_anon_decl => @sizeOf(PtrAnonDecl), .ptr_anon_decl_aligned => @sizeOf(PtrAnonDeclAligned), .ptr_comptime_field => @sizeOf(PtrComptimeField), - .ptr_int => @sizeOf(PtrBase), + .ptr_int => @sizeOf(PtrInt), .ptr_eu_payload => @sizeOf(PtrBase), .ptr_opt_payload => @sizeOf(PtrBase), .ptr_elem => @sizeOf(PtrBaseIndex), @@ -8854,13 +8984,15 @@ pub fn getBackingDecl(ip: *const InternPool, val: Index) OptionalDeclIndex { } } -pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.Addr.Tag { +pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.BaseAddr.Tag { var base = @intFromEnum(val); while (true) { switch (ip.items.items(.tag)[base]) { .ptr_decl => return .decl, .ptr_comptime_alloc => return .comptime_alloc, - .ptr_anon_decl, .ptr_anon_decl_aligned => return .anon_decl, + .ptr_anon_decl, + .ptr_anon_decl_aligned, + => return .anon_decl, .ptr_comptime_field => return .comptime_field, .ptr_int => return .int, inline .ptr_eu_payload, diff --git a/src/Module.zig b/src/Module.zig index c4d7f43fe4b9..320627a12969 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -528,21 +528,6 @@ pub const Decl = struct { return zcu.namespacePtrUnwrap(decl.getInnerNamespaceIndex(zcu)); } - pub fn dump(decl: *Decl) void { - const loc = std.zig.findLineColumn(decl.scope.source.bytes, decl.src); - std.debug.print("{s}:{d}:{d} name={d} status={s}", .{ - decl.scope.sub_file_path, - loc.line + 1, - loc.column + 1, - @intFromEnum(decl.name), - @tagName(decl.analysis), - }); - if (decl.has_tv) { - std.debug.print(" val={}", .{decl.val}); - } - std.debug.print("\n", .{}); - } - pub fn getFileScope(decl: Decl, zcu: *Zcu) *File { return zcu.namespacePtr(decl.src_namespace).file_scope; } @@ -660,6 +645,22 @@ pub const Decl = struct { }, }; } + + pub fn declPtrType(decl: Decl, zcu: *Zcu) !Type { + assert(decl.has_tv); + const decl_ty = decl.typeOf(zcu); + return zcu.ptrType(.{ + .child = decl_ty.toIntern(), + .flags = .{ + .alignment = if (decl.alignment == decl_ty.abiAlignment(zcu)) + .none + else + decl.alignment, + .address_space = decl.@"addrspace", + .is_const = decl.getOwnedVariable(zcu) == null, + }, + }); + } }; /// This state is attached to every Decl when Module emit_h is non-null. @@ -3535,6 +3536,10 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult { } log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)}); + log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(mod)).fmt(ip)}); + defer blk: { + log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)}); + } const old_has_tv = decl.has_tv; // The following values are ignored if `!old_has_tv` @@ -4122,10 +4127,11 @@ fn newEmbedFile( })).toIntern(); const ptr_val = try ip.get(gpa, .{ .ptr = .{ .ty = ptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = array_val, .orig_ty = ptr_ty, } }, + .byte_offset = 0, } }); result.* = new_file; @@ -4489,6 +4495,11 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato const decl_index = func.owner_decl; const decl = mod.declPtr(decl_index); + log.debug("func name '{}'", .{(try decl.fullyQualifiedName(mod)).fmt(ip)}); + defer blk: { + log.debug("finish func name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)}); + } + mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.Depender.wrap(.{ .func = func_index })); var comptime_err_ret_trace = std.ArrayList(SrcLoc).init(gpa); @@ -5332,7 +5343,7 @@ pub fn populateTestFunctions( const decl = mod.declPtr(decl_index); const test_fn_ty = decl.typeOf(mod).slicePtrFieldType(mod).childType(mod); - const array_anon_decl: InternPool.Key.Ptr.Addr.AnonDecl = array: { + const array_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = array: { // Add mod.test_functions to an array decl then make the test_functions // decl reference it as a slice. const test_fn_vals = try gpa.alloc(InternPool.Index, mod.test_functions.count()); @@ -5342,7 +5353,7 @@ pub fn populateTestFunctions( const test_decl = mod.declPtr(test_decl_index); const test_decl_name = try test_decl.fullyQualifiedName(mod); const test_decl_name_len = test_decl_name.length(ip); - const test_name_anon_decl: InternPool.Key.Ptr.Addr.AnonDecl = n: { + const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = n: { const test_name_ty = try mod.arrayType(.{ .len = test_decl_name_len, .child = .u8_type, @@ -5363,7 +5374,8 @@ pub fn populateTestFunctions( .ty = .slice_const_u8_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_type, - .addr = .{ .anon_decl = test_name_anon_decl }, + .base_addr = .{ .anon_decl = test_name_anon_decl }, + .byte_offset = 0, } }), .len = try mod.intern(.{ .int = .{ .ty = .usize_type, @@ -5378,7 +5390,8 @@ pub fn populateTestFunctions( .is_const = true, }, } }), - .addr = .{ .decl = test_decl_index }, + .base_addr = .{ .decl = test_decl_index }, + .byte_offset = 0, } }), }; test_fn_val.* = try mod.intern(.{ .aggregate = .{ @@ -5415,7 +5428,8 @@ pub fn populateTestFunctions( .ty = new_ty.toIntern(), .ptr = try mod.intern(.{ .ptr = .{ .ty = new_ty.slicePtrFieldType(mod).toIntern(), - .addr = .{ .anon_decl = array_anon_decl }, + .base_addr = .{ .anon_decl = array_anon_decl }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, mod.test_functions.count())).toIntern(), } }); @@ -5680,9 +5694,11 @@ pub fn errorSetFromUnsortedNames( /// Supports only pointers, not pointer-like optionals. pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value { assert(ty.zigTypeTag(mod) == .Pointer and !ty.isSlice(mod)); + assert(x != 0 or ty.isAllowzeroPtr(mod)); const i = try intern(mod, .{ .ptr = .{ .ty = ty.toIntern(), - .addr = .{ .int = (try mod.intValue_u64(Type.usize, x)).toIntern() }, + .base_addr = .int, + .byte_offset = x, } }); return Value.fromInterned(i); } diff --git a/src/Sema.zig b/src/Sema.zig index d3989f630cb5..53def0709755 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -126,16 +126,14 @@ const MaybeComptimeAlloc = struct { runtime_index: Value.RuntimeIndex, /// Backed by sema.arena. Tracks all comptime-known stores to this `alloc`. Due to /// RLS, a single comptime-known allocation may have arbitrarily many stores. - /// This may also contain `set_union_tag` instructions. + /// This list also contains `set_union_tag`, `optional_payload_ptr_set`, and + /// `errunion_payload_ptr_set` instructions. + /// If the instruction is one of these three tags, `src` may be `.unneeded`. stores: std.MultiArrayList(struct { inst: Air.Inst.Index, src_decl: InternPool.DeclIndex, src: LazySrcLoc, }) = .{}, - /// Backed by sema.arena. Contains instructions such as `optional_payload_ptr_set` - /// which have side effects so will not be elided by Liveness: we must rewrite these - /// instructions to be nops instead of relying on Liveness. - non_elideable_pointers: std.ArrayListUnmanaged(Air.Inst.Index) = .{}, }; const ComptimeAlloc = struct { @@ -177,7 +175,8 @@ const MutableValue = @import("mutable_value.zig").MutableValue; const Type = @import("type.zig").Type; const Air = @import("Air.zig"); const Zir = std.zig.Zir; -const Module = @import("Module.zig"); +const Zcu = @import("Module.zig"); +const Module = Zcu; const trace = @import("tracy.zig").trace; const Namespace = Module.Namespace; const CompileError = Module.CompileError; @@ -2138,7 +2137,7 @@ fn resolveValueIntable(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value { if (sema.mod.intern_pool.getBackingAddrTag(val.toIntern())) |addr| switch (addr) { .decl, .anon_decl, .comptime_alloc, .comptime_field => return null, .int => {}, - .eu_payload, .opt_payload, .elem, .field => unreachable, + .eu_payload, .opt_payload, .arr_elem, .field => unreachable, }; return try sema.resolveLazyValue(val); } @@ -2268,11 +2267,11 @@ fn failWithErrorSetCodeMissing( } fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: usize) CompileError { - const mod = sema.mod; - if (int_ty.zigTypeTag(mod) == .Vector) { + const zcu = sema.mod; + if (int_ty.zigTypeTag(zcu) == .Vector) { const msg = msg: { const msg = try sema.errMsg(block, src, "overflow of vector type '{}' with value '{}'", .{ - int_ty.fmt(sema.mod), val.fmtValue(sema.mod), + int_ty.fmt(zcu), val.fmtValue(zcu, sema), }); errdefer msg.destroy(sema.gpa); try sema.errNote(block, src, msg, "when computing vector element at index '{d}'", .{vector_index}); @@ -2281,7 +2280,7 @@ fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: return sema.failWithOwnedErrorMsg(block, msg); } return sema.fail(block, src, "overflow of integer type '{}' with value '{}'", .{ - int_ty.fmt(sema.mod), val.fmtValue(sema.mod), + int_ty.fmt(zcu), val.fmtValue(zcu, sema), }); } @@ -2440,7 +2439,7 @@ fn addFieldErrNote( try mod.errNoteNonLazy(field_src, parent, format, args); } -fn errMsg( +pub fn errMsg( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -2469,7 +2468,7 @@ pub fn fail( return sema.failWithOwnedErrorMsg(block, err_msg); } -fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Module.ErrorMsg) error{ AnalysisFail, OutOfMemory } { +pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Module.ErrorMsg) error{ AnalysisFail, OutOfMemory } { @setCold(true); const gpa = sema.gpa; const mod = sema.mod; @@ -2922,7 +2921,7 @@ fn createAnonymousDeclTypeNamed( return sema.createAnonymousDeclTypeNamed(block, src, val, .anon, anon_prefix, null); if (arg_i != 0) try writer.writeByte(','); - try writer.print("{}", .{arg_val.fmtValue(sema.mod)}); + try writer.print("{}", .{arg_val.fmtValue(sema.mod, sema)}); arg_i += 1; continue; @@ -3193,7 +3192,7 @@ fn zirEnumDecl( }).lazy; const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = conflict.prev_field_idx }).lazy; const msg = msg: { - const msg = try sema.errMsg(block, value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(sema.mod)}); + const msg = try sema.errMsg(block, value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(sema.mod, sema)}); errdefer msg.destroy(gpa); try sema.errNote(block, other_field_src, msg, "other occurrence here", .{}); break :msg msg; @@ -3213,7 +3212,7 @@ fn zirEnumDecl( const field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = field_i }).lazy; const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = conflict.prev_field_idx }).lazy; const msg = msg: { - const msg = try sema.errMsg(block, field_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(sema.mod)}); + const msg = try sema.errMsg(block, field_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(sema.mod, sema)}); errdefer msg.destroy(gpa); try sema.errNote(block, other_field_src, msg, "other occurrence here", .{}); break :msg msg; @@ -3235,7 +3234,7 @@ fn zirEnumDecl( .range = if (has_tag_value) .value else .name, }).lazy; const msg = try sema.errMsg(block, value_src, "enumeration value '{}' too large for type '{}'", .{ - last_tag_val.?.fmtValue(mod), int_tag_ty.fmt(mod), + last_tag_val.?.fmtValue(mod, sema), int_tag_ty.fmt(mod), }); return sema.failWithOwnedErrorMsg(block, msg); } @@ -3766,7 +3765,7 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro // If this was a comptime inferred alloc, then `storeToInferredAllocComptime` // might have already done our job and created an anon decl ref. switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| switch (ptr.base_addr) { .anon_decl => { // The comptime-ification was already done for us. // Just make sure the pointer is const. @@ -3778,22 +3777,25 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro } if (!sema.isComptimeMutablePtr(ptr_val)) break :already_ct; - const alloc_index = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr.comptime_alloc; + const ptr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr; + assert(ptr.byte_offset == 0); + const alloc_index = ptr.base_addr.comptime_alloc; const ct_alloc = sema.getComptimeAlloc(alloc_index); const interned = try ct_alloc.val.intern(mod, sema.arena); - if (Value.fromInterned(interned).canMutateComptimeVarState(mod)) { + if (interned.canMutateComptimeVarState(mod)) { // Preserve the comptime alloc, just make the pointer const. - ct_alloc.val = .{ .interned = interned }; + ct_alloc.val = .{ .interned = interned.toIntern() }; ct_alloc.is_const = true; return sema.makePtrConst(block, alloc); } else { // Promote the constant to an anon decl. const new_mut_ptr = Air.internedToRef(try mod.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .anon_decl = .{ - .val = interned, + .base_addr = .{ .anon_decl = .{ + .val = interned.toIntern(), .orig_ty = alloc_ty.toIntern(), } }, + .byte_offset = 0, } })); return sema.makePtrConst(block, new_mut_ptr); } @@ -3818,10 +3820,10 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro /// If `alloc` is an inferred allocation, `resolved_inferred_ty` is taken to be its resolved /// type. Otherwise, it may be `null`, and the type will be inferred from `alloc`. fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, resolved_alloc_ty: ?Type) CompileError!?InternPool.Index { - const mod = sema.mod; + const zcu = sema.mod; const alloc_ty = resolved_alloc_ty orelse sema.typeOf(alloc); - const ptr_info = alloc_ty.ptrInfo(mod); + const ptr_info = alloc_ty.ptrInfo(zcu); const elem_ty = Type.fromInterned(ptr_info.child); const alloc_inst = alloc.toIndex() orelse return null; @@ -3843,12 +3845,16 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, simple: { if (stores.len != 1) break :simple; - const store_inst = stores[0]; - const store_data = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op; - if (store_data.lhs != alloc) break :simple; + const store_inst = sema.air_instructions.get(@intFromEnum(stores[0])); + switch (store_inst.tag) { + .store, .store_safe => {}, + .set_union_tag, .optional_payload_ptr_set, .errunion_payload_ptr_set => break :simple, // there's OPV stuff going on! + else => unreachable, + } + if (store_inst.data.bin_op.lhs != alloc) break :simple; - const val = store_data.rhs.toInterned().?; - assert(mod.intern_pool.typeOf(val) == elem_ty.toIntern()); + const val = store_inst.data.bin_op.rhs.toInterned().?; + assert(zcu.intern_pool.typeOf(val) == elem_ty.toIntern()); return sema.finishResolveComptimeKnownAllocPtr(block, alloc_ty, val, null, alloc_inst, comptime_info.value); } @@ -3857,9 +3863,10 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, const ct_alloc = try sema.newComptimeAlloc(block, elem_ty, ptr_info.flags.alignment); - const alloc_ptr = try mod.intern(.{ .ptr = .{ + const alloc_ptr = try zcu.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .comptime_alloc = ct_alloc }, + .base_addr = .{ .comptime_alloc = ct_alloc }, + .byte_offset = 0, } }); // Maps from pointers into the runtime allocs, to comptime-mutable pointers into the comptime alloc @@ -3867,10 +3874,18 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, try ptr_mapping.ensureTotalCapacity(@intCast(stores.len)); ptr_mapping.putAssumeCapacity(alloc_inst, alloc_ptr); + // Whilst constructing our mapping, we will also initialize optional and error union payloads when + // we encounter the corresponding pointers. For this reason, the ordering of `to_map` matters. var to_map = try std.ArrayList(Air.Inst.Index).initCapacity(sema.arena, stores.len); - for (stores) |store_inst| { - const bin_op = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op; - to_map.appendAssumeCapacity(bin_op.lhs.toIndex().?); + for (stores) |store_inst_idx| { + const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx)); + const ptr_to_map = switch (store_inst.tag) { + .store, .store_safe => store_inst.data.bin_op.lhs.toIndex().?, // Map the pointer being stored to. + .set_union_tag => continue, // We can completely ignore these: we'll do it implicitly when we get the field pointer. + .optional_payload_ptr_set, .errunion_payload_ptr_set => store_inst_idx, // Map the generated pointer itself. + else => unreachable, + }; + to_map.appendAssumeCapacity(ptr_to_map); } const tmp_air = sema.getTmpAir(); @@ -3950,53 +3965,68 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, try to_map.appendSlice(&.{ air_ptr, air_parent_ptr.toIndex().? }); continue; }; - const new_ptr_ty = tmp_air.typeOfIndex(air_ptr, &mod.intern_pool).toIntern(); + const new_ptr_ty = tmp_air.typeOfIndex(air_ptr, &zcu.intern_pool).toIntern(); const new_ptr = switch (method) { - .same_addr => try mod.intern_pool.getCoerced(sema.gpa, decl_parent_ptr, new_ptr_ty), - .opt_payload => try mod.intern(.{ .ptr = .{ - .ty = new_ptr_ty, - .addr = .{ .opt_payload = decl_parent_ptr }, - } }), - .eu_payload => try mod.intern(.{ .ptr = .{ - .ty = new_ptr_ty, - .addr = .{ .eu_payload = decl_parent_ptr }, - } }), - .field => |field_idx| try mod.intern(.{ .ptr = .{ - .ty = new_ptr_ty, - .addr = .{ .field = .{ - .base = decl_parent_ptr, - .index = field_idx, - } }, - } }), - .elem => |elem_idx| (try Value.fromInterned(decl_parent_ptr).elemPtr(Type.fromInterned(new_ptr_ty), @intCast(elem_idx), mod)).toIntern(), + .same_addr => try zcu.intern_pool.getCoerced(sema.gpa, decl_parent_ptr, new_ptr_ty), + .opt_payload => ptr: { + // Set the optional to non-null at comptime. + // If the payload is OPV, we must use that value instead of undef. + const opt_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu); + const payload_ty = opt_ty.optionalChild(zcu); + const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try zcu.undefValue(payload_ty); + const opt_val = try zcu.intern(.{ .opt = .{ + .ty = opt_ty.toIntern(), + .val = payload_val.toIntern(), + } }); + try sema.storePtrVal(block, .unneeded, Value.fromInterned(decl_parent_ptr), Value.fromInterned(opt_val), opt_ty); + break :ptr (try Value.fromInterned(decl_parent_ptr).ptrOptPayload(sema)).toIntern(); + }, + .eu_payload => ptr: { + // Set the error union to non-error at comptime. + // If the payload is OPV, we must use that value instead of undef. + const eu_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu); + const payload_ty = eu_ty.errorUnionPayload(zcu); + const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try zcu.undefValue(payload_ty); + const eu_val = try zcu.intern(.{ .error_union = .{ + .ty = eu_ty.toIntern(), + .val = .{ .payload = payload_val.toIntern() }, + } }); + try sema.storePtrVal(block, .unneeded, Value.fromInterned(decl_parent_ptr), Value.fromInterned(eu_val), eu_ty); + break :ptr (try Value.fromInterned(decl_parent_ptr).ptrEuPayload(sema)).toIntern(); + }, + .field => |idx| ptr: { + const maybe_union_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu); + if (zcu.typeToUnion(maybe_union_ty)) |union_obj| { + // As this is a union field, we must store to the pointer now to set the tag. + // If the payload is OPV, there will not be a payload store, so we store that value. + // Otherwise, there will be a payload store to process later, so undef will suffice. + const payload_ty = Type.fromInterned(union_obj.field_types.get(&zcu.intern_pool)[idx]); + const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try zcu.undefValue(payload_ty); + const tag_val = try zcu.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), idx); + const store_val = try zcu.unionValue(maybe_union_ty, tag_val, payload_val); + try sema.storePtrVal(block, .unneeded, Value.fromInterned(decl_parent_ptr), store_val, maybe_union_ty); + } + break :ptr (try Value.fromInterned(decl_parent_ptr).ptrField(idx, sema)).toIntern(); + }, + .elem => |idx| (try Value.fromInterned(decl_parent_ptr).ptrElem(idx, sema)).toIntern(), }; try ptr_mapping.put(air_ptr, new_ptr); } // We have a correlation between AIR pointers and decl pointers. Perform all stores at comptime. - - for (stores) |store_inst| { - switch (sema.air_instructions.items(.tag)[@intFromEnum(store_inst)]) { - .set_union_tag => { - // If this tag has an OPV payload, there won't be a corresponding - // store instruction, so we must set the union payload now. - const bin_op = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op; - const air_ptr_inst = bin_op.lhs.toIndex().?; - const tag_val = (try sema.resolveValue(bin_op.rhs)).?; - const union_ty = sema.typeOf(bin_op.lhs).childType(mod); - const payload_ty = union_ty.unionFieldType(tag_val, mod).?; - if (try sema.typeHasOnePossibleValue(payload_ty)) |payload_val| { - const new_ptr = ptr_mapping.get(air_ptr_inst).?; - const store_val = try mod.unionValue(union_ty, tag_val, payload_val); - try sema.storePtrVal(block, .unneeded, Value.fromInterned(new_ptr), store_val, union_ty); - } - }, + // Any implicit stores performed by `optional_payload_ptr_set`, `errunion_payload_ptr_set`, or + // `set_union_tag` instructions were already done above. + + for (stores) |store_inst_idx| { + const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx)); + switch (store_inst.tag) { + .set_union_tag => {}, // Handled implicitly by field pointers above + .optional_payload_ptr_set, .errunion_payload_ptr_set => {}, // Handled explicitly above .store, .store_safe => { - const bin_op = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op; - const air_ptr_inst = bin_op.lhs.toIndex().?; - const store_val = (try sema.resolveValue(bin_op.rhs)).?; + const air_ptr_inst = store_inst.data.bin_op.lhs.toIndex().?; + const store_val = (try sema.resolveValue(store_inst.data.bin_op.rhs)).?; const new_ptr = ptr_mapping.get(air_ptr_inst).?; - try sema.storePtrVal(block, .unneeded, Value.fromInterned(new_ptr), store_val, Type.fromInterned(mod.intern_pool.typeOf(store_val.toIntern()))); + try sema.storePtrVal(block, .unneeded, Value.fromInterned(new_ptr), store_val, Type.fromInterned(zcu.intern_pool.typeOf(store_val.toIntern()))); }, else => unreachable, } @@ -4040,9 +4070,6 @@ fn finishResolveComptimeKnownAllocPtr( for (comptime_info.stores.items(.inst)) |store_inst| { sema.air_instructions.set(@intFromEnum(store_inst), nop_inst); } - for (comptime_info.non_elideable_pointers.items) |ptr_inst| { - sema.air_instructions.set(@intFromEnum(ptr_inst), nop_inst); - } if (Value.fromInterned(result_val).canMutateComptimeVarState(zcu)) { const alloc_index = existing_comptime_alloc orelse a: { @@ -4054,15 +4081,17 @@ fn finishResolveComptimeKnownAllocPtr( sema.getComptimeAlloc(alloc_index).is_const = true; return try zcu.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .comptime_alloc = alloc_index }, + .base_addr = .{ .comptime_alloc = alloc_index }, + .byte_offset = 0, } }); } else { return try zcu.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = alloc_ty.toIntern(), .val = result_val, } }, + .byte_offset = 0, } }); } } @@ -4207,11 +4236,11 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com sema.air_instructions.set(@intFromEnum(ptr_inst), .{ .tag = undefined, .data = undefined }); } - const val = switch (mod.intern_pool.indexToKey(resolved_ptr).ptr.addr) { + const val = switch (mod.intern_pool.indexToKey(resolved_ptr).ptr.base_addr) { .anon_decl => |a| a.val, .comptime_alloc => |i| val: { const alloc = sema.getComptimeAlloc(i); - break :val try alloc.val.intern(mod, sema.arena); + break :val (try alloc.val.intern(mod, sema.arena)).toIntern(); }, else => unreachable, }; @@ -4388,10 +4417,10 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. .input_index = len_idx, } }; try sema.errNote(block, a_src, msg, "length {} here", .{ - v.fmtValue(sema.mod), + v.fmtValue(sema.mod, sema), }); try sema.errNote(block, arg_src, msg, "length {} here", .{ - arg_val.fmtValue(sema.mod), + arg_val.fmtValue(sema.mod, sema), }); break :msg msg; }; @@ -4869,7 +4898,7 @@ fn validateUnionInit( const new_tag = Air.internedToRef(tag_val.toIntern()); const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, new_tag); - try sema.checkComptimeKnownStore(block, set_tag_inst, init_src); + try sema.checkComptimeKnownStore(block, set_tag_inst, .unneeded); // `unneeded` since this isn't a "proper" store } fn validateStructInit( @@ -5331,7 +5360,7 @@ fn zirValidatePtrArrayInit( if (array_is_comptime) { if (try sema.resolveDefinedValue(block, init_src, array_ptr)) |ptr_val| { switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| switch (ptr.base_addr) { .comptime_field => return, // This store was validated by the individual elem ptrs. else => {}, }, @@ -5619,17 +5648,19 @@ fn storeToInferredAllocComptime( if (iac.is_const and !operand_val.canMutateComptimeVarState(zcu)) { iac.ptr = try zcu.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = operand_val.toIntern(), .orig_ty = alloc_ty.toIntern(), } }, + .byte_offset = 0, } }); } else { const alloc_index = try sema.newComptimeAlloc(block, operand_ty, iac.alignment); sema.getComptimeAlloc(alloc_index).val = .{ .interned = operand_val.toIntern() }; iac.ptr = try zcu.intern(.{ .ptr = .{ .ty = alloc_ty.toIntern(), - .addr = .{ .comptime_alloc = alloc_index }, + .base_addr = .{ .comptime_alloc = alloc_index }, + .byte_offset = 0, } }); } } @@ -5724,10 +5755,11 @@ fn refValue(sema: *Sema, val: InternPool.Index) CompileError!InternPool.Index { })).toIntern(); return mod.intern(.{ .ptr = .{ .ty = ptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = val, .orig_ty = ptr_ty, } }, + .byte_offset = 0, } }); } @@ -5813,7 +5845,7 @@ fn zirCompileLog( const arg_ty = sema.typeOf(arg); if (try sema.resolveValueResolveLazy(arg)) |val| { try writer.print("@as({}, {})", .{ - arg_ty.fmt(mod), val.fmtValue(mod), + arg_ty.fmt(mod), val.fmtValue(mod, sema), }); } else { try writer.print("@as({}, [runtime value])", .{arg_ty.fmt(mod)}); @@ -6404,7 +6436,7 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void else => |e| return e, }; { - try mod.ensureDeclAnalyzed(decl_index); + try sema.ensureDeclAnalyzed(decl_index); const exported_decl = mod.declPtr(decl_index); if (exported_decl.val.getFunction(mod)) |function| { return sema.analyzeExport(block, src, options, function.owner_decl); @@ -6457,7 +6489,7 @@ pub fn analyzeExport( if (options.linkage == .internal) return; - try mod.ensureDeclAnalyzed(exported_decl_index); + try sema.ensureDeclAnalyzed(exported_decl_index); const exported_decl = mod.declPtr(exported_decl_index); const export_ty = exported_decl.typeOf(mod); @@ -6880,8 +6912,8 @@ fn funcDeclSrc(sema: *Sema, func_inst: Air.Inst.Ref) !?*Decl { const owner_decl_index = switch (mod.intern_pool.indexToKey(func_val.toIntern())) { .extern_func => |extern_func| extern_func.decl, .func => |func| func.owner_decl, - .ptr => |ptr| switch (ptr.addr) { - .decl => |decl| mod.declPtr(decl).val.getFunction(mod).?.owner_decl, + .ptr => |ptr| switch (ptr.base_addr) { + .decl => |decl| if (ptr.byte_offset == 0) mod.declPtr(decl).val.getFunction(mod).?.owner_decl else return null, else => return null, }, else => return null, @@ -7638,22 +7670,23 @@ fn analyzeCall( @as([]const u8, if (is_comptime_call) "comptime" else "inline"), }), .func => func_val.toIntern(), - .ptr => |ptr| switch (ptr.addr) { - .decl => |decl| blk: { - const func_val_ptr = mod.declPtr(decl).val.toIntern(); - const intern_index = mod.intern_pool.indexToKey(func_val_ptr); - if (intern_index == .extern_func or (intern_index == .variable and intern_index.variable.is_extern)) - return sema.fail(block, call_src, "{s} call of extern function pointer", .{ - @as([]const u8, if (is_comptime_call) "comptime" else "inline"), - }); - break :blk func_val_ptr; - }, - else => { - assert(callee_ty.isPtrAtRuntime(mod)); - return sema.fail(block, call_src, "{s} call of function pointer", .{ - @as([]const u8, if (is_comptime_call) "comptime" else "inline"), - }); - }, + .ptr => |ptr| blk: { + switch (ptr.base_addr) { + .decl => |decl| if (ptr.byte_offset == 0) { + const func_val_ptr = mod.declPtr(decl).val.toIntern(); + const intern_index = mod.intern_pool.indexToKey(func_val_ptr); + if (intern_index == .extern_func or (intern_index == .variable and intern_index.variable.is_extern)) + return sema.fail(block, call_src, "{s} call of extern function pointer", .{ + @as([]const u8, if (is_comptime_call) "comptime" else "inline"), + }); + break :blk func_val_ptr; + }, + else => {}, + } + assert(callee_ty.isPtrAtRuntime(mod)); + return sema.fail(block, call_src, "{s} call of function pointer", .{ + @as([]const u8, if (is_comptime_call) "comptime" else "inline"), + }); }, else => unreachable, }; @@ -7971,7 +8004,7 @@ fn analyzeCall( if (try sema.resolveValue(func)) |func_val| { switch (mod.intern_pool.indexToKey(func_val.toIntern())) { .func => break :skip_safety, - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| if (!mod.declPtr(decl).isExtern(mod)) break :skip_safety, else => {}, }, @@ -8167,7 +8200,7 @@ fn instantiateGenericCall( }); const generic_owner = switch (mod.intern_pool.indexToKey(func_val.toIntern())) { .func => func_val.toIntern(), - .ptr => |ptr| mod.declPtr(ptr.addr.decl).val.toIntern(), + .ptr => |ptr| mod.declPtr(ptr.base_addr.decl).val.toIntern(), else => unreachable, }; const generic_owner_func = mod.intern_pool.indexToKey(generic_owner).func; @@ -8919,7 +8952,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return Air.internedToRef((try mod.getCoerced(int_val, dest_ty)).toIntern()); } return sema.fail(block, src, "int value '{}' out of range of non-exhaustive enum '{}'", .{ - int_val.fmtValue(mod), dest_ty.fmt(mod), + int_val.fmtValue(mod, sema), dest_ty.fmt(mod), }); } if (int_val.isUndef(mod)) { @@ -8927,7 +8960,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (!(try sema.enumHasInt(dest_ty, int_val))) { return sema.fail(block, src, "enum '{}' has no tag with value '{}'", .{ - dest_ty.fmt(mod), int_val.fmtValue(mod), + dest_ty.fmt(mod), int_val.fmtValue(mod, sema), }); } return Air.internedToRef((try mod.getCoerced(int_val, dest_ty)).toIntern()); @@ -8984,47 +9017,47 @@ fn analyzeOptionalPayloadPtr( safety_check: bool, initializing: bool, ) CompileError!Air.Inst.Ref { - const mod = sema.mod; + const zcu = sema.mod; const optional_ptr_ty = sema.typeOf(optional_ptr); - assert(optional_ptr_ty.zigTypeTag(mod) == .Pointer); + assert(optional_ptr_ty.zigTypeTag(zcu) == .Pointer); - const opt_type = optional_ptr_ty.childType(mod); - if (opt_type.zigTypeTag(mod) != .Optional) { - return sema.fail(block, src, "expected optional type, found '{}'", .{opt_type.fmt(mod)}); + const opt_type = optional_ptr_ty.childType(zcu); + if (opt_type.zigTypeTag(zcu) != .Optional) { + return sema.fail(block, src, "expected optional type, found '{}'", .{opt_type.fmt(zcu)}); } - const child_type = opt_type.optionalChild(mod); + const child_type = opt_type.optionalChild(zcu); const child_pointer = try sema.ptrType(.{ .child = child_type.toIntern(), .flags = .{ - .is_const = optional_ptr_ty.isConstPtr(mod), - .address_space = optional_ptr_ty.ptrAddressSpace(mod), + .is_const = optional_ptr_ty.isConstPtr(zcu), + .address_space = optional_ptr_ty.ptrAddressSpace(zcu), }, }); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |ptr_val| { if (initializing) { - if (!sema.isComptimeMutablePtr(ptr_val)) { - // If the pointer resulting from this function was stored at comptime, - // the optional non-null bit would be set that way. But in this case, - // we need to emit a runtime instruction to do it. + if (sema.isComptimeMutablePtr(ptr_val)) { + // Set the optional to non-null at comptime. + // If the payload is OPV, we must use that value instead of undef. + const payload_val = try sema.typeHasOnePossibleValue(child_type) orelse try zcu.undefValue(child_type); + const opt_val = try zcu.intern(.{ .opt = .{ + .ty = opt_type.toIntern(), + .val = payload_val.toIntern(), + } }); + try sema.storePtrVal(block, src, ptr_val, Value.fromInterned(opt_val), opt_type); + } else { + // Emit runtime instructions to set the optional non-null bit. const opt_payload_ptr = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr); try sema.checkKnownAllocPtr(block, optional_ptr, opt_payload_ptr); } - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = child_pointer.toIntern(), - .addr = .{ .opt_payload = ptr_val.toIntern() }, - } }))); + return Air.internedToRef((try ptr_val.ptrOptPayload(sema)).toIntern()); } if (try sema.pointerDeref(block, src, ptr_val, optional_ptr_ty)) |val| { - if (val.isNull(mod)) { + if (val.isNull(zcu)) { return sema.fail(block, src, "unable to unwrap null", .{}); } - // The same Value represents the pointer to the optional and the payload. - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = child_pointer.toIntern(), - .addr = .{ .opt_payload = ptr_val.toIntern() }, - } }))); + return Air.internedToRef((try ptr_val.ptrOptPayload(sema)).toIntern()); } } @@ -9173,49 +9206,50 @@ fn analyzeErrUnionPayloadPtr( safety_check: bool, initializing: bool, ) CompileError!Air.Inst.Ref { - const mod = sema.mod; + const zcu = sema.mod; const operand_ty = sema.typeOf(operand); - assert(operand_ty.zigTypeTag(mod) == .Pointer); + assert(operand_ty.zigTypeTag(zcu) == .Pointer); - if (operand_ty.childType(mod).zigTypeTag(mod) != .ErrorUnion) { + if (operand_ty.childType(zcu).zigTypeTag(zcu) != .ErrorUnion) { return sema.fail(block, src, "expected error union type, found '{}'", .{ - operand_ty.childType(mod).fmt(mod), + operand_ty.childType(zcu).fmt(zcu), }); } - const err_union_ty = operand_ty.childType(mod); - const payload_ty = err_union_ty.errorUnionPayload(mod); + const err_union_ty = operand_ty.childType(zcu); + const payload_ty = err_union_ty.errorUnionPayload(zcu); const operand_pointer_ty = try sema.ptrType(.{ .child = payload_ty.toIntern(), .flags = .{ - .is_const = operand_ty.isConstPtr(mod), - .address_space = operand_ty.ptrAddressSpace(mod), + .is_const = operand_ty.isConstPtr(zcu), + .address_space = operand_ty.ptrAddressSpace(zcu), }, }); if (try sema.resolveDefinedValue(block, src, operand)) |ptr_val| { if (initializing) { - if (!sema.isComptimeMutablePtr(ptr_val)) { - // If the pointer resulting from this function was stored at comptime, - // the error union error code would be set that way. But in this case, - // we need to emit a runtime instruction to do it. + if (sema.isComptimeMutablePtr(ptr_val)) { + // Set the error union to non-error at comptime. + // If the payload is OPV, we must use that value instead of undef. + const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try zcu.undefValue(payload_ty); + const eu_val = try zcu.intern(.{ .error_union = .{ + .ty = err_union_ty.toIntern(), + .val = .{ .payload = payload_val.toIntern() }, + } }); + try sema.storePtrVal(block, src, ptr_val, Value.fromInterned(eu_val), err_union_ty); + } else { + // Emit runtime instructions to set the error union error code. try sema.requireRuntimeBlock(block, src, null); const eu_payload_ptr = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand); try sema.checkKnownAllocPtr(block, operand, eu_payload_ptr); } - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = operand_pointer_ty.toIntern(), - .addr = .{ .eu_payload = ptr_val.toIntern() }, - } }))); + return Air.internedToRef((try ptr_val.ptrEuPayload(sema)).toIntern()); } if (try sema.pointerDeref(block, src, ptr_val, operand_ty)) |val| { - if (val.getErrorName(mod).unwrap()) |name| { + if (val.getErrorName(zcu).unwrap()) |name| { return sema.failWithComptimeErrorRetTrace(block, src, name); } - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = operand_pointer_ty.toIntern(), - .addr = .{ .eu_payload = ptr_val.toIntern() }, - } }))); + return Air.internedToRef((try ptr_val.ptrEuPayload(sema)).toIntern()); } } @@ -9223,7 +9257,7 @@ fn analyzeErrUnionPayloadPtr( // If the error set has no fields then no safety check is needed. if (safety_check and block.wantSafety() and - !err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) + !err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) { try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr); } @@ -10186,49 +10220,56 @@ fn zirIntFromPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const tracy = trace(@src()); defer tracy.end(); - const mod = sema.mod; + const zcu = sema.mod; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - const ptr_ty = operand_ty.scalarType(mod); - const is_vector = operand_ty.zigTypeTag(mod) == .Vector; - if (!ptr_ty.isPtrAtRuntime(mod)) { - return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty.fmt(mod)}); + const ptr_ty = operand_ty.scalarType(zcu); + const is_vector = operand_ty.zigTypeTag(zcu) == .Vector; + if (!ptr_ty.isPtrAtRuntime(zcu)) { + return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty.fmt(zcu)}); } - const pointee_ty = ptr_ty.childType(mod); + const pointee_ty = ptr_ty.childType(zcu); if (try sema.typeRequiresComptime(ptr_ty)) { const msg = msg: { - const msg = try sema.errMsg(block, ptr_src, "comptime-only type '{}' has no pointer address", .{pointee_ty.fmt(mod)}); + const msg = try sema.errMsg(block, ptr_src, "comptime-only type '{}' has no pointer address", .{pointee_ty.fmt(zcu)}); errdefer msg.destroy(sema.gpa); - const src_decl = mod.declPtr(block.src_decl); - try sema.explainWhyTypeIsComptime(msg, src_decl.toSrcLoc(ptr_src, mod), pointee_ty); + const src_decl = zcu.declPtr(block.src_decl); + try sema.explainWhyTypeIsComptime(msg, src_decl.toSrcLoc(ptr_src, zcu), pointee_ty); break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); } if (try sema.resolveValueIntable(operand)) |operand_val| ct: { if (!is_vector) { - return Air.internedToRef((try mod.intValue( + if (operand_val.isUndef(zcu)) { + return Air.internedToRef((try zcu.undefValue(Type.usize)).toIntern()); + } + return Air.internedToRef((try zcu.intValue( Type.usize, - (try operand_val.getUnsignedIntAdvanced(mod, sema)).?, + (try operand_val.getUnsignedIntAdvanced(zcu, sema)).?, )).toIntern()); } - const len = operand_ty.vectorLen(mod); - const dest_ty = try mod.vectorType(.{ .child = .usize_type, .len = len }); + const len = operand_ty.vectorLen(zcu); + const dest_ty = try zcu.vectorType(.{ .child = .usize_type, .len = len }); const new_elems = try sema.arena.alloc(InternPool.Index, len); for (new_elems, 0..) |*new_elem, i| { - const ptr_val = try operand_val.elemValue(mod, i); - const addr = try ptr_val.getUnsignedIntAdvanced(mod, sema) orelse { + const ptr_val = try operand_val.elemValue(zcu, i); + if (ptr_val.isUndef(zcu)) { + new_elem.* = (try zcu.undefValue(Type.usize)).toIntern(); + continue; + } + const addr = try ptr_val.getUnsignedIntAdvanced(zcu, sema) orelse { // A vector element wasn't an integer pointer. This is a runtime operation. break :ct; }; - new_elem.* = (try mod.intValue( + new_elem.* = (try zcu.intValue( Type.usize, addr, )).toIntern(); } - return Air.internedToRef(try mod.intern(.{ .aggregate = .{ + return Air.internedToRef(try zcu.intern(.{ .aggregate = .{ .ty = dest_ty.toIntern(), .storage = .{ .elems = new_elems }, } })); @@ -10238,11 +10279,11 @@ fn zirIntFromPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! if (!is_vector) { return block.addUnOp(.int_from_ptr, operand); } - const len = operand_ty.vectorLen(mod); - const dest_ty = try mod.vectorType(.{ .child = .usize_type, .len = len }); + const len = operand_ty.vectorLen(zcu); + const dest_ty = try zcu.vectorType(.{ .child = .usize_type, .len = len }); const new_elems = try sema.arena.alloc(Air.Inst.Ref, len); for (new_elems, 0..) |*new_elem, i| { - const idx_ref = try mod.intRef(Type.usize, i); + const idx_ref = try zcu.intRef(Type.usize, i); const old_elem = try block.addBinOp(.array_elem_val, operand, idx_ref); new_elem.* = try block.addUnOp(.int_from_ptr, old_elem); } @@ -11077,8 +11118,8 @@ const SwitchProngAnalysis = struct { inline_case_capture: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const sema = spa.sema; - const mod = sema.mod; - const ip = &mod.intern_pool; + const zcu = sema.mod; + const ip = &zcu.intern_pool; const zir_datas = sema.code.instructions.items(.data); const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node; @@ -11089,27 +11130,21 @@ const SwitchProngAnalysis = struct { if (inline_case_capture != .none) { const item_val = sema.resolveConstDefinedValue(block, .unneeded, inline_case_capture, undefined) catch unreachable; - if (operand_ty.zigTypeTag(mod) == .Union) { - const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, mod).?); - const union_obj = mod.typeToUnion(operand_ty).?; + if (operand_ty.zigTypeTag(zcu) == .Union) { + const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?); + const union_obj = zcu.typeToUnion(operand_ty).?; const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]); if (capture_byref) { const ptr_field_ty = try sema.ptrType(.{ .child = field_ty.toIntern(), .flags = .{ - .is_const = !operand_ptr_ty.ptrIsMutable(mod), - .is_volatile = operand_ptr_ty.isVolatilePtr(mod), - .address_space = operand_ptr_ty.ptrAddressSpace(mod), + .is_const = !operand_ptr_ty.ptrIsMutable(zcu), + .is_volatile = operand_ptr_ty.isVolatilePtr(zcu), + .address_space = operand_ptr_ty.ptrAddressSpace(zcu), }, }); if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |union_ptr| { - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = union_ptr.toIntern(), - .index = field_index, - } }, - } }))); + return Air.internedToRef((try union_ptr.ptrField(field_index, sema)).toIntern()); } return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty); } else { @@ -11131,7 +11166,7 @@ const SwitchProngAnalysis = struct { return spa.operand_ptr; } - switch (operand_ty.zigTypeTag(mod)) { + switch (operand_ty.zigTypeTag(zcu)) { .ErrorSet => if (spa.else_error_ty) |ty| { return sema.bitCast(block, ty, spa.operand, operand_src, null); } else { @@ -11142,25 +11177,25 @@ const SwitchProngAnalysis = struct { } } - switch (operand_ty.zigTypeTag(mod)) { + switch (operand_ty.zigTypeTag(zcu)) { .Union => { - const union_obj = mod.typeToUnion(operand_ty).?; + const union_obj = zcu.typeToUnion(operand_ty).?; const first_item_val = sema.resolveConstDefinedValue(block, .unneeded, case_vals[0], undefined) catch unreachable; - const first_field_index: u32 = mod.unionTagFieldIndex(union_obj, first_item_val).?; + const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?; const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_field_index]); const field_indices = try sema.arena.alloc(u32, case_vals.len); for (case_vals, field_indices) |item, *field_idx| { const item_val = sema.resolveConstDefinedValue(block, .unneeded, item, undefined) catch unreachable; - field_idx.* = mod.unionTagFieldIndex(union_obj, item_val).?; + field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?; } // Fast path: if all the operands are the same type already, we don't need to hit // PTR! This will also allow us to emit simpler code. const same_types = for (field_indices[1..]) |field_idx| { const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); - if (!field_ty.eql(first_field_ty, sema.mod)) break false; + if (!field_ty.eql(first_field_ty, zcu)) break false; } else true; const capture_ty = if (same_types) first_field_ty else capture_ty: { @@ -11168,7 +11203,7 @@ const SwitchProngAnalysis = struct { const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len); for (dummy_captures, field_indices) |*dummy, field_idx| { const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); - dummy.* = try mod.undefRef(field_ty); + dummy.* = try zcu.undefRef(field_ty); } const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len); @@ -11178,12 +11213,12 @@ const SwitchProngAnalysis = struct { error.NeededSourceLocation => { // This must be a multi-prong so this must be a `multi_capture` src const multi_idx = raw_capture_src.multi_capture; - const src_decl_ptr = sema.mod.declPtr(block.src_decl); + const src_decl_ptr = zcu.declPtr(block.src_decl); for (case_srcs, 0..) |*case_src, i| { const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(i) } }; - case_src.* = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + case_src.* = raw_case_src.resolve(zcu, src_decl_ptr, switch_node_offset, .none); } - const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + const capture_src = raw_capture_src.resolve(zcu, src_decl_ptr, switch_node_offset, .none); _ = sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err1| switch (err1) { error.AnalysisFail => { const msg = sema.err orelse return error.AnalysisFail; @@ -11200,7 +11235,7 @@ const SwitchProngAnalysis = struct { // By-reference captures have some further restrictions which make them easier to emit if (capture_byref) { - const operand_ptr_info = operand_ptr_ty.ptrInfo(mod); + const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu); const capture_ptr_ty = resolve: { // By-ref captures of hetereogeneous types are only allowed if all field // pointer types are peer resolvable to each other. @@ -11217,7 +11252,7 @@ const SwitchProngAnalysis = struct { .alignment = union_obj.fieldAlign(ip, field_idx), }, }); - dummy.* = try mod.undefRef(field_ptr_ty); + dummy.* = try zcu.undefRef(field_ptr_ty); } const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len); @memset(case_srcs, .unneeded); @@ -11226,12 +11261,12 @@ const SwitchProngAnalysis = struct { error.NeededSourceLocation => { // This must be a multi-prong so this must be a `multi_capture` src const multi_idx = raw_capture_src.multi_capture; - const src_decl_ptr = sema.mod.declPtr(block.src_decl); + const src_decl_ptr = zcu.declPtr(block.src_decl); for (case_srcs, 0..) |*case_src, i| { const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(i) } }; - case_src.* = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + case_src.* = raw_case_src.resolve(zcu, src_decl_ptr, switch_node_offset, .none); } - const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + const capture_src = raw_capture_src.resolve(zcu, src_decl_ptr, switch_node_offset, .none); _ = sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err1| switch (err1) { error.AnalysisFail => { const msg = sema.err orelse return error.AnalysisFail; @@ -11248,14 +11283,9 @@ const SwitchProngAnalysis = struct { }; if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| { - if (op_ptr_val.isUndef(mod)) return mod.undefRef(capture_ptr_ty); - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = capture_ptr_ty.toIntern(), - .addr = .{ .field = .{ - .base = op_ptr_val.toIntern(), - .index = first_field_index, - } }, - } }))); + if (op_ptr_val.isUndef(zcu)) return zcu.undefRef(capture_ptr_ty); + const field_ptr_val = try op_ptr_val.ptrField(first_field_index, sema); + return Air.internedToRef((try zcu.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern()); } try sema.requireRuntimeBlock(block, operand_src, null); @@ -11263,9 +11293,9 @@ const SwitchProngAnalysis = struct { } if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| { - if (operand_val.isUndef(mod)) return mod.undefRef(capture_ty); + if (operand_val.isUndef(zcu)) return zcu.undefRef(capture_ty); const union_val = ip.indexToKey(operand_val.toIntern()).un; - if (Value.fromInterned(union_val.tag).isUndef(mod)) return mod.undefRef(capture_ty); + if (Value.fromInterned(union_val.tag).isUndef(zcu)) return zcu.undefRef(capture_ty); const uncoerced = Air.internedToRef(union_val.val); return sema.coerce(block, capture_ty, uncoerced, operand_src); } @@ -11281,7 +11311,7 @@ const SwitchProngAnalysis = struct { const first_non_imc = in_mem: { for (field_indices, 0..) |field_idx, i| { const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); - if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded)) { break :in_mem i; } } @@ -11304,7 +11334,7 @@ const SwitchProngAnalysis = struct { const next = first_non_imc + 1; for (field_indices[next..], next..) |field_idx, i| { const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); - if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded)) { in_mem_coercible.unset(i); } } @@ -11339,9 +11369,9 @@ const SwitchProngAnalysis = struct { const coerced = sema.coerce(&coerce_block, capture_ty, uncoerced, .unneeded) catch |err| switch (err) { error.NeededSourceLocation => { const multi_idx = raw_capture_src.multi_capture; - const src_decl_ptr = sema.mod.declPtr(block.src_decl); + const src_decl_ptr = zcu.declPtr(block.src_decl); const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(idx) } }; - const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + const case_src = raw_case_src.resolve(zcu, src_decl_ptr, switch_node_offset, .none); _ = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src); unreachable; }, @@ -11400,7 +11430,7 @@ const SwitchProngAnalysis = struct { }, .ErrorSet => { if (capture_byref) { - const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none); + const capture_src = raw_capture_src.resolve(zcu, zcu.declPtr(block.src_decl), switch_node_offset, .none); return sema.fail( block, capture_src, @@ -11411,7 +11441,7 @@ const SwitchProngAnalysis = struct { if (case_vals.len == 1) { const item_val = sema.resolveConstDefinedValue(block, .unneeded, case_vals[0], undefined) catch unreachable; - const item_ty = try mod.singleErrorSetType(item_val.getErrorName(mod).unwrap().?); + const item_ty = try zcu.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?); return sema.bitCast(block, item_ty, spa.operand, operand_src, null); } @@ -11419,9 +11449,9 @@ const SwitchProngAnalysis = struct { try names.ensureUnusedCapacity(sema.arena, case_vals.len); for (case_vals) |err| { const err_val = sema.resolveConstDefinedValue(block, .unneeded, err, undefined) catch unreachable; - names.putAssumeCapacityNoClobber(err_val.getErrorName(mod).unwrap().?, {}); + names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {}); } - const error_ty = try mod.errorSetFromUnsortedNames(names.keys()); + const error_ty = try zcu.errorSetFromUnsortedNames(names.keys()); return sema.bitCast(block, error_ty, spa.operand, operand_src, null); }, else => { @@ -13989,7 +14019,7 @@ fn zirShl( const rhs_elem = try rhs_val.elemValue(mod, i); if (rhs_elem.compareHetero(.gte, bit_value, mod)) { return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{ - rhs_elem.fmtValue(mod), + rhs_elem.fmtValue(mod, sema), i, scalar_ty.fmt(mod), }); @@ -13997,7 +14027,7 @@ fn zirShl( } } else if (rhs_val.compareHetero(.gte, bit_value, mod)) { return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{ - rhs_val.fmtValue(mod), + rhs_val.fmtValue(mod, sema), scalar_ty.fmt(mod), }); } @@ -14008,14 +14038,14 @@ fn zirShl( const rhs_elem = try rhs_val.elemValue(mod, i); if (rhs_elem.compareHetero(.lt, try mod.intValue(scalar_rhs_ty, 0), mod)) { return sema.fail(block, rhs_src, "shift by negative amount '{}' at index '{d}'", .{ - rhs_elem.fmtValue(mod), + rhs_elem.fmtValue(mod, sema), i, }); } } } else if (rhs_val.compareHetero(.lt, try mod.intValue(rhs_ty, 0), mod)) { return sema.fail(block, rhs_src, "shift by negative amount '{}'", .{ - rhs_val.fmtValue(mod), + rhs_val.fmtValue(mod, sema), }); } } @@ -14154,7 +14184,7 @@ fn zirShr( const rhs_elem = try rhs_val.elemValue(mod, i); if (rhs_elem.compareHetero(.gte, bit_value, mod)) { return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{ - rhs_elem.fmtValue(mod), + rhs_elem.fmtValue(mod, sema), i, scalar_ty.fmt(mod), }); @@ -14162,7 +14192,7 @@ fn zirShr( } } else if (rhs_val.compareHetero(.gte, bit_value, mod)) { return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{ - rhs_val.fmtValue(mod), + rhs_val.fmtValue(mod, sema), scalar_ty.fmt(mod), }); } @@ -14173,14 +14203,14 @@ fn zirShr( const rhs_elem = try rhs_val.elemValue(mod, i); if (rhs_elem.compareHetero(.lt, try mod.intValue(rhs_ty.childType(mod), 0), mod)) { return sema.fail(block, rhs_src, "shift by negative amount '{}' at index '{d}'", .{ - rhs_elem.fmtValue(mod), + rhs_elem.fmtValue(mod, sema), i, }); } } } else if (rhs_val.compareHetero(.lt, try mod.intValue(rhs_ty, 0), mod)) { return sema.fail(block, rhs_src, "shift by negative amount '{}'", .{ - rhs_val.fmtValue(mod), + rhs_val.fmtValue(mod, sema), }); } if (maybe_lhs_val) |lhs_val| { @@ -15101,7 +15131,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins block, src, "ambiguous coercion of division operands '{}' and '{}'; non-zero remainder '{}'", - .{ lhs_ty.fmt(mod), rhs_ty.fmt(mod), rem.fmtValue(mod) }, + .{ lhs_ty.fmt(mod), rhs_ty.fmt(mod), rem.fmtValue(mod, sema) }, ); } } @@ -16903,21 +16933,14 @@ fn analyzePtrArithmetic( const offset_int = try sema.usizeCast(block, offset_src, try offset_val.toUnsignedIntAdvanced(sema)); if (offset_int == 0) return ptr; - if (try ptr_val.getUnsignedIntAdvanced(mod, sema)) |addr| { + if (air_tag == .ptr_sub) { const elem_size = try sema.typeAbiSize(Type.fromInterned(ptr_info.child)); - const new_addr = switch (air_tag) { - .ptr_add => addr + elem_size * offset_int, - .ptr_sub => addr - elem_size * offset_int, - else => unreachable, - }; - const new_ptr_val = try mod.ptrIntValue(new_ptr_ty, new_addr); + const new_ptr_val = try sema.ptrSubtract(block, op_src, ptr_val, offset_int * elem_size, new_ptr_ty); + return Air.internedToRef(new_ptr_val.toIntern()); + } else { + const new_ptr_val = try mod.getCoerced(try ptr_val.ptrElem(offset_int, sema), new_ptr_ty); return Air.internedToRef(new_ptr_val.toIntern()); } - if (air_tag == .ptr_sub) { - return sema.fail(block, op_src, "TODO implement Sema comptime pointer subtraction", .{}); - } - const new_ptr_val = try ptr_val.elemPtr(new_ptr_ty, offset_int, mod); - return Air.internedToRef(new_ptr_val.toIntern()); } else break :rs offset_src; } else break :rs ptr_src; }; @@ -17611,13 +17634,14 @@ fn zirBuiltinSrc( .ty = .slice_const_u8_sentinel_0_type, .ptr = try ip.get(gpa, .{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = .slice_const_u8_sentinel_0_type, .val = try ip.get(gpa, .{ .aggregate = .{ .ty = array_ty, .storage = .{ .bytes = fn_owner_decl.name.toString() }, } }), } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, func_name_len)).toIntern(), } }); @@ -17635,7 +17659,7 @@ fn zirBuiltinSrc( .ty = .slice_const_u8_sentinel_0_type, .ptr = try ip.get(gpa, .{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = .slice_const_u8_sentinel_0_type, .val = try ip.get(gpa, .{ .aggregate = .{ .ty = array_ty, @@ -17644,6 +17668,7 @@ fn zirBuiltinSrc( }, } }), } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, file_name.len)).toIntern(), } }); @@ -17766,10 +17791,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = slice_ty, .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = manyptr_ty, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, param_vals.len)).toIntern(), } }); @@ -18046,10 +18072,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = .slice_const_u8_sentinel_0_type, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, error_name_len)).toIntern(), } }); @@ -18092,10 +18119,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = slice_errors_ty.toIntern(), .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_errors_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = manyptr_errors_ty, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, vals.len)).toIntern(), } }); @@ -18184,10 +18212,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = .slice_const_u8_sentinel_0_type, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, tag_name_len)).toIntern(), } }); @@ -18226,10 +18255,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = slice_ty, .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = manyptr_ty, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, enum_field_vals.len)).toIntern(), } }); @@ -18318,10 +18348,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = .slice_const_u8_sentinel_0_type, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, field_name_len)).toIntern(), } }); @@ -18368,10 +18399,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = slice_ty, .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = manyptr_ty, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, union_field_vals.len)).toIntern(), } }); @@ -18471,10 +18503,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = .slice_const_u8_sentinel_0_type, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, field_name_len)).toIntern(), } }); @@ -18534,10 +18567,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .val = new_decl_val, .orig_ty = .slice_const_u8_sentinel_0_type, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, field_name_len)).toIntern(), } }); @@ -18594,10 +18628,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .ty = slice_ty, .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = manyptr_ty, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, struct_field_vals.len)).toIntern(), } }); @@ -18733,10 +18768,11 @@ fn typeInfoDecls( .ty = slice_ty, .ptr = try mod.intern(.{ .ptr = .{ .ty = manyptr_ty, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = manyptr_ty, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, decl_vals.items.len)).toIntern(), } }); @@ -18765,7 +18801,7 @@ fn typeInfoNamespaceDecls( if (!decl.is_pub) continue; if (decl.kind == .@"usingnamespace") { if (decl.analysis == .in_progress) continue; - try mod.ensureDeclAnalyzed(decl_index); + try sema.ensureDeclAnalyzed(decl_index); try sema.typeInfoNamespaceDecls(block, decl.val.toType().getNamespaceIndex(mod), declaration_ty, decl_vals, seen_namespaces); continue; } @@ -18785,10 +18821,11 @@ fn typeInfoNamespaceDecls( .ty = .slice_const_u8_sentinel_0_type, .ptr = try mod.intern(.{ .ptr = .{ .ty = .manyptr_const_u8_sentinel_0_type, - .addr = .{ .anon_decl = .{ + .base_addr = .{ .anon_decl = .{ .orig_ty = .slice_const_u8_sentinel_0_type, .val = new_decl_val, } }, + .byte_offset = 0, } }), .len = (try mod.intValue(Type.usize, decl_name_len)).toIntern(), } }); @@ -19907,6 +19944,16 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air } } + if (host_size != 0 and !try sema.validatePackedType(elem_ty)) { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, elem_ty_src, "bit-pointer cannot refer to value of type '{}'", .{elem_ty.fmt(mod)}); + errdefer msg.destroy(sema.gpa); + const src_decl = mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsNotPacked(msg, src_decl.toSrcLoc(elem_ty_src, mod), elem_ty); + break :msg msg; + }); + } + const ty = try sema.ptrType(.{ .child = elem_ty.toIntern(), .sentinel = sentinel, @@ -21176,7 +21223,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const enum_decl = mod.declPtr(enum_decl_index); const msg = msg: { const msg = try sema.errMsg(block, src, "no field with value '{}' in enum '{}'", .{ - val.fmtValue(sema.mod), enum_decl.name.fmt(ip), + val.fmtValue(sema.mod, sema), enum_decl.name.fmt(ip), }); errdefer msg.destroy(sema.gpa); try mod.errNoteNonLazy(enum_decl.srcLoc(mod), msg, "declared here", .{}); @@ -21811,7 +21858,7 @@ fn reifyEnum( // TODO: better source location return sema.fail(block, src, "field '{}' with enumeration value '{}' is too large for backing int type '{}'", .{ field_name.fmt(ip), - field_value_val.fmtValue(mod), + field_value_val.fmtValue(mod, sema), tag_ty.fmt(mod), }); } @@ -21827,7 +21874,7 @@ fn reifyEnum( break :msg msg; }, .value => msg: { - const msg = try sema.errMsg(block, src, "enum tag value {} already taken", .{field_value_val.fmtValue(mod)}); + const msg = try sema.errMsg(block, src, "enum tag value {} already taken", .{field_value_val.fmtValue(mod, sema)}); errdefer msg.destroy(gpa); _ = conflict.prev_field_idx; // TODO: this note is incorrect try sema.errNote(block, src, msg, "other enum tag value here", .{}); @@ -22681,19 +22728,25 @@ fn ptrFromIntVal( ptr_ty: Type, ptr_align: Alignment, ) !Value { - const mod = sema.mod; + const zcu = sema.mod; + if (operand_val.isUndef(zcu)) { + if (ptr_ty.isAllowzeroPtr(zcu) and ptr_align == .@"1") { + return zcu.undefValue(ptr_ty); + } + return sema.failWithUseOfUndef(block, operand_src); + } const addr = try operand_val.toUnsignedIntAdvanced(sema); - if (!ptr_ty.isAllowzeroPtr(mod) and addr == 0) - return sema.fail(block, operand_src, "pointer type '{}' does not allow address zero", .{ptr_ty.fmt(sema.mod)}); + if (!ptr_ty.isAllowzeroPtr(zcu) and addr == 0) + return sema.fail(block, operand_src, "pointer type '{}' does not allow address zero", .{ptr_ty.fmt(zcu)}); if (addr != 0 and ptr_align != .none and !ptr_align.check(addr)) - return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{ptr_ty.fmt(sema.mod)}); + return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{ptr_ty.fmt(zcu)}); - return switch (ptr_ty.zigTypeTag(mod)) { - .Optional => Value.fromInterned((try mod.intern(.{ .opt = .{ + return switch (ptr_ty.zigTypeTag(zcu)) { + .Optional => Value.fromInterned((try zcu.intern(.{ .opt = .{ .ty = ptr_ty.toIntern(), - .val = if (addr == 0) .none else (try mod.ptrIntValue(ptr_ty.childType(mod), addr)).toIntern(), + .val = if (addr == 0) .none else (try zcu.ptrIntValue(ptr_ty.childType(zcu), addr)).toIntern(), } }))), - .Pointer => try mod.ptrIntValue(ptr_ty, addr), + .Pointer => try zcu.ptrIntValue(ptr_ty, addr), else => unreachable, }; } @@ -22980,12 +23033,12 @@ fn ptrCastFull( return sema.failWithOwnedErrorMsg(block, msg: { const msg = if (src_info.sentinel == .none) blk: { break :blk try sema.errMsg(block, src, "destination pointer requires '{}' sentinel", .{ - Value.fromInterned(dest_info.sentinel).fmtValue(mod), + Value.fromInterned(dest_info.sentinel).fmtValue(mod, sema), }); } else blk: { break :blk try sema.errMsg(block, src, "pointer sentinel '{}' cannot coerce into pointer sentinel '{}'", .{ - Value.fromInterned(src_info.sentinel).fmtValue(mod), - Value.fromInterned(dest_info.sentinel).fmtValue(mod), + Value.fromInterned(src_info.sentinel).fmtValue(mod, sema), + Value.fromInterned(dest_info.sentinel).fmtValue(mod, sema), }); }; errdefer msg.destroy(sema.gpa); @@ -23159,11 +23212,13 @@ 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)); + const ptr_val_key = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr; return Air.internedToRef((try mod.intern(.{ .slice = .{ .ty = dest_ty.toIntern(), .ptr = try mod.intern(.{ .ptr = .{ .ty = dest_ty.slicePtrFieldType(mod).toIntern(), - .addr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr, + .base_addr = ptr_val_key.base_addr, + .byte_offset = ptr_val_key.byte_offset, } }), .len = arr_len.toIntern(), } }))); @@ -23834,36 +23889,6 @@ fn checkPtrIsNotComptimeMutable( } } -fn checkComptimeVarStore( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - alloc_index: ComptimeAllocIndex, -) CompileError!void { - const runtime_index = sema.getComptimeAlloc(alloc_index).runtime_index; - if (@intFromEnum(runtime_index) < @intFromEnum(block.runtime_index)) { - if (block.runtime_cond) |cond_src| { - const msg = msg: { - const msg = try sema.errMsg(block, src, "store to comptime variable depends on runtime condition", .{}); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy(cond_src, msg, "runtime condition here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - if (block.runtime_loop) |loop_src| { - const msg = msg: { - const msg = try sema.errMsg(block, src, "cannot store to comptime variable in non-inline loop", .{}); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy(loop_src, msg, "non-inline loop here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - unreachable; - } -} - fn checkIntOrVector( sema: *Sema, block: *Block, @@ -24926,8 +24951,8 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { - const mod = sema.mod; - const ip = &mod.intern_pool; + const zcu = sema.mod; + const ip = &zcu.intern_pool; const extra = sema.code.extraData(Zir.Inst.FieldParentPtr, extended.operand).data; const FlagsInt = @typeInfo(Zir.Inst.FullPtrCastFlags).Struct.backing_integer.?; @@ -24939,23 +24964,23 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins const parent_ptr_ty = try sema.resolveDestType(block, inst_src, extra.parent_ptr_type, .remove_eu, "@fieldParentPtr"); try sema.checkPtrType(block, inst_src, parent_ptr_ty, true); - const parent_ptr_info = parent_ptr_ty.ptrInfo(mod); + const parent_ptr_info = parent_ptr_ty.ptrInfo(zcu); if (parent_ptr_info.flags.size != .One) { - return sema.fail(block, inst_src, "expected single pointer type, found '{}'", .{parent_ptr_ty.fmt(sema.mod)}); + return sema.fail(block, inst_src, "expected single pointer type, found '{}'", .{parent_ptr_ty.fmt(zcu)}); } const parent_ty = Type.fromInterned(parent_ptr_info.child); - switch (parent_ty.zigTypeTag(mod)) { + switch (parent_ty.zigTypeTag(zcu)) { .Struct, .Union => {}, - else => return sema.fail(block, inst_src, "expected pointer to struct or union type, found '{}'", .{parent_ptr_ty.fmt(sema.mod)}), + else => return sema.fail(block, inst_src, "expected pointer to struct or union type, found '{}'", .{parent_ptr_ty.fmt(zcu)}), } try sema.resolveTypeLayout(parent_ty); const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, .{ .needed_comptime_reason = "field name must be comptime-known", }); - const field_index = switch (parent_ty.zigTypeTag(mod)) { + const field_index = switch (parent_ty.zigTypeTag(zcu)) { .Struct => blk: { - if (parent_ty.isTuple(mod)) { + if (parent_ty.isTuple(zcu)) { if (field_name.eqlSlice("len", ip)) { return sema.fail(block, inst_src, "cannot get @fieldParentPtr of 'len' field of tuple", .{}); } @@ -24967,19 +24992,19 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins .Union => try sema.unionFieldIndex(block, parent_ty, field_name, field_name_src), else => unreachable, }; - if (parent_ty.zigTypeTag(mod) == .Struct and parent_ty.structFieldIsComptime(field_index, mod)) { + if (parent_ty.zigTypeTag(zcu) == .Struct and parent_ty.structFieldIsComptime(field_index, zcu)) { return sema.fail(block, field_name_src, "cannot get @fieldParentPtr of a comptime field", .{}); } const field_ptr = try sema.resolveInst(extra.field_ptr); const field_ptr_ty = sema.typeOf(field_ptr); try sema.checkPtrOperand(block, field_ptr_src, field_ptr_ty); - const field_ptr_info = field_ptr_ty.ptrInfo(mod); + const field_ptr_info = field_ptr_ty.ptrInfo(zcu); var actual_parent_ptr_info: InternPool.Key.PtrType = .{ .child = parent_ty.toIntern(), .flags = .{ - .alignment = try parent_ptr_ty.ptrAlignmentAdvanced(mod, sema), + .alignment = try parent_ptr_ty.ptrAlignmentAdvanced(zcu, sema), .is_const = field_ptr_info.flags.is_const, .is_volatile = field_ptr_info.flags.is_volatile, .is_allowzero = field_ptr_info.flags.is_allowzero, @@ -24987,11 +25012,11 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins }, .packed_offset = parent_ptr_info.packed_offset, }; - const field_ty = parent_ty.structFieldType(field_index, mod); + const field_ty = parent_ty.structFieldType(field_index, zcu); var actual_field_ptr_info: InternPool.Key.PtrType = .{ .child = field_ty.toIntern(), .flags = .{ - .alignment = try field_ptr_ty.ptrAlignmentAdvanced(mod, sema), + .alignment = try field_ptr_ty.ptrAlignmentAdvanced(zcu, sema), .is_const = field_ptr_info.flags.is_const, .is_volatile = field_ptr_info.flags.is_volatile, .is_allowzero = field_ptr_info.flags.is_allowzero, @@ -24999,14 +25024,14 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins }, .packed_offset = field_ptr_info.packed_offset, }; - switch (parent_ty.containerLayout(mod)) { + switch (parent_ty.containerLayout(zcu)) { .auto => { actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict( - if (mod.typeToStruct(parent_ty)) |struct_obj| try sema.structFieldAlignment( + if (zcu.typeToStruct(parent_ty)) |struct_obj| try sema.structFieldAlignment( struct_obj.fieldAlign(ip, field_index), field_ty, struct_obj.layout, - ) else if (mod.typeToUnion(parent_ty)) |union_obj| + ) else if (zcu.typeToUnion(parent_ty)) |union_obj| try sema.unionFieldAlignment(union_obj, field_index) else actual_field_ptr_info.flags.alignment, @@ -25016,7 +25041,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins actual_field_ptr_info.packed_offset = .{ .bit_offset = 0, .host_size = 0 }; }, .@"extern" => { - const field_offset = parent_ty.structFieldOffset(field_index, mod); + const field_offset = parent_ty.structFieldOffset(field_index, zcu); actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(if (field_offset > 0) Alignment.fromLog2Units(@ctz(field_offset)) else @@ -25027,7 +25052,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins }, .@"packed" => { const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) + - (if (mod.typeToStruct(parent_ty)) |struct_obj| mod.structPackedFieldBitOffset(struct_obj, field_index) else 0) - + (if (zcu.typeToStruct(parent_ty)) |struct_obj| zcu.structPackedFieldBitOffset(struct_obj, field_index) else 0) - actual_field_ptr_info.packed_offset.bit_offset), 8) catch return sema.fail(block, inst_src, "pointer bit-offset mismatch", .{}); actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(if (byte_offset > 0) @@ -25040,18 +25065,60 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins const actual_field_ptr_ty = try sema.ptrType(actual_field_ptr_info); const casted_field_ptr = try sema.coerce(block, actual_field_ptr_ty, field_ptr, field_ptr_src); const actual_parent_ptr_ty = try sema.ptrType(actual_parent_ptr_info); + const result = if (try sema.resolveDefinedValue(block, field_ptr_src, casted_field_ptr)) |field_ptr_val| result: { - const field = switch (ip.indexToKey(field_ptr_val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + switch (parent_ty.zigTypeTag(zcu)) { + .Struct => switch (parent_ty.containerLayout(zcu)) { + .auto => {}, + .@"extern" => { + const byte_offset = parent_ty.structFieldOffset(field_index, zcu); + const parent_ptr_val = try sema.ptrSubtract(block, field_ptr_src, field_ptr_val, byte_offset, actual_parent_ptr_ty); + break :result Air.internedToRef(parent_ptr_val.toIntern()); + }, + .@"packed" => { + // Logic lifted from type computation above - I'm just assuming it's correct. + // `catch unreachable` since error case handled above. + const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) + + zcu.structPackedFieldBitOffset(zcu.typeToStruct(parent_ty).?, field_index) - + actual_field_ptr_info.packed_offset.bit_offset), 8) catch unreachable; + const parent_ptr_val = try sema.ptrSubtract(block, field_ptr_src, field_ptr_val, byte_offset, actual_parent_ptr_ty); + break :result Air.internedToRef(parent_ptr_val.toIntern()); + }, + }, + .Union => switch (parent_ty.containerLayout(zcu)) { + .auto => {}, + .@"extern", .@"packed" => { + // For an extern or packed union, just coerce the pointer. + const parent_ptr_val = try zcu.getCoerced(field_ptr_val, actual_parent_ptr_ty); + break :result Air.internedToRef(parent_ptr_val.toIntern()); + }, + }, + else => unreachable, + } + + const opt_field: ?InternPool.Key.Ptr.BaseAddr.BaseIndex = opt_field: { + const ptr = switch (ip.indexToKey(field_ptr_val.toIntern())) { + .ptr => |ptr| ptr, + else => break :opt_field null, + }; + if (ptr.byte_offset != 0) break :opt_field null; + break :opt_field switch (ptr.base_addr) { .field => |field| field, else => null, - }, - else => null, - } orelse return sema.fail(block, field_ptr_src, "pointer value not based on parent struct", .{}); + }; + }; + + const field = opt_field orelse { + return sema.fail(block, field_ptr_src, "pointer value not based on parent struct", .{}); + }; + + if (Value.fromInterned(field.base).typeOf(zcu).childType(zcu).toIntern() != parent_ty.toIntern()) { + return sema.fail(block, field_ptr_src, "pointer value not based on parent struct", .{}); + } if (field.index != field_index) { return sema.fail(block, inst_src, "field '{}' has index '{d}' but pointer value is index '{d}' of struct '{}'", .{ - field_name.fmt(ip), field_index, field.index, parent_ty.fmt(sema.mod), + field_name.fmt(ip), field_index, field.index, parent_ty.fmt(zcu), }); } break :result try sema.coerce(block, actual_parent_ptr_ty, Air.internedToRef(field.base), inst_src); @@ -25072,6 +25139,27 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins return sema.ptrCastFull(block, flags, inst_src, result, inst_src, parent_ptr_ty, "@fieldParentPtr"); } +fn ptrSubtract(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, byte_subtract: u64, new_ty: Type) !Value { + const zcu = sema.mod; + if (byte_subtract == 0) return zcu.getCoerced(ptr_val, new_ty); + var ptr = switch (zcu.intern_pool.indexToKey(ptr_val.toIntern())) { + .undef => return sema.failWithUseOfUndef(block, src), + .ptr => |ptr| ptr, + else => unreachable, + }; + if (ptr.byte_offset < byte_subtract) { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, src, "pointer computation here causes undefined behavior", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "resulting pointer exceeds bounds of containing value which may trigger overflow", .{}); + break :msg msg; + }); + } + ptr.byte_offset -= byte_subtract; + ptr.ty = new_ty.toIntern(); + return Value.fromInterned(try zcu.intern(.{ .ptr = ptr })); +} + fn zirMinMax( sema: *Sema, block: *Block, @@ -25424,10 +25512,10 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const msg = try sema.errMsg(block, src, "non-matching @memcpy lengths", .{}); errdefer msg.destroy(sema.gpa); try sema.errNote(block, dest_src, msg, "length {} here", .{ - dest_len_val.fmtValue(sema.mod), + dest_len_val.fmtValue(sema.mod, sema), }); try sema.errNote(block, src_src, msg, "length {} here", .{ - src_len_val.fmtValue(sema.mod), + src_len_val.fmtValue(sema.mod, sema), }); break :msg msg; }; @@ -26340,7 +26428,8 @@ fn zirBuiltinExtern( .opt_type => |child_type| child_type, else => unreachable, }, - .addr = .{ .decl = new_decl_index }, + .base_addr = .{ .decl = new_decl_index }, + .byte_offset = 0, } }))), ty)).toIntern()); } @@ -26745,8 +26834,8 @@ fn explainWhyTypeIsNotExtern( /// Returns true if `ty` is allowed in packed types. /// Does not require `ty` to be resolved in any way, but may resolve whether it is comptime-only. fn validatePackedType(sema: *Sema, ty: Type) !bool { - const mod = sema.mod; - switch (ty.zigTypeTag(mod)) { + const zcu = sema.mod; + return switch (ty.zigTypeTag(zcu)) { .Type, .ComptimeFloat, .ComptimeInt, @@ -26761,18 +26850,21 @@ fn validatePackedType(sema: *Sema, ty: Type) !bool { .AnyFrame, .Fn, .Array, - => return false, - .Optional => return ty.isPtrLikeOptional(mod), + => false, + .Optional => return ty.isPtrLikeOptional(zcu), .Void, .Bool, .Float, .Int, .Vector, - .Enum, - => return true, - .Pointer => return !ty.isSlice(mod) and !try sema.typeRequiresComptime(ty), - .Struct, .Union => return ty.containerLayout(mod) == .@"packed", - } + => true, + .Enum => switch (zcu.intern_pool.loadEnumType(ty.toIntern()).tag_mode) { + .auto => false, + .explicit, .nonexhaustive => true, + }, + .Pointer => !ty.isSlice(zcu) and !try sema.typeRequiresComptime(ty), + .Struct, .Union => ty.containerLayout(zcu) == .@"packed", + }; } fn explainWhyTypeIsNotPacked( @@ -27443,13 +27535,7 @@ fn fieldPtr( }); if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = result_ty.toIntern(), - .addr = .{ .field = .{ - .base = val.toIntern(), - .index = Value.slice_ptr_index, - } }, - } }))); + return Air.internedToRef((try val.ptrField(Value.slice_ptr_index, sema)).toIntern()); } try sema.requireRuntimeBlock(block, src, null); @@ -27467,13 +27553,7 @@ fn fieldPtr( }); if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = result_ty.toIntern(), - .addr = .{ .field = .{ - .base = val.toIntern(), - .index = Value.slice_len_index, - } }, - } }))); + return Air.internedToRef((try val.ptrField(Value.slice_len_index, sema)).toIntern()); } try sema.requireRuntimeBlock(block, src, null); @@ -27785,13 +27865,8 @@ fn finishFieldCallBind( } if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| { - const pointer = Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = struct_ptr_val.toIntern(), - .index = field_index, - } }, - } }))); + const ptr_val = try struct_ptr_val.ptrField(field_index, sema); + const pointer = Air.internedToRef(ptr_val.toIntern()); return .{ .direct = try sema.analyzeLoad(block, src, pointer, src) }; } @@ -27903,6 +27978,11 @@ fn structFieldPtrByIndex( return sema.tupleFieldPtr(block, src, struct_ptr, field_src, field_index, initializing); } + if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| { + const val = try struct_ptr_val.ptrField(field_index, sema); + return Air.internedToRef(val.toIntern()); + } + const struct_type = mod.typeToStruct(struct_ty).?; const field_ty = struct_type.field_types.get(ip)[field_index]; const struct_ptr_ty = sema.typeOf(struct_ptr); @@ -27917,57 +27997,20 @@ fn structFieldPtrByIndex( }, }; - const target = mod.getTarget(); - const parent_align = if (struct_ptr_ty_info.flags.alignment != .none) struct_ptr_ty_info.flags.alignment else try sema.typeAbiAlignment(Type.fromInterned(struct_ptr_ty_info.child)); if (struct_type.layout == .@"packed") { - comptime assert(Type.packed_struct_layout_version == 2); - - var running_bits: u16 = 0; - for (0..struct_type.field_types.len) |i| { - const f_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]); - if (!(try sema.typeHasRuntimeBits(f_ty))) continue; - - if (i == field_index) { - ptr_ty_data.packed_offset.bit_offset = running_bits; - } - running_bits += @intCast(f_ty.bitSize(mod)); - } - ptr_ty_data.packed_offset.host_size = (running_bits + 7) / 8; - - // If this is a packed struct embedded in another one, we need to offset - // the bits against each other. - if (struct_ptr_ty_info.packed_offset.host_size != 0) { - ptr_ty_data.packed_offset.host_size = struct_ptr_ty_info.packed_offset.host_size; - ptr_ty_data.packed_offset.bit_offset += struct_ptr_ty_info.packed_offset.bit_offset; - } - - ptr_ty_data.flags.alignment = parent_align; - - // If the field happens to be byte-aligned, simplify the pointer type. - // The pointee type bit size must match its ABI byte size so that loads and stores - // do not interfere with the surrounding packed bits. - // We do not attempt this with big-endian targets yet because of nested - // structs and floats. I need to double-check the desired behavior for big endian - // targets before adding the necessary complications to this code. This will not - // cause miscompilations; it only means the field pointer uses bit masking when it - // might not be strictly necessary. - if (parent_align != .none and ptr_ty_data.packed_offset.bit_offset % 8 == 0 and - target.cpu.arch.endian() == .little) - { - const elem_size_bytes = try sema.typeAbiSize(Type.fromInterned(ptr_ty_data.child)); - const elem_size_bits = Type.fromInterned(ptr_ty_data.child).bitSize(mod); - if (elem_size_bytes * 8 == elem_size_bits) { - const byte_offset = ptr_ty_data.packed_offset.bit_offset / 8; - const new_align: Alignment = @enumFromInt(@ctz(byte_offset | parent_align.toByteUnits().?)); - assert(new_align != .none); - ptr_ty_data.flags.alignment = new_align; - ptr_ty_data.packed_offset = .{ .host_size = 0, .bit_offset = 0 }; - } + switch (struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, mod)) { + .bit_ptr => |packed_offset| { + ptr_ty_data.flags.alignment = parent_align; + ptr_ty_data.packed_offset = packed_offset; + }, + .byte_ptr => |ptr_info| { + ptr_ty_data.flags.alignment = ptr_info.alignment; + }, } } else if (struct_type.layout == .@"extern") { // For extern structs, field alignment might be bigger than type's @@ -27997,18 +28040,8 @@ fn structFieldPtrByIndex( try sema.resolveStructFieldInits(struct_ty); const val = try mod.intern(.{ .ptr = .{ .ty = ptr_field_ty.toIntern(), - .addr = .{ .comptime_field = struct_type.field_inits.get(ip)[field_index] }, - } }); - return Air.internedToRef(val); - } - - if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| { - const val = try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = struct_ptr_val.toIntern(), - .index = field_index, - } }, + .base_addr = .{ .comptime_field = struct_type.field_inits.get(ip)[field_index] }, + .byte_offset = 0, } }); return Air.internedToRef(val); } @@ -28206,7 +28239,13 @@ fn unionFieldPtr( if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: { switch (union_obj.getLayout(ip)) { - .auto => if (!initializing) { + .auto => if (initializing) { + // Store to the union to initialize the tag. + const field_tag = try mod.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index); + const payload_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]); + const new_union_val = try mod.unionValue(union_ty, field_tag, try mod.undefValue(payload_ty)); + try sema.storePtrVal(block, src, union_ptr_val, new_union_val, union_ty); + } else { const union_val = (try sema.pointerDeref(block, src, union_ptr_val, union_ptr_ty)) orelse break :ct; if (union_val.isUndef(mod)) { @@ -28232,13 +28271,8 @@ fn unionFieldPtr( }, .@"packed", .@"extern" => {}, } - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = union_ptr_val.toIntern(), - .index = field_index, - } }, - } }))); + const field_ptr_val = try union_ptr_val.ptrField(field_index, sema); + return Air.internedToRef(field_ptr_val.toIntern()); } try sema.requireRuntimeBlock(block, src, null); @@ -28302,18 +28336,12 @@ fn unionFieldVal( return sema.failWithOwnedErrorMsg(block, msg); } }, - .@"packed", .@"extern" => |layout| { + .@"packed", .@"extern" => { if (tag_matches) { + // Fast path - no need to use bitcast logic. return Air.internedToRef(un.val); - } else { - const old_ty = if (un.tag == .none) - Type.fromInterned(ip.typeOf(un.val)) - else - union_ty.unionFieldType(Value.fromInterned(un.tag), mod).?; - - if (try sema.bitCastUnionFieldVal(block, src, Value.fromInterned(un.val), old_ty, field_ty, layout)) |new_val| { - return Air.internedToRef(new_val.toIntern()); - } + } else if (try sema.bitCastVal(union_val, field_ty, 0)) |field_val| { + return Air.internedToRef(field_val.toIntern()); } }, } @@ -28402,8 +28430,7 @@ fn elemPtrOneLayerOnly( const ptr_val = maybe_ptr_val orelse break :rs indexable_src; const index_val = maybe_index_val orelse break :rs elem_index_src; const index: usize = @intCast(try index_val.toUnsignedIntAdvanced(sema)); - const result_ty = try sema.elemPtrType(indexable_ty, index); - const elem_ptr = try ptr_val.elemPtr(result_ty, index, mod); + const elem_ptr = try ptr_val.ptrElem(index, sema); return Air.internedToRef(elem_ptr.toIntern()); }; const result_ty = try sema.elemPtrType(indexable_ty, null); @@ -28465,7 +28492,7 @@ fn elemVal( const many_ptr_ty = try mod.manyConstPtrType(elem_ty); const many_ptr_val = try mod.getCoerced(indexable_val, many_ptr_ty); const elem_ptr_ty = try mod.singleConstPtrType(elem_ty); - const elem_ptr_val = try many_ptr_val.elemPtr(elem_ptr_ty, index, mod); + const elem_ptr_val = try many_ptr_val.ptrElem(index, sema); if (try sema.pointerDeref(block, indexable_src, elem_ptr_val, elem_ptr_ty)) |elem_val| { return Air.internedToRef((try mod.getCoerced(elem_val, elem_ty)).toIntern()); } @@ -28571,21 +28598,18 @@ fn tupleFieldPtr( if (tuple_ty.structFieldIsComptime(field_index, mod)) try sema.resolveStructFieldInits(tuple_ty); + if (try tuple_ty.structFieldValueComptime(mod, field_index)) |default_val| { return Air.internedToRef((try mod.intern(.{ .ptr = .{ .ty = ptr_field_ty.toIntern(), - .addr = .{ .comptime_field = default_val.toIntern() }, + .base_addr = .{ .comptime_field = default_val.toIntern() }, + .byte_offset = 0, } }))); } if (try sema.resolveValue(tuple_ptr)) |tuple_ptr_val| { - return Air.internedToRef((try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = tuple_ptr_val.toIntern(), - .index = field_index, - } }, - } }))); + const field_ptr_val = try tuple_ptr_val.ptrField(field_index, sema); + return Air.internedToRef(field_ptr_val.toIntern()); } if (!init) { @@ -28747,7 +28771,7 @@ fn elemPtrArray( return mod.undefRef(elem_ptr_ty); } if (offset) |index| { - const elem_ptr = try array_ptr_val.elemPtr(elem_ptr_ty, index, mod); + const elem_ptr = try array_ptr_val.ptrElem(index, sema); return Air.internedToRef(elem_ptr.toIntern()); } } @@ -28804,7 +28828,7 @@ fn elemValSlice( return sema.fail(block, elem_index_src, "index {d} outside slice of length {d}{s}", .{ index, slice_len, sentinel_label }); } const elem_ptr_ty = try sema.elemPtrType(slice_ty, index); - const elem_ptr_val = try slice_val.elemPtr(elem_ptr_ty, index, mod); + const elem_ptr_val = try slice_val.ptrElem(index, sema); if (try sema.pointerDeref(block, slice_src, elem_ptr_val, elem_ptr_ty)) |elem_val| { return Air.internedToRef(elem_val.toIntern()); } @@ -28864,7 +28888,7 @@ fn elemPtrSlice( const sentinel_label: []const u8 = if (slice_sent) " +1 (sentinel)" else ""; return sema.fail(block, elem_index_src, "index {d} outside slice of length {d}{s}", .{ index, slice_len, sentinel_label }); } - const elem_ptr_val = try slice_val.elemPtr(elem_ptr_ty, index, mod); + const elem_ptr_val = try slice_val.ptrElem(index, sema); return Air.internedToRef(elem_ptr_val.toIntern()); } } @@ -28943,14 +28967,14 @@ fn coerceExtra( opts: CoerceOpts, ) CoersionError!Air.Inst.Ref { if (dest_ty.isGenericPoison()) return inst; - const mod = sema.mod; + const zcu = sema.mod; const dest_ty_src = inst_src; // TODO better source location try sema.resolveTypeFields(dest_ty); const inst_ty = sema.typeOf(inst); try sema.resolveTypeFields(inst_ty); - const target = mod.getTarget(); + const target = zcu.getTarget(); // If the types are the same, we can return the operand. - if (dest_ty.eql(inst_ty, mod)) + if (dest_ty.eql(inst_ty, zcu)) return inst; const maybe_inst_val = try sema.resolveValue(inst); @@ -28967,17 +28991,17 @@ fn coerceExtra( return new_val; } - switch (dest_ty.zigTypeTag(mod)) { + switch (dest_ty.zigTypeTag(zcu)) { .Optional => optional: { if (maybe_inst_val) |val| { // undefined sets the optional bit also to undefined. if (val.toIntern() == .undef) { - return mod.undefRef(dest_ty); + return zcu.undefRef(dest_ty); } // null to ?T if (val.toIntern() == .null_value) { - return Air.internedToRef((try mod.intern(.{ .opt = .{ + return Air.internedToRef((try zcu.intern(.{ .opt = .{ .ty = dest_ty.toIntern(), .val = .none, } }))); @@ -28986,13 +29010,13 @@ fn coerceExtra( // cast from ?*T and ?[*]T to ?*anyopaque // but don't do it if the source type is a double pointer - if (dest_ty.isPtrLikeOptional(mod) and - dest_ty.elemType2(mod).toIntern() == .anyopaque_type and - inst_ty.isPtrAtRuntime(mod)) + if (dest_ty.isPtrLikeOptional(zcu) and + dest_ty.elemType2(zcu).toIntern() == .anyopaque_type and + inst_ty.isPtrAtRuntime(zcu)) anyopaque_check: { if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :optional; - const elem_ty = inst_ty.elemType2(mod); - if (elem_ty.zigTypeTag(mod) == .Pointer or elem_ty.isPtrLikeOptional(mod)) { + const elem_ty = inst_ty.elemType2(zcu); + if (elem_ty.zigTypeTag(zcu) == .Pointer or elem_ty.isPtrLikeOptional(zcu)) { in_memory_result = .{ .double_ptr_to_anyopaque = .{ .actual = inst_ty, .wanted = dest_ty, @@ -29001,12 +29025,12 @@ fn coerceExtra( } // Let the logic below handle wrapping the optional now that // it has been checked to correctly coerce. - if (!inst_ty.isPtrLikeOptional(mod)) break :anyopaque_check; + if (!inst_ty.isPtrLikeOptional(zcu)) break :anyopaque_check; return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } // T to ?T - const child_type = dest_ty.optionalChild(mod); + const child_type = dest_ty.optionalChild(zcu); const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { if (in_memory_result == .no_match) { @@ -29020,12 +29044,12 @@ fn coerceExtra( return try sema.wrapOptional(block, dest_ty, intermediate, inst_src); }, .Pointer => pointer: { - const dest_info = dest_ty.ptrInfo(mod); + const dest_info = dest_ty.ptrInfo(zcu); // Function body to function pointer. - if (inst_ty.zigTypeTag(mod) == .Fn) { + if (inst_ty.zigTypeTag(zcu) == .Fn) { const fn_val = try sema.resolveConstDefinedValue(block, .unneeded, inst, undefined); - const fn_decl = fn_val.pointerDecl(mod).?; + const fn_decl = fn_val.pointerDecl(zcu).?; const inst_as_ptr = try sema.analyzeDeclRef(fn_decl); return sema.coerce(block, dest_ty, inst_as_ptr, inst_src); } @@ -29033,13 +29057,13 @@ fn coerceExtra( // *T to *[1]T single_item: { if (dest_info.flags.size != .One) break :single_item; - if (!inst_ty.isSinglePointer(mod)) break :single_item; + if (!inst_ty.isSinglePointer(zcu)) break :single_item; if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; - const ptr_elem_ty = inst_ty.childType(mod); + const ptr_elem_ty = inst_ty.childType(zcu); const array_ty = Type.fromInterned(dest_info.child); - if (array_ty.zigTypeTag(mod) != .Array) break :single_item; - const array_elem_ty = array_ty.childType(mod); - if (array_ty.arrayLen(mod) != 1) break :single_item; + if (array_ty.zigTypeTag(zcu) != .Array) break :single_item; + const array_elem_ty = array_ty.childType(zcu); + if (array_ty.arrayLen(zcu) != 1) break :single_item; const dest_is_mut = !dest_info.flags.is_const; switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) { .ok => {}, @@ -29050,11 +29074,11 @@ fn coerceExtra( // Coercions where the source is a single pointer to an array. src_array_ptr: { - if (!inst_ty.isSinglePointer(mod)) break :src_array_ptr; + if (!inst_ty.isSinglePointer(zcu)) break :src_array_ptr; if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; - const array_ty = inst_ty.childType(mod); - if (array_ty.zigTypeTag(mod) != .Array) break :src_array_ptr; - const array_elem_type = array_ty.childType(mod); + const array_ty = inst_ty.childType(zcu); + if (array_ty.zigTypeTag(zcu) != .Array) break :src_array_ptr; + const array_elem_type = array_ty.childType(zcu); const dest_is_mut = !dest_info.flags.is_const; const dst_elem_type = Type.fromInterned(dest_info.child); @@ -29072,7 +29096,7 @@ fn coerceExtra( } if (dest_info.sentinel != .none) { - if (array_ty.sentinel(mod)) |inst_sent| { + if (array_ty.sentinel(zcu)) |inst_sent| { if (Air.internedToRef(dest_info.sentinel) != try sema.coerceInMemory(inst_sent, dst_elem_type)) { @@ -29111,12 +29135,12 @@ fn coerceExtra( } // coercion from C pointer - if (inst_ty.isCPtr(mod)) src_c_ptr: { + if (inst_ty.isCPtr(zcu)) src_c_ptr: { if (dest_info.flags.size == .Slice) break :src_c_ptr; if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :src_c_ptr; // In this case we must add a safety check because the C pointer // could be null. - const src_elem_ty = inst_ty.childType(mod); + const src_elem_ty = inst_ty.childType(zcu); const dest_is_mut = !dest_info.flags.is_const; const dst_elem_type = Type.fromInterned(dest_info.child); switch (try sema.coerceInMemoryAllowed(block, dst_elem_type, src_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) { @@ -29128,18 +29152,18 @@ fn coerceExtra( // cast from *T and [*]T to *anyopaque // but don't do it if the source type is a double pointer - if (dest_info.child == .anyopaque_type and inst_ty.zigTypeTag(mod) == .Pointer) to_anyopaque: { + if (dest_info.child == .anyopaque_type and inst_ty.zigTypeTag(zcu) == .Pointer) to_anyopaque: { if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; - const elem_ty = inst_ty.elemType2(mod); - if (elem_ty.zigTypeTag(mod) == .Pointer or elem_ty.isPtrLikeOptional(mod)) { + const elem_ty = inst_ty.elemType2(zcu); + if (elem_ty.zigTypeTag(zcu) == .Pointer or elem_ty.isPtrLikeOptional(zcu)) { in_memory_result = .{ .double_ptr_to_anyopaque = .{ .actual = inst_ty, .wanted = dest_ty, } }; break :pointer; } - if (dest_ty.isSlice(mod)) break :to_anyopaque; - if (inst_ty.isSlice(mod)) { + if (dest_ty.isSlice(zcu)) break :to_anyopaque; + if (inst_ty.isSlice(zcu)) { in_memory_result = .{ .slice_to_anyopaque = .{ .actual = inst_ty, .wanted = dest_ty, @@ -29151,10 +29175,11 @@ fn coerceExtra( switch (dest_info.flags.size) { // coercion to C pointer - .C => switch (inst_ty.zigTypeTag(mod)) { - .Null => return Air.internedToRef(try mod.intern(.{ .ptr = .{ + .C => switch (inst_ty.zigTypeTag(zcu)) { + .Null => return Air.internedToRef(try zcu.intern(.{ .ptr = .{ .ty = dest_ty.toIntern(), - .addr = .{ .int = .zero_usize }, + .base_addr = .int, + .byte_offset = 0, } })), .ComptimeInt => { const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { @@ -29164,7 +29189,7 @@ fn coerceExtra( return try sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src); }, .Int => { - const ptr_size_ty = switch (inst_ty.intInfo(mod).signedness) { + const ptr_size_ty = switch (inst_ty.intInfo(zcu).signedness) { .signed => Type.isize, .unsigned => Type.usize, }; @@ -29180,7 +29205,7 @@ fn coerceExtra( }, .Pointer => p: { if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :p; - const inst_info = inst_ty.ptrInfo(mod); + const inst_info = inst_ty.ptrInfo(zcu); switch (try sema.coerceInMemoryAllowed( block, Type.fromInterned(dest_info.child), @@ -29196,7 +29221,7 @@ fn coerceExtra( if (inst_info.flags.size == .Slice) { assert(dest_info.sentinel == .none); if (inst_info.sentinel == .none or - inst_info.sentinel != (try mod.intValue(Type.fromInterned(inst_info.child), 0)).toIntern()) + inst_info.sentinel != (try zcu.intValue(Type.fromInterned(inst_info.child), 0)).toIntern()) break :p; const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty); @@ -29206,11 +29231,11 @@ fn coerceExtra( }, else => {}, }, - .One => switch (Type.fromInterned(dest_info.child).zigTypeTag(mod)) { + .One => switch (Type.fromInterned(dest_info.child).zigTypeTag(zcu)) { .Union => { // pointer to anonymous struct to pointer to union - if (inst_ty.isSinglePointer(mod) and - inst_ty.childType(mod).isAnonStruct(mod) and + if (inst_ty.isSinglePointer(zcu) and + inst_ty.childType(zcu).isAnonStruct(zcu) and sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) { return sema.coerceAnonStructToUnionPtrs(block, dest_ty, dest_ty_src, inst, inst_src); @@ -29218,8 +29243,8 @@ fn coerceExtra( }, .Struct => { // pointer to anonymous struct to pointer to struct - if (inst_ty.isSinglePointer(mod) and - inst_ty.childType(mod).isAnonStruct(mod) and + if (inst_ty.isSinglePointer(zcu) and + inst_ty.childType(zcu).isAnonStruct(zcu) and sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) { return sema.coerceAnonStructToStructPtrs(block, dest_ty, dest_ty_src, inst, inst_src) catch |err| switch (err) { @@ -29230,8 +29255,8 @@ fn coerceExtra( }, .Array => { // pointer to tuple to pointer to array - if (inst_ty.isSinglePointer(mod) and - inst_ty.childType(mod).isTuple(mod) and + if (inst_ty.isSinglePointer(zcu) and + inst_ty.childType(zcu).isTuple(zcu) and sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) { return sema.coerceTupleToArrayPtrs(block, dest_ty, dest_ty_src, inst, inst_src); @@ -29240,50 +29265,38 @@ fn coerceExtra( else => {}, }, .Slice => to_slice: { - if (inst_ty.zigTypeTag(mod) == .Array) { + if (inst_ty.zigTypeTag(zcu) == .Array) { return sema.fail( block, inst_src, "array literal requires address-of operator (&) to coerce to slice type '{}'", - .{dest_ty.fmt(mod)}, + .{dest_ty.fmt(zcu)}, ); } - if (!inst_ty.isSinglePointer(mod)) break :to_slice; - const inst_child_ty = inst_ty.childType(mod); - if (!inst_child_ty.isTuple(mod)) break :to_slice; + if (!inst_ty.isSinglePointer(zcu)) break :to_slice; + const inst_child_ty = inst_ty.childType(zcu); + if (!inst_child_ty.isTuple(zcu)) break :to_slice; // empty tuple to zero-length slice // note that this allows coercing to a mutable slice. - 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(.{ .slice = .{ + if (inst_child_ty.structFieldCount(zcu) == 0) { + const align_val = try dest_ty.ptrAlignmentAdvanced(zcu, sema); + return Air.internedToRef(try zcu.intern(.{ .slice = .{ .ty = dest_ty.toIntern(), - .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.toByteUnits().?, - )).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 zcu.intern(.{ .ptr = .{ + .ty = dest_ty.slicePtrFieldType(zcu).toIntern(), + .base_addr = .int, + .byte_offset = align_val.toByteUnits().?, } }), - .len = (try mod.intValue(Type.usize, 0)).toIntern(), - } }))); + .len = .zero_usize, + } })); } // pointer to tuple to slice if (!dest_info.flags.is_const) { const err_msg = err_msg: { - const err_msg = try sema.errMsg(block, inst_src, "cannot cast pointer to tuple to '{}'", .{dest_ty.fmt(mod)}); + const err_msg = try sema.errMsg(block, inst_src, "cannot cast pointer to tuple to '{}'", .{dest_ty.fmt(zcu)}); errdefer err_msg.destroy(sema.gpa); try sema.errNote(block, dest_ty_src, err_msg, "pointers to tuples can only coerce to constant pointers", .{}); break :err_msg err_msg; @@ -29293,9 +29306,9 @@ fn coerceExtra( return sema.coerceTupleToSlicePtrs(block, dest_ty, dest_ty_src, inst, inst_src); }, .Many => p: { - if (!inst_ty.isSlice(mod)) break :p; + if (!inst_ty.isSlice(zcu)) break :p; if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :p; - const inst_info = inst_ty.ptrInfo(mod); + const inst_info = inst_ty.ptrInfo(zcu); switch (try sema.coerceInMemoryAllowed( block, @@ -29320,10 +29333,10 @@ fn coerceExtra( }, } }, - .Int, .ComptimeInt => switch (inst_ty.zigTypeTag(mod)) { + .Int, .ComptimeInt => switch (inst_ty.zigTypeTag(zcu)) { .Float, .ComptimeFloat => float: { const val = maybe_inst_val orelse { - if (dest_ty.zigTypeTag(mod) == .ComptimeInt) { + if (dest_ty.zigTypeTag(zcu) == .ComptimeInt) { if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, .{ .needed_comptime_reason = "value being casted to 'comptime_int' must be comptime-known", @@ -29339,17 +29352,17 @@ fn coerceExtra( // comptime-known integer to other number if (!(try sema.intFitsInType(val, dest_ty, null))) { if (!opts.report_err) return error.NotCoercible; - return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(mod), val.fmtValue(mod) }); + return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(zcu), val.fmtValue(zcu, sema) }); } - return switch (mod.intern_pool.indexToKey(val.toIntern())) { - .undef => try mod.undefRef(dest_ty), + return switch (zcu.intern_pool.indexToKey(val.toIntern())) { + .undef => try zcu.undefRef(dest_ty), .int => |int| Air.internedToRef( - try mod.intern_pool.getCoercedInts(mod.gpa, int, dest_ty.toIntern()), + try zcu.intern_pool.getCoercedInts(zcu.gpa, int, dest_ty.toIntern()), ), else => unreachable, }; } - if (dest_ty.zigTypeTag(mod) == .ComptimeInt) { + if (dest_ty.zigTypeTag(zcu) == .ComptimeInt) { if (!opts.report_err) return error.NotCoercible; if (opts.no_cast_to_comptime_int) return inst; return sema.failWithNeededComptime(block, inst_src, .{ @@ -29358,8 +29371,8 @@ fn coerceExtra( } // integer widening - const dst_info = dest_ty.intInfo(mod); - const src_info = inst_ty.intInfo(mod); + const dst_info = dest_ty.intInfo(zcu); + const src_info = inst_ty.intInfo(zcu); if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or // small enough unsigned ints can get casted to large enough signed ints (dst_info.signedness == .signed and dst_info.bits > src_info.bits)) @@ -29370,25 +29383,25 @@ fn coerceExtra( }, else => {}, }, - .Float, .ComptimeFloat => switch (inst_ty.zigTypeTag(mod)) { + .Float, .ComptimeFloat => switch (inst_ty.zigTypeTag(zcu)) { .ComptimeFloat => { const val = try sema.resolveConstDefinedValue(block, .unneeded, inst, undefined); - const result_val = try val.floatCast(dest_ty, mod); + const result_val = try val.floatCast(dest_ty, zcu); return Air.internedToRef(result_val.toIntern()); }, .Float => { if (maybe_inst_val) |val| { - const result_val = try val.floatCast(dest_ty, mod); - if (!val.eql(try result_val.floatCast(inst_ty, mod), inst_ty, mod)) { + const result_val = try val.floatCast(dest_ty, zcu); + if (!val.eql(try result_val.floatCast(inst_ty, zcu), inst_ty, zcu)) { return sema.fail( block, inst_src, "type '{}' cannot represent float value '{}'", - .{ dest_ty.fmt(mod), val.fmtValue(mod) }, + .{ dest_ty.fmt(zcu), val.fmtValue(zcu, sema) }, ); } return Air.internedToRef(result_val.toIntern()); - } else if (dest_ty.zigTypeTag(mod) == .ComptimeFloat) { + } else if (dest_ty.zigTypeTag(zcu) == .ComptimeFloat) { if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, .{ .needed_comptime_reason = "value being casted to 'comptime_float' must be comptime-known", @@ -29405,7 +29418,7 @@ fn coerceExtra( }, .Int, .ComptimeInt => int: { const val = maybe_inst_val orelse { - if (dest_ty.zigTypeTag(mod) == .ComptimeFloat) { + if (dest_ty.zigTypeTag(zcu) == .ComptimeFloat) { if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, .{ .needed_comptime_reason = "value being casted to 'comptime_float' must be comptime-known", @@ -29413,52 +29426,52 @@ fn coerceExtra( } break :int; }; - const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, mod, sema); + const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, zcu, sema); // TODO implement this compile error //const int_again_val = try result_val.intFromFloat(sema.arena, inst_ty); - //if (!int_again_val.eql(val, inst_ty, mod)) { + //if (!int_again_val.eql(val, inst_ty, zcu)) { // return sema.fail( // block, // inst_src, // "type '{}' cannot represent integer value '{}'", - // .{ dest_ty.fmt(mod), val }, + // .{ dest_ty.fmt(zcu), val }, // ); //} return Air.internedToRef(result_val.toIntern()); }, else => {}, }, - .Enum => switch (inst_ty.zigTypeTag(mod)) { + .Enum => switch (inst_ty.zigTypeTag(zcu)) { .EnumLiteral => { // enum literal to enum const val = try sema.resolveConstDefinedValue(block, .unneeded, inst, undefined); - const string = mod.intern_pool.indexToKey(val.toIntern()).enum_literal; - const field_index = dest_ty.enumFieldIndex(string, mod) orelse { + const string = zcu.intern_pool.indexToKey(val.toIntern()).enum_literal; + const field_index = dest_ty.enumFieldIndex(string, zcu) orelse { return sema.fail(block, inst_src, "no field named '{}' in enum '{}'", .{ - string.fmt(&mod.intern_pool), dest_ty.fmt(mod), + string.fmt(&zcu.intern_pool), dest_ty.fmt(zcu), }); }; - return Air.internedToRef((try mod.enumValueFieldIndex(dest_ty, @intCast(field_index))).toIntern()); + return Air.internedToRef((try zcu.enumValueFieldIndex(dest_ty, @intCast(field_index))).toIntern()); }, .Union => blk: { // union to its own tag type - const union_tag_ty = inst_ty.unionTagType(mod) orelse break :blk; - if (union_tag_ty.eql(dest_ty, mod)) { + const union_tag_ty = inst_ty.unionTagType(zcu) orelse break :blk; + if (union_tag_ty.eql(dest_ty, zcu)) { return sema.unionToTag(block, dest_ty, inst, inst_src); } }, else => {}, }, - .ErrorUnion => switch (inst_ty.zigTypeTag(mod)) { + .ErrorUnion => switch (inst_ty.zigTypeTag(zcu)) { .ErrorUnion => eu: { if (maybe_inst_val) |inst_val| { switch (inst_val.toIntern()) { - .undef => return mod.undefRef(dest_ty), - else => switch (mod.intern_pool.indexToKey(inst_val.toIntern())) { + .undef => return zcu.undefRef(dest_ty), + else => switch (zcu.intern_pool.indexToKey(inst_val.toIntern())) { .error_union => |error_union| switch (error_union.val) { .err_name => |err_name| { - const error_set_ty = inst_ty.errorUnionSet(mod); - const error_set_val = Air.internedToRef((try mod.intern(.{ .err = .{ + const error_set_ty = inst_ty.errorUnionSet(zcu); + const error_set_val = Air.internedToRef((try zcu.intern(.{ .err = .{ .ty = error_set_ty.toIntern(), .name = err_name, } }))); @@ -29489,31 +29502,54 @@ fn coerceExtra( }; }, }, - .Union => switch (inst_ty.zigTypeTag(mod)) { + .Union => switch (inst_ty.zigTypeTag(zcu)) { .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src), .Struct => { - if (inst_ty.isAnonStruct(mod)) { + if (inst_ty.isAnonStruct(zcu)) { return sema.coerceAnonStructToUnion(block, dest_ty, dest_ty_src, inst, inst_src); } }, else => {}, }, - .Array => switch (inst_ty.zigTypeTag(mod)) { + .Array => switch (inst_ty.zigTypeTag(zcu)) { + .Array => array_to_array: { + // Array coercions are allowed only if the child is IMC and the sentinel is unchanged or removed. + if (.ok != try sema.coerceInMemoryAllowed( + block, + dest_ty.childType(zcu), + inst_ty.childType(zcu), + false, + target, + dest_ty_src, + inst_src, + )) { + break :array_to_array; + } + + if (dest_ty.sentinel(zcu)) |dest_sent| { + const src_sent = inst_ty.sentinel(zcu) orelse break :array_to_array; + if (dest_sent.toIntern() != (try zcu.getCoerced(src_sent, dest_ty.childType(zcu))).toIntern()) { + break :array_to_array; + } + } + + return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src); + }, .Vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src), .Struct => { if (inst == .empty_struct) { return sema.arrayInitEmpty(block, inst_src, dest_ty); } - if (inst_ty.isTuple(mod)) { + if (inst_ty.isTuple(zcu)) { return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); } }, else => {}, }, - .Vector => switch (inst_ty.zigTypeTag(mod)) { + .Vector => switch (inst_ty.zigTypeTag(zcu)) { .Array, .Vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src), .Struct => { - if (inst_ty.isTuple(mod)) { + if (inst_ty.isTuple(zcu)) { return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); } }, @@ -29523,7 +29559,7 @@ fn coerceExtra( if (inst == .empty_struct) { return sema.structInitEmpty(block, dest_ty, dest_ty_src, inst_src); } - if (inst_ty.isTupleOrAnonStruct(mod)) { + if (inst_ty.isTupleOrAnonStruct(zcu)) { return sema.coerceTupleToStruct(block, dest_ty, inst, inst_src) catch |err| switch (err) { error.NotCoercible => break :blk, else => |e| return e, @@ -29536,38 +29572,38 @@ fn coerceExtra( // undefined to anything. We do this after the big switch above so that // special logic has a chance to run first, such as `*[N]T` to `[]T` which // should initialize the length field of the slice. - if (maybe_inst_val) |val| if (val.toIntern() == .undef) return mod.undefRef(dest_ty); + if (maybe_inst_val) |val| if (val.toIntern() == .undef) return zcu.undefRef(dest_ty); if (!opts.report_err) return error.NotCoercible; - if (opts.is_ret and dest_ty.zigTypeTag(mod) == .NoReturn) { + if (opts.is_ret and dest_ty.zigTypeTag(zcu) == .NoReturn) { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "function declared 'noreturn' returns", .{}); errdefer msg.destroy(sema.gpa); const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; - const src_decl = mod.funcOwnerDeclPtr(sema.func_index); - try mod.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, mod), msg, "'noreturn' declared here", .{}); + const src_decl = zcu.funcOwnerDeclPtr(sema.func_index); + try zcu.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, zcu), msg, "'noreturn' declared here", .{}); break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); } const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(mod), inst_ty.fmt(mod) }); + const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(zcu), inst_ty.fmt(zcu) }); errdefer msg.destroy(sema.gpa); // E!T to T - if (inst_ty.zigTypeTag(mod) == .ErrorUnion and - (try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(mod), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) + if (inst_ty.zigTypeTag(zcu) == .ErrorUnion and + (try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(zcu), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) { try sema.errNote(block, inst_src, msg, "cannot convert error union to payload type", .{}); try sema.errNote(block, inst_src, msg, "consider using 'try', 'catch', or 'if'", .{}); } // ?T to T - if (inst_ty.zigTypeTag(mod) == .Optional and - (try sema.coerceInMemoryAllowed(block, inst_ty.optionalChild(mod), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) + if (inst_ty.zigTypeTag(zcu) == .Optional and + (try sema.coerceInMemoryAllowed(block, inst_ty.optionalChild(zcu), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) { try sema.errNote(block, inst_src, msg, "cannot convert optional to payload type", .{}); try sema.errNote(block, inst_src, msg, "consider using '.?', 'orelse', or 'if'", .{}); @@ -29577,19 +29613,19 @@ fn coerceExtra( // Add notes about function return type if (opts.is_ret and - mod.test_functions.get(mod.funcOwnerDeclIndex(sema.func_index)) == null) + zcu.test_functions.get(zcu.funcOwnerDeclIndex(sema.func_index)) == null) { const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; - const src_decl = mod.funcOwnerDeclPtr(sema.func_index); - if (inst_ty.isError(mod) and !dest_ty.isError(mod)) { - try mod.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, mod), msg, "function cannot return an error", .{}); + const src_decl = zcu.funcOwnerDeclPtr(sema.func_index); + if (inst_ty.isError(zcu) and !dest_ty.isError(zcu)) { + try zcu.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, zcu), msg, "function cannot return an error", .{}); } else { - try mod.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, mod), msg, "function return type declared here", .{}); + try zcu.errNoteNonLazy(src_decl.toSrcLoc(ret_ty_src, zcu), msg, "function return type declared here", .{}); } } if (try opts.param_src.get(sema)) |param_src| { - try mod.errNoteNonLazy(param_src, msg, "parameter type declared here", .{}); + try zcu.errNoteNonLazy(param_src, msg, "parameter type declared here", .{}); } // TODO maybe add "cannot store an error in type '{}'" note @@ -29755,11 +29791,11 @@ const InMemoryCoercionResult = union(enum) { .array_sentinel => |sentinel| { if (sentinel.actual.toIntern() != .unreachable_value) { try sema.errNote(block, src, msg, "array sentinel '{}' cannot cast into array sentinel '{}'", .{ - sentinel.actual.fmtValue(mod), sentinel.wanted.fmtValue(mod), + sentinel.actual.fmtValue(mod, sema), sentinel.wanted.fmtValue(mod, sema), }); } else { try sema.errNote(block, src, msg, "destination array requires '{}' sentinel", .{ - sentinel.wanted.fmtValue(mod), + sentinel.wanted.fmtValue(mod, sema), }); } break; @@ -29881,11 +29917,11 @@ const InMemoryCoercionResult = union(enum) { .ptr_sentinel => |sentinel| { if (sentinel.actual.toIntern() != .unreachable_value) { try sema.errNote(block, src, msg, "pointer sentinel '{}' cannot cast into pointer sentinel '{}'", .{ - sentinel.actual.fmtValue(mod), sentinel.wanted.fmtValue(mod), + sentinel.actual.fmtValue(mod, sema), sentinel.wanted.fmtValue(mod, sema), }); } else { try sema.errNote(block, src, msg, "destination pointer requires '{}' sentinel", .{ - sentinel.wanted.fmtValue(mod), + sentinel.wanted.fmtValue(mod, sema), }); } break; @@ -29972,7 +30008,7 @@ fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 { /// * bit offset attributes must match exactly /// * `*`/`[*]` must match exactly, but `[*c]` matches either one /// * sentinel-terminated pointers can coerce into `[*]` -fn coerceInMemoryAllowed( +pub fn coerceInMemoryAllowed( sema: *Sema, block: *Block, dest_ty: Type, @@ -30082,8 +30118,9 @@ fn coerceInMemoryAllowed( .wanted = dest_info.elem_type, } }; } - const ok_sent = dest_info.sentinel == null or + const ok_sent = (dest_info.sentinel == null and src_info.sentinel == null) or (src_info.sentinel != null and + dest_info.sentinel != null and dest_info.sentinel.?.eql( try mod.getCoerced(src_info.sentinel.?, dest_info.elem_type), dest_info.elem_type, @@ -30420,9 +30457,9 @@ fn coerceInMemoryAllowedPtrs( dest_src: LazySrcLoc, src_src: LazySrcLoc, ) !InMemoryCoercionResult { - const mod = sema.mod; - const dest_info = dest_ptr_ty.ptrInfo(mod); - const src_info = src_ptr_ty.ptrInfo(mod); + const zcu = sema.mod; + const dest_info = dest_ptr_ty.ptrInfo(zcu); + const src_info = src_ptr_ty.ptrInfo(zcu); const ok_ptr_size = src_info.flags.size == dest_info.flags.size or src_info.flags.size == .C or dest_info.flags.size == .C; @@ -30453,8 +30490,18 @@ fn coerceInMemoryAllowedPtrs( } }; } - const child = try sema.coerceInMemoryAllowed(block, Type.fromInterned(dest_info.child), Type.fromInterned(src_info.child), !dest_info.flags.is_const, target, dest_src, src_src); - if (child != .ok) { + const dest_child = Type.fromInterned(dest_info.child); + const src_child = Type.fromInterned(src_info.child); + const child = try sema.coerceInMemoryAllowed(block, dest_child, src_child, !dest_info.flags.is_const, target, dest_src, src_src); + if (child != .ok) allow: { + // As a special case, we also allow coercing `*[n:s]T` to `*[n]T`, akin to dropping the sentinel from a slice. + // `*[n:s]T` cannot coerce in memory to `*[n]T` since they have different sizes. + if (src_child.zigTypeTag(zcu) == .Array and dest_child.zigTypeTag(zcu) == .Array and + src_child.sentinel(zcu) != null and dest_child.sentinel(zcu) == null and + .ok == try sema.coerceInMemoryAllowed(block, dest_child.childType(zcu), src_child.childType(zcu), !dest_info.flags.is_const, target, dest_src, src_src)) + { + break :allow; + } return InMemoryCoercionResult{ .ptr_child = .{ .child = try child.dupe(sema.arena), .actual = Type.fromInterned(src_info.child), @@ -30462,8 +30509,8 @@ fn coerceInMemoryAllowedPtrs( } }; } - const dest_allow_zero = dest_ty.ptrAllowsZero(mod); - const src_allow_zero = src_ty.ptrAllowsZero(mod); + const dest_allow_zero = dest_ty.ptrAllowsZero(zcu); + const src_allow_zero = src_ty.ptrAllowsZero(zcu); const ok_allows_zero = (dest_allow_zero and (src_allow_zero or !dest_is_mut)) or @@ -30488,7 +30535,7 @@ fn coerceInMemoryAllowedPtrs( const ok_sent = dest_info.sentinel == .none or src_info.flags.size == .C or (src_info.sentinel != .none and - dest_info.sentinel == try mod.intern_pool.getCoerced(sema.gpa, src_info.sentinel, dest_info.child)); + dest_info.sentinel == try zcu.intern_pool.getCoerced(sema.gpa, src_info.sentinel, dest_info.child)); if (!ok_sent) { return InMemoryCoercionResult{ .ptr_sentinel = .{ .actual = switch (src_info.sentinel) { @@ -30787,7 +30834,18 @@ fn checkKnownAllocPtr(sema: *Sema, block: *Block, base_ptr: Air.Inst.Ref, new_pt switch (sema.air_instructions.items(.tag)[@intFromEnum(new_ptr_inst)]) { .optional_payload_ptr_set, .errunion_payload_ptr_set => { const maybe_comptime_alloc = sema.maybe_comptime_allocs.getPtr(alloc_inst) orelse return; - try maybe_comptime_alloc.non_elideable_pointers.append(sema.arena, new_ptr_inst); + + // This is functionally a store, since it writes the optional payload bit. + // Thus, if it is behind a runtime condition, we must mark the alloc as runtime appropriately. + if (block.runtime_index != maybe_comptime_alloc.runtime_index) { + return sema.markMaybeComptimeAllocRuntime(block, alloc_inst); + } + + try maybe_comptime_alloc.stores.append(sema.arena, .{ + .inst = new_ptr_inst, + .src_decl = block.src_decl, + .src = .unneeded, + }); }, .ptr_elem_ptr => { const tmp_air = sema.getTmpAir(); @@ -30812,6 +30870,12 @@ fn markMaybeComptimeAllocRuntime(sema: *Sema, block: *Block, alloc_inst: Air.Ins const mod = sema.mod; const slice = maybe_comptime_alloc.stores.slice(); for (slice.items(.inst), slice.items(.src_decl), slice.items(.src)) |other_inst, other_src_decl, other_src| { + if (other_src == .unneeded) { + switch (sema.air_instructions.items(.tag)[@intFromEnum(other_inst)]) { + .set_union_tag, .optional_payload_ptr_set, .errunion_payload_ptr_set => continue, + else => unreachable, // assertion failure + } + } const other_data = sema.air_instructions.items(.data)[@intFromEnum(other_inst)].bin_op; const other_operand = other_data.rhs; if (!sema.checkRuntimeValue(other_operand)) { @@ -30866,748 +30930,46 @@ fn storePtrVal( operand_val: Value, operand_ty: Type, ) !void { - const mod = sema.mod; - var mut_kit = try sema.beginComptimePtrMutation(block, src, ptr_val, operand_ty); - switch (mut_kit.root) { - .alloc => |a| try sema.checkComptimeVarStore(block, src, a), - .comptime_field => {}, - } - - try sema.resolveTypeLayout(operand_ty); - switch (mut_kit.pointee) { - .opv => {}, - .direct => |val_ptr| { - if (mut_kit.root == .comptime_field) { - val_ptr.* = .{ .interned = try val_ptr.intern(mod, sema.arena) }; - if (operand_val.toIntern() != val_ptr.interned) { - // TODO use failWithInvalidComptimeFieldStore - return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{}); - } - return; - } - val_ptr.* = .{ .interned = operand_val.toIntern() }; - }, - .reinterpret => |reinterpret| { - try sema.resolveTypeLayout(mut_kit.ty); - const abi_size = try sema.usizeCast(block, src, mut_kit.ty.abiSize(mod)); - const buffer = try sema.gpa.alloc(u8, abi_size); - defer sema.gpa.free(buffer); - const interned_old = Value.fromInterned(try reinterpret.val_ptr.intern(mod, sema.arena)); - interned_old.writeToMemory(mut_kit.ty, mod, buffer) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => unreachable, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), - }; - if (reinterpret.write_packed) { - operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => unreachable, - }; - } else { - operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => unreachable, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), - }; - } - const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.IllDefinedMemoryLayout => unreachable, - error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), - }; - reinterpret.val_ptr.* = .{ .interned = val.toIntern() }; - }, - .bad_decl_ty, .bad_ptr_ty => { - // TODO show the decl declaration site in a note and explain whether the decl - // or the pointer is the problematic type - return sema.fail( - block, - src, - "comptime mutation of a reinterpreted pointer requires type '{}' to have a well-defined memory layout", - .{mut_kit.ty.fmt(mod)}, - ); - }, - } -} - -const ComptimePtrMutationKit = struct { - const Root = union(enum) { - alloc: ComptimeAllocIndex, - comptime_field, - }; - root: Root, - pointee: union(enum) { - opv, - /// The pointer type matches the actual comptime Value so a direct - /// modification is possible. - direct: *MutableValue, - /// The largest parent Value containing pointee and having a well-defined memory layout. - /// This is used for bitcasting, if direct dereferencing failed. - reinterpret: struct { - val_ptr: *MutableValue, - byte_offset: usize, - /// If set, write the operand to packed memory - write_packed: bool = false, - }, - /// If the root decl could not be used as parent, this means `ty` is the type that - /// caused that by not having a well-defined layout. - /// This one means the Decl that owns the value trying to be modified does not - /// have a well defined memory layout. - bad_decl_ty, - /// If the root decl could not be used as parent, this means `ty` is the type that - /// caused that by not having a well-defined layout. - /// This one means the pointer type that is being stored through does not - /// have a well defined memory layout. - bad_ptr_ty, - }, - ty: Type, -}; - -fn beginComptimePtrMutation( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - ptr_val: Value, - ptr_elem_ty: Type, -) CompileError!ComptimePtrMutationKit { - const mod = sema.mod; - const ptr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr; - switch (ptr.addr) { - .decl, .anon_decl, .int => unreachable, // isComptimeMutablePtr has been checked already - .comptime_alloc => |alloc_index| { - const alloc = sema.getComptimeAlloc(alloc_index); - return sema.beginComptimePtrMutationInner(block, src, alloc.val.typeOf(mod), &alloc.val, ptr_elem_ty, .{ .alloc = alloc_index }); - }, - .comptime_field => |comptime_field| { - const duped = try sema.arena.create(MutableValue); - duped.* = .{ .interned = comptime_field }; - return sema.beginComptimePtrMutationInner( - block, - src, - duped.typeOf(mod), - duped, - ptr_elem_ty, - .comptime_field, - ); - }, - .eu_payload => |eu_ptr| { - const eu_ty = Type.fromInterned(mod.intern_pool.typeOf(eu_ptr)).childType(mod); - var parent = try sema.beginComptimePtrMutation(block, src, Value.fromInterned(eu_ptr), eu_ty); - switch (parent.pointee) { - .opv => unreachable, - .direct => |val_ptr| { - const payload_ty = parent.ty.errorUnionPayload(mod); - try val_ptr.unintern(mod, sema.arena, false, false); - if (val_ptr.* == .interned) { - // An error union has been initialized to undefined at comptime and now we - // are for the first time setting the payload. We must change the - // representation of the error union to `eu_payload`. - const child = try sema.arena.create(MutableValue); - child.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) }; - val_ptr.* = .{ .eu_payload = .{ - .ty = parent.ty.toIntern(), - .child = child, - } }; - } - return .{ - .root = parent.root, - .pointee = .{ .direct = val_ptr.eu_payload.child }, - .ty = payload_ty, - }; - }, - .bad_decl_ty, .bad_ptr_ty => return parent, - // Even though the parent value type has well-defined memory layout, our - // pointer type does not. - .reinterpret => return .{ - .root = parent.root, - .pointee = .bad_ptr_ty, - .ty = eu_ty, - }, - } - }, - .opt_payload => |opt_ptr| { - const opt_ty = Type.fromInterned(mod.intern_pool.typeOf(opt_ptr)).childType(mod); - var parent = try sema.beginComptimePtrMutation(block, src, Value.fromInterned(opt_ptr), opt_ty); - switch (parent.pointee) { - .opv => unreachable, - .direct => |val_ptr| { - const payload_ty = parent.ty.optionalChild(mod); - try val_ptr.unintern(mod, sema.arena, false, false); - if (val_ptr.* == .interned) { - // An optional has been initialized to undefined at comptime and now we - // are for the first time setting the payload. We must change the - // representation of the optional to `opt_payload`. - const child = try sema.arena.create(MutableValue); - child.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) }; - val_ptr.* = .{ .opt_payload = .{ - .ty = parent.ty.toIntern(), - .child = child, - } }; - } - return .{ - .root = parent.root, - .pointee = .{ .direct = val_ptr.opt_payload.child }, - .ty = payload_ty, - }; - }, - .bad_decl_ty, .bad_ptr_ty => return parent, - // Even though the parent value type has well-defined memory layout, our - // pointer type does not. - .reinterpret => return .{ - .root = parent.root, - .pointee = .bad_ptr_ty, - .ty = opt_ty, - }, - } - }, - .elem => |elem_ptr| { - const base_elem_ty = Type.fromInterned(mod.intern_pool.typeOf(elem_ptr.base)).elemType2(mod); - var parent = try sema.beginComptimePtrMutation(block, src, Value.fromInterned(elem_ptr.base), base_elem_ty); - - switch (parent.pointee) { - .opv => unreachable, - .direct => |val_ptr| switch (parent.ty.zigTypeTag(mod)) { - .Array, .Vector => { - const elem_ty = parent.ty.childType(mod); - const check_len = parent.ty.arrayLenIncludingSentinel(mod); - if ((try sema.typeHasOnePossibleValue(ptr_elem_ty)) != null) { - if (elem_ptr.index > check_len) { - // TODO have the parent include the decl so we can say "declared here" - return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{ - elem_ptr.index, check_len, - }); - } - return .{ - .root = parent.root, - .pointee = .opv, - .ty = elem_ty, - }; - } - if (elem_ptr.index >= check_len) { - // TODO have the parent include the decl so we can say "declared here" - return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{ - elem_ptr.index, check_len, - }); - } - - // We might have a pointer to multiple elements of the array (e.g. a pointer - // to a sub-array). In this case, we just have to reinterpret the relevant - // bytes of the whole array rather than any single element. - reinterp_multi_elem: { - if (try sema.typeRequiresComptime(base_elem_ty)) break :reinterp_multi_elem; - if (try sema.typeRequiresComptime(ptr_elem_ty)) break :reinterp_multi_elem; - - const elem_abi_size_u64 = try sema.typeAbiSize(base_elem_ty); - if (elem_abi_size_u64 >= try sema.typeAbiSize(ptr_elem_ty)) break :reinterp_multi_elem; - - const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64); - const elem_idx = try sema.usizeCast(block, src, elem_ptr.index); - return .{ - .root = parent.root, - .pointee = .{ .reinterpret = .{ - .val_ptr = val_ptr, - .byte_offset = elem_abi_size * elem_idx, - } }, - .ty = parent.ty, - }; - } - - try val_ptr.unintern(mod, sema.arena, false, false); - - const aggregate = switch (val_ptr.*) { - .interned, - .bytes, - .repeated, - .eu_payload, - .opt_payload, - .slice, - .un, - => unreachable, - .aggregate => |*a| a, - }; - - return sema.beginComptimePtrMutationInner( - block, - src, - elem_ty, - &aggregate.elems[@intCast(elem_ptr.index)], - ptr_elem_ty, - parent.root, - ); - }, - else => { - if (elem_ptr.index != 0) { - // TODO include a "declared here" note for the decl - return sema.fail(block, src, "out of bounds comptime store of index {d}", .{ - elem_ptr.index, - }); - } - return beginComptimePtrMutationInner( - sema, - block, - src, - parent.ty, - val_ptr, - ptr_elem_ty, - parent.root, - ); - }, - }, - .reinterpret => |reinterpret| { - if (!base_elem_ty.hasWellDefinedLayout(mod)) { - // Even though the parent value type has well-defined memory layout, our - // pointer type does not. - return .{ - .root = parent.root, - .pointee = .bad_ptr_ty, - .ty = base_elem_ty, - }; - } - - const elem_abi_size_u64 = try sema.typeAbiSize(base_elem_ty); - const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64); - const elem_idx = try sema.usizeCast(block, src, elem_ptr.index); - return .{ - .root = parent.root, - .pointee = .{ .reinterpret = .{ - .val_ptr = reinterpret.val_ptr, - .byte_offset = reinterpret.byte_offset + elem_abi_size * elem_idx, - } }, - .ty = parent.ty, - }; - }, - .bad_decl_ty, .bad_ptr_ty => return parent, - } - }, - .field => |field_ptr| { - const base_child_ty = Type.fromInterned(mod.intern_pool.typeOf(field_ptr.base)).childType(mod); - const field_index: u32 = @intCast(field_ptr.index); - - var parent = try sema.beginComptimePtrMutation(block, src, Value.fromInterned(field_ptr.base), base_child_ty); - switch (parent.pointee) { - .opv => unreachable, - .direct => |val_ptr| { - try val_ptr.unintern(mod, sema.arena, false, false); - switch (val_ptr.*) { - .interned, - .eu_payload, - .opt_payload, - .repeated, - .bytes, - => unreachable, - .aggregate => |*a| return sema.beginComptimePtrMutationInner( - block, - src, - parent.ty.structFieldType(field_index, mod), - &a.elems[field_index], - ptr_elem_ty, - parent.root, - ), - .slice => |*s| switch (field_index) { - Value.slice_ptr_index => return sema.beginComptimePtrMutationInner( - block, - src, - parent.ty.slicePtrFieldType(mod), - s.ptr, - ptr_elem_ty, - parent.root, - ), - Value.slice_len_index => return sema.beginComptimePtrMutationInner( - block, - src, - Type.usize, - s.len, - ptr_elem_ty, - parent.root, - ), - else => unreachable, - }, - .un => |*un| { - const layout = base_child_ty.containerLayout(mod); - - const tag_type = base_child_ty.unionTagTypeHypothetical(mod); - const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index); - if (un.tag == .none and un.payload.* == .interned and un.payload.interned == .undef) { - // A union has been initialized to undefined at comptime and now we - // are for the first time setting the payload. We must change the - // tag implicitly. - const payload_ty = parent.ty.structFieldType(field_index, mod); - un.tag = hypothetical_tag.toIntern(); - un.payload.* = .{ .interned = try mod.intern(.{ .undef = payload_ty.toIntern() }) }; - return beginComptimePtrMutationInner( - sema, - block, - src, - payload_ty, - un.payload, - ptr_elem_ty, - parent.root, - ); - } - - if (layout == .auto or hypothetical_tag.toIntern() == un.tag) { - // We need to set the active field of the union. - un.tag = hypothetical_tag.toIntern(); - - const field_ty = parent.ty.structFieldType(field_index, mod); - return beginComptimePtrMutationInner( - sema, - block, - src, - field_ty, - un.payload, - ptr_elem_ty, - parent.root, - ); - } else { - // Writing to a different field (a different or unknown tag is active) requires reinterpreting - // memory of the entire union, which requires knowing its abiSize. - try sema.resolveTypeLayout(parent.ty); - // This union value no longer has a well-defined tag type. - // The reinterpretation will read it back out as .none. - try un.payload.unintern(mod, sema.arena, false, false); - return .{ - .root = parent.root, - .pointee = .{ .reinterpret = .{ - .val_ptr = val_ptr, - .byte_offset = 0, - .write_packed = layout == .@"packed", - } }, - .ty = parent.ty, - }; - } - }, - } - }, - .reinterpret => |reinterpret| { - const field_offset_u64 = base_child_ty.structFieldOffset(field_index, mod); - const field_offset = try sema.usizeCast(block, src, field_offset_u64); - return .{ - .root = parent.root, - .pointee = .{ .reinterpret = .{ - .val_ptr = reinterpret.val_ptr, - .byte_offset = reinterpret.byte_offset + field_offset, - } }, - .ty = parent.ty, - }; - }, - .bad_decl_ty, .bad_ptr_ty => return parent, - } - }, - } -} - -fn beginComptimePtrMutationInner( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - decl_ty: Type, - decl_val: *MutableValue, - ptr_elem_ty: Type, - root: ComptimePtrMutationKit.Root, -) CompileError!ComptimePtrMutationKit { - const mod = sema.mod; - const target = mod.getTarget(); - const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok; - - const old_decl_val = decl_val.*; - try decl_val.unintern(mod, sema.arena, false, false); - if (decl_val.* == .un and decl_val.un.tag == .none and decl_val.un.payload.* == .interned and decl_val.un.payload.interned == .undef) { - // HACKHACK: undefined union - re-intern it for now - // `unintern` probably should just leave these as is, but I'm leaving it until I rewrite comptime pointer access. - decl_val.* = old_decl_val; - } - - if (coerce_ok) { - return ComptimePtrMutationKit{ - .root = root, - .pointee = .{ .direct = decl_val }, - .ty = decl_ty, - }; - } - - // Handle the case that the decl is an array and we're actually trying to point to an element. - if (decl_ty.isArrayOrVector(mod)) { - const decl_elem_ty = decl_ty.childType(mod); - if ((try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_elem_ty, true, target, src, src)) == .ok) { - return ComptimePtrMutationKit{ - .root = root, - .pointee = .{ .direct = decl_val }, - .ty = decl_ty, - }; - } - } - - if (!decl_ty.hasWellDefinedLayout(mod)) { - return ComptimePtrMutationKit{ - .root = root, - .pointee = .bad_decl_ty, - .ty = decl_ty, - }; - } - if (!ptr_elem_ty.hasWellDefinedLayout(mod)) { - return ComptimePtrMutationKit{ - .root = root, - .pointee = .bad_ptr_ty, - .ty = ptr_elem_ty, - }; - } - return ComptimePtrMutationKit{ - .root = root, - .pointee = .{ .reinterpret = .{ - .val_ptr = decl_val, - .byte_offset = 0, - } }, - .ty = decl_ty, - }; -} - -const ComptimePtrLoadKit = struct { - /// The Value and Type corresponding to the pointee of the provided pointer. - /// If a direct dereference is not possible, this is null. - pointee: ?MutableValue, - /// The largest parent Value containing `pointee` and having a well-defined memory layout. - /// This is used for bitcasting, if direct dereferencing failed (i.e. `pointee` is null). - parent: ?struct { - val: MutableValue, - byte_offset: usize, - }, - /// If the root decl could not be used as `parent`, this is the type that - /// caused that by not having a well-defined layout - ty_without_well_defined_layout: ?Type, -}; - -const ComptimePtrLoadError = CompileError || error{ - RuntimeLoad, -}; - -/// If `maybe_array_ty` is provided, it will be used to directly dereference an -/// .elem_ptr of type T to a value of [N]T, if necessary. -fn beginComptimePtrLoad( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - ptr_val: Value, - maybe_array_ty: ?Type, -) ComptimePtrLoadError!ComptimePtrLoadKit { - const mod = sema.mod; - const ip = &mod.intern_pool; - const target = mod.getTarget(); - - var deref: ComptimePtrLoadKit = switch (ip.indexToKey(ptr_val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { - .decl => |decl_index| blk: { - const decl = mod.declPtr(decl_index); - try sema.declareDependency(.{ .decl_val = decl_index }); - if (decl.val.getVariable(mod) != null) return error.RuntimeLoad; - const decl_val: MutableValue = .{ .interned = decl.val.toIntern() }; - const layout_defined = decl.typeOf(mod).hasWellDefinedLayout(mod); - break :blk ComptimePtrLoadKit{ - .parent = if (layout_defined) .{ .val = decl_val, .byte_offset = 0 } else null, - .pointee = decl_val, - .ty_without_well_defined_layout = if (!layout_defined) decl.typeOf(mod) else null, - }; - }, - .comptime_alloc => |alloc_index| kit: { - const alloc = sema.getComptimeAlloc(alloc_index); - const alloc_ty = alloc.val.typeOf(mod); - const layout_defined = alloc_ty.hasWellDefinedLayout(mod); - break :kit .{ - .parent = if (layout_defined) .{ .val = alloc.val, .byte_offset = 0 } else null, - .pointee = alloc.val, - .ty_without_well_defined_layout = if (!layout_defined) alloc_ty else null, - }; - }, - .anon_decl => |anon_decl| blk: { - const decl_val = anon_decl.val; - if (Value.fromInterned(decl_val).getVariable(mod) != null) return error.RuntimeLoad; - const decl_ty = Type.fromInterned(ip.typeOf(decl_val)); - const decl_mv: MutableValue = .{ .interned = decl_val }; - const layout_defined = decl_ty.hasWellDefinedLayout(mod); - break :blk ComptimePtrLoadKit{ - .parent = if (layout_defined) .{ .val = decl_mv, .byte_offset = 0 } else null, - .pointee = decl_mv, - .ty_without_well_defined_layout = if (!layout_defined) decl_ty else null, - }; - }, - .int => return error.RuntimeLoad, - .eu_payload, .opt_payload => |container_ptr| blk: { - const container_ty = Type.fromInterned(ip.typeOf(container_ptr)).childType(mod); - var deref = try sema.beginComptimePtrLoad(block, src, Value.fromInterned(container_ptr), container_ty); - - // eu_payload and opt_payload never have a well-defined layout - if (deref.parent != null) { - deref.parent = null; - deref.ty_without_well_defined_layout = container_ty; - } - - if (deref.pointee) |pointee| { - const pointee_ty = pointee.typeOf(mod); - const coerce_in_mem_ok = - (try sema.coerceInMemoryAllowed(block, container_ty, pointee_ty, false, target, src, src)) == .ok or - (try sema.coerceInMemoryAllowed(block, pointee_ty, container_ty, false, target, src, src)) == .ok; - if (coerce_in_mem_ok) { - deref.pointee = switch (pointee) { - .interned => |ip_index| .{ .interned = switch (ip.indexToKey(ip_index)) { - .error_union => |error_union| switch (error_union.val) { - .err_name => |err_name| return sema.fail( - block, - src, - "attempt to unwrap error: {}", - .{err_name.fmt(ip)}, - ), - .payload => |payload| payload, - }, - .opt => |opt| switch (opt.val) { - .none => return sema.fail(block, src, "attempt to use null value", .{}), - else => |payload| payload, - }, - else => unreachable, - } }, - .eu_payload, .opt_payload => |p| p.child.*, - else => unreachable, - }; - break :blk deref; - } - } - deref.pointee = null; - break :blk deref; - }, - .comptime_field => |field_val| .{ - .parent = null, - .pointee = .{ .interned = field_val }, - .ty_without_well_defined_layout = Type.fromInterned(ip.typeOf(field_val)), - }, - .elem => |elem_ptr| blk: { - const elem_ty = Type.fromInterned(ip.typeOf(elem_ptr.base)).elemType2(mod); - var deref = try sema.beginComptimePtrLoad(block, src, Value.fromInterned(elem_ptr.base), 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 (ip.indexToKey(elem_ptr.base)) { - .ptr => |base_ptr| switch (base_ptr.addr) { - .elem => |base_elem| assert(!Type.fromInterned(ip.typeOf(base_elem.base)).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) |pointee| match: { - const ty = pointee.typeOf(mod); - if (!ty.isArrayOrVector(mod)) break :match false; - const deref_elem_ty = ty.childType(mod); - if ((try sema.coerceInMemoryAllowed(block, deref_elem_ty, elem_ty, false, target, src, src)) == .ok) break :match true; - if ((try sema.coerceInMemoryAllowed(block, elem_ty, deref_elem_ty, false, target, src, src)) == .ok) break :match true; - break :match false; - } else false; - if (!ty_matches) { - deref.pointee = null; - break :blk deref; - } - - var array_val = deref.pointee.?; - const check_len = array_val.typeOf(mod).arrayLenIncludingSentinel(mod); - if (maybe_array_ty) |load_ty| { - // It's possible that we're loading a [N]T, in which case we'd like to slice - // the pointee array directly from our parent array. - if (load_ty.isArrayOrVector(mod) and load_ty.childType(mod).eql(elem_ty, mod)) { - const len = try sema.usizeCast(block, src, load_ty.arrayLenIncludingSentinel(mod)); - const elem_idx = try sema.usizeCast(block, src, elem_ptr.index); - deref.pointee = if (elem_ptr.index + len <= check_len) switch (array_val) { - .aggregate => |a| .{ .aggregate = .{ - .ty = (try mod.arrayType(.{ .len = len, .child = elem_ty.toIntern() })).toIntern(), - .elems = a.elems[elem_idx..][0..len], - } }, - else => .{ - .interned = (try (Value.fromInterned( - try array_val.intern(mod, sema.arena), - ).sliceArray(sema, elem_idx, elem_idx + len))).toIntern(), - }, - } else null; - break :blk deref; - } - } - - if (elem_ptr.index >= check_len) { - deref.pointee = null; - break :blk deref; - } - if (elem_ptr.index == check_len - 1) { - if (array_val.typeOf(mod).sentinel(mod)) |sent| { - deref.pointee = .{ .interned = sent.toIntern() }; - break :blk deref; - } - } - deref.pointee = try array_val.getElem(mod, @intCast(elem_ptr.index)); - break :blk deref; - }, - .field => |field_ptr| blk: { - const field_index: u32 = @intCast(field_ptr.index); - const container_ty = Type.fromInterned(ip.typeOf(field_ptr.base)).childType(mod); - var deref = try sema.beginComptimePtrLoad(block, src, Value.fromInterned(field_ptr.base), 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 pointee = deref.pointee orelse break :blk deref; - const pointee_ty = pointee.typeOf(mod); - const coerce_in_mem_ok = - (try sema.coerceInMemoryAllowed(block, container_ty, pointee_ty, false, target, src, src)) == .ok or - (try sema.coerceInMemoryAllowed(block, pointee_ty, container_ty, false, target, src, src)) == .ok; - if (!coerce_in_mem_ok) { - deref.pointee = null; - break :blk deref; - } - - deref.pointee = try pointee.getElem(mod, field_index); - break :blk deref; - }, - }, - .opt => |opt| switch (opt.val) { - .none => return sema.fail(block, src, "attempt to use null value", .{}), - else => |payload| try sema.beginComptimePtrLoad(block, src, Value.fromInterned(payload), null), - }, - else => unreachable, - }; + const zcu = sema.mod; + const ip = &zcu.intern_pool; + // TODO: audit use sites to eliminate this coercion + const coerced_operand_val = try zcu.getCoerced(operand_val, operand_ty); + // TODO: audit use sites to eliminate this coercion + const ptr_ty = try zcu.ptrType(info: { + var info = ptr_val.typeOf(zcu).ptrInfo(zcu); + info.child = operand_ty.toIntern(); + break :info info; + }); + const coerced_ptr_val = try zcu.getCoerced(ptr_val, ptr_ty); - if (deref.pointee) |val| { - if (deref.parent == null and val.typeOf(mod).hasWellDefinedLayout(mod)) { - deref.parent = .{ .val = val, .byte_offset = 0 }; - } + switch (try sema.storeComptimePtr(block, src, coerced_ptr_val, coerced_operand_val)) { + .success => {}, + .runtime_store => unreachable, // use sites check this + // TODO use failWithInvalidComptimeFieldStore + .comptime_field_mismatch => return sema.fail( + block, + src, + "value stored in comptime field does not match the default value of the field", + .{}, + ), + .undef => return sema.failWithUseOfUndef(block, src), + .err_payload => |err_name| return sema.fail(block, src, "attempt to unwrap error: {}", .{err_name.fmt(ip)}), + .null_payload => return sema.fail(block, src, "attempt to use null value", .{}), + .inactive_union_field => return sema.fail(block, src, "access of inactive union field", .{}), + .needed_well_defined => |ty| return sema.fail( + block, + src, + "comptime dereference requires '{}' to have a well-defined layout", + .{ty.fmt(zcu)}, + ), + .out_of_bounds => |ty| return sema.fail( + block, + src, + "dereference of '{}' exceeds bounds of containing decl of type '{}'", + .{ ptr_ty.fmt(zcu), ty.fmt(zcu) }, + ), + .exceeds_host_size => return sema.fail(block, src, "bit-pointer target exceeds host size", .{}), } - return deref; } fn bitCast( @@ -31618,28 +30980,33 @@ fn bitCast( inst_src: LazySrcLoc, operand_src: ?LazySrcLoc, ) CompileError!Air.Inst.Ref { - const mod = sema.mod; + const zcu = sema.mod; try sema.resolveTypeLayout(dest_ty); const old_ty = sema.typeOf(inst); try sema.resolveTypeLayout(old_ty); - const dest_bits = dest_ty.bitSize(mod); - const old_bits = old_ty.bitSize(mod); + const dest_bits = dest_ty.bitSize(zcu); + const old_bits = old_ty.bitSize(zcu); if (old_bits != dest_bits) { return sema.fail(block, inst_src, "@bitCast size mismatch: destination type '{}' has {d} bits but source type '{}' has {d} bits", .{ - dest_ty.fmt(mod), + dest_ty.fmt(zcu), dest_bits, - old_ty.fmt(mod), + old_ty.fmt(zcu), old_bits, }); } if (try sema.resolveValue(inst)) |val| { - if (val.isUndef(mod)) - return mod.undefRef(dest_ty); - if (try sema.bitCastVal(block, inst_src, val, old_ty, dest_ty, 0)) |result_val| { + if (val.isUndef(zcu)) + return zcu.undefRef(dest_ty); + if (old_ty.zigTypeTag(zcu) == .ErrorSet and dest_ty.zigTypeTag(zcu) == .ErrorSet) { + // Special case: we sometimes call `bitCast` on error set values, but they + // don't have a well-defined layout, so we can't use `bitCastVal` on them. + return Air.internedToRef((try zcu.getCoerced(val, dest_ty)).toIntern()); + } + if (try sema.bitCastVal(val, dest_ty, 0)) |result_val| { return Air.internedToRef(result_val.toIntern()); } } @@ -31648,98 +31015,6 @@ fn bitCast( return block.addBitCast(dest_ty, inst); } -fn bitCastVal( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - val: Value, - old_ty: Type, - new_ty: Type, - buffer_offset: usize, -) !?Value { - const mod = sema.mod; - if (old_ty.eql(new_ty, mod)) return val; - - // For types with well-defined memory layouts, we serialize them a byte buffer, - // then deserialize to the new type. - const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); - - const buffer = try sema.gpa.alloc(u8, abi_size); - defer sema.gpa.free(buffer); - val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => return null, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), - }; - - return Value.readFromMemory(new_ty, mod, buffer[buffer_offset..], sema.arena) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.IllDefinedMemoryLayout => unreachable, - error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{new_ty.fmt(mod)}), - }; -} - -fn bitCastUnionFieldVal( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - val: Value, - old_ty: Type, - field_ty: Type, - layout: std.builtin.Type.ContainerLayout, -) !?Value { - const mod = sema.mod; - if (old_ty.eql(field_ty, mod)) return val; - - // Bitcasting a union field value requires that that field's layout be known - try sema.resolveTypeLayout(field_ty); - - const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); - const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod)); - const endian = mod.getTarget().cpu.arch.endian(); - - const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size)); - defer sema.gpa.free(buffer); - - // Reading a larger value means we need to reinterpret from undefined bytes. - const offset = switch (layout) { - .@"extern" => offset: { - if (field_size > old_size) @memset(buffer[old_size..], 0xaa); - val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => return null, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), - }; - break :offset 0; - }, - .@"packed" => offset: { - if (field_size > old_size) { - const min_size = @max(old_size, 1); - switch (endian) { - .little => @memset(buffer[min_size - 1 ..], 0xaa), - .big => @memset(buffer[0 .. buffer.len - min_size + 1], 0xaa), - } - } - - val.writeToPackedMemory(old_ty, mod, buffer, 0) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => return null, - }; - - break :offset if (endian == .big) buffer.len - field_size else 0; - }, - .auto => unreachable, - }; - - return Value.readFromMemory(field_ty, mod, buffer[offset..], sema.arena) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.IllDefinedMemoryLayout => unreachable, - error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}), - }; -} - fn coerceArrayPtrToSlice( sema: *Sema, block: *Block, @@ -31885,7 +31160,7 @@ fn coerceEnumToUnion( if (try sema.resolveDefinedValue(block, inst_src, enum_tag)) |val| { const field_index = union_ty.unionTagFieldIndex(val, sema.mod) orelse { return sema.fail(block, inst_src, "union '{}' has no tag with value '{}'", .{ - union_ty.fmt(sema.mod), val.fmtValue(sema.mod), + union_ty.fmt(sema.mod), val.fmtValue(sema.mod, sema), }); }; @@ -32595,7 +31870,7 @@ fn addReferencedBy( }); } -fn ensureDeclAnalyzed(sema: *Sema, decl_index: InternPool.DeclIndex) CompileError!void { +pub fn ensureDeclAnalyzed(sema: *Sema, decl_index: InternPool.DeclIndex) CompileError!void { const mod = sema.mod; const ip = &mod.intern_pool; const decl = mod.declPtr(decl_index); @@ -32673,7 +31948,8 @@ fn analyzeDeclRefInner(sema: *Sema, decl_index: InternPool.DeclIndex, analyze_fn } return Air.internedToRef((try mod.intern(.{ .ptr = .{ .ty = ptr_ty.toIntern(), - .addr = .{ .decl = decl_index }, + .base_addr = .{ .decl = decl_index }, + .byte_offset = 0, } }))); } @@ -33102,8 +32378,8 @@ fn analyzeSlice( msg, "expected '{}', found '{}'", .{ - Value.zero_comptime_int.fmtValue(mod), - start_value.fmtValue(mod), + Value.zero_comptime_int.fmtValue(mod, sema), + start_value.fmtValue(mod, sema), }, ); break :msg msg; @@ -33119,8 +32395,8 @@ fn analyzeSlice( msg, "expected '{}', found '{}'", .{ - Value.one_comptime_int.fmtValue(mod), - end_value.fmtValue(mod), + Value.one_comptime_int.fmtValue(mod, sema), + end_value.fmtValue(mod, sema), }, ); break :msg msg; @@ -33133,7 +32409,7 @@ fn analyzeSlice( block, end_src, "end index {} out of bounds for slice of single-item pointer", - .{end_value.fmtValue(mod)}, + .{end_value.fmtValue(mod, sema)}, ); } } @@ -33228,8 +32504,8 @@ fn analyzeSlice( end_src, "end index {} out of bounds for array of length {}{s}", .{ - end_val.fmtValue(mod), - len_val.fmtValue(mod), + end_val.fmtValue(mod, sema), + len_val.fmtValue(mod, sema), sentinel_label, }, ); @@ -33273,7 +32549,7 @@ fn analyzeSlice( end_src, "end index {} out of bounds for slice of length {d}{s}", .{ - end_val.fmtValue(mod), + end_val.fmtValue(mod, sema), try slice_val.sliceLen(sema), sentinel_label, }, @@ -33333,8 +32609,8 @@ fn analyzeSlice( start_src, "start index {} is larger than end index {}", .{ - start_val.fmtValue(mod), - end_val.fmtValue(mod), + start_val.fmtValue(mod, sema), + end_val.fmtValue(mod, sema), }, ); } @@ -33347,16 +32623,15 @@ fn analyzeSlice( const many_ptr_ty = try mod.manyConstPtrType(elem_ty); const many_ptr_val = try mod.getCoerced(ptr_val, many_ptr_ty); - const elem_ptr_ty = try mod.singleConstPtrType(elem_ty); - const elem_ptr = try many_ptr_val.elemPtr(elem_ptr_ty, sentinel_index, mod); - const res = try sema.pointerDerefExtra(block, src, elem_ptr, elem_ty); + const elem_ptr = try many_ptr_val.ptrElem(sentinel_index, sema); + const res = try sema.pointerDerefExtra(block, src, elem_ptr); const actual_sentinel = switch (res) { .runtime_load => break :sentinel_check, .val => |v| v, .needed_well_defined => |ty| return sema.fail( block, src, - "comptime dereference requires '{}' to have a well-defined layout, but it does not.", + "comptime dereference requires '{}' to have a well-defined layout", .{ty.fmt(mod)}, ), .out_of_bounds => |ty| return sema.fail( @@ -33372,8 +32647,8 @@ fn analyzeSlice( const msg = try sema.errMsg(block, src, "value in memory does not match slice sentinel", .{}); errdefer msg.destroy(sema.gpa); try sema.errNote(block, src, msg, "expected '{}', found '{}'", .{ - expected_sentinel.fmtValue(mod), - actual_sentinel.fmtValue(mod), + expected_sentinel.fmtValue(mod, sema), + actual_sentinel.fmtValue(mod, sema), }); break :msg msg; @@ -35599,8 +34874,8 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value { } })); }, .ptr => |ptr| { - switch (ptr.addr) { - .decl, .comptime_alloc, .anon_decl => return val, + switch (ptr.base_addr) { + .decl, .comptime_alloc, .anon_decl, .int => return val, .comptime_field => |field_val| { const resolved_field_val = (try sema.resolveLazyValue(Value.fromInterned(field_val))).toIntern(); @@ -35609,17 +34884,8 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value { else Value.fromInterned((try mod.intern(.{ .ptr = .{ .ty = ptr.ty, - .addr = .{ .comptime_field = resolved_field_val }, - } }))); - }, - .int => |int| { - const resolved_int = (try sema.resolveLazyValue(Value.fromInterned(int))).toIntern(); - return if (resolved_int == int) - val - else - Value.fromInterned((try mod.intern(.{ .ptr = .{ - .ty = ptr.ty, - .addr = .{ .int = resolved_int }, + .base_addr = .{ .comptime_field = resolved_field_val }, + .byte_offset = ptr.byte_offset, } }))); }, .eu_payload, .opt_payload => |base| { @@ -35629,22 +34895,23 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value { else Value.fromInterned((try mod.intern(.{ .ptr = .{ .ty = ptr.ty, - .addr = switch (ptr.addr) { + .base_addr = switch (ptr.base_addr) { .eu_payload => .{ .eu_payload = resolved_base }, .opt_payload => .{ .opt_payload = resolved_base }, else => unreachable, }, + .byte_offset = ptr.byte_offset, } }))); }, - .elem, .field => |base_index| { + .arr_elem, .field => |base_index| { const resolved_base = (try sema.resolveLazyValue(Value.fromInterned(base_index.base))).toIntern(); return if (resolved_base == base_index.base) val else Value.fromInterned((try mod.intern(.{ .ptr = .{ .ty = ptr.ty, - .addr = switch (ptr.addr) { - .elem => .{ .elem = .{ + .base_addr = switch (ptr.base_addr) { + .arr_elem => .{ .arr_elem = .{ .base = resolved_base, .index = base_index.index, } }, @@ -35654,6 +34921,7 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value { } }, else => unreachable, }, + .byte_offset = ptr.byte_offset, } }))); }, } @@ -36166,7 +35434,8 @@ fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void { var max_align: Alignment = .@"1"; for (0..union_type.field_types.len) |field_index| { const field_ty = Type.fromInterned(union_type.field_types.get(ip)[field_index]); - if (!(try sema.typeHasRuntimeBits(field_ty))) continue; + + if (try sema.typeRequiresComptime(field_ty) or field_ty.zigTypeTag(mod) == .NoReturn) continue; // TODO: should this affect alignment? max_size = @max(max_size, sema.typeAbiSize(field_ty) catch |err| switch (err) { error.AnalysisFail => { @@ -36496,7 +35765,15 @@ pub fn resolveTypeFieldsStruct( } defer struct_type.clearTypesWip(ip); - try semaStructFields(mod, sema.arena, struct_type); + semaStructFields(mod, sema.arena, struct_type) catch |err| switch (err) { + error.AnalysisFail => { + if (mod.declPtr(owner_decl).analysis == .complete) { + mod.declPtr(owner_decl).analysis = .dependency_failure; + } + return error.AnalysisFail; + }, + else => |e| return e, + }; } pub fn resolveStructFieldInits(sema: *Sema, ty: Type) CompileError!void { @@ -36521,7 +35798,15 @@ pub fn resolveStructFieldInits(sema: *Sema, ty: Type) CompileError!void { } defer struct_type.clearInitsWip(ip); - try semaStructFieldInits(mod, sema.arena, struct_type); + semaStructFieldInits(mod, sema.arena, struct_type) catch |err| switch (err) { + error.AnalysisFail => { + if (mod.declPtr(owner_decl).analysis == .complete) { + mod.declPtr(owner_decl).analysis = .dependency_failure; + } + return error.AnalysisFail; + }, + else => |e| return e, + }; struct_type.setHaveFieldInits(ip); } @@ -36560,7 +35845,15 @@ pub fn resolveTypeFieldsUnion(sema: *Sema, ty: Type, union_type: InternPool.Load union_type.flagsPtr(ip).status = .field_types_wip; errdefer union_type.flagsPtr(ip).status = .none; - try semaUnionFields(mod, sema.arena, union_type); + semaUnionFields(mod, sema.arena, union_type) catch |err| switch (err) { + error.AnalysisFail => { + if (owner_decl.analysis == .complete) { + owner_decl.analysis = .dependency_failure; + } + return error.AnalysisFail; + }, + else => |e| return e, + }; union_type.flagsPtr(ip).status = .have_field_types; } @@ -37391,7 +36684,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded const field_src = mod.fieldSrcLoc(union_type.decl, .{ .index = field_i }).lazy; const other_field_src = mod.fieldSrcLoc(union_type.decl, .{ .index = gop.index }).lazy; const msg = msg: { - const msg = try sema.errMsg(&block_scope, field_src, "enum tag value {} already taken", .{enum_tag_val.fmtValue(mod)}); + const msg = try sema.errMsg(&block_scope, field_src, "enum tag value {} already taken", .{enum_tag_val.fmtValue(mod, &sema)}); errdefer msg.destroy(gpa); try sema.errNote(&block_scope, other_field_src, msg, "other occurrence here", .{}); break :msg msg; @@ -38158,7 +37451,8 @@ fn analyzeComptimeAlloc( return Air.internedToRef((try mod.intern(.{ .ptr = .{ .ty = ptr_type.toIntern(), - .addr = .{ .comptime_alloc = alloc }, + .base_addr = .{ .comptime_alloc = alloc }, + .byte_offset = 0, } }))); } @@ -38247,16 +37541,15 @@ pub fn analyzeAsAddressSpace( /// Asserts the value is a pointer and dereferences it. /// Returns `null` if the pointer contents cannot be loaded at comptime. fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr_ty: Type) CompileError!?Value { - const mod = sema.mod; - const load_ty = ptr_ty.childType(mod); - const res = try sema.pointerDerefExtra(block, src, ptr_val, load_ty); - switch (res) { + // TODO: audit use sites to eliminate this coercion + const coerced_ptr_val = try sema.mod.getCoerced(ptr_val, ptr_ty); + switch (try sema.pointerDerefExtra(block, src, coerced_ptr_val)) { .runtime_load => return null, .val => |v| return v, .needed_well_defined => |ty| return sema.fail( block, src, - "comptime dereference requires '{}' to have a well-defined layout, but it does not.", + "comptime dereference requires '{}' to have a well-defined layout", .{ty.fmt(sema.mod)}, ), .out_of_bounds => |ty| return sema.fail( @@ -38275,68 +37568,19 @@ const DerefResult = union(enum) { out_of_bounds: Type, }; -fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, load_ty: Type) CompileError!DerefResult { - const mod = sema.mod; - const target = mod.getTarget(); - const deref = sema.beginComptimePtrLoad(block, src, ptr_val, load_ty) catch |err| switch (err) { - error.RuntimeLoad => return DerefResult{ .runtime_load = {} }, - else => |e| return e, - }; - - if (deref.pointee) |pointee| { - const uncoerced_val = Value.fromInterned(try pointee.intern(mod, sema.arena)); - const ty = Type.fromInterned(mod.intern_pool.typeOf(uncoerced_val.toIntern())); - const coerce_in_mem_ok = - (try sema.coerceInMemoryAllowed(block, load_ty, ty, false, target, src, src)) == .ok or - (try sema.coerceInMemoryAllowed(block, ty, load_ty, false, target, src, src)) == .ok; - if (coerce_in_mem_ok) { - // We have a Value that lines up in virtual memory exactly with what we want to load, - // and it is in-memory coercible to load_ty. It may be returned without modifications. - // Move mutable decl values to the InternPool and assert other decls are already in - // the InternPool. - const coerced_val = try mod.getCoerced(uncoerced_val, load_ty); - return .{ .val = coerced_val }; - } - } - - // The type is not in-memory coercible or the direct dereference failed, so it must - // be bitcast according to the pointer type we are performing the load through. - if (!load_ty.hasWellDefinedLayout(mod)) { - return DerefResult{ .needed_well_defined = load_ty }; - } - - const load_sz = try sema.typeAbiSize(load_ty); - - // Try the smaller bit-cast first, since that's more efficient than using the larger `parent` - if (deref.pointee) |pointee| { - const val_ip_index = try pointee.intern(mod, sema.arena); - const val = Value.fromInterned(val_ip_index); - const ty = Type.fromInterned(mod.intern_pool.typeOf(val_ip_index)); - if (load_sz <= try sema.typeAbiSize(ty)) { - return .{ .val = (try sema.bitCastVal(block, src, val, ty, load_ty, 0)) orelse return .runtime_load }; - } - } - - // If that fails, try to bit-cast from the largest parent value with a well-defined layout - if (deref.parent) |parent| { - const parent_ip_index = try parent.val.intern(mod, sema.arena); - const parent_val = Value.fromInterned(parent_ip_index); - const parent_ty = Type.fromInterned(mod.intern_pool.typeOf(parent_ip_index)); - if (load_sz + parent.byte_offset <= try sema.typeAbiSize(parent_ty)) { - return .{ .val = (try sema.bitCastVal(block, src, parent_val, parent_ty, load_ty, parent.byte_offset)) orelse return .runtime_load }; - } - } - - if (deref.ty_without_well_defined_layout) |bad_ty| { - // We got no parent for bit-casting, or the parent we got was too small. Either way, the problem - // is that some type we encountered when de-referencing does not have a well-defined layout. - return .{ .needed_well_defined = bad_ty }; - } else { - // If all encountered types had well-defined layouts, the parent is the root decl and it just - // wasn't big enough for the load. - const parent_ip_index = try deref.parent.?.val.intern(mod, sema.arena); - const parent_ty = Type.fromInterned(mod.intern_pool.typeOf(parent_ip_index)); - return .{ .out_of_bounds = parent_ty }; +fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value) CompileError!DerefResult { + const zcu = sema.mod; + const ip = &zcu.intern_pool; + switch (try sema.loadComptimePtr(block, src, ptr_val)) { + .success => |mv| return .{ .val = try mv.intern(zcu, sema.arena) }, + .runtime_load => return .runtime_load, + .undef => return sema.failWithUseOfUndef(block, src), + .err_payload => |err_name| return sema.fail(block, src, "attempt to unwrap error: {}", .{err_name.fmt(ip)}), + .null_payload => return sema.fail(block, src, "attempt to use null value", .{}), + .inactive_union_field => return sema.fail(block, src, "access of inactive union field", .{}), + .needed_well_defined => |ty| return .{ .needed_well_defined = ty }, + .out_of_bounds => |ty| return .{ .out_of_bounds = ty }, + .exceeds_host_size => return sema.fail(block, src, "bit-pointer target exceeds host size", .{}), } } @@ -38394,18 +37638,18 @@ pub fn typeHasRuntimeBits(sema: *Sema, ty: Type) CompileError!bool { }; } -fn typeAbiSize(sema: *Sema, ty: Type) !u64 { +pub fn typeAbiSize(sema: *Sema, ty: Type) !u64 { try sema.resolveTypeLayout(ty); return ty.abiSize(sema.mod); } -fn typeAbiAlignment(sema: *Sema, ty: Type) CompileError!Alignment { +pub fn typeAbiAlignment(sema: *Sema, ty: Type) CompileError!Alignment { return (try ty.abiAlignmentAdvanced(sema.mod, .{ .sema = sema })).scalar; } /// Not valid to call for packed unions. /// Keep implementation in sync with `Module.unionFieldNormalAlignment`. -fn unionFieldAlignment(sema: *Sema, u: InternPool.LoadedUnionType, field_index: u32) !Alignment { +pub fn unionFieldAlignment(sema: *Sema, u: InternPool.LoadedUnionType, field_index: u32) !Alignment { const mod = sema.mod; const ip = &mod.intern_pool; const field_align = u.fieldAlign(ip, field_index); @@ -38416,7 +37660,7 @@ fn unionFieldAlignment(sema: *Sema, u: InternPool.LoadedUnionType, field_index: } /// Keep implementation in sync with `Module.structFieldAlignment`. -fn structFieldAlignment( +pub fn structFieldAlignment( sema: *Sema, explicit_alignment: InternPool.Alignment, field_ty: Type, @@ -38724,6 +37968,13 @@ fn intSubWithOverflowScalar( const mod = sema.mod; const info = ty.intInfo(mod); + if (lhs.isUndef(mod) or rhs.isUndef(mod)) { + return .{ + .overflow_bit = try mod.undefValue(Type.u1), + .wrapped_result = try mod.undefValue(ty), + }; + } + var lhs_space: Value.BigIntSpace = undefined; var rhs_space: Value.BigIntSpace = undefined; const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, mod, sema); @@ -38808,7 +38059,7 @@ fn intFromFloatScalar( block, src, "fractional component prevents float value '{}' from coercion to type '{}'", - .{ val.fmtValue(mod), int_ty.fmt(mod) }, + .{ val.fmtValue(mod, sema), int_ty.fmt(mod) }, ); const float = val.toFloat(f128, mod); @@ -38830,7 +38081,7 @@ fn intFromFloatScalar( if (!(try sema.intFitsInType(cti_result, int_ty, null))) { return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{ - val.fmtValue(sema.mod), int_ty.fmt(sema.mod), + val.fmtValue(sema.mod, sema), int_ty.fmt(sema.mod), }); } return mod.getCoerced(cti_result, int_ty); @@ -38975,6 +38226,13 @@ fn intAddWithOverflowScalar( const mod = sema.mod; const info = ty.intInfo(mod); + if (lhs.isUndef(mod) or rhs.isUndef(mod)) { + return .{ + .overflow_bit = try mod.undefValue(Type.u1), + .wrapped_result = try mod.undefValue(ty), + }; + } + var lhs_space: Value.BigIntSpace = undefined; var rhs_space: Value.BigIntSpace = undefined; const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, mod, sema); @@ -39070,12 +38328,14 @@ fn compareVector( /// Returns the type of a pointer to an element. /// Asserts that the type is a pointer, and that the element type is indexable. +/// If the element index is comptime-known, it must be passed in `offset`. +/// For *@Vector(n, T), return *align(a:b:h:v) T /// For *[N]T, return *T /// For [*]T, returns *T /// For []T, returns *T /// Handles const-ness and address spaces in particular. /// This code is duplicated in `analyzePtrArithmetic`. -fn elemPtrType(sema: *Sema, ptr_ty: Type, offset: ?usize) !Type { +pub fn elemPtrType(sema: *Sema, ptr_ty: Type, offset: ?usize) !Type { const mod = sema.mod; const ptr_info = ptr_ty.ptrInfo(mod); const elem_ty = ptr_ty.elemType2(mod); @@ -39180,7 +38440,7 @@ fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool return sema.typeOf(ref).zigTypeTag(sema.mod) == tag; } -fn ptrType(sema: *Sema, info: InternPool.Key.PtrType) CompileError!Type { +pub fn ptrType(sema: *Sema, info: InternPool.Key.PtrType) CompileError!Type { if (info.flags.alignment != .none) { _ = try sema.typeAbiAlignment(Type.fromInterned(info.child)); } @@ -39210,12 +38470,12 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { fn isComptimeMutablePtr(sema: *Sema, val: Value) bool { return switch (sema.mod.intern_pool.indexToKey(val.toIntern())) { .slice => |slice| sema.isComptimeMutablePtr(Value.fromInterned(slice.ptr)), - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| switch (ptr.base_addr) { .anon_decl, .decl, .int => false, .comptime_field => true, .comptime_alloc => |alloc_index| !sema.getComptimeAlloc(alloc_index).is_const, .eu_payload, .opt_payload => |base| sema.isComptimeMutablePtr(Value.fromInterned(base)), - .elem, .field => |bi| sema.isComptimeMutablePtr(Value.fromInterned(bi.base)), + .arr_elem, .field => |bi| sema.isComptimeMutablePtr(Value.fromInterned(bi.base)), }, else => false, }; @@ -39321,3 +38581,11 @@ fn maybeDerefSliceAsArray( const casted_ptr = try zcu.getCoerced(Value.fromInterned(slice.ptr), ptr_ty); return sema.pointerDeref(block, src, casted_ptr, ptr_ty); } + +pub const bitCastVal = @import("Sema/bitcast.zig").bitCast; +pub const bitCastSpliceVal = @import("Sema/bitcast.zig").bitCastSplice; + +const loadComptimePtr = @import("Sema/comptime_ptr_access.zig").loadComptimePtr; +const ComptimeLoadResult = @import("Sema/comptime_ptr_access.zig").ComptimeLoadResult; +const storeComptimePtr = @import("Sema/comptime_ptr_access.zig").storeComptimePtr; +const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStoreResult; diff --git a/src/Sema/bitcast.zig b/src/Sema/bitcast.zig new file mode 100644 index 000000000000..eb6315ac509c --- /dev/null +++ b/src/Sema/bitcast.zig @@ -0,0 +1,572 @@ +/// Returns `null` if the operation must be performed at runtime. +pub fn bitCast( + sema: *Sema, + val: Value, + dest_ty: Type, + bit_offset: u64, +) CompileError!?Value { + return bitCastInner(sema, val, dest_ty, bit_offset) catch |err| switch (err) { + error.ReinterpretDeclRef => return null, + error.IllDefinedMemoryLayout => unreachable, + error.Unimplemented => @panic("unimplemented bitcast"), + else => |e| return e, + }; +} + +/// Uses bitcasting to splice the value `splice_val` at `bit_offset` bits +/// into `val`, overwriting bits of `val`. Returns the modified value. +/// Returns `null` if the operation must be performed at runtime. +pub fn bitCastSplice( + sema: *Sema, + val: Value, + splice_val: Value, + bit_offset: u64, +) CompileError!?Value { + return bitCastSpliceInner(sema, val, splice_val, bit_offset) catch |err| switch (err) { + error.ReinterpretDeclRef => return null, + error.IllDefinedMemoryLayout => unreachable, + error.Unimplemented => @panic("unimplemented bitcast"), + else => |e| return e, + }; +} + +const BitCastError = CompileError || error{ ReinterpretDeclRef, IllDefinedMemoryLayout, Unimplemented }; + +fn bitCastInner( + sema: *Sema, + val: Value, + dest_ty: Type, + bit_offset: u64, +) BitCastError!Value { + const zcu = sema.mod; + + if (dest_ty.toIntern() == val.typeOf(zcu).toIntern() and bit_offset == 0) { + return val; + } + + const val_ty = val.typeOf(zcu); + + try sema.resolveTypeLayout(val_ty); + try sema.resolveTypeLayout(dest_ty); + + assert(val_ty.hasWellDefinedLayout(zcu)); + + var unpack: UnpackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .skip_bits = bit_offset, + .remaining_bits = dest_ty.bitSize(zcu), + .unpacked = std.ArrayList(InternPool.Index).init(sema.arena), + }; + try unpack.add(val); + try unpack.padding(val_ty.abiSize(zcu) * 8 - val_ty.bitSize(zcu)); + + var pack: PackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .unpacked = unpack.unpacked.items, + }; + return pack.get(dest_ty); +} + +fn bitCastSpliceInner( + sema: *Sema, + val: Value, + splice_val: Value, + bit_offset: u64, +) BitCastError!Value { + const zcu = sema.mod; + const val_ty = val.typeOf(zcu); + const splice_val_ty = splice_val.typeOf(zcu); + + try sema.resolveTypeLayout(val_ty); + try sema.resolveTypeLayout(splice_val_ty); + + const splice_bits = splice_val_ty.bitSize(zcu); + + assert(bit_offset + splice_bits <= val_ty.abiSize(zcu) * 8); + + var unpack: UnpackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .skip_bits = 0, + .remaining_bits = bit_offset, + .unpacked = std.ArrayList(InternPool.Index).init(sema.arena), + }; + try unpack.add(val); + + unpack.remaining_bits = splice_bits; + try unpack.add(splice_val); + + unpack.skip_bits = bit_offset + splice_bits; + unpack.remaining_bits = val_ty.abiSize(zcu) * 8 - bit_offset - splice_bits; + try unpack.add(val); + const pad_bits = val_ty.abiSize(zcu) * 8 - val_ty.bitSize(zcu); + try unpack.padding(pad_bits); + + var pack: PackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .unpacked = unpack.unpacked.items, + }; + return pack.get(val_ty); +} + +const UnpackValueBits = struct { + zcu: *Zcu, + arena: Allocator, + skip_bits: u64, + remaining_bits: u64, + extra_bits: u64 = undefined, + unpacked: std.ArrayList(InternPool.Index), + + fn add(unpack: *UnpackValueBits, val: Value) BitCastError!void { + const zcu = unpack.zcu; + const ip = &zcu.intern_pool; + + if (unpack.remaining_bits == 0) { + return; + } + + const ty = val.typeOf(zcu); + const bit_size = ty.bitSize(zcu); + + if (unpack.skip_bits >= bit_size) { + unpack.skip_bits -= bit_size; + return; + } + + switch (ip.indexToKey(val.toIntern())) { + .int_type, + .ptr_type, + .array_type, + .vector_type, + .opt_type, + .anyframe_type, + .error_union_type, + .simple_type, + .struct_type, + .anon_struct_type, + .union_type, + .opaque_type, + .enum_type, + .func_type, + .error_set_type, + .inferred_error_set_type, + .variable, + .extern_func, + .func, + .err, + .error_union, + .enum_literal, + .slice, + .memoized_call, + => unreachable, // ill-defined layout or not real values + + .undef, + .int, + .enum_tag, + .simple_value, + .empty_enum_value, + .float, + .ptr, + .opt, + => try unpack.primitive(val), + + .aggregate => switch (ty.zigTypeTag(zcu)) { + .Vector => { + // This one is simple: elements are bit-packed. + for (0..@intCast(ty.arrayLen(zcu))) |i| { + const elem_val = try val.elemValue(zcu, i); + try unpack.add(elem_val); + } + }, + .Array => { + // Each element is padded up to its ABI size. Padding bits are undefined. + // The final element does not have trailing padding. + const elem_ty = ty.childType(zcu); + const pad_bits = elem_ty.abiSize(zcu) * 8 - elem_ty.bitSize(zcu); + const len = ty.arrayLen(zcu); + const maybe_sent = ty.sentinel(zcu); + for (0..@intCast(len)) |i| { + const elem_val = try val.elemValue(zcu, i); + try unpack.add(elem_val); + if (i != len - 1 or maybe_sent != null) { + try unpack.padding(pad_bits); + } + } + if (maybe_sent) |s| { + try unpack.add(s); + } + }, + .Struct => switch (ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"extern" => { + var cur_bit_off: u64 = 0; + var it = zcu.typeToStruct(ty).?.iterateRuntimeOrder(ip); + while (it.next()) |field_idx| { + const want_bit_off = ty.structFieldOffset(field_idx, zcu) * 8; + const pad_bits = want_bit_off - cur_bit_off; + const field_val = try val.fieldValue(zcu, field_idx); + try unpack.padding(pad_bits); + try unpack.add(field_val); + cur_bit_off = want_bit_off + field_val.typeOf(zcu).bitSize(zcu); + } + // Add trailing padding bits. + try unpack.padding(bit_size - cur_bit_off); + }, + .@"packed" => { + // Just add all fields in order. There are no padding bits. + for (0..ty.structFieldCount(zcu)) |i| { + const field_val = try val.fieldValue(zcu, i); + try unpack.add(field_val); + } + }, + }, + else => unreachable, + }, + + .un => |un| { + // We actually don't care about the tag *or* the container layout. + // Instead, we just need to write the payload value, plus any necessary padding. + // This correctly handles the case where `tag == .none`, since the payload is then + // either an integer or a byte array, both of which we can unpack. + const payload_val = Value.fromInterned(un.val); + try unpack.add(payload_val); + const pad_bits = bit_size - payload_val.typeOf(zcu).bitSize(zcu); + try unpack.padding(pad_bits); + }, + } + } + + fn padding(unpack: *UnpackValueBits, pad_bits: u64) BitCastError!void { + if (pad_bits == 0) return; + const zcu = unpack.zcu; + // Figure out how many full bytes and leftover bits there are. + const bytes = pad_bits / 8; + const bits = pad_bits % 8; + // Add undef u8 values for the bytes... + const undef_u8 = try zcu.undefValue(Type.u8); + for (0..@intCast(bytes)) |_| { + try unpack.primitive(undef_u8); + } + // ...and an undef int for the leftover bits. + if (bits == 0) return; + const bits_ty = try zcu.intType(.unsigned, @intCast(bits)); + const bits_val = try zcu.undefValue(bits_ty); + try unpack.primitive(bits_val); + } + + fn primitive(unpack: *UnpackValueBits, val: Value) BitCastError!void { + const zcu = unpack.zcu; + + if (unpack.remaining_bits == 0) { + return; + } + + const ty = val.typeOf(zcu); + const bit_size = ty.bitSize(zcu); + + // Note that this skips all zero-bit types. + if (unpack.skip_bits >= bit_size) { + unpack.skip_bits -= bit_size; + return; + } + + if (unpack.skip_bits > 0) { + const skip = unpack.skip_bits; + unpack.skip_bits = 0; + return unpack.splitPrimitive(val, skip, bit_size - skip); + } + + if (unpack.remaining_bits < bit_size) { + return unpack.splitPrimitive(val, 0, unpack.remaining_bits); + } + + unpack.remaining_bits -|= bit_size; + + try unpack.unpacked.append(val.toIntern()); + } + + fn splitPrimitive(unpack: *UnpackValueBits, val: Value, bit_offset: u64, bit_count: u64) BitCastError!void { + const zcu = unpack.zcu; + const ty = val.typeOf(zcu); + + const val_bits = ty.bitSize(zcu); + assert(bit_offset + bit_count <= val_bits); + + switch (zcu.intern_pool.indexToKey(val.toIntern())) { + // In the `ptr` case, this will return `error.ReinterpretDeclRef` + // if we're trying to split a non-integer pointer value. + .int, .float, .enum_tag, .ptr, .opt => { + // This @intCast is okay because no primitive can exceed the size of a u16. + const int_ty = try zcu.intType(.unsigned, @intCast(bit_count)); + const buf = try unpack.arena.alloc(u8, @intCast((val_bits + 7) / 8)); + try val.writeToPackedMemory(ty, zcu, buf, 0); + const sub_val = try Value.readFromPackedMemory(int_ty, zcu, buf, @intCast(bit_offset), unpack.arena); + try unpack.primitive(sub_val); + }, + .undef => try unpack.padding(bit_count), + // The only values here with runtime bits are `true` and `false. + // These are both 1 bit, so will never need truncating. + .simple_value => unreachable, + .empty_enum_value => unreachable, // zero-bit + else => unreachable, // zero-bit or not primitives + } + } +}; + +const PackValueBits = struct { + zcu: *Zcu, + arena: Allocator, + bit_offset: u64 = 0, + unpacked: []const InternPool.Index, + + fn get(pack: *PackValueBits, ty: Type) BitCastError!Value { + const zcu = pack.zcu; + const ip = &zcu.intern_pool; + const arena = pack.arena; + switch (ty.zigTypeTag(zcu)) { + .Vector => { + // Elements are bit-packed. + const len = ty.arrayLen(zcu); + const elem_ty = ty.childType(zcu); + const elems = try arena.alloc(InternPool.Index, @intCast(len)); + for (elems) |*elem| { + elem.* = (try pack.get(elem_ty)).toIntern(); + } + return Value.fromInterned(try zcu.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = elems }, + } })); + }, + .Array => { + // Each element is padded up to its ABI size. The final element does not have trailing padding. + const len = ty.arrayLen(zcu); + const elem_ty = ty.childType(zcu); + const maybe_sent = ty.sentinel(zcu); + const pad_bits = elem_ty.abiSize(zcu) * 8 - elem_ty.bitSize(zcu); + const elems = try arena.alloc(InternPool.Index, @intCast(len)); + for (elems, 0..) |*elem, i| { + elem.* = (try pack.get(elem_ty)).toIntern(); + if (i != len - 1 or maybe_sent != null) { + try pack.padding(pad_bits); + } + } + if (maybe_sent != null) { + // TODO: validate sentinel was preserved! + try pack.padding(elem_ty.bitSize(zcu)); + } + return Value.fromInterned(try zcu.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = elems }, + } })); + }, + .Struct => switch (ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"extern" => { + const elems = try arena.alloc(InternPool.Index, ty.structFieldCount(zcu)); + @memset(elems, .none); + var cur_bit_off: u64 = 0; + var it = zcu.typeToStruct(ty).?.iterateRuntimeOrder(ip); + while (it.next()) |field_idx| { + const want_bit_off = ty.structFieldOffset(field_idx, zcu) * 8; + try pack.padding(want_bit_off - cur_bit_off); + const field_ty = ty.structFieldType(field_idx, zcu); + elems[field_idx] = (try pack.get(field_ty)).toIntern(); + cur_bit_off = want_bit_off + field_ty.bitSize(zcu); + } + try pack.padding(ty.bitSize(zcu) - cur_bit_off); + // Any fields which do not have runtime bits should be OPV or comptime fields. + // Fill those values now. + for (elems, 0..) |*elem, field_idx| { + if (elem.* != .none) continue; + const val = (try ty.structFieldValueComptime(zcu, field_idx)).?; + elem.* = val.toIntern(); + } + return Value.fromInterned(try zcu.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = elems }, + } })); + }, + .@"packed" => { + // All fields are in order with no padding. + const elems = try arena.alloc(InternPool.Index, ty.structFieldCount(zcu)); + for (elems, 0..) |*elem, i| { + const field_ty = ty.structFieldType(i, zcu); + elem.* = (try pack.get(field_ty)).toIntern(); + } + return Value.fromInterned(try zcu.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = elems }, + } })); + }, + }, + .Union => { + // We will attempt to read as the backing representation. If this emits + // `error.ReinterpretDeclRef`, we will try each union field, preferring larger ones. + // We will also attempt smaller fields when we get `undefined`, as if some bits are + // defined we want to include them. + // TODO: this is very very bad. We need a more sophisticated union representation. + + const prev_unpacked = pack.unpacked; + const prev_bit_offset = pack.bit_offset; + + const backing_ty = try ty.unionBackingType(zcu); + + backing: { + const backing_val = pack.get(backing_ty) catch |err| switch (err) { + error.ReinterpretDeclRef => { + pack.unpacked = prev_unpacked; + pack.bit_offset = prev_bit_offset; + break :backing; + }, + else => |e| return e, + }; + if (backing_val.isUndef(zcu)) { + pack.unpacked = prev_unpacked; + pack.bit_offset = prev_bit_offset; + break :backing; + } + return Value.fromInterned(try zcu.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = .none, + .val = backing_val.toIntern(), + } })); + } + + const field_order = try pack.arena.alloc(u32, ty.unionTagTypeHypothetical(zcu).enumFieldCount(zcu)); + for (field_order, 0..) |*f, i| f.* = @intCast(i); + // Sort `field_order` to put the fields with the largest ABI sizes first. + const SizeSortCtx = struct { + zcu: *Zcu, + field_types: []const InternPool.Index, + fn lessThan(ctx: @This(), a_idx: u32, b_idx: u32) bool { + const a_ty = Type.fromInterned(ctx.field_types[a_idx]); + const b_ty = Type.fromInterned(ctx.field_types[b_idx]); + return a_ty.abiSize(ctx.zcu) > b_ty.abiSize(ctx.zcu); + } + }; + std.mem.sortUnstable(u32, field_order, SizeSortCtx{ + .zcu = zcu, + .field_types = zcu.typeToUnion(ty).?.field_types.get(ip), + }, SizeSortCtx.lessThan); + + for (field_order) |field_idx| { + const field_ty = Type.fromInterned(zcu.typeToUnion(ty).?.field_types.get(ip)[field_idx]); + const field_val = pack.get(field_ty) catch |err| switch (err) { + error.ReinterpretDeclRef => { + pack.unpacked = prev_unpacked; + pack.bit_offset = prev_bit_offset; + continue; + }, + else => |e| return e, + }; + if (field_val.isUndef(zcu)) { + pack.unpacked = prev_unpacked; + pack.bit_offset = prev_bit_offset; + continue; + } + const tag_val = try zcu.enumValueFieldIndex(ty.unionTagTypeHypothetical(zcu), field_idx); + return Value.fromInterned(try zcu.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = tag_val.toIntern(), + .val = field_val.toIntern(), + } })); + } + + // No field could represent the value. Just do whatever happens when we try to read + // the backing type - either `undefined` or `error.ReinterpretDeclRef`. + const backing_val = try pack.get(backing_ty); + return Value.fromInterned(try zcu.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = .none, + .val = backing_val.toIntern(), + } })); + }, + else => return pack.primitive(ty), + } + } + + fn padding(pack: *PackValueBits, pad_bits: u64) BitCastError!void { + _ = pack.prepareBits(pad_bits); + } + + fn primitive(pack: *PackValueBits, want_ty: Type) BitCastError!Value { + const zcu = pack.zcu; + const vals, const bit_offset = pack.prepareBits(want_ty.bitSize(zcu)); + + for (vals) |val| { + if (Value.fromInterned(val).isUndef(zcu)) { + // The value contains undef bits, so is considered entirely undef. + return zcu.undefValue(want_ty); + } + } + + ptr_cast: { + if (vals.len != 1) break :ptr_cast; + const val = Value.fromInterned(vals[0]); + if (!val.typeOf(zcu).isPtrAtRuntime(zcu)) break :ptr_cast; + if (!want_ty.isPtrAtRuntime(zcu)) break :ptr_cast; + return zcu.getCoerced(val, want_ty); + } + + // Reinterpret via an in-memory buffer. + + var buf_bits: u64 = 0; + for (vals) |ip_val| { + const val = Value.fromInterned(ip_val); + const ty = val.typeOf(zcu); + buf_bits += ty.bitSize(zcu); + } + + const buf = try pack.arena.alloc(u8, @intCast((buf_bits + 7) / 8)); + var cur_bit_off: usize = 0; + for (vals) |ip_val| { + const val = Value.fromInterned(ip_val); + const ty = val.typeOf(zcu); + try val.writeToPackedMemory(ty, zcu, buf, cur_bit_off); + cur_bit_off += @intCast(ty.bitSize(zcu)); + } + + return Value.readFromPackedMemory(want_ty, zcu, buf, @intCast(bit_offset), pack.arena); + } + + fn prepareBits(pack: *PackValueBits, need_bits: u64) struct { []const InternPool.Index, u64 } { + if (need_bits == 0) return .{ &.{}, 0 }; + + const zcu = pack.zcu; + + var bits: u64 = 0; + var len: usize = 0; + while (bits < pack.bit_offset + need_bits) { + bits += Value.fromInterned(pack.unpacked[len]).typeOf(zcu).bitSize(zcu); + len += 1; + } + + const result_vals = pack.unpacked[0..len]; + const result_offset = pack.bit_offset; + + const extra_bits = bits - pack.bit_offset - need_bits; + if (extra_bits == 0) { + pack.unpacked = pack.unpacked[len..]; + pack.bit_offset = 0; + } else { + pack.unpacked = pack.unpacked[len - 1 ..]; + pack.bit_offset = Value.fromInterned(pack.unpacked[0]).typeOf(zcu).bitSize(zcu) - extra_bits; + } + + return .{ result_vals, result_offset }; + } +}; + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const Sema = @import("../Sema.zig"); +const Zcu = @import("../Module.zig"); +const InternPool = @import("../InternPool.zig"); +const Type = @import("../type.zig").Type; +const Value = @import("../Value.zig"); +const CompileError = Zcu.CompileError; diff --git a/src/Sema/comptime_ptr_access.zig b/src/Sema/comptime_ptr_access.zig new file mode 100644 index 000000000000..c2a91fa9d5c6 --- /dev/null +++ b/src/Sema/comptime_ptr_access.zig @@ -0,0 +1,1049 @@ +pub const ComptimeLoadResult = union(enum) { + success: MutableValue, + + runtime_load, + undef, + err_payload: InternPool.NullTerminatedString, + null_payload, + inactive_union_field, + needed_well_defined: Type, + out_of_bounds: Type, + exceeds_host_size, +}; + +pub fn loadComptimePtr(sema: *Sema, block: *Block, src: LazySrcLoc, ptr: Value) !ComptimeLoadResult { + const zcu = sema.mod; + const ptr_info = ptr.typeOf(zcu).ptrInfo(zcu); + // TODO: host size for vectors is terrible + const host_size = switch (ptr_info.flags.vector_index) { + .none => ptr_info.packed_offset.host_size, + else => ptr_info.packed_offset.host_size * Type.fromInterned(ptr_info.child).bitSize(zcu), + }; + const bit_offset = if (host_size != 0) bit_offset: { + const child_bits = Type.fromInterned(ptr_info.child).bitSize(zcu); + const bit_offset = ptr_info.packed_offset.bit_offset + switch (ptr_info.flags.vector_index) { + .none => 0, + .runtime => return .runtime_load, + else => |idx| child_bits * @intFromEnum(idx), + }; + if (child_bits + bit_offset > host_size * 8) { + return .exceeds_host_size; + } + break :bit_offset bit_offset; + } else 0; + return loadComptimePtrInner(sema, block, src, ptr, bit_offset, Type.fromInterned(ptr_info.child), 0); +} + +pub const ComptimeStoreResult = union(enum) { + success, + + runtime_store, + comptime_field_mismatch: Value, + undef, + err_payload: InternPool.NullTerminatedString, + null_payload, + inactive_union_field, + needed_well_defined: Type, + out_of_bounds: Type, + exceeds_host_size, +}; + +/// Perform a comptime load of value `store_val` to a pointer. +/// The pointer's type is ignored. +pub fn storeComptimePtr( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ptr: Value, + store_val: Value, +) !ComptimeStoreResult { + const zcu = sema.mod; + const ptr_info = ptr.typeOf(zcu).ptrInfo(zcu); + assert(store_val.typeOf(zcu).toIntern() == ptr_info.child); + const bit_offset = ptr_info.packed_offset.bit_offset + switch (ptr_info.flags.vector_index) { + .none => 0, + .runtime => return .runtime_store, + else => |idx| Type.fromInterned(ptr_info.child).bitSize(zcu) * @intFromEnum(idx), + }; + // TODO: host size for vectors is terrible + const host_size = switch (ptr_info.flags.vector_index) { + .none => ptr_info.packed_offset.host_size, + else => ptr_info.packed_offset.host_size * Type.fromInterned(ptr_info.child).bitSize(zcu), + }; + const pseudo_store_ty = if (bit_offset > 0) t: { + const need_bits = Type.fromInterned(ptr_info.child).bitSize(zcu); + if (need_bits + bit_offset > host_size * 8) { + return .exceeds_host_size; + } + break :t try zcu.intType(.unsigned, @intCast(host_size)); + } else Type.fromInterned(ptr_info.child); + + const strat = try prepareComptimePtrStore(sema, block, src, ptr, pseudo_store_ty, 0); + + // Propagate errors and handle comptime fields. + switch (strat) { + .direct, .index, .flat_index, .reinterpret => {}, + .comptime_field => { + // To "store" to a comptime field, just perform a load of the field + // and see if the store value matches. + const expected_mv = switch (try loadComptimePtr(sema, block, src, ptr)) { + .success => |mv| mv, + .runtime_load => unreachable, // this is a comptime field + .exceeds_host_size => unreachable, // checked above + .undef => return .undef, + .err_payload => |err| return .{ .err_payload = err }, + .null_payload => return .null_payload, + .inactive_union_field => return .inactive_union_field, + .needed_well_defined => |ty| return .{ .needed_well_defined = ty }, + .out_of_bounds => |ty| return .{ .out_of_bounds = ty }, + }; + const expected = try expected_mv.intern(zcu, sema.arena); + if (store_val.toIntern() != expected.toIntern()) { + return .{ .comptime_field_mismatch = expected }; + } + return .success; + }, + .runtime_store => return .runtime_store, + .undef => return .undef, + .err_payload => |err| return .{ .err_payload = err }, + .null_payload => return .null_payload, + .inactive_union_field => return .inactive_union_field, + .needed_well_defined => |ty| return .{ .needed_well_defined = ty }, + .out_of_bounds => |ty| return .{ .out_of_bounds = ty }, + } + + // Check the store is not inside a runtime condition + try checkComptimeVarStore(sema, block, src, strat.alloc()); + + if (bit_offset == 0) { + // We can attempt a direct store depending on the strategy. + switch (strat) { + .direct => |direct| { + const want_ty = direct.val.typeOf(zcu); + const coerced_store_val = try zcu.getCoerced(store_val, want_ty); + direct.val.* = .{ .interned = coerced_store_val.toIntern() }; + return .success; + }, + .index => |index| { + const want_ty = index.val.typeOf(zcu).childType(zcu); + const coerced_store_val = try zcu.getCoerced(store_val, want_ty); + try index.val.setElem(zcu, sema.arena, @intCast(index.elem_index), .{ .interned = coerced_store_val.toIntern() }); + return .success; + }, + .flat_index => |flat| { + const store_elems = store_val.typeOf(zcu).arrayBase(zcu)[1]; + const flat_elems = try sema.arena.alloc(InternPool.Index, @intCast(store_elems)); + { + var next_idx: u64 = 0; + var skip: u64 = 0; + try flattenArray(sema, .{ .interned = store_val.toIntern() }, &skip, &next_idx, flat_elems); + } + for (flat_elems, 0..) |elem, idx| { + // TODO: recursiveIndex in a loop does a lot of redundant work! + // Better would be to gather all the store targets into an array. + var index: u64 = flat.flat_elem_index + idx; + const val_ptr, const final_idx = (try recursiveIndex(sema, flat.val, &index)).?; + try val_ptr.setElem(zcu, sema.arena, @intCast(final_idx), .{ .interned = elem }); + } + return .success; + }, + .reinterpret => {}, + else => unreachable, + } + } + + // Either there is a bit offset, or the strategy required reinterpreting. + // Therefore, we must perform a bitcast. + + const val_ptr: *MutableValue, const byte_offset: u64 = switch (strat) { + .direct => |direct| .{ direct.val, 0 }, + .index => |index| .{ + index.val, + index.elem_index * index.val.typeOf(zcu).childType(zcu).abiSize(zcu), + }, + .flat_index => |flat| .{ flat.val, flat.flat_elem_index * flat.val.typeOf(zcu).arrayBase(zcu)[0].abiSize(zcu) }, + .reinterpret => |reinterpret| .{ reinterpret.val, reinterpret.byte_offset }, + else => unreachable, + }; + + if (!val_ptr.typeOf(zcu).hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = val_ptr.typeOf(zcu) }; + } + + if (!store_val.typeOf(zcu).hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = store_val.typeOf(zcu) }; + } + + const new_val = try sema.bitCastSpliceVal( + try val_ptr.intern(zcu, sema.arena), + store_val, + byte_offset * 8 + bit_offset, + ) orelse return .runtime_store; + val_ptr.* = .{ .interned = new_val.toIntern() }; + return .success; +} + +/// Perform a comptime load of type `load_ty` from a pointer. +/// The pointer's type is ignored. +fn loadComptimePtrInner( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ptr_val: Value, + bit_offset: u64, + load_ty: Type, + /// If `load_ty` is an array, this is the number of array elements to skip + /// before `load_ty`. Otherwise, it is ignored and may be `undefined`. + array_offset: u64, +) !ComptimeLoadResult { + const zcu = sema.mod; + const ip = &zcu.intern_pool; + + const ptr = switch (ip.indexToKey(ptr_val.toIntern())) { + .undef => return .undef, + .ptr => |ptr| ptr, + else => unreachable, + }; + + const base_val: MutableValue = switch (ptr.base_addr) { + .decl => |decl_index| val: { + try sema.declareDependency(.{ .decl_val = decl_index }); + try sema.ensureDeclAnalyzed(decl_index); + const decl = zcu.declPtr(decl_index); + if (decl.val.getVariable(zcu) != null) return .runtime_load; + break :val .{ .interned = decl.val.toIntern() }; + }, + .comptime_alloc => |alloc_index| sema.getComptimeAlloc(alloc_index).val, + .anon_decl => |anon_decl| .{ .interned = anon_decl.val }, + .comptime_field => |val| .{ .interned = val }, + .int => return .runtime_load, + .eu_payload => |base_ptr_ip| val: { + const base_ptr = Value.fromInterned(base_ptr_ip); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + switch (try loadComptimePtrInner(sema, block, src, base_ptr, 0, base_ty, undefined)) { + .success => |eu_val| switch (eu_val.unpackErrorUnion(zcu)) { + .undef => return .undef, + .err => |err| return .{ .err_payload = err }, + .payload => |payload| break :val payload, + }, + else => |err| return err, + } + }, + .opt_payload => |base_ptr_ip| val: { + const base_ptr = Value.fromInterned(base_ptr_ip); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + switch (try loadComptimePtrInner(sema, block, src, base_ptr, 0, base_ty, undefined)) { + .success => |eu_val| switch (eu_val.unpackOptional(zcu)) { + .undef => return .undef, + .null => return .null_payload, + .payload => |payload| break :val payload, + }, + else => |err| return err, + } + }, + .arr_elem => |base_index| val: { + const base_ptr = Value.fromInterned(base_index.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + + // We have a comptime-only array. This case is a little nasty. + // To avoid loading too much data, we want to figure out how many elements we need. + // If `load_ty` and the array share a base type, we'll load the correct number of elements. + // Otherwise, we'll be reinterpreting (which we can't do, since it's comptime-only); just + // load a single element and let the logic below emit its error. + + const load_one_ty, const load_count = load_ty.arrayBase(zcu); + const count = if (load_one_ty.toIntern() == base_ty.toIntern()) load_count else 1; + + const want_ty = try zcu.arrayType(.{ + .len = count, + .child = base_ty.toIntern(), + }); + + switch (try loadComptimePtrInner(sema, block, src, base_ptr, 0, want_ty, base_index.index)) { + .success => |arr_val| break :val arr_val, + else => |err| return err, + } + }, + .field => |base_index| val: { + const base_ptr = Value.fromInterned(base_index.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + + // Field of a slice, or of an auto-layout struct or union. + const agg_val = switch (try loadComptimePtrInner(sema, block, src, base_ptr, 0, base_ty, undefined)) { + .success => |val| val, + else => |err| return err, + }; + + const agg_ty = agg_val.typeOf(zcu); + switch (agg_ty.zigTypeTag(zcu)) { + .Struct, .Pointer => break :val try agg_val.getElem(zcu, @intCast(base_index.index)), + .Union => { + const tag_val: Value, const payload_mv: MutableValue = switch (agg_val) { + .un => |un| .{ Value.fromInterned(un.tag), un.payload.* }, + .interned => |ip_index| switch (ip.indexToKey(ip_index)) { + .undef => return .undef, + .un => |un| .{ Value.fromInterned(un.tag), .{ .interned = un.val } }, + else => unreachable, + }, + else => unreachable, + }; + const tag_ty = agg_ty.unionTagTypeHypothetical(zcu); + if (tag_ty.enumTagFieldIndex(tag_val, zcu).? != base_index.index) { + return .inactive_union_field; + } + break :val payload_mv; + }, + else => unreachable, + } + + break :val try agg_val.getElem(zcu, base_index.index); + }, + }; + + if (ptr.byte_offset == 0 and bit_offset == 0) { + if (load_ty.zigTypeTag(zcu) != .Array or array_offset == 0) { + if (.ok == try sema.coerceInMemoryAllowed( + block, + load_ty, + base_val.typeOf(zcu), + false, + zcu.getTarget(), + src, + src, + )) { + // We already have a value which is IMC to the desired type. + return .{ .success = base_val }; + } + } + } + + restructure_array: { + if (bit_offset != 0) break :restructure_array; + + // We might also be changing the length of an array, or restructuring it. + // e.g. [1][2][3]T -> [3][2]T. + // This case is important because it's permitted for types with ill-defined layouts. + + const load_one_ty, const load_count = load_ty.arrayBase(zcu); + + const extra_base_index: u64 = if (ptr.byte_offset == 0) 0 else idx: { + if (try sema.typeRequiresComptime(load_one_ty)) break :restructure_array; + const elem_len = try sema.typeAbiSize(load_one_ty); + if (ptr.byte_offset % elem_len != 0) break :restructure_array; + break :idx @divExact(ptr.byte_offset, elem_len); + }; + + const val_one_ty, const val_count = base_val.typeOf(zcu).arrayBase(zcu); + if (.ok == try sema.coerceInMemoryAllowed( + block, + load_one_ty, + val_one_ty, + false, + zcu.getTarget(), + src, + src, + )) { + // Changing the length of an array. + const skip_base: u64 = extra_base_index + if (load_ty.zigTypeTag(zcu) == .Array) skip: { + break :skip load_ty.childType(zcu).arrayBase(zcu)[1] * array_offset; + } else 0; + if (skip_base + load_count > val_count) return .{ .out_of_bounds = base_val.typeOf(zcu) }; + const elems = try sema.arena.alloc(InternPool.Index, @intCast(load_count)); + var skip: u64 = skip_base; + var next_idx: u64 = 0; + try flattenArray(sema, base_val, &skip, &next_idx, elems); + next_idx = 0; + const val = try unflattenArray(sema, load_ty, elems, &next_idx); + return .{ .success = .{ .interned = val.toIntern() } }; + } + } + + // We need to reinterpret memory, which is only possible if neither the load + // type nor the type of the base value are comptime-only. + + if (!load_ty.hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = load_ty }; + } + + if (!base_val.typeOf(zcu).hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = base_val.typeOf(zcu) }; + } + + var cur_val = base_val; + var cur_offset = ptr.byte_offset; + + if (load_ty.zigTypeTag(zcu) == .Array and array_offset > 0) { + cur_offset += try sema.typeAbiSize(load_ty.childType(zcu)) * array_offset; + } + + const need_bits = bit_offset + try load_ty.bitSizeAdvanced(zcu, sema); + const need_bytes = (need_bits + 7) / 8; + + if (cur_offset + need_bytes > try sema.typeAbiSize(cur_val.typeOf(zcu))) { + return .{ .out_of_bounds = cur_val.typeOf(zcu) }; + } + + // In the worst case, we can reinterpret the entire value - however, that's + // pretty wasteful. If the memory region we're interested in refers to one + // field or array element, let's just look at that. + while (true) { + const cur_ty = cur_val.typeOf(zcu); + switch (cur_ty.zigTypeTag(zcu)) { + .NoReturn, + .Type, + .ComptimeInt, + .ComptimeFloat, + .Null, + .Undefined, + .EnumLiteral, + .Opaque, + .Fn, + .ErrorUnion, + => unreachable, // ill-defined layout + .Int, + .Float, + .Bool, + .Void, + .Pointer, + .ErrorSet, + .AnyFrame, + .Frame, + .Enum, + .Vector, + => break, // terminal types (no sub-values) + .Optional => break, // this can only be a pointer-like optional so is terminal + .Array => { + const elem_ty = cur_ty.childType(zcu); + const elem_size = try sema.typeAbiSize(elem_ty); + const elem_idx = cur_offset / elem_size; + const next_elem_off = elem_size * (elem_idx + 1); + if (cur_offset + need_bytes <= next_elem_off) { + // We can look at a single array element. + cur_val = try cur_val.getElem(zcu, @intCast(elem_idx)); + cur_offset -= elem_idx * elem_size; + } else { + break; + } + }, + .Struct => switch (cur_ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"packed" => break, // let the bitcast logic handle this + .@"extern" => for (0..cur_ty.structFieldCount(zcu)) |field_idx| { + const start_off = cur_ty.structFieldOffset(field_idx, zcu); + const end_off = start_off + try sema.typeAbiSize(cur_ty.structFieldType(field_idx, zcu)); + if (cur_offset >= start_off and cur_offset + need_bytes <= end_off) { + cur_val = try cur_val.getElem(zcu, field_idx); + cur_offset -= start_off; + break; + } + } else break, // pointer spans multiple fields + }, + .Union => switch (cur_ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"packed" => break, // let the bitcast logic handle this + .@"extern" => { + // TODO: we have to let bitcast logic handle this for now. + // Otherwise, we might traverse into a union field which doesn't allow pointers. + // Figure out a solution! + if (true) break; + const payload: MutableValue = switch (cur_val) { + .un => |un| un.payload.*, + .interned => |ip_index| switch (ip.indexToKey(ip_index)) { + .un => |un| .{ .interned = un.val }, + .undef => return .undef, + else => unreachable, + }, + else => unreachable, + }; + // The payload always has offset 0. If it's big enough + // to represent the whole load type, we can use it. + if (try sema.typeAbiSize(payload.typeOf(zcu)) >= need_bytes) { + cur_val = payload; + } else { + break; + } + }, + }, + } + } + + // Fast path: check again if we're now at the type we want to load. + // If so, just return the loaded value. + if (cur_offset == 0 and bit_offset == 0 and cur_val.typeOf(zcu).toIntern() == load_ty.toIntern()) { + return .{ .success = cur_val }; + } + + const result_val = try sema.bitCastVal( + try cur_val.intern(zcu, sema.arena), + load_ty, + cur_offset * 8 + bit_offset, + ) orelse return .runtime_load; + return .{ .success = .{ .interned = result_val.toIntern() } }; +} + +const ComptimeStoreStrategy = union(enum) { + /// The store should be performed directly to this value, which `store_ty` + /// is in-memory coercible to. + direct: struct { + alloc: ComptimeAllocIndex, + val: *MutableValue, + }, + /// The store should be performed at the index `elem_index` into `val`, + /// which is an array. + /// This strategy exists to avoid the need to convert the parent value + /// to the `aggregate` representation when `repeated` or `bytes` may + /// suffice. + index: struct { + alloc: ComptimeAllocIndex, + val: *MutableValue, + elem_index: u64, + }, + /// The store should be performed on this array value, but it is being + /// restructured, e.g. [3][2][1]T -> [2][3]T. + /// This includes the case where it is a sub-array, e.g. [3]T -> [2]T. + /// This is only returned if `store_ty` is an array type, and its array + /// base type is IMC to that of the type of `val`. + flat_index: struct { + alloc: ComptimeAllocIndex, + val: *MutableValue, + flat_elem_index: u64, + }, + /// This value should be reinterpreted using bitcast logic to perform the + /// store. Only returned if `store_ty` and the type of `val` both have + /// well-defined layouts. + reinterpret: struct { + alloc: ComptimeAllocIndex, + val: *MutableValue, + byte_offset: u64, + }, + + comptime_field, + runtime_store, + undef, + err_payload: InternPool.NullTerminatedString, + null_payload, + inactive_union_field, + needed_well_defined: Type, + out_of_bounds: Type, + + fn alloc(strat: ComptimeStoreStrategy) ComptimeAllocIndex { + return switch (strat) { + inline .direct, .index, .flat_index, .reinterpret => |info| info.alloc, + .comptime_field, + .runtime_store, + .undef, + .err_payload, + .null_payload, + .inactive_union_field, + .needed_well_defined, + .out_of_bounds, + => unreachable, + }; + } +}; + +/// Decide the strategy we will use to perform a comptime store of type `store_ty` to a pointer. +/// The pointer's type is ignored. +fn prepareComptimePtrStore( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ptr_val: Value, + store_ty: Type, + /// If `store_ty` is an array, this is the number of array elements to skip + /// before `store_ty`. Otherwise, it is ignored and may be `undefined`. + array_offset: u64, +) !ComptimeStoreStrategy { + const zcu = sema.mod; + const ip = &zcu.intern_pool; + + const ptr = switch (ip.indexToKey(ptr_val.toIntern())) { + .undef => return .undef, + .ptr => |ptr| ptr, + else => unreachable, + }; + + // `base_strat` will not be an error case. + const base_strat: ComptimeStoreStrategy = switch (ptr.base_addr) { + .decl, .anon_decl, .int => return .runtime_store, + .comptime_field => return .comptime_field, + .comptime_alloc => |alloc_index| .{ .direct = .{ + .alloc = alloc_index, + .val = &sema.getComptimeAlloc(alloc_index).val, + } }, + .eu_payload => |base_ptr_ip| base_val: { + const base_ptr = Value.fromInterned(base_ptr_ip); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + const eu_val_ptr, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) { + .direct => |direct| .{ direct.val, direct.alloc }, + .index => |index| .{ + try index.val.elem(zcu, sema.arena, @intCast(index.elem_index)), + index.alloc, + }, + .flat_index => unreachable, // base_ty is not an array + .reinterpret => unreachable, // base_ty has ill-defined layout + else => |err| return err, + }; + try eu_val_ptr.unintern(zcu, sema.arena, false, false); + switch (eu_val_ptr.*) { + .interned => |ip_index| switch (ip.indexToKey(ip_index)) { + .undef => return .undef, + .error_union => |eu| return .{ .err_payload = eu.val.err_name }, + else => unreachable, + }, + .eu_payload => |data| break :base_val .{ .direct = .{ + .val = data.child, + .alloc = alloc, + } }, + else => unreachable, + } + }, + .opt_payload => |base_ptr_ip| base_val: { + const base_ptr = Value.fromInterned(base_ptr_ip); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + const opt_val_ptr, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) { + .direct => |direct| .{ direct.val, direct.alloc }, + .index => |index| .{ + try index.val.elem(zcu, sema.arena, @intCast(index.elem_index)), + index.alloc, + }, + .flat_index => unreachable, // base_ty is not an array + .reinterpret => unreachable, // base_ty has ill-defined layout + else => |err| return err, + }; + try opt_val_ptr.unintern(zcu, sema.arena, false, false); + switch (opt_val_ptr.*) { + .interned => |ip_index| switch (ip.indexToKey(ip_index)) { + .undef => return .undef, + .opt => return .null_payload, + else => unreachable, + }, + .opt_payload => |data| break :base_val .{ .direct = .{ + .val = data.child, + .alloc = alloc, + } }, + else => unreachable, + } + }, + .arr_elem => |base_index| base_val: { + const base_ptr = Value.fromInterned(base_index.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + + // We have a comptime-only array. This case is a little nasty. + // To avoid messing with too much data, we want to figure out how many elements we need to store. + // If `store_ty` and the array share a base type, we'll store the correct number of elements. + // Otherwise, we'll be reinterpreting (which we can't do, since it's comptime-only); just + // load a single element and let the logic below emit its error. + + const store_one_ty, const store_count = store_ty.arrayBase(zcu); + const count = if (store_one_ty.toIntern() == base_ty.toIntern()) store_count else 1; + + const want_ty = try zcu.arrayType(.{ + .len = count, + .child = base_ty.toIntern(), + }); + + const result = try prepareComptimePtrStore(sema, block, src, base_ptr, want_ty, base_index.index); + switch (result) { + .direct, .index, .flat_index => break :base_val result, + .reinterpret => unreachable, // comptime-only array so ill-defined layout + else => |err| return err, + } + }, + .field => |base_index| strat: { + const base_ptr = Value.fromInterned(base_index.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + + // Field of a slice, or of an auto-layout struct or union. + const agg_val, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) { + .direct => |direct| .{ direct.val, direct.alloc }, + .index => |index| .{ + try index.val.elem(zcu, sema.arena, @intCast(index.elem_index)), + index.alloc, + }, + .flat_index => unreachable, // base_ty is not an array + .reinterpret => unreachable, // base_ty has ill-defined layout + else => |err| return err, + }; + + const agg_ty = agg_val.typeOf(zcu); + switch (agg_ty.zigTypeTag(zcu)) { + .Struct, .Pointer => break :strat .{ .direct = .{ + .val = try agg_val.elem(zcu, sema.arena, @intCast(base_index.index)), + .alloc = alloc, + } }, + .Union => { + if (agg_val.* == .interned and Value.fromInterned(agg_val.interned).isUndef(zcu)) { + return .undef; + } + try agg_val.unintern(zcu, sema.arena, false, false); + const un = agg_val.un; + const tag_ty = agg_ty.unionTagTypeHypothetical(zcu); + if (tag_ty.enumTagFieldIndex(Value.fromInterned(un.tag), zcu).? != base_index.index) { + return .inactive_union_field; + } + break :strat .{ .direct = .{ + .val = un.payload, + .alloc = alloc, + } }; + }, + else => unreachable, + } + }, + }; + + if (ptr.byte_offset == 0) { + if (store_ty.zigTypeTag(zcu) != .Array or array_offset == 0) direct: { + const base_val_ty = switch (base_strat) { + .direct => |direct| direct.val.typeOf(zcu), + .index => |index| index.val.typeOf(zcu).childType(zcu), + .flat_index, .reinterpret => break :direct, + else => unreachable, + }; + if (.ok == try sema.coerceInMemoryAllowed( + block, + base_val_ty, + store_ty, + true, + zcu.getTarget(), + src, + src, + )) { + // The base strategy already gets us a value which the desired type is IMC to. + return base_strat; + } + } + } + + restructure_array: { + // We might also be changing the length of an array, or restructuring it. + // e.g. [1][2][3]T -> [3][2]T. + // This case is important because it's permitted for types with ill-defined layouts. + + const store_one_ty, const store_count = store_ty.arrayBase(zcu); + const extra_base_index: u64 = if (ptr.byte_offset == 0) 0 else idx: { + if (try sema.typeRequiresComptime(store_one_ty)) break :restructure_array; + const elem_len = try sema.typeAbiSize(store_one_ty); + if (ptr.byte_offset % elem_len != 0) break :restructure_array; + break :idx @divExact(ptr.byte_offset, elem_len); + }; + + const base_val, const base_elem_offset, const oob_ty = switch (base_strat) { + .direct => |direct| .{ direct.val, 0, direct.val.typeOf(zcu) }, + .index => |index| restructure_info: { + const elem_ty = index.val.typeOf(zcu).childType(zcu); + const elem_off = elem_ty.arrayBase(zcu)[1] * index.elem_index; + break :restructure_info .{ index.val, elem_off, elem_ty }; + }, + .flat_index => |flat| .{ flat.val, flat.flat_elem_index, flat.val.typeOf(zcu) }, + .reinterpret => break :restructure_array, + else => unreachable, + }; + const val_one_ty, const val_count = base_val.typeOf(zcu).arrayBase(zcu); + if (.ok != try sema.coerceInMemoryAllowed(block, val_one_ty, store_one_ty, true, zcu.getTarget(), src, src)) { + break :restructure_array; + } + if (base_elem_offset + extra_base_index + store_count > val_count) return .{ .out_of_bounds = oob_ty }; + + if (store_ty.zigTypeTag(zcu) == .Array) { + const skip = store_ty.childType(zcu).arrayBase(zcu)[1] * array_offset; + return .{ .flat_index = .{ + .alloc = base_strat.alloc(), + .val = base_val, + .flat_elem_index = skip + base_elem_offset + extra_base_index, + } }; + } + + // `base_val` must be an array, since otherwise the "direct reinterpret" logic above noticed it. + assert(base_val.typeOf(zcu).zigTypeTag(zcu) == .Array); + + var index: u64 = base_elem_offset + extra_base_index; + const arr_val, const arr_index = (try recursiveIndex(sema, base_val, &index)).?; + return .{ .index = .{ + .alloc = base_strat.alloc(), + .val = arr_val, + .elem_index = arr_index, + } }; + } + + // We need to reinterpret memory, which is only possible if neither the store + // type nor the type of the base value have an ill-defined layout. + + if (!store_ty.hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = store_ty }; + } + + var cur_val: *MutableValue, var cur_offset: u64 = switch (base_strat) { + .direct => |direct| .{ direct.val, 0 }, + // It's okay to do `abiSize` - the comptime-only case will be caught below. + .index => |index| .{ index.val, index.elem_index * try sema.typeAbiSize(index.val.typeOf(zcu).childType(zcu)) }, + .flat_index => |flat_index| .{ + flat_index.val, + // It's okay to do `abiSize` - the comptime-only case will be caught below. + flat_index.flat_elem_index * try sema.typeAbiSize(flat_index.val.typeOf(zcu).arrayBase(zcu)[0]), + }, + .reinterpret => |r| .{ r.val, r.byte_offset }, + else => unreachable, + }; + cur_offset += ptr.byte_offset; + + if (!cur_val.typeOf(zcu).hasWellDefinedLayout(zcu)) { + return .{ .needed_well_defined = cur_val.typeOf(zcu) }; + } + + if (store_ty.zigTypeTag(zcu) == .Array and array_offset > 0) { + cur_offset += try sema.typeAbiSize(store_ty.childType(zcu)) * array_offset; + } + + const need_bytes = try sema.typeAbiSize(store_ty); + + if (cur_offset + need_bytes > try sema.typeAbiSize(cur_val.typeOf(zcu))) { + return .{ .out_of_bounds = cur_val.typeOf(zcu) }; + } + + // In the worst case, we can reinterpret the entire value - however, that's + // pretty wasteful. If the memory region we're interested in refers to one + // field or array element, let's just look at that. + while (true) { + const cur_ty = cur_val.typeOf(zcu); + switch (cur_ty.zigTypeTag(zcu)) { + .NoReturn, + .Type, + .ComptimeInt, + .ComptimeFloat, + .Null, + .Undefined, + .EnumLiteral, + .Opaque, + .Fn, + .ErrorUnion, + => unreachable, // ill-defined layout + .Int, + .Float, + .Bool, + .Void, + .Pointer, + .ErrorSet, + .AnyFrame, + .Frame, + .Enum, + .Vector, + => break, // terminal types (no sub-values) + .Optional => break, // this can only be a pointer-like optional so is terminal + .Array => { + const elem_ty = cur_ty.childType(zcu); + const elem_size = try sema.typeAbiSize(elem_ty); + const elem_idx = cur_offset / elem_size; + const next_elem_off = elem_size * (elem_idx + 1); + if (cur_offset + need_bytes <= next_elem_off) { + // We can look at a single array element. + cur_val = try cur_val.elem(zcu, sema.arena, @intCast(elem_idx)); + cur_offset -= elem_idx * elem_size; + } else { + break; + } + }, + .Struct => switch (cur_ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"packed" => break, // let the bitcast logic handle this + .@"extern" => for (0..cur_ty.structFieldCount(zcu)) |field_idx| { + const start_off = cur_ty.structFieldOffset(field_idx, zcu); + const end_off = start_off + try sema.typeAbiSize(cur_ty.structFieldType(field_idx, zcu)); + if (cur_offset >= start_off and cur_offset + need_bytes <= end_off) { + cur_val = try cur_val.elem(zcu, sema.arena, field_idx); + cur_offset -= start_off; + break; + } + } else break, // pointer spans multiple fields + }, + .Union => switch (cur_ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"packed" => break, // let the bitcast logic handle this + .@"extern" => { + // TODO: we have to let bitcast logic handle this for now. + // Otherwise, we might traverse into a union field which doesn't allow pointers. + // Figure out a solution! + if (true) break; + try cur_val.unintern(zcu, sema.arena, false, false); + const payload = switch (cur_val.*) { + .un => |un| un.payload, + else => unreachable, + }; + // The payload always has offset 0. If it's big enough + // to represent the whole load type, we can use it. + if (try sema.typeAbiSize(payload.typeOf(zcu)) >= need_bytes) { + cur_val = payload; + } else { + break; + } + }, + }, + } + } + + // Fast path: check again if we're now at the type we want to store. + // If so, we can use the `direct` strategy. + if (cur_offset == 0 and cur_val.typeOf(zcu).toIntern() == store_ty.toIntern()) { + return .{ .direct = .{ + .alloc = base_strat.alloc(), + .val = cur_val, + } }; + } + + return .{ .reinterpret = .{ + .alloc = base_strat.alloc(), + .val = cur_val, + .byte_offset = cur_offset, + } }; +} + +/// Given a potentially-nested array value, recursively flatten all of its elements into the given +/// output array. The result can be used by `unflattenArray` to restructure array values. +fn flattenArray( + sema: *Sema, + val: MutableValue, + skip: *u64, + next_idx: *u64, + out: []InternPool.Index, +) Allocator.Error!void { + if (next_idx.* == out.len) return; + + const zcu = sema.mod; + + const ty = val.typeOf(zcu); + const base_elem_count = ty.arrayBase(zcu)[1]; + if (skip.* >= base_elem_count) { + skip.* -= base_elem_count; + return; + } + + if (ty.zigTypeTag(zcu) != .Array) { + out[@intCast(next_idx.*)] = (try val.intern(zcu, sema.arena)).toIntern(); + next_idx.* += 1; + return; + } + + const arr_base_elem_count = ty.childType(zcu).arrayBase(zcu)[1]; + for (0..@intCast(ty.arrayLen(zcu))) |elem_idx| { + // Optimization: the `getElem` here may be expensive since we might intern an + // element of the `bytes` representation, so avoid doing it unnecessarily. + if (next_idx.* == out.len) return; + if (skip.* >= arr_base_elem_count) { + skip.* -= arr_base_elem_count; + continue; + } + try flattenArray(sema, try val.getElem(zcu, elem_idx), skip, next_idx, out); + } + if (ty.sentinel(zcu)) |s| { + try flattenArray(sema, .{ .interned = s.toIntern() }, skip, next_idx, out); + } +} + +/// Given a sequence of non-array elements, "unflatten" them into the given array type. +/// Asserts that values of `elems` are in-memory coercible to the array base type of `ty`. +fn unflattenArray( + sema: *Sema, + ty: Type, + elems: []const InternPool.Index, + next_idx: *u64, +) Allocator.Error!Value { + const zcu = sema.mod; + const arena = sema.arena; + + if (ty.zigTypeTag(zcu) != .Array) { + const val = Value.fromInterned(elems[@intCast(next_idx.*)]); + next_idx.* += 1; + return zcu.getCoerced(val, ty); + } + + const elem_ty = ty.childType(zcu); + const buf = try arena.alloc(InternPool.Index, @intCast(ty.arrayLen(zcu))); + for (buf) |*elem| { + elem.* = (try unflattenArray(sema, elem_ty, elems, next_idx)).toIntern(); + } + if (ty.sentinel(zcu) != null) { + // TODO: validate sentinel + _ = try unflattenArray(sema, elem_ty, elems, next_idx); + } + return Value.fromInterned(try zcu.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = buf }, + } })); +} + +/// Given a `MutableValue` representing a potentially-nested array, treats `index` as an index into +/// the array's base type. For instance, given a [3][3]T, the index 5 represents 'val[1][2]'. +/// The final level of array is not dereferenced. This allows use sites to use `setElem` to prevent +/// unnecessary `MutableValue` representation changes. +fn recursiveIndex( + sema: *Sema, + mv: *MutableValue, + index: *u64, +) !?struct { *MutableValue, u64 } { + const zcu = sema.mod; + + const ty = mv.typeOf(zcu); + assert(ty.zigTypeTag(zcu) == .Array); + + const ty_base_elems = ty.arrayBase(zcu)[1]; + if (index.* >= ty_base_elems) { + index.* -= ty_base_elems; + return null; + } + + const elem_ty = ty.childType(zcu); + if (elem_ty.zigTypeTag(zcu) != .Array) { + assert(index.* < ty.arrayLenIncludingSentinel(zcu)); // should be handled by initial check + return .{ mv, index.* }; + } + + for (0..@intCast(ty.arrayLenIncludingSentinel(zcu))) |elem_index| { + if (try recursiveIndex(sema, try mv.elem(zcu, sema.arena, elem_index), index)) |result| { + return result; + } + } + unreachable; // should be handled by initial check +} + +fn checkComptimeVarStore( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + alloc_index: ComptimeAllocIndex, +) !void { + const runtime_index = sema.getComptimeAlloc(alloc_index).runtime_index; + if (@intFromEnum(runtime_index) < @intFromEnum(block.runtime_index)) { + if (block.runtime_cond) |cond_src| { + const msg = msg: { + const msg = try sema.errMsg(block, src, "store to comptime variable depends on runtime condition", .{}); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNoteNonLazy(cond_src, msg, "runtime condition here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + if (block.runtime_loop) |loop_src| { + const msg = msg: { + const msg = try sema.errMsg(block, src, "cannot store to comptime variable in non-inline loop", .{}); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNoteNonLazy(loop_src, msg, "non-inline loop here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + unreachable; + } +} + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const LazySrcLoc = std.zig.LazySrcLoc; + +const InternPool = @import("../InternPool.zig"); +const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; +const Sema = @import("../Sema.zig"); +const Block = Sema.Block; +const MutableValue = @import("../mutable_value.zig").MutableValue; +const Type = @import("../type.zig").Type; +const Value = @import("../Value.zig"); diff --git a/src/Value.zig b/src/Value.zig index 0f8dc5f7dc3f..19772f753591 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -39,10 +39,11 @@ pub fn fmtDebug(val: Value) std.fmt.Formatter(dump) { return .{ .data = val }; } -pub fn fmtValue(val: Value, mod: *Module) std.fmt.Formatter(print_value.format) { +pub fn fmtValue(val: Value, mod: *Module, opt_sema: ?*Sema) std.fmt.Formatter(print_value.format) { return .{ .data = .{ .val = val, .mod = mod, + .opt_sema = opt_sema, } }; } @@ -246,18 +247,13 @@ pub fn getUnsignedIntAdvanced(val: Value, mod: *Module, opt_sema: ?*Sema) !?u64 else Type.fromInterned(ty).abiSize(mod), }, - .ptr => |ptr| switch (ptr.addr) { - .int => |int| Value.fromInterned(int).getUnsignedIntAdvanced(mod, opt_sema), - .elem => |elem| { - const base_addr = (try Value.fromInterned(elem.base).getUnsignedIntAdvanced(mod, opt_sema)) orelse return null; - const elem_ty = Value.fromInterned(elem.base).typeOf(mod).elemType2(mod); - return base_addr + elem.index * elem_ty.abiSize(mod); - }, + .ptr => |ptr| switch (ptr.base_addr) { + .int => ptr.byte_offset, .field => |field| { const base_addr = (try Value.fromInterned(field.base).getUnsignedIntAdvanced(mod, opt_sema)) orelse return null; const struct_ty = Value.fromInterned(field.base).typeOf(mod).childType(mod); if (opt_sema) |sema| try sema.resolveTypeLayout(struct_ty); - return base_addr + struct_ty.structFieldOffset(@intCast(field.index), mod); + return base_addr + struct_ty.structFieldOffset(@intCast(field.index), mod) + ptr.byte_offset; }, else => null, }, @@ -309,11 +305,11 @@ pub fn toBool(val: Value) bool { fn ptrHasIntAddr(val: Value, mod: *Module) bool { var check = val; while (true) switch (mod.intern_pool.indexToKey(check.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| switch (ptr.base_addr) { .decl, .comptime_alloc, .comptime_field, .anon_decl => return false, .int => return true, .eu_payload, .opt_payload => |base| check = Value.fromInterned(base), - .elem, .field => |base_index| check = Value.fromInterned(base_index.base), + .arr_elem, .field => |base_index| check = Value.fromInterned(base_index.base), }, else => unreachable, }; @@ -731,7 +727,8 @@ pub fn readFromMemory( const int_val = try readFromMemory(Type.usize, mod, buffer, arena); return Value.fromInterned((try mod.intern(.{ .ptr = .{ .ty = ty.toIntern(), - .addr = .{ .int = int_val.toIntern() }, + .base_addr = .int, + .byte_offset = int_val.toUnsignedInt(mod), } }))); }, .Optional => { @@ -869,12 +866,25 @@ pub fn readFromPackedMemory( }, .Pointer => { assert(!ty.isSlice(mod)); // No well defined layout. - return readFromPackedMemory(Type.usize, mod, buffer, bit_offset, arena); + const int_val = try readFromPackedMemory(Type.usize, mod, buffer, bit_offset, arena); + return Value.fromInterned(try mod.intern(.{ .ptr = .{ + .ty = ty.toIntern(), + .base_addr = .int, + .byte_offset = int_val.toUnsignedInt(mod), + } })); }, .Optional => { assert(ty.isPtrLikeOptional(mod)); - const child = ty.optionalChild(mod); - return readFromPackedMemory(child, mod, buffer, bit_offset, arena); + const child_ty = ty.optionalChild(mod); + const child_val = try readFromPackedMemory(child_ty, mod, buffer, bit_offset, arena); + return Value.fromInterned(try mod.intern(.{ .opt = .{ + .ty = ty.toIntern(), + .val = switch (child_val.orderAgainstZero(mod)) { + .lt => unreachable, + .eq => .none, + .gt => child_val.toIntern(), + }, + } })); }, else => @panic("TODO implement readFromPackedMemory for more types"), } @@ -983,16 +993,17 @@ pub fn intBitCountTwosComp(self: Value, mod: *Module) usize { /// Converts an integer or a float to a float. May result in a loss of information. /// Caller can find out by equality checking the result against the operand. -pub fn floatCast(self: Value, dest_ty: Type, mod: *Module) !Value { - const target = mod.getTarget(); - return Value.fromInterned((try mod.intern(.{ .float = .{ +pub fn floatCast(val: Value, dest_ty: Type, zcu: *Zcu) !Value { + const target = zcu.getTarget(); + if (val.isUndef(zcu)) return zcu.undefValue(dest_ty); + return Value.fromInterned((try zcu.intern(.{ .float = .{ .ty = dest_ty.toIntern(), .storage = switch (dest_ty.floatBits(target)) { - 16 => .{ .f16 = self.toFloat(f16, mod) }, - 32 => .{ .f32 = self.toFloat(f32, mod) }, - 64 => .{ .f64 = self.toFloat(f64, mod) }, - 80 => .{ .f80 = self.toFloat(f80, mod) }, - 128 => .{ .f128 = self.toFloat(f128, mod) }, + 16 => .{ .f16 = val.toFloat(f16, zcu) }, + 32 => .{ .f32 = val.toFloat(f32, zcu) }, + 64 => .{ .f64 = val.toFloat(f64, zcu) }, + 80 => .{ .f80 = val.toFloat(f80, zcu) }, + 128 => .{ .f128 = val.toFloat(f128, zcu) }, else => unreachable, }, } }))); @@ -1021,14 +1032,9 @@ pub fn orderAgainstZeroAdvanced( .bool_false => .eq, .bool_true => .gt, else => switch (mod.intern_pool.indexToKey(lhs.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset > 0) .gt else switch (ptr.base_addr) { .decl, .comptime_alloc, .comptime_field => .gt, - .int => |int| Value.fromInterned(int).orderAgainstZeroAdvanced(mod, opt_sema), - .elem => |elem| switch (try Value.fromInterned(elem.base).orderAgainstZeroAdvanced(mod, opt_sema)) { - .lt => unreachable, - .gt => .gt, - .eq => if (elem.index == 0) .eq else .gt, - }, + .int => .eq, else => unreachable, }, .int => |int| switch (int.storage) { @@ -1158,6 +1164,7 @@ pub fn compareScalar( /// Asserts the value is comparable. /// For vectors, returns true if comparison is true for ALL elements. +/// Returns `false` if the value or any vector element is undefined. /// /// Note that `!compareAllWithZero(.eq, ...) != compareAllWithZero(.neq, ...)` pub fn compareAllWithZero(lhs: Value, op: std.math.CompareOperator, mod: *Module) bool { @@ -1200,6 +1207,7 @@ pub fn compareAllWithZeroAdvancedExtra( } else true, .repeated_elem => |elem| Value.fromInterned(elem).compareAllWithZeroAdvancedExtra(op, mod, opt_sema), }, + .undef => return false, else => {}, } return (try orderAgainstZeroAdvanced(lhs, mod, opt_sema)).compare(op); @@ -1217,14 +1225,14 @@ pub fn canMutateComptimeVarState(val: Value, zcu: *Zcu) bool { .err_name => false, .payload => |payload| Value.fromInterned(payload).canMutateComptimeVarState(zcu), }, - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| switch (ptr.base_addr) { .decl => false, // The value of a Decl can never reference a comptime alloc. .int => false, .comptime_alloc => true, // A comptime alloc is either mutable or references comptime-mutable memory. .comptime_field => true, // Comptime field pointers are comptime-mutable, albeit only to the "correct" value. .eu_payload, .opt_payload => |base| Value.fromInterned(base).canMutateComptimeVarState(zcu), .anon_decl => |anon_decl| Value.fromInterned(anon_decl.val).canMutateComptimeVarState(zcu), - .elem, .field => |base_index| Value.fromInterned(base_index.base).canMutateComptimeVarState(zcu), + .arr_elem, .field => |base_index| Value.fromInterned(base_index.base).canMutateComptimeVarState(zcu), }, .slice => |slice| return Value.fromInterned(slice.ptr).canMutateComptimeVarState(zcu), .opt => |opt| switch (opt.val) { @@ -1247,10 +1255,10 @@ pub fn pointerDecl(val: Value, mod: *Module) ?InternPool.DeclIndex { .variable => |variable| variable.decl, .extern_func => |extern_func| extern_func.decl, .func => |func| func.owner_decl, - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| decl, else => null, - }, + } else null, else => null, }; } @@ -1386,44 +1394,6 @@ pub fn unionValue(val: Value, mod: *Module) Value { }; } -/// Returns a pointer to the element value at the index. -pub fn elemPtr( - val: Value, - elem_ptr_ty: Type, - index: usize, - mod: *Module, -) Allocator.Error!Value { - const elem_ty = elem_ptr_ty.childType(mod); - const ptr_val = switch (mod.intern_pool.indexToKey(val.toIntern())) { - .slice => |slice| Value.fromInterned(slice.ptr), - else => val, - }; - switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { - .elem => |elem| if (Value.fromInterned(elem.base).typeOf(mod).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; - return Value.fromInterned((try mod.intern(.{ .ptr = .{ - .ty = elem_ptr_ty.toIntern(), - .addr = .{ .elem = .{ - .base = (try mod.getCoerced(ptr_val, try mod.ptrType(ptr_ty_key))).toIntern(), - .index = index, - } }, - } }))); -} - pub fn isUndef(val: Value, mod: *Module) bool { return mod.intern_pool.isUndef(val.toIntern()); } @@ -1444,11 +1414,8 @@ pub fn isNull(val: Value, mod: *Module) bool { .null_value => true, else => return switch (mod.intern_pool.indexToKey(val.toIntern())) { .undef => unreachable, - .ptr => |ptr| switch (ptr.addr) { - .int => { - var buf: BigIntSpace = undefined; - return val.toBigInt(&buf, mod).eqlZero(); - }, + .ptr => |ptr| switch (ptr.base_addr) { + .int => ptr.byte_offset == 0, else => false, }, .opt => |opt| opt.val == .none, @@ -1725,6 +1692,13 @@ pub fn intMulWithOverflowScalar( ) !OverflowArithmeticResult { const info = ty.intInfo(mod); + if (lhs.isUndef(mod) or rhs.isUndef(mod)) { + return .{ + .overflow_bit = try mod.undefValue(Type.u1), + .wrapped_result = try mod.undefValue(ty), + }; + } + var lhs_space: Value.BigIntSpace = undefined; var rhs_space: Value.BigIntSpace = undefined; const lhs_bigint = lhs.toBigInt(&lhs_space, mod); @@ -2439,12 +2413,14 @@ pub fn intTruncScalar( allocator: Allocator, signedness: std.builtin.Signedness, bits: u16, - mod: *Module, + zcu: *Zcu, ) !Value { - if (bits == 0) return mod.intValue(ty, 0); + if (bits == 0) return zcu.intValue(ty, 0); + + if (val.isUndef(zcu)) return zcu.undefValue(ty); var val_space: Value.BigIntSpace = undefined; - const val_bigint = val.toBigInt(&val_space, mod); + const val_bigint = val.toBigInt(&val_space, zcu); const limbs = try allocator.alloc( std.math.big.Limb, @@ -2453,7 +2429,7 @@ pub fn intTruncScalar( var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; result_bigint.truncate(val_bigint, signedness, bits); - return mod.intValue_big(ty, result_bigint.toConst()); + return zcu.intValue_big(ty, result_bigint.toConst()); } pub fn shl(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value { @@ -3585,3 +3561,628 @@ pub fn makeBool(x: bool) Value { } pub const RuntimeIndex = InternPool.RuntimeIndex; + +/// `parent_ptr` must be a single-pointer to some optional. +/// Returns a pointer to the payload of the optional. +/// This takes a `Sema` because it may need to perform type resolution. +pub fn ptrOptPayload(parent_ptr: Value, sema: *Sema) !Value { + const zcu = sema.mod; + + const parent_ptr_ty = parent_ptr.typeOf(zcu); + const opt_ty = parent_ptr_ty.childType(zcu); + + assert(parent_ptr_ty.ptrSize(zcu) == .One); + assert(opt_ty.zigTypeTag(zcu) == .Optional); + + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_ty.ptrInfo(zcu); + // We can correctly preserve alignment `.none`, since an optional has the same + // natural alignment as its child type. + new.child = opt_ty.childType(zcu).toIntern(); + break :info new; + }); + + if (parent_ptr.isUndef(zcu)) return zcu.undefValue(result_ty); + + if (opt_ty.isPtrLikeOptional(zcu)) { + // Just reinterpret the pointer, since the layout is well-defined + return zcu.getCoerced(parent_ptr, result_ty); + } + + const base_ptr = try parent_ptr.canonicalizeBasePtr(.One, opt_ty, zcu); + return Value.fromInterned(try zcu.intern(.{ .ptr = .{ + .ty = result_ty.toIntern(), + .base_addr = .{ .opt_payload = base_ptr.toIntern() }, + .byte_offset = 0, + } })); +} + +/// `parent_ptr` must be a single-pointer to some error union. +/// Returns a pointer to the payload of the error union. +/// This takes a `Sema` because it may need to perform type resolution. +pub fn ptrEuPayload(parent_ptr: Value, sema: *Sema) !Value { + const zcu = sema.mod; + + const parent_ptr_ty = parent_ptr.typeOf(zcu); + const eu_ty = parent_ptr_ty.childType(zcu); + + assert(parent_ptr_ty.ptrSize(zcu) == .One); + assert(eu_ty.zigTypeTag(zcu) == .ErrorUnion); + + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_ty.ptrInfo(zcu); + // We can correctly preserve alignment `.none`, since an error union has a + // natural alignment greater than or equal to that of its payload type. + new.child = eu_ty.errorUnionPayload(zcu).toIntern(); + break :info new; + }); + + if (parent_ptr.isUndef(zcu)) return zcu.undefValue(result_ty); + + const base_ptr = try parent_ptr.canonicalizeBasePtr(.One, eu_ty, zcu); + return Value.fromInterned(try zcu.intern(.{ .ptr = .{ + .ty = result_ty.toIntern(), + .base_addr = .{ .eu_payload = base_ptr.toIntern() }, + .byte_offset = 0, + } })); +} + +/// `parent_ptr` must be a single-pointer to a struct, union, or slice. +/// Returns a pointer to the aggregate field at the specified index. +/// For slices, uses `slice_ptr_index` and `slice_len_index`. +/// This takes a `Sema` because it may need to perform type resolution. +pub fn ptrField(parent_ptr: Value, field_idx: u32, sema: *Sema) !Value { + const zcu = sema.mod; + + const parent_ptr_ty = parent_ptr.typeOf(zcu); + const aggregate_ty = parent_ptr_ty.childType(zcu); + + const parent_ptr_info = parent_ptr_ty.ptrInfo(zcu); + assert(parent_ptr_info.flags.size == .One); + + // Exiting this `switch` indicates that the `field` pointer repsentation should be used. + // `field_align` may be `.none` to represent the natural alignment of `field_ty`, but is not necessarily. + const field_ty: Type, const field_align: InternPool.Alignment = switch (aggregate_ty.zigTypeTag(zcu)) { + .Struct => field: { + const field_ty = aggregate_ty.structFieldType(field_idx, zcu); + switch (aggregate_ty.containerLayout(zcu)) { + .auto => break :field .{ field_ty, try aggregate_ty.structFieldAlignAdvanced(@intCast(field_idx), zcu, sema) }, + .@"extern" => { + // Well-defined layout, so just offset the pointer appropriately. + const byte_off = aggregate_ty.structFieldOffset(field_idx, zcu); + const field_align = a: { + const parent_align = if (parent_ptr_info.flags.alignment == .none) pa: { + break :pa try sema.typeAbiAlignment(aggregate_ty); + } else parent_ptr_info.flags.alignment; + break :a InternPool.Alignment.fromLog2Units(@min(parent_align.toLog2Units(), @ctz(byte_off))); + }; + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + new.flags.alignment = field_align; + break :info new; + }); + return parent_ptr.getOffsetPtr(byte_off, result_ty, zcu); + }, + .@"packed" => switch (aggregate_ty.packedStructFieldPtrInfo(parent_ptr_ty, field_idx, zcu)) { + .bit_ptr => |packed_offset| { + const result_ty = try zcu.ptrType(info: { + var new = parent_ptr_info; + new.packed_offset = packed_offset; + new.child = field_ty.toIntern(); + if (new.flags.alignment == .none) { + new.flags.alignment = try sema.typeAbiAlignment(aggregate_ty); + } + break :info new; + }); + return zcu.getCoerced(parent_ptr, result_ty); + }, + .byte_ptr => |ptr_info| { + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + new.packed_offset = .{ + .host_size = 0, + .bit_offset = 0, + }; + new.flags.alignment = ptr_info.alignment; + break :info new; + }); + return parent_ptr.getOffsetPtr(ptr_info.offset, result_ty, zcu); + }, + }, + } + }, + .Union => field: { + const union_obj = zcu.typeToUnion(aggregate_ty).?; + const field_ty = union_obj.field_types.get(&zcu.intern_pool)[field_idx]; + switch (aggregate_ty.containerLayout(zcu)) { + .auto => break :field .{ Type.fromInterned(field_ty), try aggregate_ty.structFieldAlignAdvanced(@intCast(field_idx), zcu, sema) }, + .@"extern", .@"packed" => { + // Point to the same address. + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty; + break :info new; + }); + return zcu.getCoerced(parent_ptr, result_ty); + }, + } + }, + .Pointer => field_ty: { + assert(aggregate_ty.isSlice(zcu)); + break :field_ty switch (field_idx) { + Value.slice_ptr_index => .{ aggregate_ty.slicePtrFieldType(zcu), Type.usize.abiAlignment(zcu) }, + Value.slice_len_index => .{ Type.usize, Type.usize.abiAlignment(zcu) }, + else => unreachable, + }; + }, + else => unreachable, + }; + + const new_align: InternPool.Alignment = if (parent_ptr_info.flags.alignment != .none) a: { + const ty_align = try sema.typeAbiAlignment(field_ty); + const true_field_align = if (field_align == .none) ty_align else field_align; + const new_align = true_field_align.min(parent_ptr_info.flags.alignment); + if (new_align == ty_align) break :a .none; + break :a new_align; + } else field_align; + + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + new.flags.alignment = new_align; + break :info new; + }); + + if (parent_ptr.isUndef(zcu)) return zcu.undefValue(result_ty); + + const base_ptr = try parent_ptr.canonicalizeBasePtr(.One, aggregate_ty, zcu); + return Value.fromInterned(try zcu.intern(.{ .ptr = .{ + .ty = result_ty.toIntern(), + .base_addr = .{ .field = .{ + .base = base_ptr.toIntern(), + .index = field_idx, + } }, + .byte_offset = 0, + } })); +} + +/// `orig_parent_ptr` must be either a single-pointer to an array or vector, or a many-pointer or C-pointer or slice. +/// Returns a pointer to the element at the specified index. +/// This takes a `Sema` because it may need to perform type resolution. +pub fn ptrElem(orig_parent_ptr: Value, field_idx: u64, sema: *Sema) !Value { + const zcu = sema.mod; + + const parent_ptr = switch (orig_parent_ptr.typeOf(zcu).ptrSize(zcu)) { + .One, .Many, .C => orig_parent_ptr, + .Slice => orig_parent_ptr.slicePtr(zcu), + }; + + const parent_ptr_ty = parent_ptr.typeOf(zcu); + const elem_ty = parent_ptr_ty.childType(zcu); + const result_ty = try sema.elemPtrType(parent_ptr_ty, @intCast(field_idx)); + + if (parent_ptr.isUndef(zcu)) return zcu.undefValue(result_ty); + + if (result_ty.ptrInfo(zcu).packed_offset.host_size != 0) { + // Since we have a bit-pointer, the pointer address should be unchanged. + assert(elem_ty.zigTypeTag(zcu) == .Vector); + return zcu.getCoerced(parent_ptr, result_ty); + } + + const PtrStrat = union(enum) { + offset: u64, + elem_ptr: Type, // many-ptr elem ty + }; + + const strat: PtrStrat = switch (parent_ptr_ty.ptrSize(zcu)) { + .One => switch (elem_ty.zigTypeTag(zcu)) { + .Vector => .{ .offset = field_idx * @divExact(try elem_ty.childType(zcu).bitSizeAdvanced(zcu, sema), 8) }, + .Array => strat: { + const arr_elem_ty = elem_ty.childType(zcu); + if (try sema.typeRequiresComptime(arr_elem_ty)) { + break :strat .{ .elem_ptr = arr_elem_ty }; + } + break :strat .{ .offset = field_idx * try sema.typeAbiSize(arr_elem_ty) }; + }, + else => unreachable, + }, + + .Many, .C => if (try sema.typeRequiresComptime(elem_ty)) + .{ .elem_ptr = elem_ty } + else + .{ .offset = field_idx * try sema.typeAbiSize(elem_ty) }, + + .Slice => unreachable, + }; + + switch (strat) { + .offset => |byte_offset| { + return parent_ptr.getOffsetPtr(byte_offset, result_ty, zcu); + }, + .elem_ptr => |manyptr_elem_ty| if (field_idx == 0) { + return zcu.getCoerced(parent_ptr, result_ty); + } else { + const arr_base_ty, const arr_base_len = manyptr_elem_ty.arrayBase(zcu); + const base_idx = arr_base_len * field_idx; + const parent_info = zcu.intern_pool.indexToKey(parent_ptr.toIntern()).ptr; + switch (parent_info.base_addr) { + .arr_elem => |arr_elem| { + if (Value.fromInterned(arr_elem.base).typeOf(zcu).childType(zcu).toIntern() == arr_base_ty.toIntern()) { + // We already have a pointer to an element of an array of this type. + // Just modify the index. + return Value.fromInterned(try zcu.intern(.{ .ptr = ptr: { + var new = parent_info; + new.base_addr.arr_elem.index += base_idx; + new.ty = result_ty.toIntern(); + break :ptr new; + } })); + } + }, + else => {}, + } + const base_ptr = try parent_ptr.canonicalizeBasePtr(.Many, arr_base_ty, zcu); + return Value.fromInterned(try zcu.intern(.{ .ptr = .{ + .ty = result_ty.toIntern(), + .base_addr = .{ .arr_elem = .{ + .base = base_ptr.toIntern(), + .index = base_idx, + } }, + .byte_offset = 0, + } })); + }, + } +} + +fn canonicalizeBasePtr(base_ptr: Value, want_size: std.builtin.Type.Pointer.Size, want_child: Type, zcu: *Zcu) !Value { + const ptr_ty = base_ptr.typeOf(zcu); + const ptr_info = ptr_ty.ptrInfo(zcu); + + if (ptr_info.flags.size == want_size and + ptr_info.child == want_child.toIntern() and + !ptr_info.flags.is_const and + !ptr_info.flags.is_volatile and + !ptr_info.flags.is_allowzero and + ptr_info.sentinel == .none and + ptr_info.flags.alignment == .none) + { + // Already canonical! + return base_ptr; + } + + const new_ty = try zcu.ptrType(.{ + .child = want_child.toIntern(), + .sentinel = .none, + .flags = .{ + .size = want_size, + .alignment = .none, + .is_const = false, + .is_volatile = false, + .is_allowzero = false, + .address_space = ptr_info.flags.address_space, + }, + }); + return zcu.getCoerced(base_ptr, new_ty); +} + +pub fn getOffsetPtr(ptr_val: Value, byte_off: u64, new_ty: Type, zcu: *Zcu) !Value { + if (ptr_val.isUndef(zcu)) return ptr_val; + var ptr = zcu.intern_pool.indexToKey(ptr_val.toIntern()).ptr; + ptr.ty = new_ty.toIntern(); + ptr.byte_offset += byte_off; + return Value.fromInterned(try zcu.intern(.{ .ptr = ptr })); +} + +pub const PointerDeriveStep = union(enum) { + int: struct { + addr: u64, + ptr_ty: Type, + }, + decl_ptr: InternPool.DeclIndex, + anon_decl_ptr: InternPool.Key.Ptr.BaseAddr.AnonDecl, + comptime_alloc_ptr: struct { + val: Value, + ptr_ty: Type, + }, + comptime_field_ptr: Value, + eu_payload_ptr: struct { + parent: *PointerDeriveStep, + /// This type will never be cast: it is provided for convenience. + result_ptr_ty: Type, + }, + opt_payload_ptr: struct { + parent: *PointerDeriveStep, + /// This type will never be cast: it is provided for convenience. + result_ptr_ty: Type, + }, + field_ptr: struct { + parent: *PointerDeriveStep, + field_idx: u32, + /// This type will never be cast: it is provided for convenience. + result_ptr_ty: Type, + }, + elem_ptr: struct { + parent: *PointerDeriveStep, + elem_idx: u64, + /// This type will never be cast: it is provided for convenience. + result_ptr_ty: Type, + }, + offset_and_cast: struct { + parent: *PointerDeriveStep, + byte_offset: u64, + new_ptr_ty: Type, + }, + + pub fn ptrType(step: PointerDeriveStep, zcu: *Zcu) !Type { + return switch (step) { + .int => |int| int.ptr_ty, + .decl_ptr => |decl| try zcu.declPtr(decl).declPtrType(zcu), + .anon_decl_ptr => |ad| Type.fromInterned(ad.orig_ty), + .comptime_alloc_ptr => |info| info.ptr_ty, + .comptime_field_ptr => |val| try zcu.singleConstPtrType(val.typeOf(zcu)), + .offset_and_cast => |oac| oac.new_ptr_ty, + inline .eu_payload_ptr, .opt_payload_ptr, .field_ptr, .elem_ptr => |x| x.result_ptr_ty, + }; + } +}; + +pub fn pointerDerivation(ptr_val: Value, arena: Allocator, zcu: *Zcu) Allocator.Error!PointerDeriveStep { + return ptr_val.pointerDerivationAdvanced(arena, zcu, null) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.AnalysisFail, + error.NeededSourceLocation, + error.GenericPoison, + error.ComptimeReturn, + error.ComptimeBreak, + => unreachable, + }; +} + +/// Given a pointer value, get the sequence of steps to derive it, ideally by taking +/// only field and element pointers with no casts. This can be used by codegen backends +/// which prefer field/elem accesses when lowering constant pointer values. +/// It is also used by the Value printing logic for pointers. +pub fn pointerDerivationAdvanced(ptr_val: Value, arena: Allocator, zcu: *Zcu, opt_sema: ?*Sema) !PointerDeriveStep { + const ptr = zcu.intern_pool.indexToKey(ptr_val.toIntern()).ptr; + const base_derive: PointerDeriveStep = switch (ptr.base_addr) { + .int => return .{ .int = .{ + .addr = ptr.byte_offset, + .ptr_ty = Type.fromInterned(ptr.ty), + } }, + .decl => |decl| .{ .decl_ptr = decl }, + .anon_decl => |ad| base: { + // A slight tweak: `orig_ty` here is sometimes not `const`, but it ought to be. + // TODO: fix this in the sites interning anon decls! + const const_ty = try zcu.ptrType(info: { + var info = Type.fromInterned(ad.orig_ty).ptrInfo(zcu); + info.flags.is_const = true; + break :info info; + }); + break :base .{ .anon_decl_ptr = .{ + .val = ad.val, + .orig_ty = const_ty.toIntern(), + } }; + }, + .comptime_alloc => |idx| base: { + const alloc = opt_sema.?.getComptimeAlloc(idx); + const val = try alloc.val.intern(zcu, opt_sema.?.arena); + const ty = val.typeOf(zcu); + break :base .{ .comptime_alloc_ptr = .{ + .val = val, + .ptr_ty = try zcu.ptrType(.{ + .child = ty.toIntern(), + .flags = .{ + .alignment = alloc.alignment, + }, + }), + } }; + }, + .comptime_field => |val| .{ .comptime_field_ptr = Value.fromInterned(val) }, + .eu_payload => |eu_ptr| base: { + const base_ptr = Value.fromInterned(eu_ptr); + const base_ptr_ty = base_ptr.typeOf(zcu); + const parent_step = try arena.create(PointerDeriveStep); + parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(eu_ptr), arena, zcu, opt_sema); + break :base .{ .eu_payload_ptr = .{ + .parent = parent_step, + .result_ptr_ty = try zcu.adjustPtrTypeChild(base_ptr_ty, base_ptr_ty.childType(zcu).errorUnionPayload(zcu)), + } }; + }, + .opt_payload => |opt_ptr| base: { + const base_ptr = Value.fromInterned(opt_ptr); + const base_ptr_ty = base_ptr.typeOf(zcu); + const parent_step = try arena.create(PointerDeriveStep); + parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(opt_ptr), arena, zcu, opt_sema); + break :base .{ .opt_payload_ptr = .{ + .parent = parent_step, + .result_ptr_ty = try zcu.adjustPtrTypeChild(base_ptr_ty, base_ptr_ty.childType(zcu).optionalChild(zcu)), + } }; + }, + .field => |field| base: { + const base_ptr = Value.fromInterned(field.base); + const base_ptr_ty = base_ptr.typeOf(zcu); + const agg_ty = base_ptr_ty.childType(zcu); + const field_ty, const field_align = switch (agg_ty.zigTypeTag(zcu)) { + .Struct => .{ agg_ty.structFieldType(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), zcu, opt_sema) }, + .Union => .{ agg_ty.unionFieldTypeByIndex(@intCast(field.index), zcu), try agg_ty.structFieldAlignAdvanced(@intCast(field.index), zcu, opt_sema) }, + .Pointer => .{ switch (field.index) { + Value.slice_ptr_index => agg_ty.slicePtrFieldType(zcu), + Value.slice_len_index => Type.usize, + else => unreachable, + }, Type.usize.abiAlignment(zcu) }, + else => unreachable, + }; + const base_align = base_ptr_ty.ptrAlignment(zcu); + const result_align = field_align.minStrict(base_align); + const result_ty = try zcu.ptrType(.{ + .child = field_ty.toIntern(), + .flags = flags: { + var flags = base_ptr_ty.ptrInfo(zcu).flags; + if (result_align == field_ty.abiAlignment(zcu)) { + flags.alignment = .none; + } else { + flags.alignment = result_align; + } + break :flags flags; + }, + }); + const parent_step = try arena.create(PointerDeriveStep); + parent_step.* = try pointerDerivationAdvanced(base_ptr, arena, zcu, opt_sema); + break :base .{ .field_ptr = .{ + .parent = parent_step, + .field_idx = @intCast(field.index), + .result_ptr_ty = result_ty, + } }; + }, + .arr_elem => |arr_elem| base: { + const parent_step = try arena.create(PointerDeriveStep); + parent_step.* = try pointerDerivationAdvanced(Value.fromInterned(arr_elem.base), arena, zcu, opt_sema); + const parent_ptr_info = (try parent_step.ptrType(zcu)).ptrInfo(zcu); + const result_ptr_ty = try zcu.ptrType(.{ + .child = parent_ptr_info.child, + .flags = flags: { + var flags = parent_ptr_info.flags; + flags.size = .One; + break :flags flags; + }, + }); + break :base .{ .elem_ptr = .{ + .parent = parent_step, + .elem_idx = arr_elem.index, + .result_ptr_ty = result_ptr_ty, + } }; + }, + }; + + if (ptr.byte_offset == 0 and ptr.ty == (try base_derive.ptrType(zcu)).toIntern()) { + return base_derive; + } + + const need_child = Type.fromInterned(ptr.ty).childType(zcu); + if (need_child.comptimeOnly(zcu)) { + // No refinement can happen - this pointer is presumably invalid. + // Just offset it. + const parent = try arena.create(PointerDeriveStep); + parent.* = base_derive; + return .{ .offset_and_cast = .{ + .parent = parent, + .byte_offset = ptr.byte_offset, + .new_ptr_ty = Type.fromInterned(ptr.ty), + } }; + } + const need_bytes = need_child.abiSize(zcu); + + var cur_derive = base_derive; + var cur_offset = ptr.byte_offset; + + // Refine through fields and array elements as much as possible. + + if (need_bytes > 0) while (true) { + const cur_ty = (try cur_derive.ptrType(zcu)).childType(zcu); + if (cur_ty.toIntern() == need_child.toIntern() and cur_offset == 0) { + break; + } + switch (cur_ty.zigTypeTag(zcu)) { + .NoReturn, + .Type, + .ComptimeInt, + .ComptimeFloat, + .Null, + .Undefined, + .EnumLiteral, + .Opaque, + .Fn, + .ErrorUnion, + .Int, + .Float, + .Bool, + .Void, + .Pointer, + .ErrorSet, + .AnyFrame, + .Frame, + .Enum, + .Vector, + .Optional, + .Union, + => break, + + .Array => { + const elem_ty = cur_ty.childType(zcu); + const elem_size = elem_ty.abiSize(zcu); + const start_idx = cur_offset / elem_size; + const end_idx = (cur_offset + need_bytes + elem_size - 1) / elem_size; + if (end_idx == start_idx + 1) { + const parent = try arena.create(PointerDeriveStep); + parent.* = cur_derive; + cur_derive = .{ .elem_ptr = .{ + .parent = parent, + .elem_idx = start_idx, + .result_ptr_ty = try zcu.adjustPtrTypeChild(try parent.ptrType(zcu), elem_ty), + } }; + cur_offset -= start_idx * elem_size; + } else { + // Go into the first element if needed, but don't go any deeper. + if (start_idx > 0) { + const parent = try arena.create(PointerDeriveStep); + parent.* = cur_derive; + cur_derive = .{ .elem_ptr = .{ + .parent = parent, + .elem_idx = start_idx, + .result_ptr_ty = try zcu.adjustPtrTypeChild(try parent.ptrType(zcu), elem_ty), + } }; + cur_offset -= start_idx * elem_size; + } + break; + } + }, + .Struct => switch (cur_ty.containerLayout(zcu)) { + .auto, .@"packed" => break, + .@"extern" => for (0..cur_ty.structFieldCount(zcu)) |field_idx| { + const field_ty = cur_ty.structFieldType(field_idx, zcu); + const start_off = cur_ty.structFieldOffset(field_idx, zcu); + const end_off = start_off + field_ty.abiSize(zcu); + if (cur_offset >= start_off and cur_offset + need_bytes <= end_off) { + const old_ptr_ty = try cur_derive.ptrType(zcu); + const parent_align = old_ptr_ty.ptrAlignment(zcu); + const field_align = InternPool.Alignment.fromLog2Units(@min(parent_align.toLog2Units(), @ctz(start_off))); + const parent = try arena.create(PointerDeriveStep); + parent.* = cur_derive; + const new_ptr_ty = try zcu.ptrType(.{ + .child = field_ty.toIntern(), + .flags = flags: { + var flags = old_ptr_ty.ptrInfo(zcu).flags; + if (field_align == field_ty.abiAlignment(zcu)) { + flags.alignment = .none; + } else { + flags.alignment = field_align; + } + break :flags flags; + }, + }); + cur_derive = .{ .field_ptr = .{ + .parent = parent, + .field_idx = @intCast(field_idx), + .result_ptr_ty = new_ptr_ty, + } }; + cur_offset -= start_off; + break; + } + } else break, // pointer spans multiple fields + }, + } + }; + + if (cur_offset == 0 and (try cur_derive.ptrType(zcu)).toIntern() == ptr.ty) { + return cur_derive; + } + + const parent = try arena.create(PointerDeriveStep); + parent.* = cur_derive; + return .{ .offset_and_cast = .{ + .parent = parent, + .byte_offset = cur_offset, + .new_ptr_ty = Type.fromInterned(ptr.ty), + } }; +} diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 83159ec80e7d..fe94c061365f 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2206,7 +2206,7 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif ); break :blk extern_func.decl; } else switch (mod.intern_pool.indexToKey(func_val.ip_index)) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| { _ = try func.bin_file.getOrCreateAtomForDecl(decl); break :blk decl; @@ -3058,72 +3058,59 @@ fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { return WValue{ .stack = {} }; } -fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue { - const mod = func.bin_file.base.comp.module.?; - const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr; - switch (ptr.addr) { - .decl => |decl_index| { - return func.lowerParentPtrDecl(ptr_val, decl_index, offset); - }, - .anon_decl => |ad| return func.lowerAnonDeclRef(ad, offset), - .eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}), - .int => |base| return func.lowerConstant(Value.fromInterned(base), Type.usize), - .opt_payload => |base_ptr| return func.lowerParentPtr(Value.fromInterned(base_ptr), offset), - .comptime_field, .comptime_alloc => unreachable, - .elem => |elem| { - const index = elem.index; - const elem_type = Type.fromInterned(mod.intern_pool.typeOf(elem.base)).elemType2(mod); - const elem_offset = index * elem_type.abiSize(mod); - return func.lowerParentPtr(Value.fromInterned(elem.base), @as(u32, @intCast(elem_offset + offset))); - }, +fn lowerPtr(func: *CodeGen, ptr_val: InternPool.Index, prev_offset: u64) InnerError!WValue { + const zcu = func.bin_file.base.comp.module.?; + const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr; + const offset: u64 = prev_offset + ptr.byte_offset; + return switch (ptr.base_addr) { + .decl => |decl| return func.lowerDeclRefValue(decl, @intCast(offset)), + .anon_decl => |ad| return func.lowerAnonDeclRef(ad, @intCast(offset)), + .int => return func.lowerConstant(try zcu.intValue(Type.usize, offset), Type.usize), + .eu_payload => return func.fail("Wasm TODO: lower error union payload pointer", .{}), + .opt_payload => |opt_ptr| return func.lowerPtr(opt_ptr, offset), .field => |field| { - const parent_ptr_ty = Type.fromInterned(mod.intern_pool.typeOf(field.base)); - const parent_ty = parent_ptr_ty.childType(mod); - const field_index: u32 = @intCast(field.index); - - const field_offset = switch (parent_ty.zigTypeTag(mod)) { - .Struct => blk: { - if (mod.typeToPackedStruct(parent_ty)) |struct_type| { - if (Type.fromInterned(ptr.ty).ptrInfo(mod).packed_offset.host_size == 0) - break :blk @divExact(mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_ty.ptrInfo(mod).packed_offset.bit_offset, 8) - else - break :blk 0; - } - break :blk parent_ty.structFieldOffset(field_index, mod); + const base_ptr = Value.fromInterned(field.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + const field_off: u64 = switch (base_ty.zigTypeTag(zcu)) { + .Pointer => off: { + assert(base_ty.isSlice(zcu)); + break :off switch (field.index) { + Value.slice_ptr_index => 0, + Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8), + else => unreachable, + }; }, - .Union => switch (parent_ty.containerLayout(mod)) { - .@"packed" => 0, - else => blk: { - const layout: Module.UnionLayout = parent_ty.unionGetLayout(mod); - if (layout.payload_size == 0) break :blk 0; - if (layout.payload_align.compare(.gt, layout.tag_align)) break :blk 0; - - // tag is stored first so calculate offset from where payload starts - break :blk layout.tag_align.forward(layout.tag_size); - }, + .Struct => switch (base_ty.containerLayout(zcu)) { + .auto => base_ty.structFieldOffset(@intCast(field.index), zcu), + .@"extern", .@"packed" => unreachable, }, - .Pointer => switch (parent_ty.ptrSize(mod)) { - .Slice => switch (field.index) { - 0 => 0, - 1 => func.ptrSize(), - else => unreachable, + .Union => switch (base_ty.containerLayout(zcu)) { + .auto => off: { + // Keep in sync with the `un` case of `generateSymbol`. + const layout = base_ty.unionGetLayout(zcu); + if (layout.payload_size == 0) break :off 0; + if (layout.tag_size == 0) break :off 0; + if (layout.tag_align.compare(.gte, layout.payload_align)) { + // Tag first. + break :off layout.tag_size; + } else { + // Payload first. + break :off 0; + } }, - else => unreachable, + .@"extern", .@"packed" => unreachable, }, else => unreachable, }; - return func.lowerParentPtr(Value.fromInterned(field.base), @as(u32, @intCast(offset + field_offset))); + return func.lowerPtr(field.base, offset + field_off); }, - } -} - -fn lowerParentPtrDecl(func: *CodeGen, ptr_val: Value, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue { - return func.lowerDeclRefValue(ptr_val, decl_index, offset); + .arr_elem, .comptime_field, .comptime_alloc => unreachable, + }; } fn lowerAnonDeclRef( func: *CodeGen, - anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, + anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl, offset: u32, ) InnerError!WValue { const mod = func.bin_file.base.comp.module.?; @@ -3153,7 +3140,7 @@ fn lowerAnonDeclRef( } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; } -fn lowerDeclRefValue(func: *CodeGen, val: Value, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue { +fn lowerDeclRefValue(func: *CodeGen, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue { const mod = func.bin_file.base.comp.module.?; const decl = mod.declPtr(decl_index); @@ -3161,11 +3148,11 @@ fn lowerDeclRefValue(func: *CodeGen, val: Value, decl_index: InternPool.DeclInde // want to lower the actual decl, rather than the alias itself. if (decl.val.getFunction(mod)) |func_val| { if (func_val.owner_decl != decl_index) { - return func.lowerDeclRefValue(val, func_val.owner_decl, offset); + return func.lowerDeclRefValue(func_val.owner_decl, offset); } } else if (decl.val.getExternFunc(mod)) |func_val| { if (func_val.decl != decl_index) { - return func.lowerDeclRefValue(val, func_val.decl, offset); + return func.lowerDeclRefValue(func_val.decl, offset); } } const decl_ty = decl.typeOf(mod); @@ -3309,23 +3296,16 @@ fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue { }, .slice => |slice| { var ptr = ip.indexToKey(slice.ptr).ptr; - const owner_decl = while (true) switch (ptr.addr) { + const owner_decl = while (true) switch (ptr.base_addr) { .decl => |decl| break 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, .comptime_alloc => unreachable, + .field => |base_index| ptr = ip.indexToKey(base_index.base).ptr, + .arr_elem, .comptime_field, .comptime_alloc => unreachable, }; return .{ .memory = try func.bin_file.lowerUnnamedConst(val, owner_decl) }; }, - .ptr => |ptr| switch (ptr.addr) { - .decl => |decl| return func.lowerDeclRefValue(val, decl, 0), - .int => |int| return func.lowerConstant(Value.fromInterned(int), Type.fromInterned(ip.typeOf(int))), - .opt_payload, .elem, .field => return func.lowerParentPtr(val, 0), - .anon_decl => |ad| return func.lowerAnonDeclRef(ad, 0), - .comptime_field, .comptime_alloc => unreachable, - else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}), - }, + .ptr => return func.lowerPtr(val.toIntern(), 0), .opt => if (ty.optionalReprIsPayload(mod)) { const pl_ty = ty.optionalChild(mod); if (val.optionalValue(mod)) |payload| { @@ -3435,7 +3415,10 @@ fn valueAsI32(func: *const CodeGen, val: Value, ty: Type) i32 { else => return switch (mod.intern_pool.indexToKey(val.ip_index)) { .enum_tag => |enum_tag| intIndexAsI32(&mod.intern_pool, enum_tag.int, mod), .int => |int| intStorageAsI32(int.storage, mod), - .ptr => |ptr| intIndexAsI32(&mod.intern_pool, ptr.addr.int, mod), + .ptr => |ptr| { + assert(ptr.base_addr == .int); + return @intCast(ptr.byte_offset); + }, .err => |err| @as(i32, @bitCast(@as(Module.ErrorInt, @intCast(mod.global_error_set.getIndex(err.name).?)))), else => unreachable, }, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c165baf7e885..e5ab82bf0d18 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -12249,10 +12249,10 @@ fn genCall(self: *Self, info: union(enum) { const func_key = mod.intern_pool.indexToKey(func_value.ip_index); switch (switch (func_key) { else => func_key, - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| mod.intern_pool.indexToKey(mod.declPtr(decl).val.toIntern()), else => func_key, - }, + } else func_key, }) { .func => |func| { if (self.bin_file.cast(link.File.Elf)) |elf_file| { @@ -17875,8 +17875,8 @@ fn airShuffle(self: *Self, inst: Air.Inst.Index) !void { break :result null; }) orelse return self.fail("TODO implement airShuffle from {} and {} to {} with {}", .{ - lhs_ty.fmt(mod), rhs_ty.fmt(mod), dst_ty.fmt(mod), - Value.fromInterned(extra.mask).fmtValue(mod), + lhs_ty.fmt(mod), rhs_ty.fmt(mod), dst_ty.fmt(mod), + Value.fromInterned(extra.mask).fmtValue(mod, null), }); return self.finishAir(inst, result, .{ extra.a, extra.b, .none }); } diff --git a/src/codegen.zig b/src/codegen.zig index b45777564aae..72fd97589941 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -16,7 +16,8 @@ const Compilation = @import("Compilation.zig"); const ErrorMsg = Module.ErrorMsg; const InternPool = @import("InternPool.zig"); const Liveness = @import("Liveness.zig"); -const Module = @import("Module.zig"); +const Zcu = @import("Module.zig"); +const Module = Zcu; const Target = std.Target; const Type = @import("type.zig").Type; const Value = @import("Value.zig"); @@ -185,7 +186,7 @@ pub fn generateSymbol( const target = mod.getTarget(); const endian = target.cpu.arch.endian(); - log.debug("generateSymbol: val = {}", .{val.fmtValue(mod)}); + log.debug("generateSymbol: val = {}", .{val.fmtValue(mod, null)}); if (val.isUndefDeep(mod)) { const abi_size = math.cast(usize, ty.abiSize(mod)) orelse return error.Overflow; @@ -314,7 +315,7 @@ pub fn generateSymbol( }, .f128 => |f128_val| writeFloat(f128, f128_val, target, endian, try code.addManyAsArray(16)), }, - .ptr => switch (try lowerParentPtr(bin_file, src_loc, val.toIntern(), code, debug_output, reloc_info)) { + .ptr => switch (try lowerPtr(bin_file, src_loc, val.toIntern(), code, debug_output, reloc_info, 0)) { .ok => {}, .fail => |em| return .{ .fail = em }, }, @@ -614,111 +615,79 @@ pub fn generateSymbol( return .ok; } -fn lowerParentPtr( +fn lowerPtr( bin_file: *link.File, src_loc: Module.SrcLoc, - parent_ptr: InternPool.Index, + ptr_val: InternPool.Index, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, reloc_info: RelocInfo, + prev_offset: u64, ) CodeGenError!Result { - const mod = bin_file.comp.module.?; - const ip = &mod.intern_pool; - const ptr = ip.indexToKey(parent_ptr).ptr; - return switch (ptr.addr) { - .decl => |decl| try lowerDeclRef(bin_file, src_loc, decl, code, debug_output, reloc_info), - .anon_decl => |ad| try lowerAnonDeclRef(bin_file, src_loc, ad, code, debug_output, reloc_info), - .int => |int| try generateSymbol(bin_file, src_loc, Value.fromInterned(int), code, debug_output, reloc_info), - .eu_payload => |eu_payload| try lowerParentPtr( + const zcu = bin_file.comp.module.?; + const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr; + const offset: u64 = prev_offset + ptr.byte_offset; + return switch (ptr.base_addr) { + .decl => |decl| try lowerDeclRef(bin_file, src_loc, decl, code, debug_output, reloc_info, offset), + .anon_decl => |ad| try lowerAnonDeclRef(bin_file, src_loc, ad, code, debug_output, reloc_info, offset), + .int => try generateSymbol(bin_file, src_loc, try zcu.intValue(Type.usize, offset), code, debug_output, reloc_info), + .eu_payload => |eu_ptr| try lowerPtr( bin_file, src_loc, - eu_payload, - code, - debug_output, - reloc_info.offset(@intCast(errUnionPayloadOffset( - Type.fromInterned(ip.typeOf(eu_payload)), - mod, - ))), - ), - .opt_payload => |opt_payload| try lowerParentPtr( - bin_file, - src_loc, - opt_payload, + eu_ptr, code, debug_output, reloc_info, + offset + errUnionPayloadOffset( + Value.fromInterned(eu_ptr).typeOf(zcu).childType(zcu).errorUnionPayload(zcu), + zcu, + ), ), - .elem => |elem| try lowerParentPtr( + .opt_payload => |opt_ptr| try lowerPtr( bin_file, src_loc, - elem.base, + opt_ptr, code, debug_output, - reloc_info.offset(@intCast(elem.index * - Type.fromInterned(ip.typeOf(elem.base)).elemType2(mod).abiSize(mod))), + reloc_info, + offset, ), .field => |field| { - const base_ptr_ty = ip.typeOf(field.base); - const base_ty = ip.indexToKey(base_ptr_ty).ptr_type.child; - return lowerParentPtr( - bin_file, - src_loc, - field.base, - code, - debug_output, - reloc_info.offset(switch (ip.indexToKey(base_ty)) { - .ptr_type => |ptr_type| switch (ptr_type.flags.size) { - .One, .Many, .C => unreachable, - .Slice => switch (field.index) { - 0 => 0, - 1 => @divExact(mod.getTarget().ptrBitWidth(), 8), - else => unreachable, - }, - }, - .struct_type, - .anon_struct_type, - .union_type, - => switch (Type.fromInterned(base_ty).containerLayout(mod)) { - .auto, .@"extern" => @intCast(Type.fromInterned(base_ty).structFieldOffset( - @intCast(field.index), - mod, - )), - .@"packed" => if (mod.typeToStruct(Type.fromInterned(base_ty))) |struct_obj| - if (Type.fromInterned(ptr.ty).ptrInfo(mod).packed_offset.host_size == 0) - @divExact(Type.fromInterned(base_ptr_ty).ptrInfo(mod) - .packed_offset.bit_offset + mod.structPackedFieldBitOffset( - struct_obj, - @intCast(field.index), - ), 8) - else - 0 - else - 0, - }, - else => unreachable, - }), - ); + const base_ptr = Value.fromInterned(field.base); + const base_ty = base_ptr.typeOf(zcu).childType(zcu); + const field_off: u64 = switch (base_ty.zigTypeTag(zcu)) { + .Pointer => off: { + assert(base_ty.isSlice(zcu)); + break :off switch (field.index) { + Value.slice_ptr_index => 0, + Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8), + else => unreachable, + }; + }, + .Struct, .Union => switch (base_ty.containerLayout(zcu)) { + .auto => base_ty.structFieldOffset(@intCast(field.index), zcu), + .@"extern", .@"packed" => unreachable, + }, + else => unreachable, + }; + return lowerPtr(bin_file, src_loc, field.base, code, debug_output, reloc_info, offset + field_off); }, - .comptime_field, .comptime_alloc => unreachable, + .arr_elem, .comptime_field, .comptime_alloc => unreachable, }; } const RelocInfo = struct { parent_atom_index: u32, - addend: ?u32 = null, - - fn offset(ri: RelocInfo, addend: u32) RelocInfo { - return .{ .parent_atom_index = ri.parent_atom_index, .addend = (ri.addend orelse 0) + addend }; - } }; fn lowerAnonDeclRef( lf: *link.File, src_loc: Module.SrcLoc, - anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, + anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, reloc_info: RelocInfo, + offset: u64, ) CodeGenError!Result { _ = debug_output; const zcu = lf.comp.module.?; @@ -745,7 +714,7 @@ fn lowerAnonDeclRef( const vaddr = try lf.getAnonDeclVAddr(decl_val, .{ .parent_atom_index = reloc_info.parent_atom_index, .offset = code.items.len, - .addend = reloc_info.addend orelse 0, + .addend = @intCast(offset), }); const endian = target.cpu.arch.endian(); switch (ptr_width_bytes) { @@ -765,6 +734,7 @@ fn lowerDeclRef( code: *std.ArrayList(u8), debug_output: DebugInfoOutput, reloc_info: RelocInfo, + offset: u64, ) CodeGenError!Result { _ = src_loc; _ = debug_output; @@ -783,7 +753,7 @@ fn lowerDeclRef( const vaddr = try lf.getDeclVAddr(decl_index, .{ .parent_atom_index = reloc_info.parent_atom_index, .offset = code.items.len, - .addend = reloc_info.addend orelse 0, + .addend = @intCast(offset), }); const endian = target.cpu.arch.endian(); switch (ptr_width) { @@ -861,7 +831,7 @@ fn genDeclRef( const zcu = lf.comp.module.?; const ip = &zcu.intern_pool; const ty = val.typeOf(zcu); - log.debug("genDeclRef: val = {}", .{val.fmtValue(zcu)}); + log.debug("genDeclRef: val = {}", .{val.fmtValue(zcu, null)}); const ptr_decl = zcu.declPtr(ptr_decl_index); const namespace = zcu.namespacePtr(ptr_decl.src_namespace); @@ -966,7 +936,7 @@ fn genUnnamedConst( ) CodeGenError!GenResult { const zcu = lf.comp.module.?; const gpa = lf.comp.gpa; - log.debug("genUnnamedConst: val = {}", .{val.fmtValue(zcu)}); + log.debug("genUnnamedConst: val = {}", .{val.fmtValue(zcu, null)}); const local_sym_index = lf.lowerUnnamedConst(val, owner_decl_index) catch |err| { return GenResult.fail(gpa, src_loc, "lowering unnamed constant failed: {s}", .{@errorName(err)}); @@ -1007,7 +977,7 @@ pub fn genTypedValue( const ip = &zcu.intern_pool; const ty = val.typeOf(zcu); - log.debug("genTypedValue: val = {}", .{val.fmtValue(zcu)}); + log.debug("genTypedValue: val = {}", .{val.fmtValue(zcu, null)}); if (val.isUndef(zcu)) return GenResult.mcv(.undef); @@ -1018,7 +988,7 @@ pub fn genTypedValue( const ptr_bits = target.ptrBitWidth(); if (!ty.isSlice(zcu)) switch (ip.indexToKey(val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| return genDeclRef(lf, src_loc, val, decl), else => {}, }, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 818267a8b819..9e782b34ed0f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -646,8 +646,7 @@ pub const DeclGen = struct { fn renderAnonDeclValue( dg: *DeclGen, writer: anytype, - ptr_val: Value, - anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, + anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl, location: ValueRenderLocation, ) error{ OutOfMemory, AnalysisFail }!void { const zcu = dg.zcu; @@ -657,16 +656,16 @@ pub const DeclGen = struct { const decl_ty = decl_val.typeOf(zcu); // Render an undefined pointer if we have a pointer to a zero-bit or comptime type. - const ptr_ty = ptr_val.typeOf(zcu); + const ptr_ty = Type.fromInterned(anon_decl.orig_ty); if (ptr_ty.isPtrAtRuntime(zcu) and !decl_ty.isFnOrHasRuntimeBits(zcu)) { return dg.writeCValue(writer, .{ .undef = ptr_ty }); } // Chase function values in order to be able to reference the original function. if (decl_val.getFunction(zcu)) |func| - return dg.renderDeclValue(writer, ptr_val, func.owner_decl, location); + return dg.renderDeclValue(writer, func.owner_decl, location); if (decl_val.getExternFunc(zcu)) |extern_func| - return dg.renderDeclValue(writer, ptr_val, extern_func.decl, location); + return dg.renderDeclValue(writer, extern_func.decl, location); assert(decl_val.getVariable(zcu) == null); @@ -712,7 +711,6 @@ pub const DeclGen = struct { fn renderDeclValue( dg: *DeclGen, writer: anytype, - val: Value, decl_index: InternPool.DeclIndex, location: ValueRenderLocation, ) error{ OutOfMemory, AnalysisFail }!void { @@ -722,17 +720,17 @@ pub const DeclGen = struct { assert(decl.has_tv); // Render an undefined pointer if we have a pointer to a zero-bit or comptime type. - const ty = val.typeOf(zcu); const decl_ty = decl.typeOf(zcu); - if (ty.isPtrAtRuntime(zcu) and !decl_ty.isFnOrHasRuntimeBits(zcu)) { - return dg.writeCValue(writer, .{ .undef = ty }); + const ptr_ty = try decl.declPtrType(zcu); + if (!decl_ty.isFnOrHasRuntimeBits(zcu)) { + return dg.writeCValue(writer, .{ .undef = ptr_ty }); } // Chase function values in order to be able to reference the original function. if (decl.val.getFunction(zcu)) |func| if (func.owner_decl != decl_index) - return dg.renderDeclValue(writer, val, func.owner_decl, location); + return dg.renderDeclValue(writer, func.owner_decl, location); if (decl.val.getExternFunc(zcu)) |extern_func| if (extern_func.decl != decl_index) - return dg.renderDeclValue(writer, val, extern_func.decl, location); + return dg.renderDeclValue(writer, extern_func.decl, location); if (decl.val.getVariable(zcu)) |variable| try dg.renderFwdDecl(decl_index, variable, .tentative); @@ -740,7 +738,7 @@ pub const DeclGen = struct { // them). The analysis until now should ensure that the C function // pointers are compatible. If they are not, then there is a bug // somewhere and we should let the C compiler tell us about it. - const ctype = try dg.ctypeFromType(ty, .complete); + const ctype = try dg.ctypeFromType(ptr_ty, .complete); const elem_ctype = ctype.info(ctype_pool).pointer.elem_ctype; const decl_ctype = try dg.ctypeFromType(decl_ty, .complete); const need_cast = !elem_ctype.eql(decl_ctype) and @@ -755,125 +753,92 @@ pub const DeclGen = struct { if (need_cast) try writer.writeByte(')'); } - /// Renders a "parent" pointer by recursing to the root decl/variable - /// that its contents are defined with respect to. - fn renderParentPtr( + fn renderPointer( dg: *DeclGen, writer: anytype, - ptr_val: InternPool.Index, + derivation: Value.PointerDeriveStep, location: ValueRenderLocation, ) error{ OutOfMemory, AnalysisFail }!void { const zcu = dg.zcu; - const ip = &zcu.intern_pool; - const ptr_ty = Type.fromInterned(ip.typeOf(ptr_val)); - const ptr_ctype = try dg.ctypeFromType(ptr_ty, .complete); - const ptr_child_ctype = ptr_ctype.info(&dg.ctype_pool).pointer.elem_ctype; - const ptr = ip.indexToKey(ptr_val).ptr; - switch (ptr.addr) { - .decl => |d| try dg.renderDeclValue(writer, Value.fromInterned(ptr_val), d, location), - .anon_decl => |anon_decl| try dg.renderAnonDeclValue(writer, Value.fromInterned(ptr_val), anon_decl, location), + switch (derivation) { + .comptime_alloc_ptr, .comptime_field_ptr => unreachable, .int => |int| { + const ptr_ctype = try dg.ctypeFromType(int.ptr_ty, .complete); + const addr_val = try zcu.intValue(Type.usize, int.addr); try writer.writeByte('('); try dg.renderCType(writer, ptr_ctype); - try writer.print("){x}", .{try dg.fmtIntLiteral(Value.fromInterned(int), .Other)}); + try writer.print("){x}", .{try dg.fmtIntLiteral(addr_val, .Other)}); }, - .eu_payload, .opt_payload => |base| { - const ptr_base_ty = Type.fromInterned(ip.typeOf(base)); - const base_ty = ptr_base_ty.childType(zcu); - // Ensure complete type definition is visible before accessing fields. - _ = try dg.ctypeFromType(base_ty, .complete); - const payload_ty = switch (ptr.addr) { - .eu_payload => base_ty.errorUnionPayload(zcu), - .opt_payload => base_ty.optionalChild(zcu), - else => unreachable, - }; - const payload_ctype = try dg.ctypeFromType(payload_ty, .forward); - if (!ptr_child_ctype.eql(payload_ctype)) { - try writer.writeByte('('); - try dg.renderCType(writer, ptr_ctype); - try writer.writeByte(')'); - } + + .decl_ptr => |decl| try dg.renderDeclValue(writer, decl, location), + .anon_decl_ptr => |ad| try dg.renderAnonDeclValue(writer, ad, location), + + inline .eu_payload_ptr, .opt_payload_ptr => |info| { try writer.writeAll("&("); - try dg.renderParentPtr(writer, base, location); + try dg.renderPointer(writer, info.parent.*, location); try writer.writeAll(")->payload"); }, - .elem => |elem| { - const ptr_base_ty = Type.fromInterned(ip.typeOf(elem.base)); - const elem_ty = ptr_base_ty.elemType2(zcu); - const elem_ctype = try dg.ctypeFromType(elem_ty, .forward); - if (!ptr_child_ctype.eql(elem_ctype)) { - try writer.writeByte('('); - try dg.renderCType(writer, ptr_ctype); - try writer.writeByte(')'); - } - try writer.writeAll("&("); - if (ip.indexToKey(ptr_base_ty.toIntern()).ptr_type.flags.size == .One) - try writer.writeByte('*'); - try dg.renderParentPtr(writer, elem.base, location); - try writer.print(")[{d}]", .{elem.index}); - }, - .field => |field| { - const ptr_base_ty = Type.fromInterned(ip.typeOf(field.base)); - const base_ty = ptr_base_ty.childType(zcu); + + .field_ptr => |field| { + const parent_ptr_ty = try field.parent.ptrType(zcu); + // Ensure complete type definition is available before accessing fields. - _ = try dg.ctypeFromType(base_ty, .complete); - switch (fieldLocation(ptr_base_ty, ptr_ty, @as(u32, @intCast(field.index)), zcu)) { + _ = try dg.ctypeFromType(parent_ptr_ty.childType(zcu), .complete); + + switch (fieldLocation(parent_ptr_ty, field.result_ptr_ty, field.field_idx, zcu)) { .begin => { - const ptr_base_ctype = try dg.ctypeFromType(ptr_base_ty, .complete); - if (!ptr_ctype.eql(ptr_base_ctype)) { - try writer.writeByte('('); - try dg.renderCType(writer, ptr_ctype); - try writer.writeByte(')'); - } - try dg.renderParentPtr(writer, field.base, location); + const ptr_ctype = try dg.ctypeFromType(field.result_ptr_ty, .complete); + try writer.writeByte('('); + try dg.renderCType(writer, ptr_ctype); + try writer.writeByte(')'); + try dg.renderPointer(writer, field.parent.*, location); }, .field => |name| { - const field_ty = switch (ip.indexToKey(base_ty.toIntern())) { - .anon_struct_type, - .struct_type, - .union_type, - => base_ty.structFieldType(@as(usize, @intCast(field.index)), zcu), - .ptr_type => |ptr_type| switch (ptr_type.flags.size) { - .One, .Many, .C => unreachable, - .Slice => switch (field.index) { - Value.slice_ptr_index => base_ty.slicePtrFieldType(zcu), - Value.slice_len_index => Type.usize, - else => unreachable, - }, - }, - else => unreachable, - }; - const field_ctype = try dg.ctypeFromType(field_ty, .forward); - if (!ptr_child_ctype.eql(field_ctype)) { - try writer.writeByte('('); - try dg.renderCType(writer, ptr_ctype); - try writer.writeByte(')'); - } try writer.writeAll("&("); - try dg.renderParentPtr(writer, field.base, location); + try dg.renderPointer(writer, field.parent.*, location); try writer.writeAll(")->"); try dg.writeCValue(writer, name); }, .byte_offset => |byte_offset| { - const u8_ptr_ty = try zcu.adjustPtrTypeChild(ptr_ty, Type.u8); - const u8_ptr_ctype = try dg.ctypeFromType(u8_ptr_ty, .complete); - - if (!ptr_ctype.eql(u8_ptr_ctype)) { - try writer.writeByte('('); - try dg.renderCType(writer, ptr_ctype); - try writer.writeByte(')'); - } - try writer.writeAll("(("); - try dg.renderCType(writer, u8_ptr_ctype); + const ptr_ctype = try dg.ctypeFromType(field.result_ptr_ty, .complete); + try writer.writeByte('('); + try dg.renderCType(writer, ptr_ctype); try writer.writeByte(')'); - try dg.renderParentPtr(writer, field.base, location); - try writer.print(" + {})", .{ - try dg.fmtIntLiteral(try zcu.intValue(Type.usize, byte_offset), .Other), - }); + const offset_val = try zcu.intValue(Type.usize, byte_offset); + try writer.writeAll("((char *)"); + try dg.renderPointer(writer, field.parent.*, location); + try writer.print(" + {})", .{try dg.fmtIntLiteral(offset_val, .Other)}); }, } }, - .comptime_field, .comptime_alloc => unreachable, + + .elem_ptr => |elem| if ((try elem.parent.ptrType(zcu)).childType(zcu).hasRuntimeBits(zcu)) { + const index_val = try zcu.intValue(Type.usize, elem.elem_idx); + try writer.writeByte('('); + try dg.renderPointer(writer, elem.parent.*, location); + try writer.print(" + {})", .{try dg.fmtIntLiteral(index_val, .Other)}); + } else { + const ptr_ctype = try dg.ctypeFromType(elem.result_ptr_ty, .complete); + try writer.writeByte('('); + try dg.renderCType(writer, ptr_ctype); + try writer.writeByte(')'); + try dg.renderPointer(writer, elem.parent.*, location); + }, + + .offset_and_cast => |oac| { + const ptr_ctype = try dg.ctypeFromType(oac.new_ptr_ty, .complete); + try writer.writeByte('('); + try dg.renderCType(writer, ptr_ctype); + try writer.writeByte(')'); + if (oac.byte_offset == 0) { + try dg.renderPointer(writer, oac.parent.*, location); + } else { + const offset_val = try zcu.intValue(Type.usize, oac.byte_offset); + try writer.writeAll("((char *)"); + try dg.renderPointer(writer, oac.parent.*, location); + try writer.print(" + {})", .{try dg.fmtIntLiteral(offset_val, .Other)}); + } + }, } } @@ -1103,20 +1068,11 @@ pub const DeclGen = struct { } try writer.writeByte('}'); }, - .ptr => |ptr| switch (ptr.addr) { - .decl => |d| try dg.renderDeclValue(writer, val, d, location), - .anon_decl => |decl_val| try dg.renderAnonDeclValue(writer, val, decl_val, location), - .int => |int| { - try writer.writeAll("(("); - try dg.renderCType(writer, ctype); - try writer.print("){x})", .{try dg.fmtIntLiteral(Value.fromInterned(int), location)}); - }, - .eu_payload, - .opt_payload, - .elem, - .field, - => try dg.renderParentPtr(writer, val.toIntern(), location), - .comptime_field, .comptime_alloc => unreachable, + .ptr => { + var arena = std.heap.ArenaAllocator.init(zcu.gpa); + defer arena.deinit(); + const derivation = try val.pointerDerivation(arena.allocator(), zcu); + try dg.renderPointer(writer, derivation, location); }, .opt => |opt| switch (ctype.info(ctype_pool)) { .basic => if (ctype.isBool()) try writer.writeAll(switch (opt.val) { @@ -4574,10 +4530,10 @@ fn airCall( break :fn_decl switch (zcu.intern_pool.indexToKey(callee_val.toIntern())) { .extern_func => |extern_func| extern_func.decl, .func => |func| func.owner_decl, - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => |decl| decl, else => break :known, - }, + } else break :known, else => break :known, }; }; @@ -5147,10 +5103,10 @@ fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool 'I' => !target.cpu.arch.isArmOrThumb(), else => switch (value) { .constant => |val| switch (f.object.dg.zcu.intern_pool.indexToKey(val.toIntern())) { - .ptr => |ptr| switch (ptr.addr) { + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .decl => false, else => true, - }, + } else true, else => true, }, else => false, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index db0eaa3ce5e6..8d2bca2f6cd6 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3262,6 +3262,7 @@ pub const Object = struct { try o.lowerType(Type.fromInterned(vector_type.child)), ), .opt_type => |child_ty| { + // Must stay in sync with `opt_payload` logic in `lowerPtr`. if (!Type.fromInterned(child_ty).hasRuntimeBitsIgnoreComptime(mod)) return .i8; const payload_ty = try o.lowerType(Type.fromInterned(child_ty)); @@ -3281,6 +3282,8 @@ pub const Object = struct { }, .anyframe_type => @panic("TODO implement lowerType for AnyFrame types"), .error_union_type => |error_union_type| { + // Must stay in sync with `codegen.errUnionPayloadOffset`. + // See logic in `lowerPtr`. const error_type = try o.errorIntType(); if (!Type.fromInterned(error_union_type.payload_type).hasRuntimeBitsIgnoreComptime(mod)) return error_type; @@ -3792,17 +3795,7 @@ pub const Object = struct { 128 => try o.builder.fp128Const(val.toFloat(f128, mod)), else => unreachable, }, - .ptr => |ptr| return switch (ptr.addr) { - .decl => |decl| try o.lowerDeclRefValue(ty, 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, .comptime_alloc => unreachable, - }, + .ptr => try o.lowerPtr(arg_val, 0), .slice => |slice| return o.builder.structConst(try o.lowerType(ty), &.{ try o.lowerValue(slice.ptr), try o.lowerValue(slice.len), @@ -4223,20 +4216,6 @@ pub const Object = struct { }; } - fn lowerIntAsPtr(o: *Object, val: InternPool.Index) Allocator.Error!Builder.Constant { - const mod = o.module; - switch (mod.intern_pool.indexToKey(val)) { - .undef => return o.builder.undefConst(.ptr), - .int => { - var bigint_space: Value.BigIntSpace = undefined; - const bigint = Value.fromInterned(val).toBigInt(&bigint_space, mod); - const llvm_int = try lowerBigInt(o, Type.usize, bigint); - return o.builder.castConst(.inttoptr, llvm_int, .ptr); - }, - else => unreachable, - } - } - fn lowerBigInt( o: *Object, ty: Type, @@ -4246,129 +4225,60 @@ pub const Object = struct { return o.builder.bigIntConst(try o.builder.intType(ty.intInfo(mod).bits), bigint); } - fn lowerParentPtrDecl(o: *Object, decl_index: InternPool.DeclIndex) Allocator.Error!Builder.Constant { - const mod = o.module; - const decl = mod.declPtr(decl_index); - const ptr_ty = try mod.singleMutPtrType(decl.typeOf(mod)); - return o.lowerDeclRefValue(ptr_ty, decl_index); - } - - fn lowerParentPtr(o: *Object, ptr_val: Value) Error!Builder.Constant { - const mod = o.module; - const ip = &mod.intern_pool; - const ptr = ip.indexToKey(ptr_val.toIntern()).ptr; - return switch (ptr.addr) { - .decl => |decl| try o.lowerParentPtrDecl(decl), - .anon_decl => |ad| try o.lowerAnonDeclRef(Type.fromInterned(ad.orig_ty), ad), - .int => |int| try o.lowerIntAsPtr(int), - .eu_payload => |eu_ptr| { - const parent_ptr = try o.lowerParentPtr(Value.fromInterned(eu_ptr)); - - const eu_ty = Type.fromInterned(ip.typeOf(eu_ptr)).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_ptr; - } - - const err_int_ty = try mod.errorIntType(); - const payload_align = payload_ty.abiAlignment(mod); - const err_align = err_int_ty.abiAlignment(mod); - const index: u32 = if (payload_align.compare(.gt, err_align)) 2 else 1; - return o.builder.gepConst(.inbounds, try o.lowerType(eu_ty), parent_ptr, null, &.{ - .@"0", try o.builder.intConst(.i32, index), + fn lowerPtr( + o: *Object, + ptr_val: InternPool.Index, + prev_offset: u64, + ) Error!Builder.Constant { + const zcu = o.module; + const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr; + const offset: u64 = prev_offset + ptr.byte_offset; + return switch (ptr.base_addr) { + .decl => |decl| { + const base_ptr = try o.lowerDeclRefValue(decl); + return o.builder.gepConst(.inbounds, .i8, base_ptr, null, &.{ + try o.builder.intConst(.i64, offset), }); }, - .opt_payload => |opt_ptr| { - const parent_ptr = try o.lowerParentPtr(Value.fromInterned(opt_ptr)); - - const opt_ty = Type.fromInterned(ip.typeOf(opt_ptr)).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_ptr; - } - - return o.builder.gepConst(.inbounds, try o.lowerType(opt_ty), parent_ptr, null, &.{ .@"0", .@"0" }); - }, - .comptime_field, .comptime_alloc => unreachable, - .elem => |elem_ptr| { - const parent_ptr = try o.lowerParentPtr(Value.fromInterned(elem_ptr.base)); - const elem_ty = Type.fromInterned(ip.typeOf(elem_ptr.base)).elemType2(mod); - - return o.builder.gepConst(.inbounds, try o.lowerType(elem_ty), parent_ptr, null, &.{ - try o.builder.intConst(try o.lowerType(Type.usize), elem_ptr.index), + .anon_decl => |ad| { + const base_ptr = try o.lowerAnonDeclRef(ad); + return o.builder.gepConst(.inbounds, .i8, base_ptr, null, &.{ + try o.builder.intConst(.i64, offset), }); }, - .field => |field_ptr| { - const parent_ptr = try o.lowerParentPtr(Value.fromInterned(field_ptr.base)); - const parent_ptr_ty = Type.fromInterned(ip.typeOf(field_ptr.base)); - const parent_ty = parent_ptr_ty.childType(mod); - const field_index: u32 = @intCast(field_ptr.index); - switch (parent_ty.zigTypeTag(mod)) { - .Union => { - if (parent_ty.containerLayout(mod) == .@"packed") { - return parent_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_ptr; - } - - const parent_llvm_ty = try o.lowerType(parent_ty); - return o.builder.gepConst(.inbounds, parent_llvm_ty, parent_ptr, null, &.{ - .@"0", - try o.builder.intConst(.i32, @intFromBool( - layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align), - )), - }); - }, - .Struct => { - if (mod.typeToPackedStruct(parent_ty)) |struct_type| { - const ptr_info = Type.fromInterned(ptr.ty).ptrInfo(mod); - if (ptr_info.packed_offset.host_size != 0) return parent_ptr; - - const parent_ptr_info = parent_ptr_ty.ptrInfo(mod); - const bit_offset = mod.structPackedFieldBitOffset(struct_type, field_index) + parent_ptr_info.packed_offset.bit_offset; - const llvm_usize = try o.lowerType(Type.usize); - const base_addr = try o.builder.castConst(.ptrtoint, parent_ptr, llvm_usize); - const byte_offset = try o.builder.intConst(llvm_usize, @divExact(bit_offset, 8)); - const field_addr = try o.builder.binConst(.add, base_addr, byte_offset); - return o.builder.castConst(.inttoptr, field_addr, .ptr); - } - - return o.builder.gepConst( - .inbounds, - try o.lowerType(parent_ty), - parent_ptr, - null, - if (o.llvmFieldIndex(parent_ty, field_index)) |llvm_field_index| &.{ - .@"0", - try o.builder.intConst(.i32, llvm_field_index), - } else &.{ - try o.builder.intConst(.i32, @intFromBool( - parent_ty.hasRuntimeBitsIgnoreComptime(mod), - )), - }, - ); + .int => try o.builder.castConst( + .inttoptr, + try o.builder.intConst(try o.lowerType(Type.usize), offset), + .ptr, + ), + .eu_payload => |eu_ptr| try o.lowerPtr( + eu_ptr, + offset + @import("../codegen.zig").errUnionPayloadOffset( + Value.fromInterned(eu_ptr).typeOf(zcu).childType(zcu), + zcu, + ), + ), + .opt_payload => |opt_ptr| try o.lowerPtr(opt_ptr, offset), + .field => |field| { + const agg_ty = Value.fromInterned(field.base).typeOf(zcu).childType(zcu); + const field_off: u64 = switch (agg_ty.zigTypeTag(zcu)) { + .Pointer => off: { + assert(agg_ty.isSlice(zcu)); + break :off switch (field.index) { + Value.slice_ptr_index => 0, + Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8), + else => unreachable, + }; }, - .Pointer => { - assert(parent_ty.isSlice(mod)); - const parent_llvm_ty = try o.lowerType(parent_ty); - return o.builder.gepConst(.inbounds, parent_llvm_ty, parent_ptr, null, &.{ - .@"0", try o.builder.intConst(.i32, field_index), - }); + .Struct, .Union => switch (agg_ty.containerLayout(zcu)) { + .auto => agg_ty.structFieldOffset(@intCast(field.index), zcu), + .@"extern", .@"packed" => unreachable, }, else => unreachable, - } + }; + return o.lowerPtr(field.base, offset + field_off); }, + .arr_elem, .comptime_field, .comptime_alloc => unreachable, }; } @@ -4376,8 +4286,7 @@ pub const Object = struct { /// Maybe the logic could be unified. fn lowerAnonDeclRef( o: *Object, - ptr_ty: Type, - anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, + anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl, ) Error!Builder.Constant { const mod = o.module; const ip = &mod.intern_pool; @@ -4393,6 +4302,8 @@ pub const Object = struct { @panic("TODO"); } + const ptr_ty = Type.fromInterned(anon_decl.orig_ty); + const is_fn_body = decl_ty.zigTypeTag(mod) == .Fn; if ((!is_fn_body and !decl_ty.hasRuntimeBits(mod)) or (is_fn_body and mod.typeToFunc(decl_ty).?.is_generic)) return o.lowerPtrToVoid(ptr_ty); @@ -4400,9 +4311,8 @@ pub const Object = struct { if (is_fn_body) @panic("TODO"); - const orig_ty = Type.fromInterned(anon_decl.orig_ty); - const llvm_addr_space = toLlvmAddressSpace(orig_ty.ptrAddressSpace(mod), target); - const alignment = orig_ty.ptrAlignment(mod); + const llvm_addr_space = toLlvmAddressSpace(ptr_ty.ptrAddressSpace(mod), target); + const alignment = ptr_ty.ptrAlignment(mod); const llvm_global = (try o.resolveGlobalAnonDecl(decl_val, llvm_addr_space, alignment)).ptrConst(&o.builder).global; const llvm_val = try o.builder.convConst( @@ -4411,13 +4321,10 @@ pub const Object = struct { try o.builder.ptrType(llvm_addr_space), ); - return o.builder.convConst(if (ptr_ty.isAbiInt(mod)) switch (ptr_ty.intInfo(mod).signedness) { - .signed => .signed, - .unsigned => .unsigned, - } else .unneeded, llvm_val, try o.lowerType(ptr_ty)); + return o.builder.convConst(.unneeded, llvm_val, try o.lowerType(ptr_ty)); } - fn lowerDeclRefValue(o: *Object, ty: Type, decl_index: InternPool.DeclIndex) Allocator.Error!Builder.Constant { + fn lowerDeclRefValue(o: *Object, decl_index: InternPool.DeclIndex) Allocator.Error!Builder.Constant { const mod = o.module; // In the case of something like: @@ -4428,18 +4335,23 @@ pub const Object = struct { const decl = mod.declPtr(decl_index); if (decl.val.getFunction(mod)) |func| { if (func.owner_decl != decl_index) { - return o.lowerDeclRefValue(ty, func.owner_decl); + return o.lowerDeclRefValue(func.owner_decl); } } else if (decl.val.getExternFunc(mod)) |func| { if (func.decl != decl_index) { - return o.lowerDeclRefValue(ty, func.decl); + return o.lowerDeclRefValue(func.decl); } } const decl_ty = decl.typeOf(mod); + const ptr_ty = try decl.declPtrType(mod); + const is_fn_body = decl_ty.zigTypeTag(mod) == .Fn; if ((!is_fn_body and !decl_ty.hasRuntimeBits(mod)) or - (is_fn_body and mod.typeToFunc(decl_ty).?.is_generic)) return o.lowerPtrToVoid(ty); + (is_fn_body and mod.typeToFunc(decl_ty).?.is_generic)) + { + return o.lowerPtrToVoid(ptr_ty); + } const llvm_global = if (is_fn_body) (try o.resolveLlvmFunction(decl_index)).ptrConst(&o.builder).global @@ -4452,10 +4364,7 @@ pub const Object = struct { try o.builder.ptrType(toLlvmAddressSpace(decl.@"addrspace", mod.getTarget())), ); - return o.builder.convConst(if (ty.isAbiInt(mod)) switch (ty.intInfo(mod).signedness) { - .signed => .signed, - .unsigned => .unsigned, - } else .unneeded, llvm_val, try o.lowerType(ty)); + return o.builder.convConst(.unneeded, llvm_val, try o.lowerType(ptr_ty)); } fn lowerPtrToVoid(o: *Object, ptr_ty: Type) Allocator.Error!Builder.Constant { diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 53ec59d53174..ed04ee475bf8 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -863,7 +863,7 @@ const DeclGen = struct { const result_ty_id = try self.resolveType(ty, repr); const ip = &mod.intern_pool; - log.debug("lowering constant: ty = {}, val = {}", .{ ty.fmt(mod), val.fmtValue(mod) }); + log.debug("lowering constant: ty = {}, val = {}", .{ ty.fmt(mod), val.fmtValue(mod, null) }); if (val.isUndefDeep(mod)) { return self.spv.constUndef(result_ty_id); } @@ -983,10 +983,10 @@ const DeclGen = struct { const int_ty = ty.intTagType(mod); break :cache try self.constant(int_ty, int_val, repr); }, - .ptr => return self.constantPtr(ty, val), + .ptr => return self.constantPtr(val), .slice => |slice| { const ptr_ty = ty.slicePtrFieldType(mod); - const ptr_id = try self.constantPtr(ptr_ty, Value.fromInterned(slice.ptr)); + const ptr_id = try self.constantPtr(Value.fromInterned(slice.ptr)); const len_id = try self.constant(Type.usize, Value.fromInterned(slice.len), .indirect); return self.constructStruct( ty, @@ -1107,62 +1107,86 @@ const DeclGen = struct { return cacheable_id; } - fn constantPtr(self: *DeclGen, ptr_ty: Type, ptr_val: Value) Error!IdRef { + fn constantPtr(self: *DeclGen, ptr_val: Value) Error!IdRef { // TODO: Caching?? - const result_ty_id = try self.resolveType(ptr_ty, .direct); - const mod = self.module; + const zcu = self.module; + + if (ptr_val.isUndef(zcu)) { + const result_ty = ptr_val.typeOf(zcu); + const result_ty_id = try self.resolveType(result_ty, .direct); + return self.spv.constUndef(result_ty_id); + } - if (ptr_val.isUndef(mod)) return self.spv.constUndef(result_ty_id); + var arena = std.heap.ArenaAllocator.init(self.gpa); + defer arena.deinit(); - switch (mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr) { - .decl => |decl| return try self.constantDeclRef(ptr_ty, decl), - .anon_decl => |anon_decl| return try self.constantAnonDeclRef(ptr_ty, anon_decl), + const derivation = try ptr_val.pointerDerivation(arena.allocator(), zcu); + return self.derivePtr(derivation); + } + + fn derivePtr(self: *DeclGen, derivation: Value.PointerDeriveStep) Error!IdRef { + const zcu = self.module; + switch (derivation) { + .comptime_alloc_ptr, .comptime_field_ptr => unreachable, .int => |int| { - const ptr_id = self.spv.allocId(); + const result_ty_id = try self.resolveType(int.ptr_ty, .direct); // TODO: This can probably be an OpSpecConstantOp Bitcast, but // that is not implemented by Mesa yet. Therefore, just generate it // as a runtime operation. + const result_ptr_id = self.spv.allocId(); try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ .id_result_type = result_ty_id, - .id_result = ptr_id, - .integer_value = try self.constant(Type.usize, Value.fromInterned(int), .direct), + .id_result = result_ptr_id, + .integer_value = try self.constant(Type.usize, try zcu.intValue(Type.usize, int.addr), .direct), }); - return ptr_id; + return result_ptr_id; + }, + .decl_ptr => |decl| { + const result_ptr_ty = try zcu.declPtr(decl).declPtrType(zcu); + return self.constantDeclRef(result_ptr_ty, decl); }, - .eu_payload => unreachable, // TODO - .opt_payload => unreachable, // TODO - .comptime_field, .comptime_alloc => unreachable, - .elem => |elem_ptr| { - const parent_ptr_ty = Type.fromInterned(mod.intern_pool.typeOf(elem_ptr.base)); - const parent_ptr_id = try self.constantPtr(parent_ptr_ty, Value.fromInterned(elem_ptr.base)); - const index_id = try self.constInt(Type.usize, elem_ptr.index, .direct); - - const elem_ptr_id = try self.ptrElemPtr(parent_ptr_ty, parent_ptr_id, index_id); - - // TODO: Can we consolidate this in ptrElemPtr? - const elem_ty = parent_ptr_ty.elemType2(mod); // use elemType() so that we get T for *[N]T. - const elem_ptr_ty_id = try self.ptrType(elem_ty, self.spvStorageClass(parent_ptr_ty.ptrAddressSpace(mod))); - - // TODO: Can we remove this ID comparison? - if (elem_ptr_ty_id == result_ty_id) { - return elem_ptr_id; + .anon_decl_ptr => |ad| { + const result_ptr_ty = Type.fromInterned(ad.orig_ty); + return self.constantAnonDeclRef(result_ptr_ty, ad); + }, + .eu_payload_ptr => @panic("TODO"), + .opt_payload_ptr => @panic("TODO"), + .field_ptr => |field| { + const parent_ptr_id = try self.derivePtr(field.parent.*); + const parent_ptr_ty = try field.parent.ptrType(zcu); + return self.structFieldPtr(field.result_ptr_ty, parent_ptr_ty, parent_ptr_id, field.field_idx); + }, + .elem_ptr => |elem| { + const parent_ptr_id = try self.derivePtr(elem.parent.*); + const parent_ptr_ty = try elem.parent.ptrType(zcu); + const index_id = try self.constInt(Type.usize, elem.elem_idx, .direct); + return self.ptrElemPtr(parent_ptr_ty, parent_ptr_id, index_id); + }, + .offset_and_cast => |oac| { + const parent_ptr_id = try self.derivePtr(oac.parent.*); + const parent_ptr_ty = try oac.parent.ptrType(zcu); + disallow: { + if (oac.byte_offset != 0) break :disallow; + // Allow changing the pointer type child only to restructure arrays. + // e.g. [3][2]T to T is fine, as is [2]T -> [2][1]T. + const src_base_ty = parent_ptr_ty.arrayBase(zcu)[0]; + const dest_base_ty = oac.new_ptr_ty.arrayBase(zcu)[0]; + if (self.getTarget().os.tag == .vulkan and src_base_ty.toIntern() != dest_base_ty.toIntern()) break :disallow; + + const result_ty_id = try self.resolveType(oac.new_ptr_ty, .direct); + const result_ptr_id = self.spv.allocId(); + try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ + .id_result_type = result_ty_id, + .id_result = result_ptr_id, + .operand = parent_ptr_id, + }); + return result_ptr_id; } - // This may happen when we have pointer-to-array and the result is - // another pointer-to-array instead of a pointer-to-element. - const result_id = self.spv.allocId(); - try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ - .id_result_type = result_ty_id, - .id_result = result_id, - .operand = elem_ptr_id, + return self.fail("Cannot perform pointer cast: '{}' to '{}'", .{ + parent_ptr_ty.fmt(zcu), + oac.new_ptr_ty.fmt(zcu), }); - return result_id; - }, - .field => |field| { - const base_ptr_ty = Type.fromInterned(mod.intern_pool.typeOf(field.base)); - const base_ptr = try self.constantPtr(base_ptr_ty, Value.fromInterned(field.base)); - const field_index: u32 = @intCast(field.index); - return try self.structFieldPtr(ptr_ty, base_ptr_ty, base_ptr, field_index); }, } } @@ -1170,7 +1194,7 @@ const DeclGen = struct { fn constantAnonDeclRef( self: *DeclGen, ty: Type, - anon_decl: InternPool.Key.Ptr.Addr.AnonDecl, + anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl, ) !IdRef { // TODO: Merge this function with constantDeclRef. @@ -4456,16 +4480,20 @@ const DeclGen = struct { ) !IdRef { const result_ty_id = try self.resolveType(result_ptr_ty, .direct); - const mod = self.module; - const object_ty = object_ptr_ty.childType(mod); - switch (object_ty.zigTypeTag(mod)) { - .Struct => switch (object_ty.containerLayout(mod)) { + const zcu = self.module; + const object_ty = object_ptr_ty.childType(zcu); + switch (object_ty.zigTypeTag(zcu)) { + .Pointer => { + assert(object_ty.isSlice(zcu)); + return self.accessChain(result_ty_id, object_ptr, &.{field_index}); + }, + .Struct => switch (object_ty.containerLayout(zcu)) { .@"packed" => unreachable, // TODO else => { return try self.accessChain(result_ty_id, object_ptr, &.{field_index}); }, }, - .Union => switch (object_ty.containerLayout(mod)) { + .Union => switch (object_ty.containerLayout(zcu)) { .@"packed" => unreachable, // TODO else => { const layout = self.unionLayout(object_ty); @@ -4475,7 +4503,7 @@ const DeclGen = struct { return try self.spv.constUndef(result_ty_id); } - const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(mod)); + const storage_class = self.spvStorageClass(object_ptr_ty.ptrAddressSpace(zcu)); const pl_ptr_ty_id = try self.ptrType(layout.payload_ty, storage_class); const pl_ptr_id = try self.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index}); diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index bcd98c3d3cef..dc49fffaa36c 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -539,7 +539,6 @@ fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, val: V .none, .{ .parent_atom_index = @intFromEnum(atom.sym_index), - .addend = null, }, ); break :code switch (result) { diff --git a/src/mutable_value.zig b/src/mutable_value.zig index f16a8fd3f911..e9f19e65967a 100644 --- a/src/mutable_value.zig +++ b/src/mutable_value.zig @@ -54,22 +54,22 @@ pub const MutableValue = union(enum) { payload: *MutableValue, }; - pub fn intern(mv: MutableValue, zcu: *Zcu, arena: Allocator) Allocator.Error!InternPool.Index { + pub fn intern(mv: MutableValue, zcu: *Zcu, arena: Allocator) Allocator.Error!Value { const ip = &zcu.intern_pool; const gpa = zcu.gpa; - return switch (mv) { + return Value.fromInterned(switch (mv) { .interned => |ip_index| ip_index, .eu_payload => |sv| try ip.get(gpa, .{ .error_union = .{ .ty = sv.ty, - .val = .{ .payload = try sv.child.intern(zcu, arena) }, + .val = .{ .payload = (try sv.child.intern(zcu, arena)).toIntern() }, } }), .opt_payload => |sv| try ip.get(gpa, .{ .opt = .{ .ty = sv.ty, - .val = try sv.child.intern(zcu, arena), + .val = (try sv.child.intern(zcu, arena)).toIntern(), } }), .repeated => |sv| try ip.get(gpa, .{ .aggregate = .{ .ty = sv.ty, - .storage = .{ .repeated_elem = try sv.child.intern(zcu, arena) }, + .storage = .{ .repeated_elem = (try sv.child.intern(zcu, arena)).toIntern() }, } }), .bytes => |b| try ip.get(gpa, .{ .aggregate = .{ .ty = b.ty, @@ -78,24 +78,24 @@ pub const MutableValue = union(enum) { .aggregate => |a| { const elems = try arena.alloc(InternPool.Index, a.elems.len); for (a.elems, elems) |mut_elem, *interned_elem| { - interned_elem.* = try mut_elem.intern(zcu, arena); + interned_elem.* = (try mut_elem.intern(zcu, arena)).toIntern(); } - return ip.get(gpa, .{ .aggregate = .{ + return Value.fromInterned(try ip.get(gpa, .{ .aggregate = .{ .ty = a.ty, .storage = .{ .elems = elems }, - } }); + } })); }, .slice => |s| try ip.get(gpa, .{ .slice = .{ .ty = s.ty, - .ptr = try s.ptr.intern(zcu, arena), - .len = try s.len.intern(zcu, arena), + .ptr = (try s.ptr.intern(zcu, arena)).toIntern(), + .len = (try s.len.intern(zcu, arena)).toIntern(), } }), .un => |u| try ip.get(gpa, .{ .un = .{ .ty = u.ty, .tag = u.tag, - .val = try u.payload.intern(zcu, arena), + .val = (try u.payload.intern(zcu, arena)).toIntern(), } }), - }; + }); } /// Un-interns the top level of this `MutableValue`, if applicable. @@ -248,9 +248,11 @@ pub const MutableValue = union(enum) { }, .Union => { const payload = try arena.create(MutableValue); - // HACKHACK: this logic is silly, but Sema detects it and reverts the change where needed. - // See comment at the top of `Sema.beginComptimePtrMutationInner`. - payload.* = .{ .interned = .undef }; + const backing_ty = try Type.fromInterned(ty_ip).unionBackingType(zcu); + payload.* = .{ .interned = try ip.get( + gpa, + .{ .undef = backing_ty.toIntern() }, + ) }; mv.* = .{ .un = .{ .ty = ty_ip, .tag = .none, @@ -294,7 +296,6 @@ pub const MutableValue = union(enum) { /// Get a pointer to the `MutableValue` associated with a field/element. /// The returned pointer can be safety mutated through to modify the field value. /// The returned pointer is valid until the representation of `mv` changes. - /// This function does *not* support accessing the ptr/len field of slices. pub fn elem( mv: *MutableValue, zcu: *Zcu, @@ -304,18 +305,18 @@ pub const MutableValue = union(enum) { const ip = &zcu.intern_pool; const gpa = zcu.gpa; // Convert to the `aggregate` representation. - switch (mv) { - .eu_payload, .opt_payload, .slice, .un => unreachable, + switch (mv.*) { + .eu_payload, .opt_payload, .un => unreachable, .interned => { try mv.unintern(zcu, arena, false, false); }, .bytes => |bytes| { const elems = try arena.alloc(MutableValue, bytes.data.len); - for (bytes.data, elems) |byte, interned_byte| { - interned_byte.* = try ip.get(gpa, .{ .int = .{ + for (bytes.data, elems) |byte, *interned_byte| { + interned_byte.* = .{ .interned = try ip.get(gpa, .{ .int = .{ .ty = .u8_type, .storage = .{ .u64 = byte }, - } }); + } }) }; } mv.* = .{ .aggregate = .{ .ty = bytes.ty, @@ -331,9 +332,17 @@ pub const MutableValue = union(enum) { .elems = elems, } }; }, - .aggregate => {}, + .slice, .aggregate => {}, + } + switch (mv.*) { + .aggregate => |*agg| return &agg.elems[field_idx], + .slice => |*slice| return switch (field_idx) { + Value.slice_ptr_index => slice.ptr, + Value.slice_len_index => slice.len, + else => unreachable, + }, + else => unreachable, } - return &mv.aggregate.elems[field_idx]; } /// Modify a single field of a `MutableValue` which represents an aggregate or slice, leaving others @@ -349,43 +358,44 @@ pub const MutableValue = union(enum) { ) Allocator.Error!void { const ip = &zcu.intern_pool; const is_trivial_int = field_val.isTrivialInt(zcu); - try mv.unintern(arena, is_trivial_int, true); - switch (mv) { + try mv.unintern(zcu, arena, is_trivial_int, true); + switch (mv.*) { .interned, .eu_payload, .opt_payload, .un, => unreachable, .slice => |*s| switch (field_idx) { - Value.slice_ptr_index => s.ptr = field_val, - Value.slice_len_index => s.len = field_val, + Value.slice_ptr_index => s.ptr.* = field_val, + Value.slice_len_index => s.len.* = field_val, + else => unreachable, }, .bytes => |b| { assert(is_trivial_int); - assert(field_val.typeOf() == Type.u8); - b.data[field_idx] = Value.fromInterned(field_val.interned).toUnsignedInt(zcu); + assert(field_val.typeOf(zcu).toIntern() == .u8_type); + b.data[field_idx] = @intCast(Value.fromInterned(field_val.interned).toUnsignedInt(zcu)); }, .repeated => |r| { if (field_val.eqlTrivial(r.child.*)) return; // We must switch to either the `aggregate` or the `bytes` representation. const len_inc_sent = ip.aggregateTypeLenIncludingSentinel(r.ty); - if (ip.zigTypeTag(r.ty) != .Struct and + if (Type.fromInterned(r.ty).zigTypeTag(zcu) != .Struct and is_trivial_int and - Type.fromInterned(r.ty).childType(zcu) == .u8_type and + Type.fromInterned(r.ty).childType(zcu).toIntern() == .u8_type and r.child.isTrivialInt(zcu)) { // We can use the `bytes` representation. const bytes = try arena.alloc(u8, @intCast(len_inc_sent)); - const repeated_byte = Value.fromInterned(r.child.interned).getUnsignedInt(zcu); - @memset(bytes, repeated_byte); - bytes[field_idx] = Value.fromInterned(field_val.interned).getUnsignedInt(zcu); + const repeated_byte = Value.fromInterned(r.child.interned).toUnsignedInt(zcu); + @memset(bytes, @intCast(repeated_byte)); + bytes[field_idx] = @intCast(Value.fromInterned(field_val.interned).toUnsignedInt(zcu)); mv.* = .{ .bytes = .{ .ty = r.ty, .data = bytes, } }; } else { // We must use the `aggregate` representation. - const mut_elems = try arena.alloc(u8, @intCast(len_inc_sent)); + const mut_elems = try arena.alloc(MutableValue, @intCast(len_inc_sent)); @memset(mut_elems, r.child.*); mut_elems[field_idx] = field_val; mv.* = .{ .aggregate = .{ @@ -396,12 +406,12 @@ pub const MutableValue = union(enum) { }, .aggregate => |a| { a.elems[field_idx] = field_val; - const is_struct = ip.zigTypeTag(a.ty) == .Struct; + const is_struct = Type.fromInterned(a.ty).zigTypeTag(zcu) == .Struct; // Attempt to switch to a more efficient representation. const is_repeated = for (a.elems) |e| { if (!e.eqlTrivial(field_val)) break false; } else true; - if (is_repeated) { + if (!is_struct and is_repeated) { // Switch to `repeated` repr const mut_repeated = try arena.create(MutableValue); mut_repeated.* = field_val; @@ -425,7 +435,7 @@ pub const MutableValue = union(enum) { } else { const bytes = try arena.alloc(u8, a.elems.len); for (a.elems, bytes) |elem_val, *b| { - b.* = Value.fromInterned(elem_val.interned).toUnsignedInt(zcu); + b.* = @intCast(Value.fromInterned(elem_val.interned).toUnsignedInt(zcu)); } mv.* = .{ .bytes = .{ .ty = a.ty, @@ -505,4 +515,67 @@ pub const MutableValue = union(enum) { inline else => |x| Type.fromInterned(x.ty), }; } + + pub fn unpackOptional(mv: MutableValue, zcu: *Zcu) union(enum) { + undef, + null, + payload: MutableValue, + } { + return switch (mv) { + .opt_payload => |pl| return .{ .payload = pl.child.* }, + .interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) { + .undef => return .undef, + .opt => |opt| if (opt.val == .none) .null else .{ .payload = .{ .interned = opt.val } }, + else => unreachable, + }, + else => unreachable, + }; + } + + pub fn unpackErrorUnion(mv: MutableValue, zcu: *Zcu) union(enum) { + undef, + err: InternPool.NullTerminatedString, + payload: MutableValue, + } { + return switch (mv) { + .eu_payload => |pl| return .{ .payload = pl.child.* }, + .interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) { + .undef => return .undef, + .error_union => |eu| switch (eu.val) { + .err_name => |name| .{ .err = name }, + .payload => |pl| .{ .payload = .{ .interned = pl } }, + }, + else => unreachable, + }, + else => unreachable, + }; + } + + /// Fast equality checking which may return false negatives. + /// Used for deciding when to switch aggregate representations without fully + /// interning many values. + fn eqlTrivial(a: MutableValue, b: MutableValue) bool { + const Tag = @typeInfo(MutableValue).Union.tag_type.?; + if (@as(Tag, a) != @as(Tag, b)) return false; + return switch (a) { + .interned => |a_ip| a_ip == b.interned, + .eu_payload => |a_pl| a_pl.ty == b.eu_payload.ty and a_pl.child.eqlTrivial(b.eu_payload.child.*), + .opt_payload => |a_pl| a_pl.ty == b.opt_payload.ty and a_pl.child.eqlTrivial(b.opt_payload.child.*), + .repeated => |a_rep| a_rep.ty == b.repeated.ty and a_rep.child.eqlTrivial(b.repeated.child.*), + .bytes => |a_bytes| a_bytes.ty == b.bytes.ty and std.mem.eql(u8, a_bytes.data, b.bytes.data), + .aggregate => |a_agg| { + const b_agg = b.aggregate; + if (a_agg.ty != b_agg.ty) return false; + if (a_agg.elems.len != b_agg.elems.len) return false; + for (a_agg.elems, b_agg.elems) |a_elem, b_elem| { + if (!a_elem.eqlTrivial(b_elem)) return false; + } + return true; + }, + .slice => |a_slice| a_slice.ty == b.slice.ty and + a_slice.ptr.interned == b.slice.ptr.interned and + a_slice.len.interned == b.slice.len.interned, + .un => |a_un| a_un.ty == b.un.ty and a_un.tag == b.un.tag and a_un.payload.eqlTrivial(b.un.payload.*), + }; + } }; diff --git a/src/print_air.zig b/src/print_air.zig index 12e2825d4ef0..e61ae9fff004 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -951,7 +951,7 @@ const Writer = struct { const ty = Type.fromInterned(mod.intern_pool.indexToKey(ip_index).typeOf()); try s.print("<{}, {}>", .{ ty.fmt(mod), - Value.fromInterned(ip_index).fmtValue(mod), + Value.fromInterned(ip_index).fmtValue(mod, null), }); } else { return w.writeInstIndex(s, operand.toIndex().?, dies); diff --git a/src/print_value.zig b/src/print_value.zig index c021b011c1ac..9c9e2fd718e6 100644 --- a/src/print_value.zig +++ b/src/print_value.zig @@ -17,6 +17,7 @@ const max_string_len = 256; const FormatContext = struct { val: Value, mod: *Module, + opt_sema: ?*Sema, }; pub fn format( @@ -27,10 +28,10 @@ pub fn format( ) !void { _ = options; comptime std.debug.assert(fmt.len == 0); - return print(ctx.val, writer, 3, ctx.mod, null) catch |err| switch (err) { + return print(ctx.val, writer, 3, ctx.mod, ctx.opt_sema) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), // We're not allowed to return this from a format function error.ComptimeBreak, error.ComptimeReturn => unreachable, - error.AnalysisFail, error.NeededSourceLocation => unreachable, // TODO: re-evaluate when we actually pass `opt_sema` + error.AnalysisFail, error.NeededSourceLocation => unreachable, // TODO: re-evaluate when we use `opt_sema` more fully else => |e| return e, }; } @@ -117,7 +118,7 @@ pub fn print( }, .slice => |slice| { const print_contents = switch (ip.getBackingAddrTag(slice.ptr).?) { - .field, .elem, .eu_payload, .opt_payload => unreachable, + .field, .arr_elem, .eu_payload, .opt_payload => unreachable, .anon_decl, .comptime_alloc, .comptime_field => true, .decl, .int => false, }; @@ -125,7 +126,7 @@ pub fn print( // TODO: eventually we want to load the slice as an array with `opt_sema`, but that's // currently not possible without e.g. triggering compile errors. } - try printPtr(slice.ptr, writer, false, false, 0, level, mod, opt_sema); + try printPtr(Value.fromInterned(slice.ptr), writer, level, mod, opt_sema); try writer.writeAll("[0.."); if (level == 0) { try writer.writeAll("(...)"); @@ -136,7 +137,7 @@ pub fn print( }, .ptr => { const print_contents = switch (ip.getBackingAddrTag(val.toIntern()).?) { - .field, .elem, .eu_payload, .opt_payload => unreachable, + .field, .arr_elem, .eu_payload, .opt_payload => unreachable, .anon_decl, .comptime_alloc, .comptime_field => true, .decl, .int => false, }; @@ -144,13 +145,13 @@ pub fn print( // TODO: eventually we want to load the pointer with `opt_sema`, but that's // currently not possible without e.g. triggering compile errors. } - try printPtr(val.toIntern(), writer, false, false, 0, level, mod, opt_sema); + try printPtr(val, writer, level, mod, opt_sema); }, .opt => |opt| switch (opt.val) { .none => try writer.writeAll("null"), else => |payload| try print(Value.fromInterned(payload), writer, level, mod, opt_sema), }, - .aggregate => |aggregate| try printAggregate(val, aggregate, writer, level, false, mod, opt_sema), + .aggregate => |aggregate| try printAggregate(val, aggregate, false, writer, level, mod, opt_sema), .un => |un| { if (level == 0) { try writer.writeAll(".{ ... }"); @@ -176,13 +177,14 @@ pub fn print( fn printAggregate( val: Value, aggregate: InternPool.Key.Aggregate, + is_ref: bool, writer: anytype, level: u8, - is_ref: bool, zcu: *Zcu, opt_sema: ?*Sema, ) (@TypeOf(writer).Error || Module.CompileError)!void { if (level == 0) { + if (is_ref) try writer.writeByte('&'); return writer.writeAll(".{ ... }"); } const ip = &zcu.intern_pool; @@ -257,101 +259,87 @@ fn printAggregate( return writer.writeAll(" }"); } -fn printPtr( - ptr_val: InternPool.Index, - writer: anytype, - force_type: bool, - force_addrof: bool, - leading_parens: u32, - level: u8, - zcu: *Zcu, - opt_sema: ?*Sema, -) (@TypeOf(writer).Error || Module.CompileError)!void { - const ip = &zcu.intern_pool; - const ptr = switch (ip.indexToKey(ptr_val)) { - .undef => |ptr_ty| { - if (force_addrof) try writer.writeAll("&"); - try writer.writeByteNTimes('(', leading_parens); - try writer.print("@as({}, undefined)", .{Type.fromInterned(ptr_ty).fmt(zcu)}); - return; - }, +fn printPtr(ptr_val: Value, writer: anytype, level: u8, zcu: *Zcu, opt_sema: ?*Sema) (@TypeOf(writer).Error || Module.CompileError)!void { + const ptr = switch (zcu.intern_pool.indexToKey(ptr_val.toIntern())) { + .undef => return writer.writeAll("undefined"), .ptr => |ptr| ptr, else => unreachable, }; - if (level == 0) { - return writer.writeAll("&..."); - } - switch (ptr.addr) { - .int => |int| { - if (force_addrof) try writer.writeAll("&"); - try writer.writeByteNTimes('(', leading_parens); - if (force_type) { - try writer.print("@as({}, @ptrFromInt(", .{Type.fromInterned(ptr.ty).fmt(zcu)}); - try print(Value.fromInterned(int), writer, level - 1, zcu, opt_sema); - try writer.writeAll("))"); - } else { - try writer.writeAll("@ptrFromInt("); - try print(Value.fromInterned(int), writer, level - 1, zcu, opt_sema); - try writer.writeAll(")"); - } - }, - .decl => |index| { - try writer.writeAll("&"); - try zcu.declPtr(index).renderFullyQualifiedName(zcu, writer); - }, - .comptime_alloc => try writer.writeAll("&(comptime alloc)"), - .anon_decl => |anon| switch (ip.indexToKey(anon.val)) { - .aggregate => |aggregate| try printAggregate( - Value.fromInterned(anon.val), - aggregate, - writer, - level - 1, + + if (ptr.base_addr == .anon_decl) { + // If the value is an aggregate, we can potentially print it more nicely. + switch (zcu.intern_pool.indexToKey(ptr.base_addr.anon_decl.val)) { + .aggregate => |agg| return printAggregate( + Value.fromInterned(ptr.base_addr.anon_decl.val), + agg, true, + writer, + level, zcu, opt_sema, ), - else => { - const ty = Type.fromInterned(ip.typeOf(anon.val)); - try writer.print("&@as({}, ", .{ty.fmt(zcu)}); - try print(Value.fromInterned(anon.val), writer, level - 1, zcu, opt_sema); - try writer.writeAll(")"); - }, + else => {}, + } + } + + var arena = std.heap.ArenaAllocator.init(zcu.gpa); + defer arena.deinit(); + const derivation = try ptr_val.pointerDerivationAdvanced(arena.allocator(), zcu, opt_sema); + try printPtrDerivation(derivation, writer, level, zcu, opt_sema); +} + +/// Print `derivation` as an lvalue, i.e. such that writing `&` before this gives the pointer value. +fn printPtrDerivation(derivation: Value.PointerDeriveStep, writer: anytype, level: u8, zcu: *Zcu, opt_sema: ?*Sema) (@TypeOf(writer).Error || Module.CompileError)!void { + const ip = &zcu.intern_pool; + switch (derivation) { + .int => |int| try writer.print("@as({}, @ptrFromInt({x})).*", .{ + int.ptr_ty.fmt(zcu), + int.addr, + }), + .decl_ptr => |decl| { + try zcu.declPtr(decl).renderFullyQualifiedName(zcu, writer); }, - .comptime_field => |val| { - const ty = Type.fromInterned(ip.typeOf(val)); - try writer.print("&@as({}, ", .{ty.fmt(zcu)}); - try print(Value.fromInterned(val), writer, level - 1, zcu, opt_sema); - try writer.writeAll(")"); + .anon_decl_ptr => |anon| { + const ty = Value.fromInterned(anon.val).typeOf(zcu); + try writer.print("@as({}, ", .{ty.fmt(zcu)}); + try print(Value.fromInterned(anon.val), writer, level - 1, zcu, opt_sema); + try writer.writeByte(')'); }, - .eu_payload => |base| { - try printPtr(base, writer, true, true, leading_parens, level, zcu, opt_sema); - try writer.writeAll(".?"); + .comptime_alloc_ptr => |info| { + try writer.print("@as({}, ", .{info.val.typeOf(zcu).fmt(zcu)}); + try print(info.val, writer, level - 1, zcu, opt_sema); + try writer.writeByte(')'); }, - .opt_payload => |base| { - try writer.writeAll("("); - try printPtr(base, writer, true, true, leading_parens + 1, level, zcu, opt_sema); - try writer.writeAll(" catch unreachable"); + .comptime_field_ptr => |val| { + const ty = val.typeOf(zcu); + try writer.print("@as({}, ", .{ty.fmt(zcu)}); + try print(val, writer, level - 1, zcu, opt_sema); + try writer.writeByte(')'); }, - .elem => |elem| { - try printPtr(elem.base, writer, true, true, leading_parens, level, zcu, opt_sema); - try writer.print("[{d}]", .{elem.index}); + .eu_payload_ptr => |info| { + try writer.writeByte('('); + try printPtrDerivation(info.parent.*, writer, level, zcu, opt_sema); + try writer.writeAll(" catch unreachable)"); }, - .field => |field| { - try printPtr(field.base, writer, true, true, leading_parens, level, zcu, opt_sema); - const base_ty = Type.fromInterned(ip.typeOf(field.base)).childType(zcu); - switch (base_ty.zigTypeTag(zcu)) { - .Struct => if (base_ty.isTuple(zcu)) { - try writer.print("[{d}]", .{field.index}); - } else { - const field_name = base_ty.structFieldName(@intCast(field.index), zcu).unwrap().?; + .opt_payload_ptr => |info| { + try printPtrDerivation(info.parent.*, writer, level, zcu, opt_sema); + try writer.writeAll(".?"); + }, + .field_ptr => |field| { + try printPtrDerivation(field.parent.*, writer, level, zcu, opt_sema); + const agg_ty = (try field.parent.ptrType(zcu)).childType(zcu); + switch (agg_ty.zigTypeTag(zcu)) { + .Struct => if (agg_ty.structFieldName(field.field_idx, zcu).unwrap()) |field_name| { try writer.print(".{i}", .{field_name.fmt(ip)}); + } else { + try writer.print("[{d}]", .{field.field_idx}); }, .Union => { - const tag_ty = base_ty.unionTagTypeHypothetical(zcu); - const field_name = tag_ty.enumFieldName(@intCast(field.index), zcu); + const tag_ty = agg_ty.unionTagTypeHypothetical(zcu); + const field_name = tag_ty.enumFieldName(field.field_idx, zcu); try writer.print(".{i}", .{field_name.fmt(ip)}); }, - .Pointer => switch (field.index) { + .Pointer => switch (field.field_idx) { Value.slice_ptr_index => try writer.writeAll(".ptr"), Value.slice_len_index => try writer.writeAll(".len"), else => unreachable, @@ -359,5 +347,18 @@ fn printPtr( else => unreachable, } }, + .elem_ptr => |elem| { + try printPtrDerivation(elem.parent.*, writer, level, zcu, opt_sema); + try writer.print("[{d}]", .{elem.elem_idx}); + }, + .offset_and_cast => |oac| if (oac.byte_offset == 0) { + try writer.print("@as({}, @ptrCast(", .{oac.new_ptr_ty.fmt(zcu)}); + try printPtrDerivation(oac.parent.*, writer, level, zcu, opt_sema); + try writer.writeAll("))"); + } else { + try writer.print("@as({}, @ptrFromInt(@intFromPtr(", .{oac.new_ptr_ty.fmt(zcu)}); + try printPtrDerivation(oac.parent.*, writer, level, zcu, opt_sema); + try writer.print(") + {d}))", .{oac.byte_offset}); + }, } } diff --git a/src/type.zig b/src/type.zig index 264125c6d04d..fcacfaf9e608 100644 --- a/src/type.zig +++ b/src/type.zig @@ -172,6 +172,7 @@ pub const Type = struct { } /// Prints a name suitable for `@typeName`. + /// TODO: take an `opt_sema` to pass to `fmtValue` when printing sentinels. pub fn print(ty: Type, writer: anytype, mod: *Module) @TypeOf(writer).Error!void { const ip = &mod.intern_pool; switch (ip.indexToKey(ty.toIntern())) { @@ -187,8 +188,8 @@ pub const Type = struct { if (info.sentinel != .none) switch (info.flags.size) { .One, .C => unreachable, - .Many => try writer.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(mod)}), - .Slice => try writer.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(mod)}), + .Many => try writer.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(mod, null)}), + .Slice => try writer.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(mod, null)}), } else switch (info.flags.size) { .One => try writer.writeAll("*"), .Many => try writer.writeAll("[*]"), @@ -234,7 +235,7 @@ pub const Type = struct { } else { try writer.print("[{d}:{}]", .{ array_type.len, - Value.fromInterned(array_type.sentinel).fmtValue(mod), + Value.fromInterned(array_type.sentinel).fmtValue(mod, null), }); try print(Type.fromInterned(array_type.child), writer, mod); } @@ -352,7 +353,7 @@ pub const Type = struct { try print(Type.fromInterned(field_ty), writer, mod); if (val != .none) { - try writer.print(" = {}", .{Value.fromInterned(val).fmtValue(mod)}); + try writer.print(" = {}", .{Value.fromInterned(val).fmtValue(mod, null)}); } } try writer.writeAll("}"); @@ -1965,6 +1966,12 @@ pub const Type = struct { return Type.fromInterned(union_fields[index]); } + pub fn unionFieldTypeByIndex(ty: Type, index: usize, mod: *Module) Type { + const ip = &mod.intern_pool; + const union_obj = mod.typeToUnion(ty).?; + return Type.fromInterned(union_obj.field_types.get(ip)[index]); + } + pub fn unionTagFieldIndex(ty: Type, enum_tag: Value, mod: *Module) ?u32 { const union_obj = mod.typeToUnion(ty).?; return mod.unionTagFieldIndex(union_obj, enum_tag); @@ -3049,22 +3056,34 @@ pub const Type = struct { }; } - pub fn structFieldAlign(ty: Type, index: usize, mod: *Module) Alignment { - const ip = &mod.intern_pool; + pub fn structFieldAlign(ty: Type, index: usize, zcu: *Zcu) Alignment { + return ty.structFieldAlignAdvanced(index, zcu, null) catch unreachable; + } + + pub fn structFieldAlignAdvanced(ty: Type, index: usize, zcu: *Zcu, opt_sema: ?*Sema) !Alignment { + const ip = &zcu.intern_pool; switch (ip.indexToKey(ty.toIntern())) { .struct_type => { const struct_type = ip.loadStructType(ty.toIntern()); assert(struct_type.layout != .@"packed"); const explicit_align = struct_type.fieldAlign(ip, index); const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[index]); - return mod.structFieldAlignment(explicit_align, field_ty, struct_type.layout); + if (opt_sema) |sema| { + return sema.structFieldAlignment(explicit_align, field_ty, struct_type.layout); + } else { + return zcu.structFieldAlignment(explicit_align, field_ty, struct_type.layout); + } }, .anon_struct_type => |anon_struct| { - return Type.fromInterned(anon_struct.types.get(ip)[index]).abiAlignment(mod); + return (try Type.fromInterned(anon_struct.types.get(ip)[index]).abiAlignmentAdvanced(zcu, if (opt_sema) |sema| .{ .sema = sema } else .eager)).scalar; }, .union_type => { const union_obj = ip.loadUnionType(ty.toIntern()); - return mod.unionFieldNormalAlignment(union_obj, @intCast(index)); + if (opt_sema) |sema| { + return sema.unionFieldAlignment(union_obj, @intCast(index)); + } else { + return zcu.unionFieldNormalAlignment(union_obj, @intCast(index)); + } }, else => unreachable, } @@ -3301,6 +3320,71 @@ pub const Type = struct { }; } + pub fn arrayBase(ty: Type, zcu: *const Zcu) struct { Type, u64 } { + var cur_ty: Type = ty; + var cur_len: u64 = 1; + while (cur_ty.zigTypeTag(zcu) == .Array) { + cur_len *= cur_ty.arrayLenIncludingSentinel(zcu); + cur_ty = cur_ty.childType(zcu); + } + return .{ cur_ty, cur_len }; + } + + pub fn packedStructFieldPtrInfo(struct_ty: Type, parent_ptr_ty: Type, field_idx: u32, zcu: *Zcu) union(enum) { + /// The result is a bit-pointer with the same value and a new packed offset. + bit_ptr: InternPool.Key.PtrType.PackedOffset, + /// The result is a standard pointer. + byte_ptr: struct { + /// The byte offset of the field pointer from the parent pointer value. + offset: u64, + /// The alignment of the field pointer type. + alignment: InternPool.Alignment, + }, + } { + comptime assert(Type.packed_struct_layout_version == 2); + + const parent_ptr_info = parent_ptr_ty.ptrInfo(zcu); + const field_ty = struct_ty.structFieldType(field_idx, zcu); + + var bit_offset: u16 = 0; + var running_bits: u16 = 0; + for (0..struct_ty.structFieldCount(zcu)) |i| { + const f_ty = struct_ty.structFieldType(i, zcu); + if (i == field_idx) { + bit_offset = running_bits; + } + running_bits += @intCast(f_ty.bitSize(zcu)); + } + + const res_host_size: u16, const res_bit_offset: u16 = if (parent_ptr_info.packed_offset.host_size != 0) + .{ parent_ptr_info.packed_offset.host_size, parent_ptr_info.packed_offset.bit_offset + bit_offset } + else + .{ (running_bits + 7) / 8, bit_offset }; + + // If the field happens to be byte-aligned, simplify the pointer type. + // We can only do this if the pointee's bit size matches its ABI byte size, + // so that loads and stores do not interfere with surrounding packed bits. + // + // TODO: we do not attempt this with big-endian targets yet because of nested + // structs and floats. I need to double-check the desired behavior for big endian + // targets before adding the necessary complications to this code. This will not + // cause miscompilations; it only means the field pointer uses bit masking when it + // might not be strictly necessary. + if (res_bit_offset % 8 == 0 and field_ty.bitSize(zcu) == field_ty.abiSize(zcu) * 8 and zcu.getTarget().cpu.arch.endian() == .little) { + const byte_offset = res_bit_offset / 8; + const new_align = Alignment.fromLog2Units(@ctz(byte_offset | parent_ptr_ty.ptrAlignment(zcu).toByteUnits().?)); + return .{ .byte_ptr = .{ + .offset = byte_offset, + .alignment = new_align, + } }; + } + + return .{ .bit_ptr = .{ + .host_size = res_host_size, + .bit_offset = res_bit_offset, + } }; + } + pub const @"u1": Type = .{ .ip_index = .u1_type }; pub const @"u8": Type = .{ .ip_index = .u8_type }; pub const @"u16": Type = .{ .ip_index = .u16_type }; diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 9afd11b80bd3..a27455366504 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -517,3 +517,61 @@ test "@bitCast of packed struct of bools all false" { p.b3 = false; try expect(@as(u8, @as(u4, @bitCast(p))) == 0); } + +test "@bitCast of packed struct containing pointer" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const S = struct { + const A = packed struct { + ptr: *const u32, + }; + + const B = packed struct { + ptr: *const i32, + }; + + fn doTheTest() !void { + const x: u32 = 123; + var a: A = undefined; + a = .{ .ptr = &x }; + const b: B = @bitCast(a); + try expect(b.ptr.* == 123); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "@bitCast of extern struct containing pointer" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const S = struct { + const A = extern struct { + ptr: *const u32, + }; + + const B = extern struct { + ptr: *const i32, + }; + + fn doTheTest() !void { + const x: u32 = 123; + var a: A = undefined; + a = .{ .ptr = &x }; + const b: B = @bitCast(a); + try expect(b.ptr.* == 123); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} diff --git a/test/behavior/cast_int.zig b/test/behavior/cast_int.zig index 8a832f782061..065710c5c29c 100644 --- a/test/behavior/cast_int.zig +++ b/test/behavior/cast_int.zig @@ -139,8 +139,8 @@ const Piece = packed struct { color: Color, type: Type, - const Type = enum { KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN }; - const Color = enum { WHITE, BLACK }; + const Type = enum(u3) { KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN }; + const Color = enum(u1) { WHITE, BLACK }; fn charToPiece(c: u8) !@This() { return .{ diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index 99d173c5de98..968b7be79dde 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -32,32 +32,22 @@ test "type pun signed and unsigned as array pointer" { } test "type pun signed and unsigned as offset many pointer" { - if (true) { - // TODO https://github.com/ziglang/zig/issues/9646 - return error.SkipZigTest; - } - comptime { - var x: u32 = 0; - var y = @as([*]i32, @ptrCast(&x)); + var x: [11]u32 = undefined; + var y: [*]i32 = @ptrCast(&x[10]); y -= 10; y[10] = -1; - try testing.expectEqual(@as(u32, 0xFFFFFFFF), x); + try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]); } } test "type pun signed and unsigned as array pointer with pointer arithemtic" { - if (true) { - // TODO https://github.com/ziglang/zig/issues/9646 - return error.SkipZigTest; - } - comptime { - var x: u32 = 0; - const y = @as([*]i32, @ptrCast(&x)) - 10; + var x: [11]u32 = undefined; + const y = @as([*]i32, @ptrCast(&x[10])) - 10; const z: *[15]i32 = y[0..15]; z[10] = -1; - try testing.expectEqual(@as(u32, 0xFFFFFFFF), x); + try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]); } } @@ -171,10 +161,13 @@ fn doTypePunBitsTest(as_bits: *Bits) !void { test "type pun bits" { if (true) { - // TODO https://github.com/ziglang/zig/issues/9646 + // TODO: currently, marking one bit of `Bits` as `undefined` does + // mark the whole value as `undefined`, since the pointer interpretation + // logic reads it back in as a `u32`, which is partially-undef and thus + // has value `undefined`. We need an improved comptime memory representation + // to make this work. return error.SkipZigTest; } - comptime { var v: u32 = undefined; try doTypePunBitsTest(@as(*Bits, @ptrCast(&v))); @@ -296,11 +289,6 @@ test "dance on linker values" { } test "offset array ptr by element size" { - if (true) { - // TODO https://github.com/ziglang/zig/issues/9646 - return error.SkipZigTest; - } - comptime { const VirtualStruct = struct { x: u32 }; var arr: [4]VirtualStruct = .{ @@ -310,15 +298,10 @@ test "offset array ptr by element size" { .{ .x = bigToNativeEndian(u32, 0x03070b0f) }, }; - const address = @intFromPtr(&arr); - try testing.expectEqual(@intFromPtr(&arr[0]), address); - try testing.expectEqual(@intFromPtr(&arr[0]) + 10, address + 10); - try testing.expectEqual(@intFromPtr(&arr[1]), address + @sizeOf(VirtualStruct)); - try testing.expectEqual(@intFromPtr(&arr[2]), address + 2 * @sizeOf(VirtualStruct)); - try testing.expectEqual(@intFromPtr(&arr[3]), address + @sizeOf(VirtualStruct) * 3); + const buf: [*]align(@alignOf(VirtualStruct)) u8 = @ptrCast(&arr); - const secondElement = @as(*VirtualStruct, @ptrFromInt(@intFromPtr(&arr[0]) + 2 * @sizeOf(VirtualStruct))); - try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), secondElement.x); + const second_element: *VirtualStruct = @ptrCast(buf + 2 * @sizeOf(VirtualStruct)); + try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), second_element.x); } } @@ -364,7 +347,7 @@ test "offset field ptr by enclosing array element size" { var i: usize = 0; while (i < 4) : (i += 1) { - var ptr: [*]u8 = @as([*]u8, @ptrCast(&arr[0])); + var ptr: [*]u8 = @ptrCast(&arr[0]); ptr += i; ptr += @offsetOf(VirtualStruct, "x"); var j: usize = 0; @@ -400,23 +383,18 @@ test "accessing reinterpreted memory of parent object" { } test "bitcast packed union to integer" { - if (true) { - // https://github.com/ziglang/zig/issues/19384 - return error.SkipZigTest; - } const U = packed union { - x: u1, + x: i2, y: u2, }; comptime { - const a = U{ .x = 1 }; - const b = U{ .y = 2 }; - const cast_a = @as(u2, @bitCast(a)); - const cast_b = @as(u2, @bitCast(b)); + const a: U = .{ .x = -1 }; + const b: U = .{ .y = 2 }; + const cast_a: u2 = @bitCast(a); + const cast_b: u2 = @bitCast(b); - // truncated because the upper bit is garbage memory that we don't care about - try testing.expectEqual(@as(u1, 1), @as(u1, @truncate(cast_a))); + try testing.expectEqual(@as(u2, 3), cast_a); try testing.expectEqual(@as(u2, 2), cast_b); } } diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 8380c8961918..408dc9600597 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -1054,3 +1054,26 @@ test "errorCast from error sets to error unions" { const err_union: Set1!void = @errorCast(error.A); try expectError(error.A, err_union); } + +test "result location initialization of error union with OPV payload" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + + const S = struct { + x: u0, + }; + + const a: anyerror!S = .{ .x = 0 }; + comptime assert((a catch unreachable).x == 0); + + comptime { + var b: anyerror!S = .{ .x = 0 }; + _ = &b; + assert((b catch unreachable).x == 0); + } + + var c: anyerror!S = .{ .x = 0 }; + _ = &c; + try expectEqual(0, (c catch return error.TestFailed).x); +} diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 944ec85f8537..d9da426b3b72 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -610,3 +610,26 @@ test "copied optional doesn't alias source" { try expect(x[0] == 0.0); } + +test "result location initialization of optional with OPV payload" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + + const S = struct { + x: u0, + }; + + const a: ?S = .{ .x = 0 }; + comptime assert(a.?.x == 0); + + comptime { + var b: ?S = .{ .x = 0 }; + _ = &b; + assert(b.?.x == 0); + } + + var c: ?S = .{ .x = 0 }; + _ = &c; + try expectEqual(0, (c orelse return error.TestFailed).x); +} diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 3556c06f9cbb..b194f7ac9e4b 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -1025,7 +1025,7 @@ test "modify nested packed struct aligned field" { pretty_print: packed struct { enabled: bool = false, num_spaces: u4 = 4, - space_char: enum { space, tab } = .space, + space_char: enum(u1) { space, tab } = .space, indent: u8 = 0, } = .{}, baz: bool = false, diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig index 1045319fe4f2..55b76e2625b6 100644 --- a/test/behavior/packed-union.zig +++ b/test/behavior/packed-union.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; const expectEqual = std.testing.expectEqual; test "flags in packed union" { @@ -106,7 +107,7 @@ test "packed union in packed struct" { fn testPackedUnionInPackedStruct() !void { const ReadRequest = packed struct { key: i32 }; - const RequestType = enum { + const RequestType = enum(u1) { read, insert, }; @@ -169,3 +170,15 @@ test "assigning to non-active field at comptime" { test_bits.bits = .{}; } } + +test "comptime packed union of pointers" { + const U = packed union { + a: *const u32, + b: *const [1]u32, + }; + + const x: u32 = 123; + const u: U = .{ .a = &x }; + + comptime assert(u.b[0] == 123); +} diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index 2539dd886d13..27c6403e77c8 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -621,3 +621,39 @@ test "cast pointers with zero sized elements" { const d: []u8 = c; _ = d; } + +test "comptime pointer equality through distinct fields with well-defined layout" { + const A = extern struct { + x: u32, + z: u16, + }; + const B = extern struct { + x: u16, + y: u16, + z: u16, + }; + + const a: A = .{ + .x = undefined, + .z = 123, + }; + + const ap: *const A = &a; + const bp: *const B = @ptrCast(ap); + + comptime assert(&ap.z == &bp.z); + comptime assert(ap.z == 123); + comptime assert(bp.z == 123); +} + +test "comptime pointer equality through distinct elements with well-defined layout" { + const buf: [2]u32 = .{ 123, 456 }; + + const ptr: *const [2]u32 = &buf; + const byte_ptr: *align(4) const [8]u8 = @ptrCast(ptr); + const second_elem: *const u32 = @ptrCast(byte_ptr[4..8]); + + comptime assert(&buf[1] == second_elem); + comptime assert(buf[1] == 456); + comptime assert(second_elem.* == 456); +} diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig index 215fd447f066..11afc9474a4a 100644 --- a/test/behavior/ptrcast.zig +++ b/test/behavior/ptrcast.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const expect = std.testing.expect; +const assert = std.debug.assert; const native_endian = builtin.target.cpu.arch.endian(); test "reinterpret bytes as integer with nonzero offset" { @@ -277,7 +278,7 @@ test "@ptrCast undefined value at comptime" { } }; comptime { - const x = S.transmute([]u8, i32, undefined); + const x = S.transmute(u64, i32, undefined); _ = x; } } @@ -292,3 +293,60 @@ test "comptime @ptrCast with packed struct leaves value unmodified" { try expect(p.*[0] == 6); try expect(st.three == 6); } + +test "@ptrCast restructures comptime-only array" { + { + const a3a2: [3][2]comptime_int = .{ + .{ 1, 2 }, + .{ 3, 4 }, + .{ 5, 6 }, + }; + const a2a3: *const [2][3]comptime_int = @ptrCast(&a3a2); + comptime assert(a2a3[0][0] == 1); + comptime assert(a2a3[0][1] == 2); + comptime assert(a2a3[0][2] == 3); + comptime assert(a2a3[1][0] == 4); + comptime assert(a2a3[1][1] == 5); + comptime assert(a2a3[1][2] == 6); + } + + { + const a6a1: [6][1]comptime_int = .{ + .{1}, .{2}, .{3}, .{4}, .{5}, .{6}, + }; + const a1a2a3: *const [1][2][3]comptime_int = @ptrCast(&a6a1); + comptime assert(a1a2a3[0][0][0] == 1); + comptime assert(a1a2a3[0][0][1] == 2); + comptime assert(a1a2a3[0][0][2] == 3); + comptime assert(a1a2a3[0][1][0] == 4); + comptime assert(a1a2a3[0][1][1] == 5); + comptime assert(a1a2a3[0][1][2] == 6); + } + + { + const a1: [1]comptime_int = .{123}; + const raw: *const comptime_int = @ptrCast(&a1); + comptime assert(raw.* == 123); + } + + { + const raw: comptime_int = 123; + const a1: *const [1]comptime_int = @ptrCast(&raw); + comptime assert(a1[0] == 123); + } +} + +test "@ptrCast restructures sliced comptime-only array" { + const a3a2: [4][2]comptime_int = .{ + .{ 1, 2 }, + .{ 3, 4 }, + .{ 5, 6 }, + .{ 7, 8 }, + }; + + const sub: *const [4]comptime_int = @ptrCast(a3a2[1..]); + comptime assert(sub[0] == 3); + comptime assert(sub[1] == 4); + comptime assert(sub[2] == 5); + comptime assert(sub[3] == 6); +} diff --git a/test/behavior/type.zig b/test/behavior/type.zig index c9290ecc31ec..ba2964077453 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -758,3 +758,24 @@ test "matching captures causes opaque equivalence" { comptime assert(@TypeOf(a) == @TypeOf(b)); try testing.expect(a == b); } + +test "reify enum where fields refers to part of array" { + const fields: [3]std.builtin.Type.EnumField = .{ + .{ .name = "foo", .value = 0 }, + .{ .name = "bar", .value = 1 }, + undefined, + }; + const E = @Type(.{ .Enum = .{ + .tag_type = u8, + .fields = fields[0..2], + .decls = &.{}, + .is_exhaustive = true, + } }); + var a: E = undefined; + var b: E = undefined; + a = .foo; + b = .bar; + try testing.expect(a == .foo); + try testing.expect(b == .bar); + try testing.expect(a != b); +} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 7cc272fd77fc..5ed336b50b3c 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1532,7 +1532,7 @@ test "reinterpreting enum value inside packed union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const U = packed union { - tag: enum { a, b }, + tag: enum(u8) { a, b }, val: u8, fn doTest() !void { @@ -1850,9 +1850,8 @@ test "reinterpret packed union" { { // Union initialization - var u: U = .{ - .qux = 0xe2a, - }; + var u: U = .{ .baz = 0 }; // ensure all bits are defined + u.qux = 0xe2a; try expectEqual(@as(u8, 0x2a), u.foo); try expectEqual(@as(u12, 0xe2a), u.qux); try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff); diff --git a/test/cases/compile_errors/bad_usingnamespace_transitive_failure.zig b/test/cases/compile_errors/bad_usingnamespace_transitive_failure.zig new file mode 100644 index 000000000000..f48863091b34 --- /dev/null +++ b/test/cases/compile_errors/bad_usingnamespace_transitive_failure.zig @@ -0,0 +1,31 @@ +//! The full test name would be: +//! struct field type resolution marks transitive error from bad usingnamespace in @typeInfo call from non-initial field type +//! +//! This test is rather esoteric. It's ensuring that errors triggered by `@typeInfo` analyzing +//! a bad `usingnamespace` correctly trigger transitive errors when analyzed by struct field type +//! resolution, meaning we don't incorrectly analyze code past the uses of `S`. + +const S = struct { + ok: u32, + bad: @typeInfo(T), +}; + +const T = struct { + pub usingnamespace @compileError("usingnamespace analyzed"); +}; + +comptime { + const a: S = .{ .ok = 123, .bad = undefined }; + _ = a; + @compileError("should not be reached"); +} + +comptime { + const b: S = .{ .ok = 123, .bad = undefined }; + _ = b; + @compileError("should not be reached"); +} + +// error +// +// :14:24: error: usingnamespace analyzed diff --git a/test/cases/compile_errors/bit_ptr_non_packed.zig b/test/cases/compile_errors/bit_ptr_non_packed.zig new file mode 100644 index 000000000000..2b8190836906 --- /dev/null +++ b/test/cases/compile_errors/bit_ptr_non_packed.zig @@ -0,0 +1,22 @@ +export fn entry1() void { + const S = extern struct { x: u32 }; + _ = *align(1:2:8) S; +} + +export fn entry2() void { + const S = struct { x: u32 }; + _ = *align(1:2:@sizeOf(S) * 2) S; +} + +export fn entry3() void { + const E = enum { implicit, backing, type }; + _ = *align(1:2:8) E; +} + +// error +// +// :3:23: error: bit-pointer cannot refer to value of type 'tmp.entry1.S' +// :3:23: note: only packed structs layout are allowed in packed types +// :8:36: error: bit-pointer cannot refer to value of type 'tmp.entry2.S' +// :8:36: note: only packed structs layout are allowed in packed types +// :13:23: error: bit-pointer cannot refer to value of type 'tmp.entry3.E' diff --git a/test/cases/compile_errors/bitcast_undef.zig b/test/cases/compile_errors/bitcast_undef.zig new file mode 100644 index 000000000000..7b4a67167288 --- /dev/null +++ b/test/cases/compile_errors/bitcast_undef.zig @@ -0,0 +1,20 @@ +export fn entry1() void { + const x: i32 = undefined; + const y: u32 = @bitCast(x); + @compileLog(y); +} + +export fn entry2() void { + const x: packed struct { x: u16, y: u16 } = .{ .x = 123, .y = undefined }; + const y: u32 = @bitCast(x); + @compileLog(y); +} + +// error +// +// :4:5: error: found compile log statement +// :10:5: note: also here +// +// Compile Log Output: +// @as(u32, undefined) +// @as(u32, undefined) diff --git a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig index 32076d3705e1..7120c2dec28c 100644 --- a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig +++ b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig @@ -9,4 +9,4 @@ export fn entry() void { // :2:5: error: found compile log statement // // Compile Log Output: -// @as(*const anyopaque, &tmp.entry) +// @as(*const anyopaque, @as(*const anyopaque, @ptrCast(tmp.entry))) diff --git a/test/cases/compile_errors/comptime_dereference_slice_of_struct.zig b/test/cases/compile_errors/comptime_dereference_slice_of_struct.zig deleted file mode 100644 index 8d3619d90627..000000000000 --- a/test/cases/compile_errors/comptime_dereference_slice_of_struct.zig +++ /dev/null @@ -1,13 +0,0 @@ -const MyStruct = struct { x: bool = false }; - -comptime { - const x = &[_]MyStruct{ .{}, .{} }; - const y = x[0..1] ++ &[_]MyStruct{}; - _ = y; -} - -// error -// backend=stage2 -// target=native -// -// :5:16: error: comptime dereference requires '[1]tmp.MyStruct' to have a well-defined layout, but it does not. diff --git a/test/cases/compile_errors/dereferencing_invalid_payload_ptr_at_comptime.zig b/test/cases/compile_errors/dereferencing_invalid_payload_ptr_at_comptime.zig index 17117713ed60..b1a7d5daa60c 100644 --- a/test/cases/compile_errors/dereferencing_invalid_payload_ptr_at_comptime.zig +++ b/test/cases/compile_errors/dereferencing_invalid_payload_ptr_at_comptime.zig @@ -6,7 +6,7 @@ comptime { const payload_ptr = &opt_ptr.?; opt_ptr = null; - _ = payload_ptr.*.*; + _ = payload_ptr.*.*; // TODO: this case was regressed by #19630 } comptime { var opt: ?u8 = 15; @@ -28,6 +28,5 @@ comptime { // backend=stage2 // target=native // -// :9:20: error: attempt to use null value // :16:20: error: attempt to use null value // :24:20: error: attempt to unwrap error: Foo diff --git a/test/cases/compile_errors/function_call_assigned_to_incorrect_type.zig b/test/cases/compile_errors/function_call_assigned_to_incorrect_type.zig index 1060987b9a19..e2bfd4fd6d1a 100644 --- a/test/cases/compile_errors/function_call_assigned_to_incorrect_type.zig +++ b/test/cases/compile_errors/function_call_assigned_to_incorrect_type.zig @@ -11,4 +11,5 @@ fn concat() [16]f32 { // target=native // // :3:17: error: expected type '[4]f32', found '[16]f32' -// :3:17: note: array of length 16 cannot cast into an array of length 4 +// :3:17: note: destination has length 4 +// :3:17: note: source has length 16 diff --git a/test/cases/compile_errors/issue_7810-comptime_slice-len_increment_beyond_bounds.zig b/test/cases/compile_errors/issue_7810-comptime_slice-len_increment_beyond_bounds.zig index 8c014ec19137..c83ce59e21da 100644 --- a/test/cases/compile_errors/issue_7810-comptime_slice-len_increment_beyond_bounds.zig +++ b/test/cases/compile_errors/issue_7810-comptime_slice-len_increment_beyond_bounds.zig @@ -8,7 +8,5 @@ export fn foo_slice_len_increment_beyond_bounds() void { } // error -// backend=stage2 -// target=native // -// :6:16: error: comptime store of index 8 out of bounds of array length 8 +// :6:16: error: dereference of '*u8' exceeds bounds of containing decl of type '[8]u8' diff --git a/test/cases/compile_errors/overflow_arithmetic_on_vector_with_undefined_elems.zig b/test/cases/compile_errors/overflow_arithmetic_on_vector_with_undefined_elems.zig new file mode 100644 index 000000000000..8055756a114e --- /dev/null +++ b/test/cases/compile_errors/overflow_arithmetic_on_vector_with_undefined_elems.zig @@ -0,0 +1,26 @@ +comptime { + const a: @Vector(3, u8) = .{ 1, 200, undefined }; + @compileLog(@addWithOverflow(a, a)); +} + +comptime { + const a: @Vector(3, u8) = .{ 1, 2, undefined }; + const b: @Vector(3, u8) = .{ 0, 3, 10 }; + @compileLog(@subWithOverflow(a, b)); +} + +comptime { + const a: @Vector(3, u8) = .{ 1, 200, undefined }; + @compileLog(@mulWithOverflow(a, a)); +} + +// error +// +// :3:5: error: found compile log statement +// :9:5: note: also here +// :14:5: note: also here +// +// Compile Log Output: +// @as(struct{@Vector(3, u8), @Vector(3, u1)}, .{ .{ 2, 144, undefined }, .{ 0, 1, undefined } }) +// @as(struct{@Vector(3, u8), @Vector(3, u1)}, .{ .{ 1, 255, undefined }, .{ 0, 1, undefined } }) +// @as(struct{@Vector(3, u8), @Vector(3, u1)}, .{ .{ 1, 64, undefined }, .{ 0, 1, undefined } }) diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig index b4b8b626f585..fe86990d31ea 100644 --- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig @@ -30,7 +30,7 @@ export fn entry6() void { } export fn entry7() void { _ = @sizeOf(packed struct { - x: enum { A, B }, + x: enum(u1) { A, B }, }); } export fn entry8() void { @@ -70,6 +70,12 @@ export fn entry13() void { x: *type, }); } +export fn entry14() void { + const E = enum { implicit, backing, type }; + _ = @sizeOf(packed struct { + x: E, + }); +} // error // backend=llvm @@ -97,3 +103,5 @@ export fn entry13() void { // :70:12: error: packed structs cannot contain fields of type '*type' // :70:12: note: comptime-only pointer has no guaranteed in-memory representation // :70:12: note: types are not available at runtime +// :76:12: error: packed structs cannot contain fields of type 'tmp.entry14.E' +// :74:15: note: enum declared here diff --git a/test/cases/compile_errors/pointer_exceeds_containing_value.zig b/test/cases/compile_errors/pointer_exceeds_containing_value.zig new file mode 100644 index 000000000000..d9e9376848d4 --- /dev/null +++ b/test/cases/compile_errors/pointer_exceeds_containing_value.zig @@ -0,0 +1,19 @@ +export fn entry1() void { + const x: u32 = 123; + const ptr: [*]const u32 = @ptrCast(&x); + _ = ptr - 1; +} + +export fn entry2() void { + const S = extern struct { x: u32, y: u32 }; + const y: u32 = 123; + const parent_ptr: *const S = @fieldParentPtr("y", &y); + _ = parent_ptr; +} + +// error +// +// :4:13: error: pointer computation here causes undefined behavior +// :4:13: note: resulting pointer exceeds bounds of containing value which may trigger overflow +// :10:55: error: pointer computation here causes undefined behavior +// :10:55: note: resulting pointer exceeds bounds of containing value which may trigger overflow diff --git a/test/cases/compile_errors/reading_past_end_of_pointer_casted_array.zig b/test/cases/compile_errors/reading_past_end_of_pointer_casted_array.zig index b06b54198485..3dda6c063abf 100644 --- a/test/cases/compile_errors/reading_past_end_of_pointer_casted_array.zig +++ b/test/cases/compile_errors/reading_past_end_of_pointer_casted_array.zig @@ -3,6 +3,13 @@ comptime { const sub_array = array[1..]; const int_ptr: *const u24 = @ptrCast(@alignCast(sub_array)); const deref = int_ptr.*; + _ = deref; // TODO: this case was regressed by #19630, because pointer loads are currently performed at the bit level. +} +comptime { + const array: [4]u8 = "aoeu".*; + const sub_array = array[1..]; + const int_ptr: *const u32 = @ptrCast(@alignCast(sub_array)); + const deref = int_ptr.*; _ = deref; } @@ -10,4 +17,4 @@ comptime { // backend=stage2 // target=native // -// :5:26: error: dereference of '*const u24' exceeds bounds of containing decl of type '[4]u8' +// :12:26: error: dereference of '*const u32' exceeds bounds of containing decl of type '[4]u8' diff --git a/test/cases/compile_errors/slice_cannot_have_its_bytes_reinterpreted.zig b/test/cases/compile_errors/slice_cannot_have_its_bytes_reinterpreted.zig index 31fe909a4b87..fee0c2586d1a 100644 --- a/test/cases/compile_errors/slice_cannot_have_its_bytes_reinterpreted.zig +++ b/test/cases/compile_errors/slice_cannot_have_its_bytes_reinterpreted.zig @@ -7,4 +7,4 @@ export fn foo() void { // backend=stage2 // target=native // -// :3:49: error: comptime dereference requires '[]const u8' to have a well-defined layout, but it does not. +// :3:49: error: comptime dereference requires '[]const u8' to have a well-defined layout diff --git a/test/cases/comptime_aggregate_print.zig b/test/cases/comptime_aggregate_print.zig index 88206398b030..926b52a4daf2 100644 --- a/test/cases/comptime_aggregate_print.zig +++ b/test/cases/comptime_aggregate_print.zig @@ -31,5 +31,5 @@ pub fn main() !void {} // :20:5: error: found compile log statement // // Compile Log Output: -// @as([]i32, &(comptime alloc).buf[0..2]) -// @as([]i32, &(comptime alloc).buf[0..2]) +// @as([]i32, @as([*]i32, @ptrCast(@as(tmp.UnionContainer, .{ .buf = .{ 1, 2 } }).buf[0]))[0..2]) +// @as([]i32, @as([*]i32, @ptrCast(@as(tmp.StructContainer, .{ .buf = .{ 3, 4 } }).buf[0]))[0..2])