From d0e74ffe52d0ae0d876d4e3f7ef5d32b5f5460a5 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 8 Apr 2024 16:14:39 +0100 Subject: [PATCH] compiler: rework comptime pointer representation and access We've got a big one here! This commit reworks how we represent pointers in the InternPool, and rewrites the logic for loading and storing from them at comptime. Firstly, the pointer representation. Previously, pointers were represented in a highly structured manner: pointers to fields, array elements, etc, were explicitly represented. This works well for simple cases, but is quite difficult to handle in the cases of unusual reinterpretations, pointer casts, offsets, etc. Therefore, pointers are now represented in a more "flat" manner. For types without well-defined layouts -- such as comptime-only types, automatic-layout aggregates, and so on -- we still use this "hierarchical" structure. However, for types with well-defined layouts, we use a byte offset associated with the pointer. This allows the comptime pointer access logic to deal with reinterpreted pointers far more gracefully, because the "base address" of a pointer -- for instance a `field` -- is a single value which pointer accesses cannot exceed since the parent has undefined layout. This strategy is also more useful to most backends -- see the updated logic in `codegen.zig` and `codegen/llvm.zig`. For backends which do prefer a chain of field and elements accesses for lowering pointer values, such as SPIR-V, there is a helpful function in `Value` which creates a strategy to derive a pointer value using ideally only field and element accesses. This is actually more correct than the previous logic, since it correctly handles pointer casts which, after the dust has settled, end up referring exactly to an aggregate field or array element. In terms of the pointer access code, it has been rewritten from the ground up. The old logic had become rather a mess of special cases being added whenever bugs were hit, and was still riddled with bugs. The new logic was written to handle the "difficult" cases correctly, the most notable of which is restructuring of a comptime-only array (for instance, converting a `[3][2]comptime_int` to a `[2][3]comptime_int`. Currently, the logic for loading and storing work somewhat differently, but a future change will likely improve the loading logic to bring it more in line with the store strategy. As far as I can tell, the rewrite has fixed all bugs exposed by #19414. As a part of this, the comptime bitcast logic has also been rewritten. Previously, bitcasts simply worked by serializing the entire value into an in-memory buffer, then deserializing it. This strategy has two key weaknesses: pointers, and undefined values. Representations of these values at comptime cannot be easily serialized/deserialized whilst preserving data, which means many bitcasts would become runtime-known if pointers were involved, or would turn `undefined` values into `0xAA`. The new logic works by "flattening" the datastructure to be cast into a sequence of bit-packed atomic values, and then "unflattening" it; using serialization when necessary, but with special handling for `undefined` values and for pointers which align in virtual memory. The resulting code is definitely slower -- more on this later -- but it is correct. The pointer access and bitcast logic required some helper functions and types which are not generally useful elsewhere, so I opted to split them into separate files `Sema/comptime_ptr_access.zig` and `Sema/bitcast.zig`, with simple re-exports in `Sema.zig` for their small public APIs. Whilst working on this branch, I caught various unrelated bugs with transitive Sema errors, and with the handling of `undefined` values. These bugs have been fixed, and corresponding behavior test added. In terms of performance, I do anticipate that this commit will regress performance somewhat, because the new pointer access and bitcast logic is necessarily more complex. I have not yet taken performance measurements, but will do shortly, and post the results in this PR. If the performance regression is severe, I will do work to to optimize the new logic before merge. Resolves: #19452 Resolves: #19460 --- lib/compiler/resinator/ico.zig | 4 +- lib/docs/wasm/markdown/Document.zig | 2 +- lib/std/net.zig | 11 +- lib/std/packed_int_array.zig | 8 + src/InternPool.zig | 452 +++- src/Module.zig | 60 +- src/Sema.zig | 2410 ++++++----------- src/Sema/bitcast.zig | 756 ++++++ src/Sema/comptime_ptr_access.zig | 1059 ++++++++ src/Value.zig | 795 +++++- src/arch/wasm/CodeGen.zig | 123 +- src/arch/x86_64/CodeGen.zig | 8 +- src/codegen.zig | 134 +- src/codegen/c.zig | 220 +- src/codegen/llvm.zig | 223 +- src/codegen/spirv.zig | 132 +- src/link/Wasm/ZigObject.zig | 1 - src/mutable_value.zig | 149 +- src/print_air.zig | 2 +- src/print_value.zig | 173 +- src/type.zig | 102 +- test/behavior/bitcast.zig | 58 + test/behavior/cast_int.zig | 4 +- test/behavior/comptime_memory.zig | 64 +- test/behavior/error.zig | 23 + test/behavior/optional.zig | 36 +- test/behavior/packed-struct.zig | 2 +- test/behavior/packed-union.zig | 15 +- test/behavior/pointers.zig | 36 + test/behavior/ptrcast.zig | 60 +- test/behavior/type.zig | 21 + test/behavior/union.zig | 7 +- .../bad_usingnamespace_transitive_failure.zig | 31 + .../compile_errors/bit_ptr_non_packed.zig | 22 + test/cases/compile_errors/bitcast_undef.zig | 20 + ...mpile_log_a_pointer_to_an_opaque_value.zig | 2 +- .../comptime_dereference_slice_of_struct.zig | 13 - ...encing_invalid_payload_ptr_at_comptime.zig | 3 +- ...nction_call_assigned_to_incorrect_type.zig | 3 +- ...time_slice-len_increment_beyond_bounds.zig | 4 +- ...thmetic_on_vector_with_undefined_elems.zig | 26 + ...truct_with_fields_of_not_allowed_types.zig | 10 +- .../pointer_exceeds_containing_value.zig | 19 + ...ading_past_end_of_pointer_casted_array.zig | 8 + ...ce_cannot_have_its_bytes_reinterpreted.zig | 2 +- test/cases/comptime_aggregate_print.zig | 4 +- 46 files changed, 4794 insertions(+), 2523 deletions(-) create mode 100644 src/Sema/bitcast.zig create mode 100644 src/Sema/comptime_ptr_access.zig create mode 100644 test/cases/compile_errors/bad_usingnamespace_transitive_failure.zig create mode 100644 test/cases/compile_errors/bit_ptr_non_packed.zig create mode 100644 test/cases/compile_errors/bitcast_undef.zig delete mode 100644 test/cases/compile_errors/comptime_dereference_slice_of_struct.zig create mode 100644 test/cases/compile_errors/overflow_arithmetic_on_vector_with_undefined_elems.zig create mode 100644 test/cases/compile_errors/pointer_exceeds_containing_value.zig diff --git a/lib/compiler/resinator/ico.zig b/lib/compiler/resinator/ico.zig index 310db6ece289..664def038be4 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, @@ -272,7 +272,7 @@ pub const BitmapHeader = extern struct { } /// https://en.wikipedia.org/wiki/BMP_file_format#DIB_header_(bitmap_information_header) - pub const Version = enum { + pub const Version = enum(u3) { unknown, @"win2.0", // Windows 2.0 or later @"nt3.1", // Windows NT, 3.1x or later diff --git a/lib/docs/wasm/markdown/Document.zig b/lib/docs/wasm/markdown/Document.zig index f3c0fdeed064..8eec97415c0e 100644 --- a/lib/docs/wasm/markdown/Document.zig +++ b/lib/docs/wasm/markdown/Document.zig @@ -131,7 +131,7 @@ pub const Node = struct { } }; - pub const TableCellAlignment = enum { + pub const TableCellAlignment = enum(u2) { unset, left, center, 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..e62bdc849962 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), }; }, @@ -2271,6 +2326,46 @@ pub const LoadedStructType = struct { .struct_type = s, }; } + + pub const ReverseRuntimeOrderIterator = struct { + ip: *InternPool, + last_index: u32, + struct_type: InternPool.LoadedStructType, + + pub fn next(it: *@This()) ?u32 { + if (it.last_index == 0) + return null; + + if (it.struct_type.hasReorderedFields()) { + it.last_index -= 1; + const order = it.struct_type.runtime_order.get(it.ip); + while (order[it.last_index] == .omitted) { + it.last_index -= 1; + if (it.last_index == 0) + return null; + } + return order[it.last_index].toInt(); + } + + it.last_index -= 1; + while (it.struct_type.fieldIsComptime(it.ip, it.last_index)) { + it.last_index -= 1; + if (it.last_index == 0) + return null; + } + + return it.last_index; + } + }; + + pub fn iterateRuntimeOrderReverse(s: @This(), ip: *InternPool) ReverseRuntimeOrderIterator { + assert(s.layout != .@"packed"); + return .{ + .ip = ip, + .last_index = s.field_types.len, + .struct_type = s, + }; + } }; pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { @@ -2836,7 +2931,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 +3399,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 +3592,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 +4248,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 +4286,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 +4773,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 +4831,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 +5389,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 +5432,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 +5482,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 +7741,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 +7789,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 +7848,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 +7864,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 +8351,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 +9024,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..60f61947ecd0 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); @@ -28268,21 +28302,21 @@ fn unionFieldVal( field_name_src: LazySrcLoc, union_ty: Type, ) CompileError!Air.Inst.Ref { - const mod = sema.mod; - const ip = &mod.intern_pool; - assert(union_ty.zigTypeTag(mod) == .Union); + const zcu = sema.mod; + const ip = &zcu.intern_pool; + assert(union_ty.zigTypeTag(zcu) == .Union); try sema.resolveTypeFields(union_ty); - const union_obj = mod.typeToUnion(union_ty).?; + const union_obj = zcu.typeToUnion(union_ty).?; const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]); - const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_ty).enumFieldIndex(field_name, mod).?); + const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_ty).enumFieldIndex(field_name, zcu).?); if (try sema.resolveValue(union_byval)) |union_val| { - if (union_val.isUndef(mod)) return mod.undefRef(field_ty); + if (union_val.isUndef(zcu)) return zcu.undefRef(field_ty); const un = ip.indexToKey(union_val.toIntern()).un; - const field_tag = try mod.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index); + const field_tag = try zcu.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index); const tag_matches = un.tag == field_tag.toIntern(); switch (union_obj.getLayout(ip)) { .auto => { @@ -28290,8 +28324,8 @@ fn unionFieldVal( return Air.internedToRef(un.val); } else { const msg = msg: { - const active_index = Type.fromInterned(union_obj.enum_tag_ty).enumTagFieldIndex(Value.fromInterned(un.tag), mod).?; - const active_field_name = Type.fromInterned(union_obj.enum_tag_ty).enumFieldName(active_index, mod); + const active_index = Type.fromInterned(union_obj.enum_tag_ty).enumTagFieldIndex(Value.fromInterned(un.tag), zcu).?; + const active_field_name = Type.fromInterned(union_obj.enum_tag_ty).enumFieldName(active_index, zcu); const msg = try sema.errMsg(block, src, "access of union field '{}' while field '{}' is active", .{ field_name.fmt(ip), active_field_name.fmt(ip), }); @@ -28302,33 +28336,31 @@ fn unionFieldVal( return sema.failWithOwnedErrorMsg(block, msg); } }, - .@"packed", .@"extern" => |layout| { - if (tag_matches) { - 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()); - } - } + .@"extern" => if (tag_matches) { + // Fast path - no need to use bitcast logic. + return Air.internedToRef(un.val); + } else if (try sema.bitCastVal(union_val, field_ty, 0, 0, 0)) |field_val| { + return Air.internedToRef(field_val.toIntern()); + }, + .@"packed" => if (tag_matches) { + // Fast path - no need to use bitcast logic. + return Air.internedToRef(un.val); + } else if (try sema.bitCastVal(union_val, field_ty, 0, try union_ty.bitSizeAdvanced(zcu, sema), 0)) |field_val| { + return Air.internedToRef(field_val.toIntern()); }, } } try sema.requireRuntimeBlock(block, src, null); if (union_obj.getLayout(ip) == .auto and block.wantSafety() and - union_ty.unionTagTypeSafety(mod) != null and union_obj.field_types.len > 1) + union_ty.unionTagTypeSafety(zcu) != null and union_obj.field_types.len > 1) { - const wanted_tag_val = try mod.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index); + const wanted_tag_val = try zcu.enumValueFieldIndex(Type.fromInterned(union_obj.enum_tag_ty), enum_field_index); const wanted_tag = Air.internedToRef(wanted_tag_val.toIntern()); const active_tag = try block.addTyOp(.get_union_tag, Type.fromInterned(union_obj.enum_tag_ty), union_byval); try sema.panicInactiveUnionField(block, src, active_tag, wanted_tag); } - if (field_ty.zigTypeTag(mod) == .NoReturn) { + if (field_ty.zigTypeTag(zcu) == .NoReturn) { _ = try block.addNoOp(.unreach); return .unreachable_value; } @@ -28402,8 +28434,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 +28496,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 +28602,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 +28775,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 +28832,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 +28892,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 +28971,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 +28995,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 +29014,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 +29029,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 +29048,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 +29061,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 +29078,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 +29100,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 +29139,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 +29156,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 +29179,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 +29193,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 +29209,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 +29225,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 +29235,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 +29247,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 +29259,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 +29269,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 +29310,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 +29337,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 +29356,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 +29375,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 +29387,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 +29422,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 +29430,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 +29506,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 +29563,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 +29576,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 +29617,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 +29795,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 +29921,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 +30012,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 +30122,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 +30461,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 +30494,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 +30513,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 +30539,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 +30838,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 +30874,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 +30934,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 +30984,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, 0, 0)) |result_val| { return Air.internedToRef(result_val.toIntern()); } } @@ -31648,98 +31019,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 +31164,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 +31874,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 +31952,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 +32382,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 +32399,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 +32413,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 +32508,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 +32553,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 +32613,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 +32627,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 +32651,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 +34878,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 +34888,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 +34899,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 +34925,7 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value { } }, else => unreachable, }, + .byte_offset = ptr.byte_offset, } }))); }, } @@ -36166,7 +35438,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 +35769,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 +35802,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 +35849,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 +36688,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 +37455,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 +37545,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 +37572,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 +37642,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 +37664,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 +37972,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 +38063,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 +38085,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 +38230,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 +38332,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 +38444,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 +38474,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 +38585,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..7be51e87d8ef --- /dev/null +++ b/src/Sema/bitcast.zig @@ -0,0 +1,756 @@ +//! This file contains logic for bit-casting arbitrary values at comptime, including splicing +//! bits together for comptime stores of bit-pointers. The strategy is to "flatten" values to +//! a sequence of values in *packed* memory, and then unflatten through a combination of special +//! cases (particularly for pointers and `undefined` values) and in-memory buffer reinterprets. +//! +//! This is a little awkward on big-endian targets, as non-packed datastructures (e.g. `extern struct`) +//! have their fields reversed when represented as packed memory on such targets. + +/// If `host_bits` is `0`, attempts to convert the memory at offset +/// `byte_offset` into `val` to a non-packed value of type `dest_ty`, +/// ignoring `bit_offset`. +/// +/// Otherwise, `byte_offset` is an offset in bytes into `val` to a +/// non-packed value consisting of `host_bits` bits. A value of type +/// `dest_ty` will be interpreted at a packed offset of `bit_offset` +/// into this value. +/// +/// Returns `null` if the operation must be performed at runtime. +pub fn bitCast( + sema: *Sema, + val: Value, + dest_ty: Type, + byte_offset: u64, + host_bits: u64, + bit_offset: u64, +) CompileError!?Value { + return bitCastInner(sema, val, dest_ty, byte_offset, host_bits, 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` into `val`, +/// replacing overlapping bits and returning the modified value. +/// +/// If `host_bits` is `0`, splices `splice_val` at an offset +/// `byte_offset` bytes into the virtual memory of `val`, ignoring +/// `bit_offset`. +/// +/// Otherwise, `byte_offset` is an offset into bytes into `val` to +/// a non-packed value consisting of `host_bits` bits. The value +/// `splice_val` will be placed at a packed offset of `bit_offset` +/// into this value. +pub fn bitCastSplice( + sema: *Sema, + val: Value, + splice_val: Value, + byte_offset: u64, + host_bits: u64, + bit_offset: u64, +) CompileError!?Value { + return bitCastSpliceInner(sema, val, splice_val, byte_offset, host_bits, 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, + byte_offset: u64, + host_bits: u64, + bit_offset: u64, +) BitCastError!Value { + const zcu = sema.mod; + const endian = zcu.getTarget().cpu.arch.endian(); + + 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)); + + const abi_pad_bits, const host_pad_bits = if (host_bits > 0) + .{ val_ty.abiSize(zcu) * 8 - host_bits, host_bits - val_ty.bitSize(zcu) } + else + .{ val_ty.abiSize(zcu) * 8 - val_ty.bitSize(zcu), 0 }; + + const skip_bits = switch (endian) { + .little => bit_offset + byte_offset * 8, + .big => if (host_bits > 0) + val_ty.abiSize(zcu) * 8 - byte_offset * 8 - host_bits + bit_offset + else + val_ty.abiSize(zcu) * 8 - byte_offset * 8 - dest_ty.bitSize(zcu), + }; + + var unpack: UnpackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .skip_bits = skip_bits, + .remaining_bits = dest_ty.bitSize(zcu), + .unpacked = std.ArrayList(InternPool.Index).init(sema.arena), + }; + switch (endian) { + .little => { + try unpack.add(val); + try unpack.padding(abi_pad_bits); + }, + .big => { + try unpack.padding(abi_pad_bits); + try unpack.add(val); + }, + } + try unpack.padding(host_pad_bits); + + 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, + byte_offset: u64, + host_bits: u64, + bit_offset: u64, +) BitCastError!Value { + const zcu = sema.mod; + const endian = zcu.getTarget().cpu.arch.endian(); + 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); + + const splice_offset = switch (endian) { + .little => bit_offset + byte_offset * 8, + .big => if (host_bits > 0) + val_ty.abiSize(zcu) * 8 - byte_offset * 8 - host_bits + bit_offset + else + val_ty.abiSize(zcu) * 8 - byte_offset * 8 - splice_bits, + }; + + assert(splice_offset + splice_bits <= val_ty.abiSize(zcu) * 8); + + const abi_pad_bits, const host_pad_bits = if (host_bits > 0) + .{ val_ty.abiSize(zcu) * 8 - host_bits, host_bits - val_ty.bitSize(zcu) } + else + .{ val_ty.abiSize(zcu) * 8 - val_ty.bitSize(zcu), 0 }; + + var unpack: UnpackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .skip_bits = 0, + .remaining_bits = splice_offset, + .unpacked = std.ArrayList(InternPool.Index).init(sema.arena), + }; + switch (endian) { + .little => { + try unpack.add(val); + try unpack.padding(abi_pad_bits); + }, + .big => { + try unpack.padding(abi_pad_bits); + try unpack.add(val); + }, + } + try unpack.padding(host_pad_bits); + + unpack.remaining_bits = splice_bits; + try unpack.add(splice_val); + + unpack.skip_bits = splice_offset + splice_bits; + unpack.remaining_bits = val_ty.abiSize(zcu) * 8 - splice_offset - splice_bits; + switch (endian) { + .little => { + try unpack.add(val); + try unpack.padding(abi_pad_bits); + }, + .big => { + try unpack.padding(abi_pad_bits); + try unpack.add(val); + }, + } + try unpack.padding(host_pad_bits); + + var pack: PackValueBits = .{ + .zcu = zcu, + .arena = sema.arena, + .unpacked = unpack.unpacked.items, + }; + switch (endian) { + .little => {}, + .big => try pack.padding(abi_pad_bits), + } + return pack.get(val_ty); +} + +/// Recurses through struct fields, array elements, etc, to get a sequence of "primitive" values +/// which are bit-packed in memory to represent a single value. `unpacked` represents a series +/// of values in *packed* memory - therefore, on big-endian targets, the first element of this +/// list contains bits from the *final* byte of the value. +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 endian = zcu.getTarget().cpu.arch.endian(); + 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 => { + const len: usize = @intCast(ty.arrayLen(zcu)); + for (0..len) |i| { + // We reverse vector elements in packed memory on BE targets. + const real_idx = switch (endian) { + .little => i, + .big => len - i - 1, + }; + const elem_val = try val.elemValue(zcu, real_idx); + 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. + // Elements are reversed in packed memory on BE targets. + 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); + + if (endian == .big) if (maybe_sent) |s| { + try unpack.add(s); + if (len != 0) try unpack.padding(pad_bits); + }; + + for (0..@intCast(len)) |i| { + // We reverse array elements in packed memory on BE targets. + const real_idx = switch (endian) { + .little => i, + .big => len - i - 1, + }; + const elem_val = try val.elemValue(zcu, @intCast(real_idx)); + try unpack.add(elem_val); + if (i != len - 1) try unpack.padding(pad_bits); + } + + if (endian == .little) if (maybe_sent) |s| { + if (len != 0) try unpack.padding(pad_bits); + try unpack.add(s); + }; + }, + .Struct => switch (ty.containerLayout(zcu)) { + .auto => unreachable, // ill-defined layout + .@"extern" => switch (endian) { + .little => { + 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); + }, + .big => { + var cur_bit_off: u64 = bit_size; + var it = zcu.typeToStruct(ty).?.iterateRuntimeOrderReverse(ip); + while (it.next()) |field_idx| { + const field_val = try val.fieldValue(zcu, field_idx); + const field_ty = field_val.typeOf(zcu); + const want_bit_off = ty.structFieldOffset(field_idx, zcu) * 8 + field_ty.bitSize(zcu); + const pad_bits = cur_bit_off - want_bit_off; + try unpack.padding(pad_bits); + try unpack.add(field_val); + cur_bit_off = want_bit_off - field_ty.bitSize(zcu); + } + assert(cur_bit_off == 0); + }, + }, + .@"packed" => { + // Just add all fields in order. There are no padding bits. + // This is identical between LE and BE targets. + 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 here! + // 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); + const pad_bits = bit_size - payload_val.typeOf(zcu).bitSize(zcu); + if (endian == .little or ty.containerLayout(zcu) == .@"packed") { + try unpack.add(payload_val); + try unpack.padding(pad_bits); + } else { + try unpack.padding(pad_bits); + try unpack.add(payload_val); + } + }, + } + } + + 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 + } + } +}; + +/// Given a sequence of bit-packed values in packed memory (see `UnpackValueBits`), +/// reconstructs a value of an arbitrary type, with correct handling of `undefined` +/// values and of pointers which align in virtual memory. +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 endian = zcu.getTarget().cpu.arch.endian(); + 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)); + // We reverse vector elements in packed memory on BE targets. + switch (endian) { + .little => for (elems) |*elem| { + elem.* = (try pack.get(elem_ty)).toIntern(); + }, + .big => { + var i = elems.len; + while (i > 0) { + i -= 1; + elems[i] = (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)); + + if (endian == .big and maybe_sent != null) { + // TODO: validate sentinel was preserved! + try pack.padding(elem_ty.bitSize(zcu)); + if (len != 0) try pack.padding(pad_bits); + } + + for (0..elems.len) |i| { + const real_idx = switch (endian) { + .little => i, + .big => len - i - 1, + }; + elems[@intCast(real_idx)] = (try pack.get(elem_ty)).toIntern(); + if (i != len - 1) try pack.padding(pad_bits); + } + + if (endian == .little and maybe_sent != null) { + // TODO: validate sentinel was preserved! + if (len != 0) try pack.padding(pad_bits); + 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); + switch (endian) { + .little => { + 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); + }, + .big => { + var cur_bit_off: u64 = ty.bitSize(zcu); + var it = zcu.typeToStruct(ty).?.iterateRuntimeOrderReverse(ip); + while (it.next()) |field_idx| { + const field_ty = ty.structFieldType(field_idx, zcu); + const want_bit_off = ty.structFieldOffset(field_idx, zcu) * 8 + field_ty.bitSize(zcu); + try pack.padding(cur_bit_off - want_bit_off); + elems[field_idx] = (try pack.get(field_ty)).toIntern(); + cur_bit_off = want_bit_off - field_ty.bitSize(zcu); + } + assert(cur_bit_off == 0); + }, + } + // 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. + // This is identical between LE and BE targets. + 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 bit 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.bitSize(ctx.zcu) > b_ty.bitSize(ctx.zcu); + } + }; + std.mem.sortUnstable(u32, field_order, SizeSortCtx{ + .zcu = zcu, + .field_types = zcu.typeToUnion(ty).?.field_types.get(ip), + }, SizeSortCtx.lessThan); + + const padding_after = endian == .little or ty.containerLayout(zcu) == .@"packed"; + + for (field_order) |field_idx| { + const field_ty = Type.fromInterned(zcu.typeToUnion(ty).?.field_types.get(ip)[field_idx]); + const pad_bits = ty.bitSize(zcu) - field_ty.bitSize(zcu); + if (!padding_after) try pack.padding(pad_bits); + 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 (padding_after) try pack.padding(pad_bits); + 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..c1fb54456132 --- /dev/null +++ b/src/Sema/comptime_ptr_access.zig @@ -0,0 +1,1059 @@ +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_bits = switch (ptr_info.flags.vector_index) { + .none => ptr_info.packed_offset.host_size * 8, + else => ptr_info.packed_offset.host_size * Type.fromInterned(ptr_info.child).bitSize(zcu), + }; + const bit_offset = if (host_bits != 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| switch (zcu.getTarget().cpu.arch.endian()) { + .little => child_bits * @intFromEnum(idx), + .big => host_bits - child_bits * (@intFromEnum(idx) + 1), // element order reversed on big endian + }, + }; + if (child_bits + bit_offset > host_bits) { + return .exceeds_host_size; + } + break :bit_offset bit_offset; + } else 0; + return loadComptimePtrInner(sema, block, src, ptr, bit_offset, host_bits, 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); + // TODO: host size for vectors is terrible + const host_bits = switch (ptr_info.flags.vector_index) { + .none => ptr_info.packed_offset.host_size * 8, + else => ptr_info.packed_offset.host_size * 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_store, + else => |idx| switch (zcu.getTarget().cpu.arch.endian()) { + .little => Type.fromInterned(ptr_info.child).bitSize(zcu) * @intFromEnum(idx), + .big => host_bits - Type.fromInterned(ptr_info.child).bitSize(zcu) * (@intFromEnum(idx) + 1), // element order reversed on big endian + }, + }; + const pseudo_store_ty = if (host_bits > 0) t: { + const need_bits = Type.fromInterned(ptr_info.child).bitSize(zcu); + if (need_bits + bit_offset > host_bits) { + return .exceeds_host_size; + } + break :t try zcu.intType(.unsigned, @intCast(host_bits)); + } 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 (host_bits == 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, + host_bits, + 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, + host_bits: 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, 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, 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, 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, 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 host_bits == 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 (host_bits != 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_bytes = if (host_bits > 0) (host_bits + 7) / 8 else try sema.typeAbiSize(load_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.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 host_bits == 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, + host_bits, + 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..20674ff7d9cc 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,660 @@ 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 = Type.fromInterned(union_obj.field_types.get(&zcu.intern_pool)[field_idx]); + switch (aggregate_ty.containerLayout(zcu)) { + .auto => break :field .{ field_ty, try aggregate_ty.structFieldAlignAdvanced(@intCast(field_idx), zcu, sema) }, + .@"extern" => { + // Point to the same address. + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + break :info new; + }); + return zcu.getCoerced(parent_ptr, result_ty); + }, + .@"packed" => { + // If the field has an ABI size matching its bit size, then we can continue to use a + // non-bit pointer if the parent pointer is also a non-bit pointer. + if (parent_ptr_info.packed_offset.host_size == 0 and try sema.typeAbiSize(field_ty) * 8 == try field_ty.bitSizeAdvanced(zcu, sema)) { + // We must offset the pointer on big-endian targets, since the bits of packed memory don't align nicely. + const byte_offset = switch (zcu.getTarget().cpu.arch.endian()) { + .little => 0, + .big => try sema.typeAbiSize(aggregate_ty) - try sema.typeAbiSize(field_ty), + }; + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + new.flags.alignment = InternPool.Alignment.fromLog2Units( + @ctz(byte_offset | (try parent_ptr_ty.ptrAlignmentAdvanced(zcu, sema)).toByteUnits().?), + ); + break :info new; + }); + return parent_ptr.getOffsetPtr(byte_offset, result_ty, zcu); + } else { + // The result must be a bit-pointer if it is not already. + const result_ty = try sema.ptrType(info: { + var new = parent_ptr_info; + new.child = field_ty.toIntern(); + if (new.packed_offset.host_size == 0) { + new.packed_offset.host_size = @intCast(((try aggregate_ty.bitSizeAdvanced(zcu, sema)) + 7) / 8); + assert(new.packed_offset.bit_offset == 0); + } + 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 9e18dbb42ee3..0681bc975d59 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| { @@ -17877,8 +17877,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..1725658a372f 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,108 @@ 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)) { + // Element type is zero-bit, so lowers to `void`. The index is irrelevant; just cast the pointer. + 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); + } else { + const index_val = try zcu.intValue(Type.usize, elem.elem_idx); + // We want to do pointer arithmetic on a pointer to the element type. + // We might have a pointer-to-array. In this case, we must cast first. + const result_ctype = try dg.ctypeFromType(elem.result_ptr_ty, .complete); + const parent_ctype = try dg.ctypeFromType(try elem.parent.ptrType(zcu), .complete); + if (result_ctype.eql(parent_ctype)) { + // The pointer already has an appropriate type - just do the arithmetic. + try writer.writeByte('('); + try dg.renderPointer(writer, elem.parent.*, location); + try writer.print(" + {})", .{try dg.fmtIntLiteral(index_val, .Other)}); + } else { + // We probably have an array pointer `T (*)[n]`. Cast to an element pointer, + // and *then* apply the index. + try writer.writeAll("(("); + try dg.renderCType(writer, result_ctype); + try writer.writeByte(')'); + try dg.renderPointer(writer, elem.parent.*, location); + try writer.print(" + {})", .{try dg.fmtIntLiteral(index_val, .Other)}); + } + }, + + .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 +1084,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 +4546,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 +5119,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..f370f324eae9 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -92,13 +92,11 @@ test "optional with zero-bit type" { var two: ?struct { ZeroBit, ZeroBit } = undefined; two = .{ with_runtime.zero_bit, with_runtime.zero_bit }; - if (!@inComptime()) { - try expect(two != null); - try expect(two.?[0] == zero_bit); - try expect(two.?[0] == with_runtime.zero_bit); - try expect(two.?[1] == zero_bit); - try expect(two.?[1] == with_runtime.zero_bit); - } + try expect(two != null); + try expect(two.?[0] == zero_bit); + try expect(two.?[0] == with_runtime.zero_bit); + try expect(two.?[1] == zero_bit); + try expect(two.?[1] == with_runtime.zero_bit); } }; @@ -610,3 +608,27 @@ 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 + if (builtin.zig_backend == .stage2_wasm) 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..05588e7cfce1 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 @@ -5,9 +5,17 @@ comptime { const deref = int_ptr.*; _ = deref; } +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; +} // error // 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])