From 85492f2b9146b91470ca613cf5208906fff12701 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jun 2022 00:56:01 -0700 Subject: [PATCH 1/3] std.mem.zeroes: remove call to std.meta everybody is so horny for std.meta --- lib/std/mem.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 73cc24c45c..61d6b84874 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -268,7 +268,7 @@ pub fn zeroes(comptime T: type) T { }, .Struct => |struct_info| { if (@sizeOf(T) == 0) return T{}; - if (comptime meta.containerLayout(T) == .Extern) { + if (struct_info.layout == .Extern) { var item: T = undefined; set(u8, asBytes(&item), 0); return item; From c29746aa553a72fe2ef2d414c1b616ee2a94eab4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jun 2022 00:57:59 -0700 Subject: [PATCH 2/3] add std.debug.Trace.format This makes it show up in some useful places; for example in the self-hosted compiler we already print it now with --debug-compile-errors. --- lib/std/debug.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index ba45b16d1b..ba1f509e6c 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -2023,5 +2023,22 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize }) catch return; } } + + pub fn format( + t: Trace, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = fmt; + _ = options; + if (enabled) { + try writer.writeAll("\n"); + t.dump(); + try writer.writeAll("\n"); + } else { + return writer.writeAll("(value tracing disabled)"); + } + } }; } From e64d5a0753dd31702032a458d95c327005c09f85 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jun 2022 01:00:33 -0700 Subject: [PATCH 3/3] Sema: rework beginComptimePtrMutation This comment is now deleted because the task is completed in this commit: ``` // TODO: Update this to behave like `beginComptimePtrLoad` and properly check/use // `container_ty` and `array_ty`, instead of trusting that the parent decl type // matches the type used to derive the elem_ptr/field_ptr/etc. // // This is needed because the types will not match if the pointer we're mutating // through is reinterpreting comptime memory. ``` The main strategy is to change the ComptimePtrMutationKit struct so that instead of `val: *Value` it now returns a tagged union which can be one of three possibilities: * The pointer type matches the actual comptime Value so a direct modification is possible. Before this commit, the implementation incorrectly assumed this was always the case. * In the case of needing to write through a reinterpreted pointer, a mutable base Value pointer is provided along with a byte offset pointing to the element value in virtual memory. * Otherwise, it means a compile error must be emitted because one or both of the types (the owner of the value, or the pointer type being used to write through) do not have a well-defined memory layout. After calling beginComptimePtrMutation, the one callsite now switches on this tagged union and does the appropriate thing. The main new logic is for the second case, which involves pointer reinterpretation, which now takes this strategy: 1. write the base value to a memory buffer. 2. perform the pointer store at the proper byte offset, thereby modifying a subset of the buffer. 3. read the base value from the memory buffer, overwriting the old base value. --- src/Sema.zig | 809 +++++++++++++++++---------- test/behavior/eval.zig | 17 + test/behavior/translate_c_macros.zig | 5 +- 3 files changed, 532 insertions(+), 299 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 073f1e7e2e..bad343f941 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19212,9 +19212,9 @@ fn elemValArray( elem_index: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const array_ty = sema.typeOf(array); - const array_sent = array_ty.sentinel() != null; + const array_sent = array_ty.sentinel(); const array_len = array_ty.arrayLen(); - const array_len_s = array_len + @boolToInt(array_sent); + const array_len_s = array_len + @boolToInt(array_sent != null); const elem_ty = array_ty.childType(); if (array_len_s == 0) { @@ -19228,8 +19228,13 @@ fn elemValArray( if (maybe_index_val) |index_val| { const index = @intCast(usize, index_val.toUnsignedInt(target)); + if (array_sent) |s| { + if (index == array_len) { + return sema.addConstant(elem_ty, s); + } + } if (index >= array_len_s) { - const sentinel_label: []const u8 = if (array_sent) " +1 (sentinel)" else ""; + const sentinel_label: []const u8 = if (array_sent != null) " +1 (sentinel)" else ""; return sema.fail(block, elem_index_src, "index {d} outside array of length {d}{s}", .{ index, array_len, sentinel_label }); } } @@ -19269,7 +19274,7 @@ fn elemValArray( // Runtime check is only needed if unable to comptime check if (maybe_index_val == null) { const len_inst = try sema.addIntUnsigned(Type.usize, array_len); - const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt; + const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt; try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); } } @@ -20521,27 +20526,67 @@ fn storePtrVal( operand_val: Value, operand_ty: Type, ) !void { - var mut_kit = try beginComptimePtrMutation(sema, block, src, ptr_val); + var mut_kit = try beginComptimePtrMutation(sema, block, src, ptr_val, operand_ty); try sema.checkComptimeVarStore(block, src, mut_kit.decl_ref_mut); - const bitcasted_val = try sema.bitCastVal(block, src, operand_val, operand_ty, mut_kit.ty, 0); + switch (mut_kit.pointee) { + .direct => |val_ptr| { + if (mut_kit.decl_ref_mut.runtime_index == .comptime_field_ptr) { + if (!operand_val.eql(val_ptr.*, operand_ty, sema.mod)) { + // TODO add note showing where default value is provided + return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{}); + } + return; + } + const arena = mut_kit.beginArena(sema.mod); + defer mut_kit.finishArena(sema.mod); - if (mut_kit.decl_ref_mut.runtime_index == .comptime_field_ptr) { - if (!mut_kit.val.eql(bitcasted_val, mut_kit.ty, sema.mod)) { - return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{}); - } - return; + val_ptr.* = try operand_val.copy(arena); + }, + .reinterpret => |reinterpret| { + const target = sema.mod.getTarget(); + const abi_size = try sema.usizeCast(block, src, mut_kit.ty.abiSize(target)); + const buffer = try sema.gpa.alloc(u8, abi_size); + defer sema.gpa.free(buffer); + reinterpret.val_ptr.*.writeToMemory(mut_kit.ty, sema.mod, buffer); + operand_val.writeToMemory(operand_ty, sema.mod, buffer[reinterpret.byte_offset..]); + + const arena = mut_kit.beginArena(sema.mod); + defer mut_kit.finishArena(sema.mod); + + reinterpret.val_ptr.* = try Value.readFromMemory(mut_kit.ty, sema.mod, buffer, arena); + }, + .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(sema.mod)}); + }, } - - const arena = mut_kit.beginArena(sema.mod); - defer mut_kit.finishArena(sema.mod); - - mut_kit.val.* = try bitcasted_val.copy(arena); } const ComptimePtrMutationKit = struct { decl_ref_mut: Value.Payload.DeclRefMut.Data, - val: *Value, + pointee: union(enum) { + /// The pointer type matches the actual comptime Value so a direct + /// modification is possible. + direct: *Value, + /// 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: *Value, + byte_offset: usize, + }, + /// 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, decl_arena: std.heap.ArenaAllocator = undefined, @@ -20563,354 +20608,469 @@ fn beginComptimePtrMutation( block: *Block, src: LazySrcLoc, ptr_val: Value, + ptr_elem_ty: Type, ) CompileError!ComptimePtrMutationKit { - - // TODO: Update this to behave like `beginComptimePtrLoad` and properly check/use - // `container_ty` and `array_ty`, instead of trusting that the parent decl type - // matches the type used to derive the elem_ptr/field_ptr/etc. - // - // This is needed because the types will not match if the pointer we're mutating - // through is reinterpreting comptime memory. - + const target = sema.mod.getTarget(); switch (ptr_val.tag()) { .decl_ref_mut => { const decl_ref_mut = ptr_val.castTag(.decl_ref_mut).?.data; const decl = sema.mod.declPtr(decl_ref_mut.decl_index); - return ComptimePtrMutationKit{ - .decl_ref_mut = decl_ref_mut, - .val = &decl.val, - .ty = decl.ty, - }; + return beginComptimePtrMutationInner(sema, block, src, decl.ty, &decl.val, ptr_elem_ty, decl_ref_mut); }, .comptime_field_ptr => { const payload = ptr_val.castTag(.comptime_field_ptr).?.data; const duped = try sema.arena.create(Value); duped.* = payload.field_val; - return ComptimePtrMutationKit{ - .decl_ref_mut = .{ - .decl_index = @intToEnum(Module.Decl.Index, 0), - .runtime_index = .comptime_field_ptr, - }, - .val = duped, - .ty = payload.field_ty, - }; + return beginComptimePtrMutationInner(sema, block, src, payload.field_ty, duped, ptr_elem_ty, .{ + .decl_index = @intToEnum(Module.Decl.Index, 0), + .runtime_index = .comptime_field_ptr, + }); }, .elem_ptr => { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr); - switch (parent.ty.zigTypeTag()) { - .Array, .Vector => { - const check_len = parent.ty.arrayLenIncludingSentinel(); - 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, - }); - } - const elem_ty = parent.ty.childType(); - switch (parent.val.tag()) { - .undef => { - // An array has been initialized to undefined at comptime and now we - // are for the first time setting an element. We must change the representation - // of the array from `undef` to `array`. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr, elem_ptr.elem_ty); + switch (parent.pointee) { + .direct => |val_ptr| switch (parent.ty.zigTypeTag()) { + .Array, .Vector => { + const check_len = parent.ty.arrayLenIncludingSentinel(); + 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, + }); + } + const elem_ty = parent.ty.childType(); + switch (val_ptr.tag()) { + .undef => { + // An array has been initialized to undefined at comptime and now we + // are for the first time setting an element. We must change the representation + // of the array from `undef` to `array`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const array_len_including_sentinel = - try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); - const elems = try arena.alloc(Value, array_len_including_sentinel); - mem.set(Value, elems, Value.undef); + const array_len_including_sentinel = + try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); + const elems = try arena.alloc(Value, array_len_including_sentinel); + mem.set(Value, elems, Value.undef); - parent.val.* = try Value.Tag.aggregate.create(arena, elems); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, - .bytes => { - // An array is memory-optimized to store a slice of bytes, but we are about - // to modify an individual field and the representation has to change. - // If we wanted to avoid this, there would need to be special detection - // elsewhere to identify when writing a value to an array element that is stored - // using the `bytes` tag, and handle it without making a call to this function. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .bytes => { + // An array is memory-optimized to store a slice of bytes, but we are about + // to modify an individual field and the representation has to change. + // If we wanted to avoid this, there would need to be special detection + // elsewhere to identify when writing a value to an array element that is stored + // using the `bytes` tag, and handle it without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const bytes = parent.val.castTag(.bytes).?.data; - const dest_len = parent.ty.arrayLenIncludingSentinel(); - // bytes.len may be one greater than dest_len because of the case when - // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted. - assert(bytes.len >= dest_len); - const elems = try arena.alloc(Value, @intCast(usize, dest_len)); - for (elems) |*elem, i| { - elem.* = try Value.Tag.int_u64.create(arena, bytes[i]); - } + const bytes = val_ptr.castTag(.bytes).?.data; + const dest_len = parent.ty.arrayLenIncludingSentinel(); + // bytes.len may be one greater than dest_len because of the case when + // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted. + assert(bytes.len >= dest_len); + const elems = try arena.alloc(Value, @intCast(usize, dest_len)); + for (elems) |*elem, i| { + elem.* = try Value.Tag.int_u64.create(arena, bytes[i]); + } - parent.val.* = try Value.Tag.aggregate.create(arena, elems); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, - .str_lit => { - // An array is memory-optimized to store a slice of bytes, but we are about - // to modify an individual field and the representation has to change. - // If we wanted to avoid this, there would need to be special detection - // elsewhere to identify when writing a value to an array element that is stored - // using the `str_lit` tag, and handle it without making a call to this function. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .str_lit => { + // An array is memory-optimized to store a slice of bytes, but we are about + // to modify an individual field and the representation has to change. + // If we wanted to avoid this, there would need to be special detection + // elsewhere to identify when writing a value to an array element that is stored + // using the `str_lit` tag, and handle it without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const str_lit = parent.val.castTag(.str_lit).?.data; - const dest_len = parent.ty.arrayLenIncludingSentinel(); - const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; - const elems = try arena.alloc(Value, @intCast(usize, dest_len)); - for (bytes) |byte, i| { - elems[i] = try Value.Tag.int_u64.create(arena, byte); - } - if (parent.ty.sentinel()) |sent_val| { - assert(elems.len == bytes.len + 1); - elems[bytes.len] = sent_val; - } + const str_lit = val_ptr.castTag(.str_lit).?.data; + const dest_len = parent.ty.arrayLenIncludingSentinel(); + const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; + const elems = try arena.alloc(Value, @intCast(usize, dest_len)); + for (bytes) |byte, i| { + elems[i] = try Value.Tag.int_u64.create(arena, byte); + } + if (parent.ty.sentinel()) |sent_val| { + assert(elems.len == bytes.len + 1); + elems[bytes.len] = sent_val; + } - parent.val.* = try Value.Tag.aggregate.create(arena, elems); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, - .repeated => { - // An array is memory-optimized to store only a single element value, and - // that value is understood to be the same for the entire length of the array. - // However, now we want to modify an individual field and so the - // representation has to change. If we wanted to avoid this, there would - // need to be special detection elsewhere to identify when writing a value to an - // array element that is stored using the `repeated` tag, and handle it - // without making a call to this function. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .repeated => { + // An array is memory-optimized to store only a single element value, and + // that value is understood to be the same for the entire length of the array. + // However, now we want to modify an individual field and so the + // representation has to change. If we wanted to avoid this, there would + // need to be special detection elsewhere to identify when writing a value to an + // array element that is stored using the `repeated` tag, and handle it + // without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena); - const array_len_including_sentinel = - try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); - const elems = try arena.alloc(Value, array_len_including_sentinel); - mem.set(Value, elems, repeated_val); + const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena); + const array_len_including_sentinel = + try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); + const elems = try arena.alloc(Value, array_len_including_sentinel); + mem.set(Value, elems, repeated_val); - parent.val.* = try Value.Tag.aggregate.create(arena, elems); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, - .aggregate => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.aggregate).?.data[elem_ptr.index], - .ty = elem_ty, - }, + .aggregate => return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &val_ptr.castTag(.aggregate).?.data[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ), - .the_only_possible_value => { - const duped = try sema.arena.create(Value); - duped.* = Value.initTag(.the_only_possible_value); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = duped, - .ty = elem_ty, - }; - }, + .the_only_possible_value => { + const duped = try sema.arena.create(Value); + duped.* = Value.initTag(.the_only_possible_value); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + duped, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, - else => unreachable, - } + else => unreachable, + } + }, + 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.decl_ref_mut, + ); + }, }, - 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, - }); + .reinterpret => |reinterpret| { + if (!elem_ptr.elem_ty.hasWellDefinedLayout()) { + // Even though the parent value type has well-defined memory layout, our + // pointer type does not. + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .bad_ptr_ty, + .ty = elem_ptr.elem_ty, + }; } + + const elem_abi_size_u64 = try sema.typeAbiSize(block, src, elem_ptr.elem_ty); + const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64); return ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = parent.val, + .pointee = .{ .reinterpret = .{ + .val_ptr = reinterpret.val_ptr, + .byte_offset = reinterpret.byte_offset + elem_abi_size * elem_ptr.index, + } }, .ty = parent.ty, }; }, + .bad_decl_ty, .bad_ptr_ty => return parent, } }, .field_ptr => { const field_ptr = ptr_val.castTag(.field_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, field_ptr.container_ptr); const field_index = @intCast(u32, field_ptr.field_index); - switch (parent.val.tag()) { - .undef => { - // A struct or union has been initialized to undefined at comptime and now we - // are for the first time setting a field. We must change the representation - // of the struct/union from `undef` to `struct`/`union`. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); - switch (parent.ty.zigTypeTag()) { - .Struct => { - const fields = try arena.alloc(Value, parent.ty.structFieldCount()); - mem.set(Value, fields, Value.undef); + var parent = try beginComptimePtrMutation(sema, block, src, field_ptr.container_ptr, field_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| switch (val_ptr.tag()) { + .undef => { + // A struct or union has been initialized to undefined at comptime and now we + // are for the first time setting a field. We must change the representation + // of the struct/union from `undef` to `struct`/`union`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - parent.val.* = try Value.Tag.aggregate.create(arena, fields); + switch (parent.ty.zigTypeTag()) { + .Struct => { + const fields = try arena.alloc(Value, parent.ty.structFieldCount()); + mem.set(Value, fields, Value.undef); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &fields[field_index], - .ty = parent.ty.structFieldType(field_index), - }; - }, - .Union => { - const payload = try arena.create(Value.Payload.Union); - payload.* = .{ .data = .{ - .tag = try Value.Tag.enum_field_index.create(arena, field_index), - .val = Value.undef, - } }; + val_ptr.* = try Value.Tag.aggregate.create(arena, fields); - parent.val.* = Value.initPayload(&payload.base); + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &fields[field_index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .Union => { + const payload = try arena.create(Value.Payload.Union); + payload.* = .{ .data = .{ + .tag = try Value.Tag.enum_field_index.create(arena, field_index), + .val = Value.undef, + } }; - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.data.val, - .ty = parent.ty.structFieldType(field_index), - }; - }, - .Pointer => { - assert(parent.ty.isSlice()); - parent.val.* = try Value.Tag.slice.create(arena, .{ - .ptr = Value.undef, - .len = Value.undef, - }); + val_ptr.* = Value.initPayload(&payload.base); + + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &payload.data.val, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .Pointer => { + assert(parent.ty.isSlice()); + val_ptr.* = try Value.Tag.slice.create(arena, .{ + .ptr = Value.undef, + .len = Value.undef, + }); + + switch (field_index) { + Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), + &val_ptr.castTag(.slice).?.data.ptr, + ptr_elem_ty, + parent.decl_ref_mut, + ), + Value.Payload.Slice.len_index => return beginComptimePtrMutationInner( + sema, + block, + src, + Type.usize, + &val_ptr.castTag(.slice).?.data.len, + ptr_elem_ty, + parent.decl_ref_mut, + ), + + else => unreachable, + } + }, + else => unreachable, + } + }, + .aggregate => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &val_ptr.castTag(.aggregate).?.data[field_index], + ptr_elem_ty, + parent.decl_ref_mut, + ), + + .@"union" => { + // We need to set the active field of the union. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const payload = &val_ptr.castTag(.@"union").?.data; + payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); + + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &payload.val, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .slice => switch (field_index) { + Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), + &val_ptr.castTag(.slice).?.data.ptr, + ptr_elem_ty, + parent.decl_ref_mut, + ), + + Value.Payload.Slice.len_index => return beginComptimePtrMutationInner( + sema, + block, + src, + Type.usize, + &val_ptr.castTag(.slice).?.data.len, + ptr_elem_ty, + parent.decl_ref_mut, + ), - switch (field_index) { - Value.Payload.Slice.ptr_index => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.slice).?.data.ptr, - .ty = parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), - }, - Value.Payload.Slice.len_index => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.slice).?.data.len, - .ty = Type.usize, - }, - else => unreachable, - } - }, else => unreachable, - } - }, - .aggregate => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.aggregate).?.data[field_index], - .ty = parent.ty.structFieldType(field_index), - }, - .@"union" => { - // We need to set the active field of the union. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); - - const payload = &parent.val.castTag(.@"union").?.data; - payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); - - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.val, - .ty = parent.ty.structFieldType(field_index), - }; - }, - .slice => switch (field_index) { - Value.Payload.Slice.ptr_index => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.slice).?.data.ptr, - .ty = parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), - }, - Value.Payload.Slice.len_index => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.slice).?.data.len, - .ty = Type.usize, }, + else => unreachable, }, - - else => unreachable, + .reinterpret => |reinterpret| { + const field_offset_u64 = field_ptr.container_ty.structFieldOffset(field_index, target); + const field_offset = try sema.usizeCast(block, src, field_offset_u64); + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .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, } }, .eu_payload_ptr => { const eu_ptr = ptr_val.castTag(.eu_payload_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, eu_ptr.container_ptr); - const payload_ty = parent.ty.errorUnionPayload(); - switch (parent.val.tag()) { - else => { - // 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 from `undef` to `opt_payload`. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + var parent = try beginComptimePtrMutation(sema, block, src, eu_ptr.container_ptr, eu_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| { + const payload_ty = parent.ty.errorUnionPayload(); + switch (val_ptr.tag()) { + else => { + // 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 from `undef` to `opt_payload`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const payload = try arena.create(Value.Payload.SubValue); - payload.* = .{ - .base = .{ .tag = .eu_payload }, - .data = Value.undef, - }; + const payload = try arena.create(Value.Payload.SubValue); + payload.* = .{ + .base = .{ .tag = .eu_payload }, + .data = Value.undef, + }; - parent.val.* = Value.initPayload(&payload.base); + val_ptr.* = Value.initPayload(&payload.base); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.data, - .ty = payload_ty, - }; + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &payload.data }, + .ty = payload_ty, + }; + }, + .eu_payload => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &val_ptr.castTag(.eu_payload).?.data }, + .ty = payload_ty, + }, + } }, - .eu_payload => return ComptimePtrMutationKit{ + .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 ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.eu_payload).?.data, - .ty = payload_ty, + .pointee = .bad_ptr_ty, + .ty = eu_ptr.container_ty, }, } }, .opt_payload_ptr => { const opt_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, opt_ptr.container_ptr); - const payload_ty = try parent.ty.optionalChildAlloc(sema.arena); - switch (parent.val.tag()) { - .undef, .null_value => { - // 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 from `undef` to `opt_payload`. - const arena = parent.beginArena(sema.mod); - defer parent.finishArena(sema.mod); + var parent = try beginComptimePtrMutation(sema, block, src, opt_ptr.container_ptr, opt_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| { + const payload_ty = try parent.ty.optionalChildAlloc(sema.arena); + switch (val_ptr.tag()) { + .undef, .null_value => { + // 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 from `undef` to `opt_payload`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - const payload = try arena.create(Value.Payload.SubValue); - payload.* = .{ - .base = .{ .tag = .opt_payload }, - .data = Value.undef, - }; + const payload = try arena.create(Value.Payload.SubValue); + payload.* = .{ + .base = .{ .tag = .opt_payload }, + .data = Value.undef, + }; - parent.val.* = Value.initPayload(&payload.base); + val_ptr.* = Value.initPayload(&payload.base); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.data, - .ty = payload_ty, - }; + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &payload.data }, + .ty = payload_ty, + }; + }, + .opt_payload => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &val_ptr.castTag(.opt_payload).?.data }, + .ty = payload_ty, + }, + + else => unreachable, + } }, - .opt_payload => return ComptimePtrMutationKit{ + .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 ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.opt_payload).?.data, - .ty = payload_ty, + .pointee = .bad_ptr_ty, + .ty = opt_ptr.container_ty, }, - - else => unreachable, } }, .decl_ref => unreachable, // isComptimeMutablePtr() has been checked already @@ -20918,10 +21078,63 @@ fn beginComptimePtrMutation( } } +fn beginComptimePtrMutationInner( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + decl_ty: Type, + decl_val: *Value, + ptr_elem_ty: Type, + decl_ref_mut: Value.Payload.DeclRefMut.Data, +) CompileError!ComptimePtrMutationKit { + const target = sema.mod.getTarget(); + const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok; + if (coerce_ok) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .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()) { + const decl_elem_ty = decl_ty.childType(); + if ((try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_elem_ty, true, target, src, src)) == .ok) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .direct = decl_val }, + .ty = decl_ty, + }; + } + } + + if (!decl_ty.hasWellDefinedLayout()) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .bad_decl_ty = {} }, + .ty = decl_ty, + }; + } + if (!ptr_elem_ty.hasWellDefinedLayout()) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .bad_ptr_ty = {} }, + .ty = ptr_elem_ty, + }; + } + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .reinterpret = .{ + .val_ptr = decl_val, + .byte_offset = 0, + } }, + .ty = decl_ty, + }; +} + const TypedValueAndOffset = struct { tv: TypedValue, - /// The starting byte offset of `val` from `root_val`. - /// If the type does not have a well-defined memory layout, this is null. byte_offset: usize, }; @@ -21197,7 +21410,7 @@ fn bitCast( return block.addBitCast(dest_ty, inst); } -pub fn bitCastVal( +fn bitCastVal( sema: *Sema, block: *Block, src: LazySrcLoc, diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index e56ea0cad5..0ea3a33990 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -1252,3 +1252,20 @@ test "pass pointer to field of comptime-only type as a runtime parameter" { }; try S.doTheTest(); } + +test "comptime write through extern struct reinterpreted as array" { + comptime { + const S = extern struct { + a: u8, + b: u8, + c: u8, + }; + var s: S = undefined; + @ptrCast(*[3]u8, &s)[0] = 1; + @ptrCast(*[3]u8, &s)[1] = 2; + @ptrCast(*[3]u8, &s)[2] = 3; + assert(s.a == 1); + assert(s.b == 2); + assert(s.c == 3); + } +} diff --git a/test/behavior/translate_c_macros.zig b/test/behavior/translate_c_macros.zig index ec2695b4a0..e44996b990 100644 --- a/test/behavior/translate_c_macros.zig +++ b/test/behavior/translate_c_macros.zig @@ -19,7 +19,10 @@ test "casting to void with a macro" { } test "initializer list expression" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try expectEqual(h.Color{ .r = 200,