authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-02-28 17:42:59+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-02-28 17:42:59+01:00
log331cc810ded2058f2e2767b0485eb18d888a45e5
tree9e5ba37e5b06eaccf5ff36976955a32cda5ddfc4
parent90059a12e0ffe433132450f9f43221a198a22106
parent16f9774d2d6f358c97637e35609dfe0fc14cb501
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11012 from ziglang/x64-union-tag

stage2,x64: basic (un)tagged unions

4 files changed, 217 insertions(+), 76 deletions(-)

src/arch/x86_64/CodeGen.zig+96-46
......@@ -2098,17 +2098,72 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
20982098
20992099fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
21002100 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2101 _ = bin_op;
2102 return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch});
2101 const ptr_ty = self.air.typeOf(bin_op.lhs);
2102 const union_ty = ptr_ty.childType();
2103 const tag_ty = self.air.typeOf(bin_op.rhs);
2104 const layout = union_ty.unionGetLayout(self.target.*);
2105
2106 if (layout.tag_size == 0) {
2107 return self.finishAir(inst, .none, .{ bin_op.lhs, bin_op.rhs, .none });
2108 }
2109
2110 const ptr = try self.resolveInst(bin_op.lhs);
2111 ptr.freezeIfRegister(&self.register_manager);
2112 defer ptr.unfreezeIfRegister(&self.register_manager);
2113
2114 const tag = try self.resolveInst(bin_op.rhs);
2115 tag.freezeIfRegister(&self.register_manager);
2116 defer tag.unfreezeIfRegister(&self.register_manager);
2117
2118 const adjusted_ptr: MCValue = if (layout.payload_size > 0 and layout.tag_align < layout.payload_align) blk: {
2119 // TODO reusing the operand
2120 const reg = try self.copyToTmpRegister(ptr_ty, ptr);
2121 try self.genBinMathOpMir(.add, ptr_ty, .{ .register = reg }, .{ .immediate = layout.payload_size });
2122 break :blk MCValue{ .register = reg };
2123 } else ptr;
2124
2125 try self.store(adjusted_ptr, tag, ptr_ty, tag_ty);
2126
2127 return self.finishAir(inst, .none, .{ bin_op.lhs, bin_op.rhs, .none });
21032128}
21042129
21052130fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
21062131 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2107 const result: MCValue = if (self.liveness.isUnused(inst))
2108 .dead
2109 else
2110 return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch});
2111 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2132 if (self.liveness.isUnused(inst)) {
2133 return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
2134 }
2135
2136 const tag_ty = self.air.typeOfIndex(inst);
2137 const union_ty = self.air.typeOf(ty_op.operand);
2138 const layout = union_ty.unionGetLayout(self.target.*);
2139
2140 if (layout.tag_size == 0) {
2141 return self.finishAir(inst, .none, .{ ty_op.operand, .none, .none });
2142 }
2143
2144 // TODO reusing the operand
2145 const operand = try self.resolveInst(ty_op.operand);
2146 operand.freezeIfRegister(&self.register_manager);
2147 defer operand.unfreezeIfRegister(&self.register_manager);
2148
2149 const tag_abi_size = tag_ty.abiSize(self.target.*);
2150 const offset: i32 = if (layout.tag_align < layout.payload_align) @intCast(i32, layout.payload_size) else 0;
2151 const dst_mcv: MCValue = blk: {
2152 switch (operand) {
2153 .stack_offset => |off| {
2154 if (tag_abi_size <= 8) {
2155 break :blk try self.copyToRegisterWithInstTracking(inst, tag_ty, .{
2156 .stack_offset = off - offset,
2157 });
2158 }
2159
2160 return self.fail("TODO implement get_union_tag for ABI larger than 8 bytes and operand {}", .{operand});
2161 },
2162 else => return self.fail("TODO implement get_union_tag for {}", .{operand}),
2163 }
2164 };
2165
2166 return self.finishAir(inst, dst_mcv, .{ ty_op.operand, .none, .none });
21122167}
21132168
21142169fn airClz(self: *Self, inst: Air.Inst.Index) !void {
......@@ -2429,8 +2484,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
24292484 });
24302485 },
24312486 .stack_offset => {
2432 const tmp_reg = try self.copyToTmpRegister(value_ty, value);
2433 return self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
2487 if (abi_size <= 8) {
2488 const tmp_reg = try self.copyToTmpRegister(value_ty, value);
2489 return self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
2490 }
2491
2492 try self.genInlineMemcpy(0, value_ty, value, .{
2493 .source_stack_base = .rbp,
2494 .dest_stack_base = reg.to64(),
2495 });
24342496 },
24352497 else => |other| {
24362498 return self.fail("TODO implement set pointee with {}", .{other});
......@@ -3905,36 +3967,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u
39053967 .dead, .unreach => unreachable,
39063968 .immediate => |imm| {
39073969 _ = try self.addInst(.{
3908 .tag = .@"test",
3970 .tag = .xor,
39093971 .ops = (Mir.Ops{
39103972 .reg1 = registerAlias(cond_reg, abi_size),
39113973 }).encode(),
39123974 .data = .{ .imm = @intCast(u32, imm) },
39133975 });
3914 return self.addInst(.{
3915 .tag = .cond_jmp_eq_ne,
3916 .ops = (Mir.Ops{
3917 .flags = 0b00,
3918 }).encode(),
3919 .data = .{ .inst = undefined },
3920 });
39213976 },
39223977 .register => |reg| {
39233978 _ = try self.addInst(.{
3924 .tag = .@"test",
3979 .tag = .xor,
39253980 .ops = (Mir.Ops{
39263981 .reg1 = registerAlias(cond_reg, abi_size),
39273982 .reg2 = registerAlias(reg, abi_size),
39283983 }).encode(),
39293984 .data = undefined,
39303985 });
3931 return self.addInst(.{
3932 .tag = .cond_jmp_eq_ne,
3933 .ops = (Mir.Ops{
3934 .flags = 0b00,
3935 }).encode(),
3936 .data = .{ .inst = undefined },
3937 });
39383986 },
39393987 .stack_offset => {
39403988 if (abi_size <= 8) {
......@@ -3948,6 +3996,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u
39483996 return self.fail("TODO implement switch mir when case is {}", .{case});
39493997 },
39503998 }
3999
4000 _ = try self.addInst(.{
4001 .tag = .@"test",
4002 .ops = (Mir.Ops{
4003 .reg1 = registerAlias(cond_reg, abi_size),
4004 .reg2 = registerAlias(cond_reg, abi_size),
4005 }).encode(),
4006 .data = undefined,
4007 });
4008 return self.addInst(.{
4009 .tag = .cond_jmp_eq_ne,
4010 .ops = (Mir.Ops{
4011 .flags = 0b00,
4012 }).encode(),
4013 .data = .{ .inst = undefined },
4014 });
39514015 },
39524016 .stack_offset => {
39534017 try self.spillCompareFlagsIfOccupied();
......@@ -5408,24 +5472,14 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
54085472 }
54095473
54105474 switch (typed_value.ty.zigTypeTag()) {
5411 .Array => {
5412 return self.lowerUnnamedConst(typed_value);
5413 },
54145475 .Pointer => switch (typed_value.ty.ptrSize()) {
5415 .Slice => {
5416 return self.lowerUnnamedConst(typed_value);
5417 },
5476 .Slice => {},
54185477 else => {
54195478 switch (typed_value.val.tag()) {
54205479 .int_u64 => {
54215480 return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
54225481 },
5423 .slice => {
5424 return self.lowerUnnamedConst(typed_value);
5425 },
5426 else => {
5427 return self.fail("TODO codegen more kinds of const pointers: {}", .{typed_value.val.tag()});
5428 },
5482 else => {},
54295483 }
54305484 },
54315485 },
......@@ -5434,10 +5488,9 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
54345488 if (info.bits <= ptr_bits and info.signedness == .signed) {
54355489 return MCValue{ .immediate = @bitCast(u64, typed_value.val.toSignedInt()) };
54365490 }
5437 if (info.bits > ptr_bits or info.signedness == .signed) {
5438 return self.fail("TODO const int bigger than ptr and signed int", .{});
5491 if (!(info.bits > ptr_bits or info.signedness == .signed)) {
5492 return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
54395493 }
5440 return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
54415494 },
54425495 .Bool => {
54435496 return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) };
......@@ -5457,7 +5510,6 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
54575510 } else if (typed_value.ty.abiSize(self.target.*) == 1) {
54585511 return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) };
54595512 }
5460 return self.fail("TODO non pointer optionals", .{});
54615513 },
54625514 .Enum => {
54635515 if (typed_value.val.castTag(.enum_field_index)) |field_index| {
......@@ -5504,13 +5556,11 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
55045556 return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val });
55055557 }
55065558 }
5507 return self.lowerUnnamedConst(typed_value);
5508 },
5509 .Struct => {
5510 return self.lowerUnnamedConst(typed_value);
55115559 },
5512 else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}),
5560 else => {},
55135561 }
5562
5563 return self.lowerUnnamedConst(typed_value);
55145564}
55155565
55165566const CallMCValues = struct {
src/arch/x86_64/Emit.zig+6
......@@ -1859,6 +1859,9 @@ fn lowerToRmEnc(
18591859 switch (reg_or_mem) {
18601860 .register => |src_reg| {
18611861 const encoder = try Encoder.init(code, 4);
1862 if (reg.size() == 16) {
1863 encoder.prefix16BitMode();
1864 }
18621865 encoder.rex(.{
18631866 .w = setRexWRegister(reg) or setRexWRegister(src_reg),
18641867 .r = reg.isExtended(),
......@@ -1902,6 +1905,9 @@ fn lowerToMrEnc(
19021905 switch (reg_or_mem) {
19031906 .register => |dst_reg| {
19041907 const encoder = try Encoder.init(code, 3);
1908 if (dst_reg.size() == 16) {
1909 encoder.prefix16BitMode();
1910 }
19051911 encoder.rex(.{
19061912 .w = setRexWRegister(dst_reg) or setRexWRegister(reg),
19071913 .r = reg.isExtended(),
src/codegen.zig+67-3
......@@ -466,10 +466,74 @@ pub fn generateSymbol(
466466 return Result{ .appended = {} };
467467 },
468468 .Union => {
469 // TODO generateSymbol for unions
469 // TODO generate debug info for unions
470470 const target = bin_file.options.target;
471 const abi_size = try math.cast(usize, typed_value.ty.abiSize(target));
472 try code.writer().writeByteNTimes(0xaa, abi_size);
471 const union_obj = typed_value.val.castTag(.@"union").?.data;
472 const layout = typed_value.ty.unionGetLayout(target);
473
474 if (layout.payload_size == 0) {
475 switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
476 .ty = typed_value.ty.unionTagType().?,
477 .val = union_obj.tag,
478 }, code, debug_output)) {
479 .appended => {},
480 .externally_managed => |external_slice| {
481 code.appendSliceAssumeCapacity(external_slice);
482 },
483 .fail => |em| return Result{ .fail = em },
484 }
485 }
486
487 // Check if we should store the tag first.
488 if (layout.tag_align >= layout.payload_align) {
489 switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
490 .ty = typed_value.ty.unionTagType().?,
491 .val = union_obj.tag,
492 }, code, debug_output)) {
493 .appended => {},
494 .externally_managed => |external_slice| {
495 code.appendSliceAssumeCapacity(external_slice);
496 },
497 .fail => |em| return Result{ .fail = em },
498 }
499 }
500
501 const union_ty = typed_value.ty.cast(Type.Payload.Union).?.data;
502 const field_index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag).?;
503 assert(union_ty.haveFieldTypes());
504 const field_ty = union_ty.fields.values()[field_index].ty;
505 if (!field_ty.hasRuntimeBits()) {
506 try code.writer().writeByteNTimes(0xaa, try math.cast(usize, layout.payload_size));
507 } else {
508 switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
509 .ty = field_ty,
510 .val = union_obj.val,
511 }, code, debug_output)) {
512 .appended => {},
513 .externally_managed => |external_slice| {
514 code.appendSliceAssumeCapacity(external_slice);
515 },
516 .fail => |em| return Result{ .fail = em },
517 }
518
519 const padding = try math.cast(usize, layout.payload_size - field_ty.abiSize(target));
520 if (padding > 0) {
521 try code.writer().writeByteNTimes(0, padding);
522 }
523 }
524
525 if (layout.tag_size > 0) {
526 switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
527 .ty = union_ty.tag_ty,
528 .val = union_obj.tag,
529 }, code, debug_output)) {
530 .appended => {},
531 .externally_managed => |external_slice| {
532 code.appendSliceAssumeCapacity(external_slice);
533 },
534 .fail => |em| return Result{ .fail = em },
535 }
536 }
473537
474538 return Result{ .appended = {} };
475539 },
test/behavior/union.zig+48-27
......@@ -4,68 +4,100 @@ const expect = std.testing.expect;
44const expectEqual = std.testing.expectEqual;
55const Tag = std.meta.Tag;
66
7const Foo = union {
7const FooWithFloats = union {
88 float: f64,
99 int: i32,
1010};
1111
12test "basic unions" {
12test "basic unions with floats" {
1313 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1414 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
1515 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1616
17 var foo = Foo{ .int = 1 };
17 var foo = FooWithFloats{ .int = 1 };
1818 try expect(foo.int == 1);
19 foo = Foo{ .float = 12.34 };
19 foo = FooWithFloats{ .float = 12.34 };
2020 try expect(foo.float == 12.34);
2121}
2222
23test "init union with runtime value" {
23fn setFloat(foo: *FooWithFloats, x: f64) void {
24 foo.* = FooWithFloats{ .float = x };
25}
26
27test "init union with runtime value - floats" {
2428 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
2529 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
2630 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
2731
28 var foo: Foo = undefined;
32 var foo: FooWithFloats = undefined;
2933
3034 setFloat(&foo, 12.34);
3135 try expect(foo.float == 12.34);
36}
37
38test "basic unions" {
39 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
40 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
41
42 var foo = Foo{ .int = 1 };
43 try expect(foo.int == 1);
44 foo = Foo{ .str = .{ .slice = "Hello!" } };
45 try expect(std.mem.eql(u8, foo.str.slice, "Hello!"));
46}
47
48const Foo = union {
49 int: i32,
50 str: struct {
51 slice: []const u8,
52 },
53};
54
55test "init union with runtime value" {
56 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
57 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
58
59 var foo: Foo = undefined;
3260
3361 setInt(&foo, 42);
3462 try expect(foo.int == 42);
35}
3663
37fn setFloat(foo: *Foo, x: f64) void {
38 foo.* = Foo{ .float = x };
64 setStr(&foo, "Hello!");
65 try expect(std.mem.eql(u8, foo.str.slice, "Hello!"));
3966}
4067
4168fn setInt(foo: *Foo, x: i32) void {
4269 foo.* = Foo{ .int = x };
4370}
4471
72fn setStr(foo: *Foo, slice: []const u8) void {
73 foo.* = Foo{ .str = .{ .slice = slice } };
74}
75
4576test "comptime union field access" {
4677 comptime {
47 var foo = Foo{ .int = 0 };
78 var foo = FooWithFloats{ .int = 0 };
4879 try expect(foo.int == 0);
4980
50 foo = Foo{ .float = 42.42 };
51 try expect(foo.float == 42.42);
81 foo = FooWithFloats{ .float = 12.34 };
82 try expect(foo.float == 12.34);
5283 }
5384}
5485
5586const FooExtern = extern union {
56 float: f64,
5787 int: i32,
88 str: struct {
89 slice: []const u8,
90 },
5891};
5992
6093test "basic extern unions" {
61 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
6294 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
6395 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
6496
6597 var foo = FooExtern{ .int = 1 };
6698 try expect(foo.int == 1);
67 foo.float = 12.34;
68 try expect(foo.float == 12.34);
99 foo.str.slice = "Well";
100 try expect(std.mem.eql(u8, foo.str.slice, "Well"));
69101}
70102
71103const ExternPtrOrInt = extern union {
......@@ -129,7 +161,6 @@ test "access a member of tagged union with conflicting enum tag name" {
129161}
130162
131163test "constant tagged union with payload" {
132 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
133164 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
134165 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
135166
......@@ -227,7 +258,6 @@ fn testComparison() !void {
227258}
228259
229260test "comparison between union and enum literal" {
230 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
231261 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
232262 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
233263
......@@ -297,7 +327,6 @@ pub const PackThis = union(enum) {
297327};
298328
299329test "constant packed union" {
300 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
301330 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
302331 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
303332
......@@ -457,7 +486,6 @@ test "update the tag value for zero-sized unions" {
457486}
458487
459488test "union initializer generates padding only if needed" {
460 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
461489 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
462490 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
463491
......@@ -470,7 +498,6 @@ test "union initializer generates padding only if needed" {
470498}
471499
472500test "runtime tag name with single field" {
473 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
474501 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
475502 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
476503
......@@ -483,7 +510,6 @@ test "runtime tag name with single field" {
483510}
484511
485512test "method call on an empty union" {
486 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
487513 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
488514 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
489515
......@@ -547,7 +573,6 @@ test "tagged union type" {
547573}
548574
549575test "tagged union as return value" {
550 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
551576 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
552577 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
553578
......@@ -628,7 +653,6 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
628653}
629654
630655test "switch on union with only 1 field" {
631 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
632656 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
633657 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
634658
......@@ -658,7 +682,6 @@ const PartialInstWithPayload = union(enum) {
658682};
659683
660684test "union with only 1 field casted to its enum type which has enum value specified" {
661 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
662685 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
663686 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
664687
......@@ -796,7 +819,6 @@ test "@unionInit stored to a const" {
796819}
797820
798821test "@unionInit can modify a union type" {
799 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
800822 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
801823 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
802824
......@@ -839,7 +861,6 @@ test "@unionInit can modify a pointer value" {
839861}
840862
841863test "union no tag with struct member" {
842 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
843864 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
844865 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
845866