| author | |
| committer | |
| log | 87d09edf2d328b21e02abb233899ebaa257a5204 |
| tree | c43c0688ff29c2f488d78dc39cf304d2c2c50a2e |
| parent | c933a7c58c6685f65da26e7c4cc826a32c3d1e43 |
| parent | 1b8a50ea5e9a179bf85486230d6b058d1379a4aa |
| signature |
sema: Support reinterpreting extern/packed unions at comptime via field access7 files changed, 376 insertions(+), 109 deletions(-)
src/Module.zig+1| ... | ... | @@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in |
| 6607 | 6607 | return field_ty.abiAlignment(mod); |
| 6608 | 6608 | } |
| 6609 | 6609 | |
| 6610 | /// Returns the index of the active field, given the current tag value | |
| 6610 | 6611 | pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { |
| 6611 | 6612 | const ip = &mod.intern_pool; |
| 6612 | 6613 | if (enum_tag.toIntern() == .none) return null; |
src/Sema.zig+110-22| ... | ... | @@ -27258,7 +27258,7 @@ fn unionFieldVal( |
| 27258 | 27258 | return sema.failWithOwnedErrorMsg(block, msg); |
| 27259 | 27259 | } |
| 27260 | 27260 | }, |
| 27261 | .Packed, .Extern => { | |
| 27261 | .Packed, .Extern => |layout| { | |
| 27262 | 27262 | if (tag_matches) { |
| 27263 | 27263 | return Air.internedToRef(un.val); |
| 27264 | 27264 | } else { |
| ... | ... | @@ -27267,7 +27267,7 @@ fn unionFieldVal( |
| 27267 | 27267 | else |
| 27268 | 27268 | union_ty.unionFieldType(un.tag.toValue(), mod).?; |
| 27269 | 27269 | |
| 27270 | if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| { | |
| 27270 | if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| { | |
| 27271 | 27271 | return Air.internedToRef(new_val.toIntern()); |
| 27272 | 27272 | } |
| 27273 | 27273 | } |
| ... | ... | @@ -29788,13 +29788,19 @@ fn storePtrVal( |
| 29788 | 29788 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already |
| 29789 | 29789 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), |
| 29790 | 29790 | }; |
| 29791 | operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) { | |
| 29792 | error.OutOfMemory => return error.OutOfMemory, | |
| 29793 | error.ReinterpretDeclRef => unreachable, | |
| 29794 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already | |
| 29795 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), | |
| 29796 | }; | |
| 29797 | ||
| 29791 | if (reinterpret.write_packed) { | |
| 29792 | operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) { | |
| 29793 | error.OutOfMemory => return error.OutOfMemory, | |
| 29794 | error.ReinterpretDeclRef => unreachable, | |
| 29795 | }; | |
| 29796 | } else { | |
| 29797 | operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) { | |
| 29798 | error.OutOfMemory => return error.OutOfMemory, | |
| 29799 | error.ReinterpretDeclRef => unreachable, | |
| 29800 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already | |
| 29801 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), | |
| 29802 | }; | |
| 29803 | } | |
| 29798 | 29804 | const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { |
| 29799 | 29805 | error.OutOfMemory => return error.OutOfMemory, |
| 29800 | 29806 | error.IllDefinedMemoryLayout => unreachable, |
| ... | ... | @@ -29826,6 +29832,8 @@ const ComptimePtrMutationKit = struct { |
| 29826 | 29832 | reinterpret: struct { |
| 29827 | 29833 | val_ptr: *Value, |
| 29828 | 29834 | byte_offset: usize, |
| 29835 | /// If set, write the operand to packed memory | |
| 29836 | write_packed: bool = false, | |
| 29829 | 29837 | }, |
| 29830 | 29838 | /// If the root decl could not be used as parent, this means `ty` is the type that |
| 29831 | 29839 | /// caused that by not having a well-defined layout. |
| ... | ... | @@ -30189,21 +30197,43 @@ fn beginComptimePtrMutation( |
| 30189 | 30197 | ); |
| 30190 | 30198 | }, |
| 30191 | 30199 | .@"union" => { |
| 30192 | // We need to set the active field of the union. | |
| 30193 | const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod); | |
| 30194 | ||
| 30195 | 30200 | const payload = &val_ptr.castTag(.@"union").?.data; |
| 30196 | payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index); | |
| 30201 | const layout = base_child_ty.containerLayout(mod); | |
| 30197 | 30202 | |
| 30198 | return beginComptimePtrMutationInner( | |
| 30199 | sema, | |
| 30200 | block, | |
| 30201 | src, | |
| 30202 | parent.ty.structFieldType(field_index, mod), | |
| 30203 | &payload.val, | |
| 30204 | ptr_elem_ty, | |
| 30205 | parent.mut_decl, | |
| 30206 | ); | |
| 30203 | const tag_type = base_child_ty.unionTagTypeHypothetical(mod); | |
| 30204 | const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index); | |
| 30205 | if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) { | |
| 30206 | // We need to set the active field of the union. | |
| 30207 | payload.tag = hypothetical_tag; | |
| 30208 | ||
| 30209 | const field_ty = parent.ty.structFieldType(field_index, mod); | |
| 30210 | return beginComptimePtrMutationInner( | |
| 30211 | sema, | |
| 30212 | block, | |
| 30213 | src, | |
| 30214 | field_ty, | |
| 30215 | &payload.val, | |
| 30216 | ptr_elem_ty, | |
| 30217 | parent.mut_decl, | |
| 30218 | ); | |
| 30219 | } else { | |
| 30220 | // Writing to a different field (a different or unknown tag is active) requires reinterpreting | |
| 30221 | // memory of the entire union, which requires knowing its abiSize. | |
| 30222 | try sema.resolveTypeLayout(parent.ty); | |
| 30223 | ||
| 30224 | // This union value no longer has a well-defined tag type. | |
| 30225 | // The reinterpretation will read it back out as .none. | |
| 30226 | payload.val = try payload.val.unintern(sema.arena, mod); | |
| 30227 | return ComptimePtrMutationKit{ | |
| 30228 | .mut_decl = parent.mut_decl, | |
| 30229 | .pointee = .{ .reinterpret = .{ | |
| 30230 | .val_ptr = val_ptr, | |
| 30231 | .byte_offset = 0, | |
| 30232 | .write_packed = layout == .Packed, | |
| 30233 | } }, | |
| 30234 | .ty = parent.ty, | |
| 30235 | }; | |
| 30236 | } | |
| 30207 | 30237 | }, |
| 30208 | 30238 | .slice => switch (field_index) { |
| 30209 | 30239 | Value.slice_ptr_index => return beginComptimePtrMutationInner( |
| ... | ... | @@ -30704,6 +30734,7 @@ fn bitCastVal( |
| 30704 | 30734 | // For types with well-defined memory layouts, we serialize them a byte buffer, |
| 30705 | 30735 | // then deserialize to the new type. |
| 30706 | 30736 | const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); |
| 30737 | ||
| 30707 | 30738 | const buffer = try sema.gpa.alloc(u8, abi_size); |
| 30708 | 30739 | defer sema.gpa.free(buffer); |
| 30709 | 30740 | val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { |
| ... | ... | @@ -30720,6 +30751,63 @@ fn bitCastVal( |
| 30720 | 30751 | }; |
| 30721 | 30752 | } |
| 30722 | 30753 | |
| 30754 | fn bitCastUnionFieldVal( | |
| 30755 | sema: *Sema, | |
| 30756 | block: *Block, | |
| 30757 | src: LazySrcLoc, | |
| 30758 | val: Value, | |
| 30759 | old_ty: Type, | |
| 30760 | field_ty: Type, | |
| 30761 | layout: std.builtin.Type.ContainerLayout, | |
| 30762 | ) !?Value { | |
| 30763 | const mod = sema.mod; | |
| 30764 | if (old_ty.eql(field_ty, mod)) return val; | |
| 30765 | ||
| 30766 | const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); | |
| 30767 | const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod)); | |
| 30768 | const endian = mod.getTarget().cpu.arch.endian(); | |
| 30769 | ||
| 30770 | const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size)); | |
| 30771 | defer sema.gpa.free(buffer); | |
| 30772 | ||
| 30773 | // Reading a larger value means we need to reinterpret from undefined bytes. | |
| 30774 | const offset = switch (layout) { | |
| 30775 | .Extern => offset: { | |
| 30776 | if (field_size > old_size) @memset(buffer[old_size..], 0xaa); | |
| 30777 | val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { | |
| 30778 | error.OutOfMemory => return error.OutOfMemory, | |
| 30779 | error.ReinterpretDeclRef => return null, | |
| 30780 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already | |
| 30781 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), | |
| 30782 | }; | |
| 30783 | break :offset 0; | |
| 30784 | }, | |
| 30785 | .Packed => offset: { | |
| 30786 | if (field_size > old_size) { | |
| 30787 | const min_size = @max(old_size, 1); | |
| 30788 | switch (endian) { | |
| 30789 | .Little => @memset(buffer[min_size - 1 ..], 0xaa), | |
| 30790 | .Big => @memset(buffer[0 .. buffer.len - min_size + 1], 0xaa), | |
| 30791 | } | |
| 30792 | } | |
| 30793 | ||
| 30794 | val.writeToPackedMemory(old_ty, mod, buffer, 0) catch |err| switch (err) { | |
| 30795 | error.OutOfMemory => return error.OutOfMemory, | |
| 30796 | error.ReinterpretDeclRef => return null, | |
| 30797 | }; | |
| 30798 | ||
| 30799 | break :offset if (endian == .Big) buffer.len - field_size else 0; | |
| 30800 | }, | |
| 30801 | .Auto => unreachable, | |
| 30802 | }; | |
| 30803 | ||
| 30804 | return Value.readFromMemory(field_ty, mod, buffer[offset..], sema.arena) catch |err| switch (err) { | |
| 30805 | error.OutOfMemory => return error.OutOfMemory, | |
| 30806 | error.IllDefinedMemoryLayout => unreachable, | |
| 30807 | error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}), | |
| 30808 | }; | |
| 30809 | } | |
| 30810 | ||
| 30723 | 30811 | fn coerceArrayPtrToSlice( |
| 30724 | 30812 | sema: *Sema, |
| 30725 | 30813 | block: *Block, |
src/TypedValue.zig+17-7| ... | ... | @@ -84,22 +84,27 @@ pub fn print( |
| 84 | 84 | if (level == 0) { |
| 85 | 85 | return writer.writeAll(".{ ... }"); |
| 86 | 86 | } |
| 87 | const union_val = val.castTag(.@"union").?.data; | |
| 87 | const payload = val.castTag(.@"union").?.data; | |
| 88 | 88 | try writer.writeAll(".{ "); |
| 89 | 89 | |
| 90 | if (union_val.tag.toIntern() != .none) { | |
| 90 | if (payload.tag) |tag| { | |
| 91 | 91 | try print(.{ |
| 92 | 92 | .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), |
| 93 | .val = union_val.tag, | |
| 93 | .val = tag, | |
| 94 | 94 | }, writer, level - 1, mod); |
| 95 | 95 | try writer.writeAll(" = "); |
| 96 | const field_ty = ty.unionFieldType(union_val.tag, mod).?; | |
| 96 | const field_ty = ty.unionFieldType(tag, mod).?; | |
| 97 | 97 | try print(.{ |
| 98 | 98 | .ty = field_ty, |
| 99 | .val = union_val.val, | |
| 99 | .val = payload.val, | |
| 100 | 100 | }, writer, level - 1, mod); |
| 101 | 101 | } else { |
| 102 | return writer.writeAll("(unknown tag)"); | |
| 102 | try writer.writeAll("(unknown tag) = "); | |
| 103 | const backing_ty = try ty.unionBackingType(mod); | |
| 104 | try print(.{ | |
| 105 | .ty = backing_ty, | |
| 106 | .val = payload.val, | |
| 107 | }, writer, level - 1, mod); | |
| 103 | 108 | } |
| 104 | 109 | |
| 105 | 110 | return writer.writeAll(" }"); |
| ... | ... | @@ -421,7 +426,12 @@ pub fn print( |
| 421 | 426 | .val = un.val.toValue(), |
| 422 | 427 | }, writer, level - 1, mod); |
| 423 | 428 | } else { |
| 424 | try writer.writeAll("(unknown tag)"); | |
| 429 | try writer.writeAll("(unknown tag) = "); | |
| 430 | const backing_ty = try ty.unionBackingType(mod); | |
| 431 | try print(.{ | |
| 432 | .ty = backing_ty, | |
| 433 | .val = un.val.toValue(), | |
| 434 | }, writer, level - 1, mod); | |
| 425 | 435 | } |
| 426 | 436 | } else try writer.writeAll("..."); |
| 427 | 437 | return writer.writeAll(" }"); |
src/type.zig+10| ... | ... | @@ -1954,6 +1954,16 @@ pub const Type = struct { |
| 1954 | 1954 | return true; |
| 1955 | 1955 | } |
| 1956 | 1956 | |
| 1957 | /// Returns the type used for backing storage of this union during comptime operations. | |
| 1958 | /// Asserts the type is either an extern or packed union. | |
| 1959 | pub fn unionBackingType(ty: Type, mod: *Module) !Type { | |
| 1960 | return switch (ty.containerLayout(mod)) { | |
| 1961 | .Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }), | |
| 1962 | .Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))), | |
| 1963 | .Auto => unreachable, | |
| 1964 | }; | |
| 1965 | } | |
| 1966 | ||
| 1957 | 1967 | pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout { |
| 1958 | 1968 | const ip = &mod.intern_pool; |
| 1959 | 1969 | const union_type = ip.indexToKey(ty.toIntern()).union_type; |
src/value.zig+25-22| ... | ... | @@ -327,11 +327,19 @@ pub const Value = struct { |
| 327 | 327 | }, |
| 328 | 328 | .@"union" => { |
| 329 | 329 | const pl = val.castTag(.@"union").?.data; |
| 330 | return mod.intern(.{ .un = .{ | |
| 331 | .ty = ty.toIntern(), | |
| 332 | .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod), | |
| 333 | .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod), | |
| 334 | } }); | |
| 330 | if (pl.tag) |pl_tag| { | |
| 331 | return mod.intern(.{ .un = .{ | |
| 332 | .ty = ty.toIntern(), | |
| 333 | .tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod), | |
| 334 | .val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod), | |
| 335 | } }); | |
| 336 | } else { | |
| 337 | return mod.intern(.{ .un = .{ | |
| 338 | .ty = ty.toIntern(), | |
| 339 | .tag = .none, | |
| 340 | .val = try pl.val.intern(try ty.unionBackingType(mod), mod), | |
| 341 | } }); | |
| 342 | } | |
| 335 | 343 | }, |
| 336 | 344 | } |
| 337 | 345 | } |
| ... | ... | @@ -399,10 +407,7 @@ pub const Value = struct { |
| 399 | 407 | |
| 400 | 408 | .un => |un| Tag.@"union".create(arena, .{ |
| 401 | 409 | // toValue asserts that the value cannot be .none which is valid on unions. |
| 402 | .tag = .{ | |
| 403 | .ip_index = un.tag, | |
| 404 | .legacy = undefined, | |
| 405 | }, | |
| 410 | .tag = if (un.tag == .none) null else un.tag.toValue(), | |
| 406 | 411 | .val = un.val.toValue(), |
| 407 | 412 | }), |
| 408 | 413 | |
| ... | ... | @@ -709,21 +714,22 @@ pub const Value = struct { |
| 709 | 714 | .Union => switch (ty.containerLayout(mod)) { |
| 710 | 715 | .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already |
| 711 | 716 | .Extern => { |
| 712 | const union_obj = mod.typeToUnion(ty).?; | |
| 713 | 717 | if (val.unionTag(mod)) |union_tag| { |
| 718 | const union_obj = mod.typeToUnion(ty).?; | |
| 714 | 719 | const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; |
| 715 | 720 | const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); |
| 716 | 721 | const field_val = try val.fieldValue(mod, field_index); |
| 717 | 722 | const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); |
| 718 | 723 | return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); |
| 719 | 724 | } else { |
| 720 | const union_size = ty.abiSize(mod); | |
| 721 | const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); | |
| 722 | return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]); | |
| 725 | const backing_ty = try ty.unionBackingType(mod); | |
| 726 | const byte_count: usize = @intCast(backing_ty.abiSize(mod)); | |
| 727 | return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]); | |
| 723 | 728 | } |
| 724 | 729 | }, |
| 725 | 730 | .Packed => { |
| 726 | const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8; | |
| 731 | const backing_ty = try ty.unionBackingType(mod); | |
| 732 | const byte_count: usize = @intCast(backing_ty.abiSize(mod)); | |
| 727 | 733 | return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0); |
| 728 | 734 | }, |
| 729 | 735 | }, |
| ... | ... | @@ -842,9 +848,8 @@ pub const Value = struct { |
| 842 | 848 | const field_val = try val.fieldValue(mod, field_index); |
| 843 | 849 | return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); |
| 844 | 850 | } else { |
| 845 | const union_bits: u16 = @intCast(ty.bitSize(mod)); | |
| 846 | const int_ty = try mod.intType(.unsigned, union_bits); | |
| 847 | return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset); | |
| 851 | const backing_ty = try ty.unionBackingType(mod); | |
| 852 | return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset); | |
| 848 | 853 | } |
| 849 | 854 | }, |
| 850 | 855 | } |
| ... | ... | @@ -1146,10 +1151,8 @@ pub const Value = struct { |
| 1146 | 1151 | .Union => switch (ty.containerLayout(mod)) { |
| 1147 | 1152 | .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory |
| 1148 | 1153 | .Packed => { |
| 1149 | const union_bits: u16 = @intCast(ty.bitSize(mod)); | |
| 1150 | assert(union_bits != 0); | |
| 1151 | const int_ty = try mod.intType(.unsigned, union_bits); | |
| 1152 | const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern(); | |
| 1154 | const backing_ty = try ty.unionBackingType(mod); | |
| 1155 | const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern(); | |
| 1153 | 1156 | return (try mod.intern(.{ .un = .{ |
| 1154 | 1157 | .ty = ty.toIntern(), |
| 1155 | 1158 | .tag = .none, |
| ... | ... | @@ -4017,7 +4020,7 @@ pub const Value = struct { |
| 4017 | 4020 | data: Data, |
| 4018 | 4021 | |
| 4019 | 4022 | pub const Data = struct { |
| 4020 | tag: Value, | |
| 4023 | tag: ?Value, | |
| 4021 | 4024 | val: Value, |
| 4022 | 4025 | }; |
| 4023 | 4026 | }; |
test/behavior/comptime_memory.zig-51| ... | ... | @@ -455,54 +455,3 @@ test "type pun null pointer-like optional" { |
| 455 | 455 | // note that expectEqual hides the bug |
| 456 | 456 | try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null); |
| 457 | 457 | } |
| 458 | ||
| 459 | test "reinterpret extern union" { | |
| 460 | { | |
| 461 | const U = extern union { | |
| 462 | a: u32, | |
| 463 | b: u8 align(8), | |
| 464 | }; | |
| 465 | ||
| 466 | comptime var u: U = undefined; | |
| 467 | comptime @memset(std.mem.asBytes(&u), 42); | |
| 468 | try comptime testing.expect(0x2a2a2a2a == u.a); | |
| 469 | try comptime testing.expect(42 == u.b); | |
| 470 | try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); | |
| 471 | try testing.expectEqual(42, u.b); | |
| 472 | } | |
| 473 | } | |
| 474 | ||
| 475 | test "reinterpret packed union" { | |
| 476 | { | |
| 477 | const U = packed union { | |
| 478 | a: u32, | |
| 479 | b: u8 align(8), | |
| 480 | }; | |
| 481 | ||
| 482 | comptime var u: U = undefined; | |
| 483 | comptime @memset(std.mem.asBytes(&u), 42); | |
| 484 | try comptime testing.expect(0x2a2a2a2a == u.a); | |
| 485 | try comptime testing.expect(0x2a == u.b); | |
| 486 | try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); | |
| 487 | try testing.expectEqual(0x2a, u.b); | |
| 488 | } | |
| 489 | ||
| 490 | { | |
| 491 | const U = packed union { | |
| 492 | a: u7, | |
| 493 | b: u1, | |
| 494 | }; | |
| 495 | ||
| 496 | const S = packed struct { | |
| 497 | lsb: U, | |
| 498 | msb: U, | |
| 499 | }; | |
| 500 | ||
| 501 | comptime var s: S = undefined; | |
| 502 | comptime @memset(std.mem.asBytes(&s), 0xaa); | |
| 503 | try comptime testing.expectEqual(@as(u7, 0x2a), s.lsb.a); | |
| 504 | try comptime testing.expectEqual(@as(u1, 0), s.lsb.b); | |
| 505 | try comptime testing.expectEqual(@as(u7, 0x55), s.msb.a); | |
| 506 | try comptime testing.expectEqual(@as(u1, 1), s.msb.b); | |
| 507 | } | |
| 508 | } |
test/behavior/union.zig+213-7| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | 2 | const std = @import("std"); |
| 3 | const endian = builtin.cpu.arch.endian(); | |
| 3 | 4 | const expect = std.testing.expect; |
| 4 | 5 | const assert = std.debug.assert; |
| 5 | 6 | const expectEqual = std.testing.expectEqual; |
| ... | ... | @@ -1660,15 +1661,220 @@ test "union with 128 bit integer" { |
| 1660 | 1661 | } |
| 1661 | 1662 | } |
| 1662 | 1663 | |
| 1663 | test "memset extern union at comptime" { | |
| 1664 | test "memset extern union" { | |
| 1664 | 1665 | const U = extern union { |
| 1665 | 1666 | foo: u8, |
| 1667 | bar: u32, | |
| 1668 | }; | |
| 1669 | ||
| 1670 | const S = struct { | |
| 1671 | fn doTheTest() !void { | |
| 1672 | var u: U = undefined; | |
| 1673 | @memset(std.mem.asBytes(&u), 0); | |
| 1674 | try expectEqual(@as(u8, 0), u.foo); | |
| 1675 | try expectEqual(@as(u32, 0), u.bar); | |
| 1676 | } | |
| 1677 | }; | |
| 1678 | ||
| 1679 | try comptime S.doTheTest(); | |
| 1680 | try S.doTheTest(); | |
| 1681 | } | |
| 1682 | ||
| 1683 | test "memset packed union" { | |
| 1684 | const U = packed union { | |
| 1685 | a: u32, | |
| 1686 | b: u8, | |
| 1687 | }; | |
| 1688 | ||
| 1689 | const S = struct { | |
| 1690 | fn doTheTest() !void { | |
| 1691 | var u: U = undefined; | |
| 1692 | @memset(std.mem.asBytes(&u), 42); | |
| 1693 | try expectEqual(@as(u32, 0x2a2a2a2a), u.a); | |
| 1694 | try expectEqual(@as(u8, 0x2a), u.b); | |
| 1695 | } | |
| 1696 | }; | |
| 1697 | ||
| 1698 | try comptime S.doTheTest(); | |
| 1699 | ||
| 1700 | if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO | |
| 1701 | try S.doTheTest(); | |
| 1702 | } | |
| 1703 | ||
| 1704 | fn littleToNativeEndian(comptime T: type, v: T) T { | |
| 1705 | return if (endian == .Little) v else @byteSwap(v); | |
| 1706 | } | |
| 1707 | ||
| 1708 | test "reinterpret extern union" { | |
| 1709 | const U = extern union { | |
| 1710 | foo: u8, | |
| 1711 | baz: u32 align(8), | |
| 1712 | bar: u32, | |
| 1713 | }; | |
| 1714 | ||
| 1715 | const S = struct { | |
| 1716 | fn doTheTest() !void { | |
| 1717 | { | |
| 1718 | // Undefined initialization | |
| 1719 | const u = blk: { | |
| 1720 | var u: U = undefined; | |
| 1721 | @memset(std.mem.asBytes(&u), 0); | |
| 1722 | u.bar = 0xbbbbbbbb; | |
| 1723 | u.foo = 0x2a; | |
| 1724 | break :blk u; | |
| 1725 | }; | |
| 1726 | ||
| 1727 | try expectEqual(@as(u8, 0x2a), u.foo); | |
| 1728 | try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.bar); | |
| 1729 | try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.baz); | |
| 1730 | } | |
| 1731 | ||
| 1732 | { | |
| 1733 | // Union initialization | |
| 1734 | var u: U = .{ | |
| 1735 | .foo = 0x2a, | |
| 1736 | }; | |
| 1737 | ||
| 1738 | { | |
| 1739 | const expected, const mask = switch (endian) { | |
| 1740 | .Little => .{ 0x2a, 0xff }, | |
| 1741 | .Big => .{ 0x2a000000, 0xff000000 }, | |
| 1742 | }; | |
| 1743 | ||
| 1744 | try expectEqual(@as(u8, 0x2a), u.foo); | |
| 1745 | try expectEqual(@as(u32, expected), u.bar & mask); | |
| 1746 | try expectEqual(@as(u32, expected), u.baz & mask); | |
| 1747 | } | |
| 1748 | ||
| 1749 | // Writing to a larger field | |
| 1750 | u.baz = 0xbbbbbbbb; | |
| 1751 | try expectEqual(@as(u8, 0xbb), u.foo); | |
| 1752 | try expectEqual(@as(u32, 0xbbbbbbbb), u.bar); | |
| 1753 | try expectEqual(@as(u32, 0xbbbbbbbb), u.baz); | |
| 1754 | ||
| 1755 | // Writing to the same field | |
| 1756 | u.baz = 0xcccccccc; | |
| 1757 | try expectEqual(@as(u8, 0xcc), u.foo); | |
| 1758 | try expectEqual(@as(u32, 0xcccccccc), u.bar); | |
| 1759 | try expectEqual(@as(u32, 0xcccccccc), u.baz); | |
| 1760 | ||
| 1761 | // Writing to a smaller field | |
| 1762 | u.foo = 0xdd; | |
| 1763 | try expectEqual(@as(u8, 0xdd), u.foo); | |
| 1764 | try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.bar); | |
| 1765 | try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.baz); | |
| 1766 | } | |
| 1767 | } | |
| 1768 | }; | |
| 1769 | ||
| 1770 | try comptime S.doTheTest(); | |
| 1771 | ||
| 1772 | if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO | |
| 1773 | try S.doTheTest(); | |
| 1774 | } | |
| 1775 | ||
| 1776 | test "reinterpret packed union" { | |
| 1777 | const U = packed union { | |
| 1778 | foo: u8, | |
| 1779 | bar: u29, | |
| 1780 | baz: u64, | |
| 1781 | qux: u12, | |
| 1782 | }; | |
| 1783 | ||
| 1784 | const S = struct { | |
| 1785 | fn doTheTest() !void { | |
| 1786 | { | |
| 1787 | const u = blk: { | |
| 1788 | var u: U = undefined; | |
| 1789 | @memset(std.mem.asBytes(&u), 0); | |
| 1790 | u.baz = 0xbbbbbbbb; | |
| 1791 | u.qux = 0xe2a; | |
| 1792 | break :blk u; | |
| 1793 | }; | |
| 1794 | ||
| 1795 | try expectEqual(@as(u8, 0x2a), u.foo); | |
| 1796 | try expectEqual(@as(u12, 0xe2a), u.qux); | |
| 1797 | ||
| 1798 | // https://github.com/ziglang/zig/issues/17360 | |
| 1799 | if (@inComptime()) { | |
| 1800 | try expectEqual(@as(u29, 0x1bbbbe2a), u.bar); | |
| 1801 | try expectEqual(@as(u64, 0xbbbbbe2a), u.baz); | |
| 1802 | } | |
| 1803 | } | |
| 1804 | ||
| 1805 | { | |
| 1806 | // Union initialization | |
| 1807 | var u: U = .{ | |
| 1808 | .qux = 0xe2a, | |
| 1809 | }; | |
| 1810 | try expectEqual(@as(u8, 0x2a), u.foo); | |
| 1811 | try expectEqual(@as(u12, 0xe2a), u.qux); | |
| 1812 | try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff); | |
| 1813 | try expectEqual(@as(u64, 0xe2a), u.baz & 0xfff); | |
| 1814 | ||
| 1815 | // Writing to a larger field | |
| 1816 | u.baz = 0xbbbbbbbb; | |
| 1817 | try expectEqual(@as(u8, 0xbb), u.foo); | |
| 1818 | try expectEqual(@as(u12, 0xbbb), u.qux); | |
| 1819 | try expectEqual(@as(u29, 0x1bbbbbbb), u.bar); | |
| 1820 | try expectEqual(@as(u64, 0xbbbbbbbb), u.baz); | |
| 1821 | ||
| 1822 | // Writing to the same field | |
| 1823 | u.baz = 0xcccccccc; | |
| 1824 | try expectEqual(@as(u8, 0xcc), u.foo); | |
| 1825 | try expectEqual(@as(u12, 0xccc), u.qux); | |
| 1826 | try expectEqual(@as(u29, 0x0ccccccc), u.bar); | |
| 1827 | try expectEqual(@as(u64, 0xcccccccc), u.baz); | |
| 1828 | ||
| 1829 | // Writing to a smaller field | |
| 1830 | u.foo = 0xdd; | |
| 1831 | try expectEqual(@as(u8, 0xdd), u.foo); | |
| 1832 | try expectEqual(@as(u12, 0xcdd), u.qux); | |
| 1833 | try expectEqual(@as(u29, 0x0cccccdd), u.bar); | |
| 1834 | try expectEqual(@as(u64, 0xccccccdd), u.baz); | |
| 1835 | } | |
| 1836 | } | |
| 1666 | 1837 | }; |
| 1667 | const u = comptime blk: { | |
| 1668 | var u: U = undefined; | |
| 1669 | @memset(std.mem.asBytes(&u), 0); | |
| 1670 | u.foo = 0; | |
| 1671 | break :blk u; | |
| 1838 | ||
| 1839 | try comptime S.doTheTest(); | |
| 1840 | ||
| 1841 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO | |
| 1842 | if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; // TODO | |
| 1843 | if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO | |
| 1844 | try S.doTheTest(); | |
| 1845 | } | |
| 1846 | ||
| 1847 | test "reinterpret packed union inside packed struct" { | |
| 1848 | const U = packed union { | |
| 1849 | a: u7, | |
| 1850 | b: u1, | |
| 1851 | }; | |
| 1852 | ||
| 1853 | const V = packed struct { | |
| 1854 | lo: U, | |
| 1855 | hi: U, | |
| 1672 | 1856 | }; |
| 1673 | try expect(u.foo == 0); | |
| 1857 | ||
| 1858 | const S = struct { | |
| 1859 | fn doTheTest() !void { | |
| 1860 | var v: V = undefined; | |
| 1861 | @memset(std.mem.asBytes(&v), 0x55); | |
| 1862 | try expectEqual(@as(u7, 0x55), v.lo.a); | |
| 1863 | try expectEqual(@as(u1, 1), v.lo.b); | |
| 1864 | try expectEqual(@as(u7, 0x2a), v.hi.a); | |
| 1865 | try expectEqual(@as(u1, 0), v.hi.b); | |
| 1866 | ||
| 1867 | v.lo.b = 0; | |
| 1868 | try expectEqual(@as(u7, 0x54), v.lo.a); | |
| 1869 | try expectEqual(@as(u1, 0), v.lo.b); | |
| 1870 | v.hi.b = 1; | |
| 1871 | try expectEqual(@as(u7, 0x2b), v.hi.a); | |
| 1872 | try expectEqual(@as(u1, 1), v.hi.b); | |
| 1873 | } | |
| 1874 | }; | |
| 1875 | ||
| 1876 | try comptime S.doTheTest(); | |
| 1877 | ||
| 1878 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO | |
| 1879 | try S.doTheTest(); | |
| 1674 | 1880 | } |