diff options
| -rw-r--r-- | src/Module.zig | 1 | ||||
| -rw-r--r-- | src/Sema.zig | 106 | ||||
| -rw-r--r-- | src/TypedValue.zig | 24 | ||||
| -rw-r--r-- | src/type.zig | 10 | ||||
| -rw-r--r-- | src/value.zig | 47 | ||||
| -rw-r--r-- | test/behavior/comptime_memory.zig | 156 |
6 files changed, 271 insertions, 73 deletions
diff --git a/src/Module.zig b/src/Module.zig index c847fadc17..c4c2b634f7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in return field_ty.abiAlignment(mod); } +/// Returns the index of the active field, given the current tag value pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { const ip = &mod.intern_pool; if (enum_tag.toIntern() == .none) return null; diff --git a/src/Sema.zig b/src/Sema.zig index 096ebb0589..26247fbb2c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -27260,7 +27260,7 @@ fn unionFieldVal( else union_ty.unionFieldType(un.tag.toValue(), mod).?; - if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| { + if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| { return Air.internedToRef(new_val.toIntern()); } } @@ -29781,13 +29781,19 @@ fn storePtrVal( error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), }; - operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => unreachable, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), - }; - + if (reinterpret.write_packed) { + operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReinterpretDeclRef => unreachable, + }; + } else { + operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReinterpretDeclRef => unreachable, + error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already + error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), + }; + } const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.IllDefinedMemoryLayout => unreachable, @@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct { reinterpret: struct { val_ptr: *Value, byte_offset: usize, + /// If set, write the operand to packed memory + write_packed: bool = false, }, /// If the root decl could not be used as parent, this means `ty` is the type that /// caused that by not having a well-defined layout. @@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation( ); }, .@"union" => { - // We need to set the active field of the union. - const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod); - const payload = &val_ptr.castTag(.@"union").?.data; - payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index); + const layout = base_child_ty.containerLayout(mod); - return beginComptimePtrMutationInner( - sema, - block, - src, - parent.ty.structFieldType(field_index, mod), - &payload.val, - ptr_elem_ty, - parent.mut_decl, - ); + const tag_type = base_child_ty.unionTagTypeHypothetical(mod); + const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index); + if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) { + // We need to set the active field of the union. + payload.tag = hypothetical_tag; + + const field_ty = parent.ty.structFieldType(field_index, mod); + return beginComptimePtrMutationInner( + sema, + block, + src, + field_ty, + &payload.val, + ptr_elem_ty, + parent.mut_decl, + ); + } else { + // Writing to a different field (a different or unknown tag is active) requires reinterpreting + // memory of the entire union, which requires knowing its abiSize. + try sema.resolveTypeLayout(parent.ty); + + // This union value no longer has a well-defined tag type. + // The reinterpretation will read it back out as .none. + payload.val = try payload.val.unintern(sema.arena, mod); + return ComptimePtrMutationKit{ + .mut_decl = parent.mut_decl, + .pointee = .{ .reinterpret = .{ + .val_ptr = val_ptr, + .byte_offset = 0, + .write_packed = layout == .Packed, + } }, + .ty = parent.ty, + }; + } }, .slice => switch (field_index) { Value.slice_ptr_index => return beginComptimePtrMutationInner( @@ -30697,6 +30727,7 @@ fn bitCastVal( // For types with well-defined memory layouts, we serialize them a byte buffer, // then deserialize to the new type. const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); + const buffer = try sema.gpa.alloc(u8, abi_size); defer sema.gpa.free(buffer); val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { @@ -30713,6 +30744,39 @@ fn bitCastVal( }; } +fn bitCastUnionFieldVal( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + val: Value, + old_ty: Type, + field_ty: Type, +) !?Value { + const mod = sema.mod; + if (old_ty.eql(field_ty, mod)) return val; + + const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); + const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod)); + + const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size)); + defer sema.gpa.free(buffer); + val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReinterpretDeclRef => return null, + error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already + error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), + }; + + // Reading a larger value means we need to reinterpret from undefined bytes + if (field_size > old_size) @memset(buffer[old_size..], 0xaa); + + return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.IllDefinedMemoryLayout => unreachable, + error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}), + }; +} + fn coerceArrayPtrToSlice( sema: *Sema, block: *Block, diff --git a/src/TypedValue.zig b/src/TypedValue.zig index cf705cdf89..cef22543a3 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -84,22 +84,27 @@ pub fn print( if (level == 0) { return writer.writeAll(".{ ... }"); } - const union_val = val.castTag(.@"union").?.data; + const payload = val.castTag(.@"union").?.data; try writer.writeAll(".{ "); - if (union_val.tag.toIntern() != .none) { + if (payload.tag) |tag| { try print(.{ .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), - .val = union_val.tag, + .val = tag, }, writer, level - 1, mod); try writer.writeAll(" = "); - const field_ty = ty.unionFieldType(union_val.tag, mod).?; + const field_ty = ty.unionFieldType(tag, mod).?; try print(.{ .ty = field_ty, - .val = union_val.val, + .val = payload.val, }, writer, level - 1, mod); } else { - return writer.writeAll("(unknown tag)"); + try writer.writeAll("(unknown tag) = "); + const backing_ty = try ty.unionBackingType(mod); + try print(.{ + .ty = backing_ty, + .val = payload.val, + }, writer, level - 1, mod); } return writer.writeAll(" }"); @@ -421,7 +426,12 @@ pub fn print( .val = un.val.toValue(), }, writer, level - 1, mod); } else { - try writer.writeAll("(unknown tag)"); + try writer.writeAll("(unknown tag) = "); + const backing_ty = try ty.unionBackingType(mod); + try print(.{ + .ty = backing_ty, + .val = un.val.toValue(), + }, writer, level - 1, mod); } } else try writer.writeAll("..."); return writer.writeAll(" }"); diff --git a/src/type.zig b/src/type.zig index 79be8b4c5b..396d1496de 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1954,6 +1954,16 @@ pub const Type = struct { return true; } + /// Returns the type used for backing storage of this union during comptime operations. + /// Asserts the type is either an extern or packed union. + pub fn unionBackingType(ty: Type, mod: *Module) !Type { + return switch (ty.containerLayout(mod)) { + .Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }), + .Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))), + .Auto => unreachable, + }; + } + pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout { const ip = &mod.intern_pool; const union_type = ip.indexToKey(ty.toIntern()).union_type; diff --git a/src/value.zig b/src/value.zig index 48a2f0fca2..d37e039abf 100644 --- a/src/value.zig +++ b/src/value.zig @@ -327,11 +327,19 @@ pub const Value = struct { }, .@"union" => { const pl = val.castTag(.@"union").?.data; - return mod.intern(.{ .un = .{ - .ty = ty.toIntern(), - .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod), - .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod), - } }); + if (pl.tag) |pl_tag| { + return mod.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod), + .val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod), + } }); + } else { + return mod.intern(.{ .un = .{ + .ty = ty.toIntern(), + .tag = .none, + .val = try pl.val.intern(try ty.unionBackingType(mod), mod), + } }); + } }, } } @@ -399,10 +407,7 @@ pub const Value = struct { .un => |un| Tag.@"union".create(arena, .{ // toValue asserts that the value cannot be .none which is valid on unions. - .tag = .{ - .ip_index = un.tag, - .legacy = undefined, - }, + .tag = if (un.tag == .none) null else un.tag.toValue(), .val = un.val.toValue(), }), @@ -709,21 +714,22 @@ pub const Value = struct { .Union => switch (ty.containerLayout(mod)) { .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already .Extern => { - const union_obj = mod.typeToUnion(ty).?; if (val.unionTag(mod)) |union_tag| { + const union_obj = mod.typeToUnion(ty).?; const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); const field_val = try val.fieldValue(mod, field_index); const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); } else { - const union_size = ty.abiSize(mod); - const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); - return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]); + const backing_ty = try ty.unionBackingType(mod); + const byte_count: usize = @intCast(backing_ty.abiSize(mod)); + return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]); } }, .Packed => { - const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; + const backing_ty = try ty.unionBackingType(mod); + const byte_count: usize = @intCast(backing_ty.abiSize(mod)); return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0); }, }, @@ -842,9 +848,8 @@ pub const Value = struct { const field_val = try val.fieldValue(mod, field_index); return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); } else { - const union_bits: u16 = @intCast(ty.bitSize(mod)); - const int_ty = try mod.intType(.unsigned, union_bits); - return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset); + const backing_ty = try ty.unionBackingType(mod); + return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset); } }, } @@ -1146,10 +1151,8 @@ pub const Value = struct { .Union => switch (ty.containerLayout(mod)) { .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory .Packed => { - const union_bits: u16 = @intCast(ty.bitSize(mod)); - assert(union_bits != 0); - const int_ty = try mod.intType(.unsigned, union_bits); - const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern(); + const backing_ty = try ty.unionBackingType(mod); + const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern(); return (try mod.intern(.{ .un = .{ .ty = ty.toIntern(), .tag = .none, @@ -4017,7 +4020,7 @@ pub const Value = struct { data: Data, pub const Data = struct { - tag: Value, + tag: ?Value, val: Value, }; }; diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index 8a28d40743..c3d80fe4e8 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -457,18 +457,60 @@ test "type pun null pointer-like optional" { } test "reinterpret extern union" { - { - const U = extern union { - a: u32, - b: u8 align(8), - }; + const U = extern union { + foo: u8, + baz: u32 align(8), + bar: u32, + }; - comptime var u: U = undefined; - comptime @memset(std.mem.asBytes(&u), 42); - try comptime testing.expect(0x2a2a2a2a == u.a); - try comptime testing.expect(42 == u.b); - try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); - try testing.expectEqual(42, u.b); + comptime { + { + // Undefined initialization + const u = blk: { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 0); + u.bar = 0xbbbbbbbb; + u.foo = 0x2a; + break :blk u; + }; + try testing.expectEqual(@as(u8, 0x2a), u.foo); + try testing.expectEqual(@as(u32, 0xbbbbbb2a), u.bar); + try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz); + } + + { + // Union initialization + var u: U = .{ + .foo = 0x2a, + }; + try testing.expectEqual(@as(u8, 0x2a), u.foo); + try testing.expectEqual(@as(u32, 0x2a), u.bar & 0xff); + try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff); + + // Writing to a larger field + u = .{ + .baz = 0xbbbbbbbb, + }; + try testing.expectEqual(@as(u8, 0xbb), u.foo); + try testing.expectEqual(@as(u32, 0xbbbbbbbb), u.bar); + try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz); + + // Writing to the same field + u = .{ + .baz = 0xcccccccc, + }; + try testing.expectEqual(@as(u8, 0xcc), u.foo); + try testing.expectEqual(@as(u32, 0xcccccccc), u.bar); + try testing.expectEqual(@as(u64, 0xcccccccc), u.baz); + + // Writing to a smaller field + u = .{ + .foo = 0xdd, + }; + try testing.expectEqual(@as(u8, 0xdd), u.foo); + try testing.expectEqual(@as(u32, 0xccccccdd), u.bar); + try testing.expectEqual(@as(u64, 0xccccccdd), u.baz); + } } } @@ -479,12 +521,14 @@ test "reinterpret packed union" { b: u8 align(8), }; - comptime var u: U = undefined; - comptime @memset(std.mem.asBytes(&u), 42); - try comptime testing.expect(0x2a2a2a2a == u.a); - try comptime testing.expect(0x2a == u.b); - try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); - try testing.expectEqual(0x2a, u.b); + comptime { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 42); + try testing.expect(0x2a2a2a2a == u.a); + try testing.expect(0x2a == u.b); + try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); + try testing.expectEqual(0x2a, u.b); + } } { @@ -498,11 +542,77 @@ test "reinterpret packed union" { msb: U, }; - comptime var s: S = undefined; - comptime @memset(std.mem.asBytes(&s), 0xaa); - try comptime testing.expectEqual(@as(u7, 0x2a), s.lsb.a); - try comptime testing.expectEqual(@as(u1, 0), s.lsb.b); - try comptime testing.expectEqual(@as(u7, 0x55), s.msb.a); - try comptime testing.expectEqual(@as(u1, 1), s.msb.b); + comptime { + var s: S = undefined; + @memset(std.mem.asBytes(&s), 0x55); + try testing.expectEqual(@as(u7, 0x55), s.lsb.a); + try testing.expectEqual(@as(u1, 1), s.lsb.b); + try testing.expectEqual(@as(u7, 0x2a), s.msb.a); + try testing.expectEqual(@as(u1, 0), s.msb.b); + + s.lsb.b = 0; + try testing.expectEqual(@as(u7, 0x54), s.lsb.a); + try testing.expectEqual(@as(u1, 0), s.lsb.b); + s.msb.b = 1; + try testing.expectEqual(@as(u7, 0x2b), s.msb.a); + try testing.expectEqual(@as(u1, 1), s.msb.b); + } + } + + { + const U = packed union { + foo: u8, + bar: u29, + baz: u64, + }; + + comptime { + { + const u = blk: { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 0); + u.baz = 0xbbbbbbbb; + u.foo = 0x2a; + break :blk u; + }; + try testing.expectEqual(@as(u8, 0x2a), u.foo); + try testing.expectEqual(@as(u29, 0x1bbbbb2a), u.bar); + try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz); + } + + { + // Union initialization + var u: U = .{ + .foo = 0x2a, + }; + try testing.expectEqual(@as(u8, 0x2a), u.foo); + try testing.expectEqual(@as(u29, 0x2a), u.bar & 0xff); + try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff); + + // Writing to a larger field + u = .{ + .baz = 0xbbbbbbbb, + }; + try testing.expectEqual(@as(u8, 0xbb), u.foo); + try testing.expectEqual(@as(u29, 0x1bbbbbbb), u.bar); + try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz); + + // Writing to the same field + u = .{ + .baz = 0xcccccccc, + }; + try testing.expectEqual(@as(u8, 0xcc), u.foo); + try testing.expectEqual(@as(u29, 0x0ccccccc), u.bar); + try testing.expectEqual(@as(u64, 0xcccccccc), u.baz); + + // Writing to a smaller field + u = .{ + .foo = 0xdd, + }; + try testing.expectEqual(@as(u8, 0xdd), u.foo); + try testing.expectEqual(@as(u29, 0x0cccccdd), u.bar); + try testing.expectEqual(@as(u64, 0xccccccdd), u.baz); + } + } } } |
