authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-28 01:52:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-28 01:53:58-07:00
log4b9b9e725777a8f9e4ea9391beaeea34c834615f
treef1cbd788134cdbcde7f90dc2b9ada91dcd9159ba
parent232f8a291d2debd8a0fe9df2ce36d9035a15aefb

stage2: LLVM backend: fix lowering of union constants

Comment from this commit reproduced here: LLVM does not allow us to change the type of globals. So we must create a new global with the correct type, copy all its attributes, and then update all references to point to the new global, delete the original, and rename the new one to the old one's name. This is necessary because LLVM does not support const bitcasting a struct with padding bytes, which is needed to lower a const union value to LLVM, when a field other than the most-aligned is active. Instead, we must lower to an unnamed struct, and pointer cast at usage sites of the global. Such an unnamed struct is the cause of the global type mismatch, because we don't have the LLVM type until the *value* is created, whereas the global needs to be created based on the type alone, because lowering the value may reference the global as a pointer.

8 files changed, 205 insertions(+), 129 deletions(-)

src/codegen/llvm.zig+70-19
...@@ -557,8 +557,8 @@ pub const DeclGen = struct {...@@ -557,8 +557,8 @@ pub const DeclGen = struct {
557 return self.object.llvm_module;557 return self.object.llvm_module;
558 }558 }
559559
560 fn genDecl(self: *DeclGen) !void {560 fn genDecl(dg: *DeclGen) !void {
561 const decl = self.decl;561 const decl = dg.decl;
562 assert(decl.has_tv);562 assert(decl.has_tv);
563563
564 log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });564 log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
...@@ -567,10 +567,10 @@ pub const DeclGen = struct {...@@ -567,10 +567,10 @@ pub const DeclGen = struct {
567 _ = func_payload;567 _ = func_payload;
568 @panic("TODO llvm backend genDecl function pointer");568 @panic("TODO llvm backend genDecl function pointer");
569 } else if (decl.val.castTag(.extern_fn)) |extern_fn| {569 } else if (decl.val.castTag(.extern_fn)) |extern_fn| {
570 _ = try self.resolveLlvmFunction(extern_fn.data);570 _ = try dg.resolveLlvmFunction(extern_fn.data);
571 } else {571 } else {
572 const target = self.module.getTarget();572 const target = dg.module.getTarget();
573 const global = try self.resolveGlobalDecl(decl);573 const global = try dg.resolveGlobalDecl(decl);
574 global.setAlignment(decl.getAlignment(target));574 global.setAlignment(decl.getAlignment(target));
575 assert(decl.has_tv);575 assert(decl.has_tv);
576 const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {576 const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
...@@ -581,8 +581,35 @@ pub const DeclGen = struct {...@@ -581,8 +581,35 @@ pub const DeclGen = struct {
581 break :init_val decl.val;581 break :init_val decl.val;
582 };582 };
583 if (init_val.tag() != .unreachable_value) {583 if (init_val.tag() != .unreachable_value) {
584 const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val });584 const llvm_init = try dg.genTypedValue(.{ .ty = decl.ty, .val = init_val });
585 global.setInitializer(llvm_init);585 if (global.globalGetValueType() == llvm_init.typeOf()) {
586 global.setInitializer(llvm_init);
587 } else {
588 // LLVM does not allow us to change the type of globals. So we must
589 // create a new global with the correct type, copy all its attributes,
590 // and then update all references to point to the new global,
591 // delete the original, and rename the new one to the old one's name.
592 // This is necessary because LLVM does not support const bitcasting
593 // a struct with padding bytes, which is needed to lower a const union value
594 // to LLVM, when a field other than the most-aligned is active. Instead,
595 // we must lower to an unnamed struct, and pointer cast at usage sites
596 // of the global. Such an unnamed struct is the cause of the global type
597 // mismatch, because we don't have the LLVM type until the *value* is created,
598 // whereas the global needs to be created based on the type alone, because
599 // lowering the value may reference the global as a pointer.
600 const new_global = dg.object.llvm_module.addGlobalInAddressSpace(
601 llvm_init.typeOf(),
602 "",
603 dg.llvmAddressSpace(decl.@"addrspace"),
604 );
605 new_global.setLinkage(global.getLinkage());
606 new_global.setUnnamedAddr(global.getUnnamedAddress());
607 new_global.setAlignment(global.getAlignment());
608 new_global.setInitializer(llvm_init);
609 global.replaceAllUsesWith(new_global);
610 new_global.takeName(global);
611 global.deleteGlobal();
612 }
586 }613 }
587 }614 }
588 }615 }
...@@ -1456,9 +1483,15 @@ pub const DeclGen = struct {...@@ -1456,9 +1483,15 @@ pub const DeclGen = struct {
1456 const layout = tv.ty.unionGetLayout(target);1483 const layout = tv.ty.unionGetLayout(target);
14571484
1458 if (layout.payload_size == 0) {1485 if (layout.payload_size == 0) {
1459 return genTypedValue(dg, .{ .ty = tv.ty.unionTagType().?, .val = tag_and_val.tag });1486 return genTypedValue(dg, .{
1487 .ty = tv.ty.unionTagType().?,
1488 .val = tag_and_val.tag,
1489 });
1460 }1490 }
1461 const field_ty = tv.ty.unionFieldType(tag_and_val.tag);1491 const union_obj = tv.ty.cast(Type.Payload.Union).?.data;
1492 const field_index = union_obj.tag_ty.enumTagFieldIndex(tag_and_val.tag).?;
1493 assert(union_obj.haveFieldTypes());
1494 const field_ty = union_obj.fields.values()[field_index].ty;
1462 const payload = p: {1495 const payload = p: {
1463 if (!field_ty.hasCodeGenBits()) {1496 if (!field_ty.hasCodeGenBits()) {
1464 const padding_len = @intCast(c_uint, layout.payload_size);1497 const padding_len = @intCast(c_uint, layout.payload_size);
...@@ -1475,10 +1508,20 @@ pub const DeclGen = struct {...@@ -1475,10 +1508,20 @@ pub const DeclGen = struct {
1475 };1508 };
1476 break :p dg.context.constStruct(&fields, fields.len, .False);1509 break :p dg.context.constStruct(&fields, fields.len, .False);
1477 };1510 };
1511
1512 // In this case we must make an unnamed struct because LLVM does
1513 // not support bitcasting our payload struct to the true union payload type.
1514 // Instead we use an unnamed struct and every reference to the global
1515 // must pointer cast to the expected type before accessing the union.
1516 const need_unnamed = layout.most_aligned_field != field_index;
1517
1478 if (layout.tag_size == 0) {1518 if (layout.tag_size == 0) {
1479 const llvm_payload_ty = llvm_union_ty.structGetTypeAtIndex(0);1519 const fields: [1]*const llvm.Value = .{payload};
1480 const fields: [1]*const llvm.Value = .{payload.constBitCast(llvm_payload_ty)};1520 if (need_unnamed) {
1481 return llvm_union_ty.constNamedStruct(&fields, fields.len);1521 return dg.context.constStruct(&fields, fields.len, .False);
1522 } else {
1523 return llvm_union_ty.constNamedStruct(&fields, fields.len);
1524 }
1482 }1525 }
1483 const llvm_tag_value = try genTypedValue(dg, .{1526 const llvm_tag_value = try genTypedValue(dg, .{
1484 .ty = tv.ty.unionTagType().?,1527 .ty = tv.ty.unionTagType().?,
...@@ -1486,13 +1529,15 @@ pub const DeclGen = struct {...@@ -1486,13 +1529,15 @@ pub const DeclGen = struct {
1486 });1529 });
1487 var fields: [2]*const llvm.Value = undefined;1530 var fields: [2]*const llvm.Value = undefined;
1488 if (layout.tag_align >= layout.payload_align) {1531 if (layout.tag_align >= layout.payload_align) {
1489 fields[0] = llvm_tag_value;1532 fields = .{ llvm_tag_value, payload };
1490 fields[1] = payload.constBitCast(llvm_union_ty.structGetTypeAtIndex(1));1533 } else {
1534 fields = .{ payload, llvm_tag_value };
1535 }
1536 if (need_unnamed) {
1537 return dg.context.constStruct(&fields, fields.len, .False);
1491 } else {1538 } else {
1492 fields[0] = payload.constBitCast(llvm_union_ty.structGetTypeAtIndex(0));1539 return llvm_union_ty.constNamedStruct(&fields, fields.len);
1493 fields[1] = llvm_tag_value;
1494 }1540 }
1495 return llvm_union_ty.constNamedStruct(&fields, fields.len);
1496 },1541 },
1497 .Vector => switch (tv.val.tag()) {1542 .Vector => switch (tv.val.tag()) {
1498 .bytes => {1543 .bytes => {
...@@ -1859,8 +1904,14 @@ pub const FuncGen = struct {...@@ -1859,8 +1904,14 @@ pub const FuncGen = struct {
1859 global.setGlobalConstant(.True);1904 global.setGlobalConstant(.True);
1860 global.setUnnamedAddr(.True);1905 global.setUnnamedAddr(.True);
1861 global.setAlignment(ty.abiAlignment(target));1906 global.setAlignment(ty.abiAlignment(target));
1862 gop.value_ptr.* = global;1907 // Because of LLVM limitations for lowering certain types such as unions,
1863 return global;1908 // the type of global constants might not match the type it is supposed to
1909 // be, and so we must bitcast the pointer at the usage sites.
1910 const wanted_llvm_ty = try self.dg.llvmType(ty);
1911 const wanted_llvm_ptr_ty = wanted_llvm_ty.pointerType(0);
1912 const casted_ptr = global.constBitCast(wanted_llvm_ptr_ty);
1913 gop.value_ptr.* = casted_ptr;
1914 return casted_ptr;
1864 }1915 }
18651916
1866 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {1917 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
src/codegen/llvm/bindings.zig+18
...@@ -184,6 +184,9 @@ pub const Value = opaque {...@@ -184,6 +184,9 @@ pub const Value = opaque {
184 pub const setValueName2 = LLVMSetValueName2;184 pub const setValueName2 = LLVMSetValueName2;
185 extern fn LLVMSetValueName2(Val: *const Value, Name: [*]const u8, NameLen: usize) void;185 extern fn LLVMSetValueName2(Val: *const Value, Name: [*]const u8, NameLen: usize) void;
186186
187 pub const takeName = ZigLLVMTakeName;
188 extern fn ZigLLVMTakeName(new_owner: *const Value, victim: *const Value) void;
189
187 pub const deleteFunction = LLVMDeleteFunction;190 pub const deleteFunction = LLVMDeleteFunction;
188 extern fn LLVMDeleteFunction(Fn: *const Value) void;191 extern fn LLVMDeleteFunction(Fn: *const Value) void;
189192
...@@ -206,6 +209,21 @@ pub const Value = opaque {...@@ -206,6 +209,21 @@ pub const Value = opaque {
206 return LLVMIsPoison(Val).toBool();209 return LLVMIsPoison(Val).toBool();
207 }210 }
208 extern fn LLVMIsPoison(Val: *const Value) Bool;211 extern fn LLVMIsPoison(Val: *const Value) Bool;
212
213 pub const replaceAllUsesWith = LLVMReplaceAllUsesWith;
214 extern fn LLVMReplaceAllUsesWith(OldVal: *const Value, NewVal: *const Value) void;
215
216 pub const globalGetValueType = LLVMGlobalGetValueType;
217 extern fn LLVMGlobalGetValueType(Global: *const Value) *const Type;
218
219 pub const getLinkage = LLVMGetLinkage;
220 extern fn LLVMGetLinkage(Global: *const Value) Linkage;
221
222 pub const getUnnamedAddress = LLVMGetUnnamedAddress;
223 extern fn LLVMGetUnnamedAddress(Global: *const Value) Bool;
224
225 pub const getAlignment = LLVMGetAlignment;
226 extern fn LLVMGetAlignment(V: *const Value) c_uint;
209};227};
210228
211pub const Type = opaque {229pub const Type = opaque {
src/zig_llvm.cpp+4
...@@ -1319,6 +1319,10 @@ LLVMValueRef ZigLLVMBuildFPMulReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMVal...@@ -1319,6 +1319,10 @@ LLVMValueRef ZigLLVMBuildFPMulReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMVal
1319 return wrap(unwrap(B)->CreateFMulReduce(unwrap(Acc), unwrap(Val)));1319 return wrap(unwrap(B)->CreateFMulReduce(unwrap(Acc), unwrap(Val)));
1320}1320}
13211321
1322void ZigLLVMTakeName(LLVMValueRef new_owner, LLVMValueRef victim) {
1323 unwrap(new_owner)->takeName(unwrap(victim));
1324}
1325
1322static_assert((Triple::ArchType)ZigLLVM_UnknownArch == Triple::UnknownArch, "");1326static_assert((Triple::ArchType)ZigLLVM_UnknownArch == Triple::UnknownArch, "");
1323static_assert((Triple::ArchType)ZigLLVM_arm == Triple::arm, "");1327static_assert((Triple::ArchType)ZigLLVM_arm == Triple::arm, "");
1324static_assert((Triple::ArchType)ZigLLVM_armeb == Triple::armeb, "");1328static_assert((Triple::ArchType)ZigLLVM_armeb == Triple::armeb, "");
src/zig_llvm.h+13-11
...@@ -460,17 +460,19 @@ enum ZigLLVM_ObjectFormatType {...@@ -460,17 +460,19 @@ enum ZigLLVM_ObjectFormatType {
460 ZigLLVM_XCOFF,460 ZigLLVM_XCOFF,
461};461};
462462
463LLVMValueRef ZigLLVMBuildAndReduce(LLVMBuilderRef B, LLVMValueRef Val);463ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildAndReduce(LLVMBuilderRef B, LLVMValueRef Val);
464LLVMValueRef ZigLLVMBuildOrReduce(LLVMBuilderRef B, LLVMValueRef Val);464ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildOrReduce(LLVMBuilderRef B, LLVMValueRef Val);
465LLVMValueRef ZigLLVMBuildXorReduce(LLVMBuilderRef B, LLVMValueRef Val);465ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildXorReduce(LLVMBuilderRef B, LLVMValueRef Val);
466LLVMValueRef ZigLLVMBuildIntMaxReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed);466ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildIntMaxReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed);
467LLVMValueRef ZigLLVMBuildIntMinReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed);467ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildIntMinReduce(LLVMBuilderRef B, LLVMValueRef Val, bool is_signed);
468LLVMValueRef ZigLLVMBuildFPMaxReduce(LLVMBuilderRef B, LLVMValueRef Val);468ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildFPMaxReduce(LLVMBuilderRef B, LLVMValueRef Val);
469LLVMValueRef ZigLLVMBuildFPMinReduce(LLVMBuilderRef B, LLVMValueRef Val);469ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildFPMinReduce(LLVMBuilderRef B, LLVMValueRef Val);
470LLVMValueRef ZigLLVMBuildAddReduce(LLVMBuilderRef B, LLVMValueRef Val);470ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildAddReduce(LLVMBuilderRef B, LLVMValueRef Val);
471LLVMValueRef ZigLLVMBuildMulReduce(LLVMBuilderRef B, LLVMValueRef Val);471ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMulReduce(LLVMBuilderRef B, LLVMValueRef Val);
472LLVMValueRef ZigLLVMBuildFPAddReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMValueRef Val);472ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildFPAddReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMValueRef Val);
473LLVMValueRef ZigLLVMBuildFPMulReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMValueRef Val);473ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildFPMulReduce(LLVMBuilderRef B, LLVMValueRef Acc, LLVMValueRef Val);
474
475ZIG_EXTERN_C void ZigLLVMTakeName(LLVMValueRef new_owner, LLVMValueRef victim);
474476
475#define ZigLLVM_DIFlags_Zero 0U477#define ZigLLVM_DIFlags_Zero 0U
476#define ZigLLVM_DIFlags_Private 1U478#define ZigLLVM_DIFlags_Private 1U
test/behavior/switch.zig+19
...@@ -294,3 +294,22 @@ test "switch on union with some prongs capturing" {...@@ -294,3 +294,22 @@ test "switch on union with some prongs capturing" {
294 };294 };
295 try expect(y == 11);295 try expect(y == 11);
296}296}
297
298const Number = union(enum) {
299 One: u64,
300 Two: u8,
301 Three: f32,
302};
303
304const number = Number{ .Three = 1.23 };
305
306fn returnsFalse() bool {
307 switch (number) {
308 Number.One => |x| return x > 1234,
309 Number.Two => |x| return x == 'a',
310 Number.Three => |x| return x > 12.34,
311 }
312}
313test "switch on const enum with var" {
314 try expect(!returnsFalse());
315}
test/behavior/switch_stage1.zig-18
...@@ -3,24 +3,6 @@ const expect = std.testing.expect;...@@ -3,24 +3,6 @@ const expect = std.testing.expect;
3const expectError = std.testing.expectError;3const expectError = std.testing.expectError;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
55
6const Number = union(enum) {
7 One: u64,
8 Two: u8,
9 Three: f32,
10};
11
12const number = Number{ .Three = 1.23 };
13
14fn returnsFalse() bool {
15 switch (number) {
16 Number.One => |x| return x > 1234,
17 Number.Two => |x| return x == 'a',
18 Number.Three => |x| return x > 12.34,
19 }
20}
21test "switch on const enum with var" {
22 try expect(!returnsFalse());
23}
24test "switch all prongs unreachable" {6test "switch all prongs unreachable" {
25 try testAllProngsUnreachable();7 try testAllProngsUnreachable();
26 comptime try testAllProngsUnreachable();8 comptime try testAllProngsUnreachable();
test/behavior/union.zig+81
...@@ -71,3 +71,84 @@ test "0-sized extern union definition" {...@@ -71,3 +71,84 @@ test "0-sized extern union definition" {
7171
72 try expect(U.f == 1);72 try expect(U.f == 1);
73}73}
74
75const Value = union(enum) {
76 Int: u64,
77 Array: [9]u8,
78};
79
80const Agg = struct {
81 val1: Value,
82 val2: Value,
83};
84
85const v1 = Value{ .Int = 1234 };
86const v2 = Value{ .Array = [_]u8{3} ** 9 };
87
88const err = @as(anyerror!Agg, Agg{
89 .val1 = v1,
90 .val2 = v2,
91});
92
93const array = [_]Value{ v1, v2, v1, v2 };
94
95test "unions embedded in aggregate types" {
96 switch (array[1]) {
97 Value.Array => |arr| try expect(arr[4] == 3),
98 else => unreachable,
99 }
100 switch ((err catch unreachable).val1) {
101 Value.Int => |x| try expect(x == 1234),
102 else => unreachable,
103 }
104}
105
106test "access a member of tagged union with conflicting enum tag name" {
107 const Bar = union(enum) {
108 A: A,
109 B: B,
110
111 const A = u8;
112 const B = void;
113 };
114
115 comptime try expect(Bar.A == u8);
116}
117
118test "constant tagged union with payload" {
119 var empty = TaggedUnionWithPayload{ .Empty = {} };
120 var full = TaggedUnionWithPayload{ .Full = 13 };
121 shouldBeEmpty(empty);
122 shouldBeNotEmpty(full);
123}
124
125fn shouldBeEmpty(x: TaggedUnionWithPayload) void {
126 switch (x) {
127 TaggedUnionWithPayload.Empty => {},
128 else => unreachable,
129 }
130}
131
132fn shouldBeNotEmpty(x: TaggedUnionWithPayload) void {
133 switch (x) {
134 TaggedUnionWithPayload.Empty => unreachable,
135 else => {},
136 }
137}
138
139const TaggedUnionWithPayload = union(enum) {
140 Empty: void,
141 Full: i32,
142};
143
144test "union alignment" {
145 comptime {
146 try expect(@alignOf(AlignTestTaggedUnion) >= @alignOf([9]u8));
147 try expect(@alignOf(AlignTestTaggedUnion) >= @alignOf(u64));
148 }
149}
150
151const AlignTestTaggedUnion = union(enum) {
152 A: [9]u8,
153 B: u64,
154};
test/behavior/union_stage1.zig-81
...@@ -3,37 +3,6 @@ const expect = std.testing.expect;...@@ -3,37 +3,6 @@ const expect = std.testing.expect;
3const expectEqual = std.testing.expectEqual;3const expectEqual = std.testing.expectEqual;
4const Tag = std.meta.Tag;4const Tag = std.meta.Tag;
55
6const Value = union(enum) {
7 Int: u64,
8 Array: [9]u8,
9};
10
11const Agg = struct {
12 val1: Value,
13 val2: Value,
14};
15
16const v1 = Value{ .Int = 1234 };
17const v2 = Value{ .Array = [_]u8{3} ** 9 };
18
19const err = @as(anyerror!Agg, Agg{
20 .val1 = v1,
21 .val2 = v2,
22});
23
24const array = [_]Value{ v1, v2, v1, v2 };
25
26test "unions embedded in aggregate types" {
27 switch (array[1]) {
28 Value.Array => |arr| try expect(arr[4] == 3),
29 else => unreachable,
30 }
31 switch ((err catch unreachable).val1) {
32 Value.Int => |x| try expect(x == 1234),
33 else => unreachable,
34 }
35}
36
37const Letter = enum { A, B, C };6const Letter = enum { A, B, C };
38const Payload = union(Letter) {7const Payload = union(Letter) {
39 A: i32,8 A: i32,
...@@ -202,18 +171,6 @@ const PartialInstWithPayload = union(enum) {...@@ -202,18 +171,6 @@ const PartialInstWithPayload = union(enum) {
202 Compiled: i32,171 Compiled: i32,
203};172};
204173
205test "access a member of tagged union with conflicting enum tag name" {
206 const Bar = union(enum) {
207 A: A,
208 B: B,
209
210 const A = u8;
211 const B = void;
212 };
213
214 comptime try expect(Bar.A == u8);
215}
216
217test "tagged union initialization with runtime void" {174test "tagged union initialization with runtime void" {
218 try expect(testTaggedUnionInit({}));175 try expect(testTaggedUnionInit({}));
219}176}
...@@ -775,41 +732,3 @@ test "tagged union as return value" {...@@ -775,41 +732,3 @@ test "tagged union as return value" {
775fn returnAnInt(x: i32) TaggedFoo {732fn returnAnInt(x: i32) TaggedFoo {
776 return TaggedFoo{ .One = x };733 return TaggedFoo{ .One = x };
777}734}
778
779test "constant tagged union with payload" {
780 var empty = TaggedUnionWithPayload{ .Empty = {} };
781 var full = TaggedUnionWithPayload{ .Full = 13 };
782 shouldBeEmpty(empty);
783 shouldBeNotEmpty(full);
784}
785
786fn shouldBeEmpty(x: TaggedUnionWithPayload) void {
787 switch (x) {
788 TaggedUnionWithPayload.Empty => {},
789 else => unreachable,
790 }
791}
792
793fn shouldBeNotEmpty(x: TaggedUnionWithPayload) void {
794 switch (x) {
795 TaggedUnionWithPayload.Empty => unreachable,
796 else => {},
797 }
798}
799
800const TaggedUnionWithPayload = union(enum) {
801 Empty: void,
802 Full: i32,
803};
804
805test "union alignment" {
806 comptime {
807 try expect(@alignOf(AlignTestTaggedUnion) >= @alignOf([9]u8));
808 try expect(@alignOf(AlignTestTaggedUnion) >= @alignOf(u64));
809 }
810}
811
812const AlignTestTaggedUnion = union(enum) {
813 A: [9]u8,
814 B: u64,
815};