aboutsummaryrefslogtreecommitdiff
path: root/src/Sema
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2024-04-08 16:14:39 +0100
committermlugg <mlugg@mlugg.co.uk>2024-04-17 13:41:25 +0100
commitd0e74ffe52d0ae0d876d4e3f7ef5d32b5f5460a5 (patch)
tree001cb2b59a48e913e6036675b71f4736c55647c7 /src/Sema
parent77abd3a96aa8c8c1277cdbb33d88149d4674d389 (diff)
downloadzig-d0e74ffe52d0ae0d876d4e3f7ef5d32b5f5460a5.tar.gz
zig-d0e74ffe52d0ae0d876d4e3f7ef5d32b5f5460a5.zip
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
Diffstat (limited to 'src/Sema')
-rw-r--r--src/Sema/bitcast.zig756
-rw-r--r--src/Sema/comptime_ptr_access.zig1059
2 files changed, 1815 insertions, 0 deletions
diff --git a/src/Sema/bitcast.zig b/src/Sema/bitcast.zig
new file mode 100644
index 0000000000..7be51e87d8
--- /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 0000000000..c1fb544561
--- /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");