| 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,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in |
| 6607 | return field_ty.abiAlignment(mod); | 6607 | return field_ty.abiAlignment(mod); |
| 6608 | } | 6608 | } |
| 6609 | 6609 | ||
| 6610 | /// Returns the index of the active field, given the current tag value | ||
| 6610 | pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { | 6611 | pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 { |
| 6611 | const ip = &mod.intern_pool; | 6612 | const ip = &mod.intern_pool; |
| 6612 | if (enum_tag.toIntern() == .none) return null; | 6613 | if (enum_tag.toIntern() == .none) return null; |
src/Sema.zig+110-22| ... | @@ -27258,7 +27258,7 @@ fn unionFieldVal( | ... | @@ -27258,7 +27258,7 @@ fn unionFieldVal( |
| 27258 | return sema.failWithOwnedErrorMsg(block, msg); | 27258 | return sema.failWithOwnedErrorMsg(block, msg); |
| 27259 | } | 27259 | } |
| 27260 | }, | 27260 | }, |
| 27261 | .Packed, .Extern => { | 27261 | .Packed, .Extern => |layout| { |
| 27262 | if (tag_matches) { | 27262 | if (tag_matches) { |
| 27263 | return Air.internedToRef(un.val); | 27263 | return Air.internedToRef(un.val); |
| 27264 | } else { | 27264 | } else { |
| ... | @@ -27267,7 +27267,7 @@ fn unionFieldVal( | ... | @@ -27267,7 +27267,7 @@ fn unionFieldVal( |
| 27267 | else | 27267 | else |
| 27268 | union_ty.unionFieldType(un.tag.toValue(), mod).?; | 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 | return Air.internedToRef(new_val.toIntern()); | 27271 | return Air.internedToRef(new_val.toIntern()); |
| 27272 | } | 27272 | } |
| 27273 | } | 27273 | } |
| ... | @@ -29788,13 +29788,19 @@ fn storePtrVal( | ... | @@ -29788,13 +29788,19 @@ fn storePtrVal( |
| 29788 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already | 29788 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already |
| 29789 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}), | 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) { | 29791 | if (reinterpret.write_packed) { |
| 29792 | error.OutOfMemory => return error.OutOfMemory, | 29792 | operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) { |
| 29793 | error.ReinterpretDeclRef => unreachable, | 29793 | error.OutOfMemory => return error.OutOfMemory, |
| 29794 | error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already | 29794 | error.ReinterpretDeclRef => unreachable, |
| 29795 | error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}), | 29795 | }; |
| 29796 | }; | 29796 | } else { |
| 29797 | 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 | const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { | 29804 | const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) { |
| 29799 | error.OutOfMemory => return error.OutOfMemory, | 29805 | error.OutOfMemory => return error.OutOfMemory, |
| 29800 | error.IllDefinedMemoryLayout => unreachable, | 29806 | error.IllDefinedMemoryLayout => unreachable, |
| ... | @@ -29826,6 +29832,8 @@ const ComptimePtrMutationKit = struct { | ... | @@ -29826,6 +29832,8 @@ const ComptimePtrMutationKit = struct { |
| 29826 | reinterpret: struct { | 29832 | reinterpret: struct { |
| 29827 | val_ptr: *Value, | 29833 | val_ptr: *Value, |
| 29828 | byte_offset: usize, | 29834 | byte_offset: usize, |
| 29835 | /// If set, write the operand to packed memory | ||
| 29836 | write_packed: bool = false, | ||
| 29829 | }, | 29837 | }, |
| 29830 | /// If the root decl could not be used as parent, this means `ty` is the type that | 29838 | /// If the root decl could not be used as parent, this means `ty` is the type that |
| 29831 | /// caused that by not having a well-defined layout. | 29839 | /// caused that by not having a well-defined layout. |
| ... | @@ -30189,21 +30197,43 @@ fn beginComptimePtrMutation( | ... | @@ -30189,21 +30197,43 @@ fn beginComptimePtrMutation( |
| 30189 | ); | 30197 | ); |
| 30190 | }, | 30198 | }, |
| 30191 | .@"union" => { | 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 | const payload = &val_ptr.castTag(.@"union").?.data; | 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( | 30203 | const tag_type = base_child_ty.unionTagTypeHypothetical(mod); |
| 30199 | sema, | 30204 | const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index); |
| 30200 | block, | 30205 | if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) { |
| 30201 | src, | 30206 | // We need to set the active field of the union. |
| 30202 | parent.ty.structFieldType(field_index, mod), | 30207 | payload.tag = hypothetical_tag; |
| 30203 | &payload.val, | 30208 | |
| 30204 | ptr_elem_ty, | 30209 | const field_ty = parent.ty.structFieldType(field_index, mod); |
| 30205 | parent.mut_decl, | 30210 | return beginComptimePtrMutationInner( |
| 30206 | ); | 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 | .slice => switch (field_index) { | 30238 | .slice => switch (field_index) { |
| 30209 | Value.slice_ptr_index => return beginComptimePtrMutationInner( | 30239 | Value.slice_ptr_index => return beginComptimePtrMutationInner( |
| ... | @@ -30704,6 +30734,7 @@ fn bitCastVal( | ... | @@ -30704,6 +30734,7 @@ fn bitCastVal( |
| 30704 | // For types with well-defined memory layouts, we serialize them a byte buffer, | 30734 | // For types with well-defined memory layouts, we serialize them a byte buffer, |
| 30705 | // then deserialize to the new type. | 30735 | // then deserialize to the new type. |
| 30706 | const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); | 30736 | const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); |
| 30737 | |||
| 30707 | const buffer = try sema.gpa.alloc(u8, abi_size); | 30738 | const buffer = try sema.gpa.alloc(u8, abi_size); |
| 30708 | defer sema.gpa.free(buffer); | 30739 | defer sema.gpa.free(buffer); |
| 30709 | val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { | 30740 | val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { |
| ... | @@ -30720,6 +30751,63 @@ fn bitCastVal( | ... | @@ -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 | fn coerceArrayPtrToSlice( | 30811 | fn coerceArrayPtrToSlice( |
| 30724 | sema: *Sema, | 30812 | sema: *Sema, |
| 30725 | block: *Block, | 30813 | block: *Block, |
src/TypedValue.zig+17-7| ... | @@ -84,22 +84,27 @@ pub fn print( | ... | @@ -84,22 +84,27 @@ pub fn print( |
| 84 | if (level == 0) { | 84 | if (level == 0) { |
| 85 | return writer.writeAll(".{ ... }"); | 85 | return writer.writeAll(".{ ... }"); |
| 86 | } | 86 | } |
| 87 | const union_val = val.castTag(.@"union").?.data; | 87 | const payload = val.castTag(.@"union").?.data; |
| 88 | try writer.writeAll(".{ "); | 88 | try writer.writeAll(".{ "); |
| 89 | 89 | ||
| 90 | if (union_val.tag.toIntern() != .none) { | 90 | if (payload.tag) |tag| { |
| 91 | try print(.{ | 91 | try print(.{ |
| 92 | .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), | 92 | .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(), |
| 93 | .val = union_val.tag, | 93 | .val = tag, |
| 94 | }, writer, level - 1, mod); | 94 | }, writer, level - 1, mod); |
| 95 | try writer.writeAll(" = "); | 95 | try writer.writeAll(" = "); |
| 96 | const field_ty = ty.unionFieldType(union_val.tag, mod).?; | 96 | const field_ty = ty.unionFieldType(tag, mod).?; |
| 97 | try print(.{ | 97 | try print(.{ |
| 98 | .ty = field_ty, | 98 | .ty = field_ty, |
| 99 | .val = union_val.val, | 99 | .val = payload.val, |
| 100 | }, writer, level - 1, mod); | 100 | }, writer, level - 1, mod); |
| 101 | } else { | 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 | return writer.writeAll(" }"); | 110 | return writer.writeAll(" }"); |
| ... | @@ -421,7 +426,12 @@ pub fn print( | ... | @@ -421,7 +426,12 @@ pub fn print( |
| 421 | .val = un.val.toValue(), | 426 | .val = un.val.toValue(), |
| 422 | }, writer, level - 1, mod); | 427 | }, writer, level - 1, mod); |
| 423 | } else { | 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 | } else try writer.writeAll("..."); | 436 | } else try writer.writeAll("..."); |
| 427 | return writer.writeAll(" }"); | 437 | return writer.writeAll(" }"); |
src/type.zig+10| ... | @@ -1954,6 +1954,16 @@ pub const Type = struct { | ... | @@ -1954,6 +1954,16 @@ pub const Type = struct { |
| 1954 | return true; | 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 | pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout { | 1967 | pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout { |
| 1958 | const ip = &mod.intern_pool; | 1968 | const ip = &mod.intern_pool; |
| 1959 | const union_type = ip.indexToKey(ty.toIntern()).union_type; | 1969 | const union_type = ip.indexToKey(ty.toIntern()).union_type; |
src/value.zig+25-22| ... | @@ -327,11 +327,19 @@ pub const Value = struct { | ... | @@ -327,11 +327,19 @@ pub const Value = struct { |
| 327 | }, | 327 | }, |
| 328 | .@"union" => { | 328 | .@"union" => { |
| 329 | const pl = val.castTag(.@"union").?.data; | 329 | const pl = val.castTag(.@"union").?.data; |
| 330 | return mod.intern(.{ .un = .{ | 330 | if (pl.tag) |pl_tag| { |
| 331 | .ty = ty.toIntern(), | 331 | return mod.intern(.{ .un = .{ |
| 332 | .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod), | 332 | .ty = ty.toIntern(), |
| 333 | .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod), | 333 | .tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod), |
| 334 | } }); | 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,10 +407,7 @@ pub const Value = struct { |
| 399 | 407 | ||
| 400 | .un => |un| Tag.@"union".create(arena, .{ | 408 | .un => |un| Tag.@"union".create(arena, .{ |
| 401 | // toValue asserts that the value cannot be .none which is valid on unions. | 409 | // toValue asserts that the value cannot be .none which is valid on unions. |
| 402 | .tag = .{ | 410 | .tag = if (un.tag == .none) null else un.tag.toValue(), |
| 403 | .ip_index = un.tag, | ||
| 404 | .legacy = undefined, | ||
| 405 | }, | ||
| 406 | .val = un.val.toValue(), | 411 | .val = un.val.toValue(), |
| 407 | }), | 412 | }), |
| 408 | 413 | ||
| ... | @@ -709,21 +714,22 @@ pub const Value = struct { | ... | @@ -709,21 +714,22 @@ pub const Value = struct { |
| 709 | .Union => switch (ty.containerLayout(mod)) { | 714 | .Union => switch (ty.containerLayout(mod)) { |
| 710 | .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already | 715 | .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already |
| 711 | .Extern => { | 716 | .Extern => { |
| 712 | const union_obj = mod.typeToUnion(ty).?; | ||
| 713 | if (val.unionTag(mod)) |union_tag| { | 717 | if (val.unionTag(mod)) |union_tag| { |
| 718 | const union_obj = mod.typeToUnion(ty).?; | ||
| 714 | const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; | 719 | const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?; |
| 715 | const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); | 720 | const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType(); |
| 716 | const field_val = try val.fieldValue(mod, field_index); | 721 | const field_val = try val.fieldValue(mod, field_index); |
| 717 | const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); | 722 | const byte_count = @as(usize, @intCast(field_type.abiSize(mod))); |
| 718 | return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); | 723 | return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]); |
| 719 | } else { | 724 | } else { |
| 720 | const union_size = ty.abiSize(mod); | 725 | const backing_ty = try ty.unionBackingType(mod); |
| 721 | const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type }); | 726 | const byte_count: usize = @intCast(backing_ty.abiSize(mod)); |
| 722 | return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]); | 727 | return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]); |
| 723 | } | 728 | } |
| 724 | }, | 729 | }, |
| 725 | .Packed => { | 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 | return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0); | 733 | return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0); |
| 728 | }, | 734 | }, |
| 729 | }, | 735 | }, |
| ... | @@ -842,9 +848,8 @@ pub const Value = struct { | ... | @@ -842,9 +848,8 @@ pub const Value = struct { |
| 842 | const field_val = try val.fieldValue(mod, field_index); | 848 | const field_val = try val.fieldValue(mod, field_index); |
| 843 | return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); | 849 | return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset); |
| 844 | } else { | 850 | } else { |
| 845 | const union_bits: u16 = @intCast(ty.bitSize(mod)); | 851 | const backing_ty = try ty.unionBackingType(mod); |
| 846 | const int_ty = try mod.intType(.unsigned, union_bits); | 852 | return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset); |
| 847 | return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset); | ||
| 848 | } | 853 | } |
| 849 | }, | 854 | }, |
| 850 | } | 855 | } |
| ... | @@ -1146,10 +1151,8 @@ pub const Value = struct { | ... | @@ -1146,10 +1151,8 @@ pub const Value = struct { |
| 1146 | .Union => switch (ty.containerLayout(mod)) { | 1151 | .Union => switch (ty.containerLayout(mod)) { |
| 1147 | .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory | 1152 | .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory |
| 1148 | .Packed => { | 1153 | .Packed => { |
| 1149 | const union_bits: u16 = @intCast(ty.bitSize(mod)); | 1154 | const backing_ty = try ty.unionBackingType(mod); |
| 1150 | assert(union_bits != 0); | 1155 | const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern(); |
| 1151 | const int_ty = try mod.intType(.unsigned, union_bits); | ||
| 1152 | const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern(); | ||
| 1153 | return (try mod.intern(.{ .un = .{ | 1156 | return (try mod.intern(.{ .un = .{ |
| 1154 | .ty = ty.toIntern(), | 1157 | .ty = ty.toIntern(), |
| 1155 | .tag = .none, | 1158 | .tag = .none, |
| ... | @@ -4017,7 +4020,7 @@ pub const Value = struct { | ... | @@ -4017,7 +4020,7 @@ pub const Value = struct { |
| 4017 | data: Data, | 4020 | data: Data, |
| 4018 | 4021 | ||
| 4019 | pub const Data = struct { | 4022 | pub const Data = struct { |
| 4020 | tag: Value, | 4023 | tag: ?Value, |
| 4021 | val: Value, | 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,54 +455,3 @@ test "type pun null pointer-like optional" { |
| 455 | // note that expectEqual hides the bug | 455 | // note that expectEqual hides the bug |
| 456 | try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null); | 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,5 +1,6 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | const endian = builtin.cpu.arch.endian(); | ||
| 3 | const expect = std.testing.expect; | 4 | const expect = std.testing.expect; |
| 4 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 5 | const expectEqual = std.testing.expectEqual; | 6 | const expectEqual = std.testing.expectEqual; |
| ... | @@ -1660,15 +1661,220 @@ test "union with 128 bit integer" { | ... | @@ -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 | const U = extern union { | 1665 | const U = extern union { |
| 1665 | foo: u8, | 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: { | 1838 | |
| 1668 | var u: U = undefined; | 1839 | try comptime S.doTheTest(); |
| 1669 | @memset(std.mem.asBytes(&u), 0); | 1840 | |
| 1670 | u.foo = 0; | 1841 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 1671 | break :blk u; | 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 | } |