diff options
| -rw-r--r-- | src/Sema.zig | 825 | ||||
| -rw-r--r-- | test/behavior/eval.zig | 17 | ||||
| -rw-r--r-- | test/behavior/translate_c_macros.zig | 5 |
3 files changed, 540 insertions, 307 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); + const arena = mut_kit.beginArena(sema.mod); + defer mut_kit.finishArena(sema.mod); - mut_kit.val.* = try bitcasted_val.copy(arena); + 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 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); - - 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); - - 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); - - 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]); - } - - parent.val.* = 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); - - 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; - } - - parent.val.* = 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); + 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); + + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); + + 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 = 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]); + } - 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); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - parent.val.* = try Value.Tag.aggregate.create(arena, elems); + 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 = 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; + } - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - .aggregate => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.aggregate).?.data[elem_ptr.index], - .ty = elem_ty, - }, + 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 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); + + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); + + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[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, - }; - }, + .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 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); + + switch (parent.ty.zigTypeTag()) { + .Struct => { + const fields = try arena.alloc(Value, parent.ty.structFieldCount()); + mem.set(Value, fields, Value.undef); + + val_ptr.* = try Value.Tag.aggregate.create(arena, fields); + + 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, + } }; - parent.val.* = try Value.Tag.aggregate.create(arena, fields); + val_ptr.* = Value.initPayload(&payload.base); - 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, - } }; + 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, + ), - parent.val.* = Value.initPayload(&payload.base); + .@"union" => { + // We need to set the active field of the union. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); - 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, - }); + const payload = &val_ptr.castTag(.@"union").?.data; + payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); - 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); + 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, + ), - const payload = &parent.val.castTag(.@"union").?.data; - payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); + 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, - .val = &payload.val, - .ty = parent.ty.structFieldType(field_index), + .pointee = .{ .reinterpret = .{ + .val_ptr = reinterpret.val_ptr, + .byte_offset = reinterpret.byte_offset + field_offset, + } }, + .ty = parent.ty, }; }, - .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, + .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); - - const payload = try arena.create(Value.Payload.SubValue); - payload.* = .{ - .base = .{ .tag = .eu_payload }, - .data = Value.undef, - }; + 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, + }; - 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); - - const payload = try arena.create(Value.Payload.SubValue); - payload.* = .{ - .base = .{ .tag = .opt_payload }, - .data = Value.undef, - }; + 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); - parent.val.* = Value.initPayload(&payload.base); + const payload = try arena.create(Value.Payload.SubValue); + payload.* = .{ + .base = .{ .tag = .opt_payload }, + .data = Value.undef, + }; - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.data, - .ty = payload_ty, - }; + val_ptr.* = Value.initPayload(&payload.base); + + 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, |
