authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-27 23:11:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-27 23:11:00-07:00
log09e1f37cb6a8e0df4c521f4b76eab07f0c811852
treecce1b86a1cd273841a660e5438f714cfbaea74a7
parentc2a7542df5e9e93289a5d487ba3bbc37c12ffc11

stage2: implement union coercion to its own tag

* AIR: add `get_union_tag` instruction - implement in LLVM backend * Sema: implement == and != for union and enum literal - Also implement coercion from union to its own tag type * Value: implement hashing for union values The motivating example is this snippet: comptime assert(@typeInfo(T) == .Float); This was the next blocker for stage2 building compiler-rt. Now it is switch at compile-time on an integer.

11 files changed, 162 insertions(+), 33 deletions(-)

src/Air.zig+4
...@@ -290,6 +290,9 @@ pub const Inst = struct {...@@ -290,6 +290,9 @@ pub const Inst = struct {
290 /// Result type is always void.290 /// Result type is always void.
291 /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value.291 /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value.
292 set_union_tag,292 set_union_tag,
293 /// Given a tagged union value, get its tag value.
294 /// Uses the `ty_op` field.
295 get_union_tag,
293 /// Given a slice value, return the length.296 /// Given a slice value, return the length.
294 /// Result type is always usize.297 /// Result type is always usize.
295 /// Uses the `ty_op` field.298 /// Uses the `ty_op` field.
...@@ -630,6 +633,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {...@@ -630,6 +633,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
630 .array_to_slice,633 .array_to_slice,
631 .float_to_int,634 .float_to_int,
632 .int_to_float,635 .int_to_float,
636 .get_union_tag,
633 => return air.getRefType(datas[inst].ty_op.ty),637 => return air.getRefType(datas[inst].ty_op.ty),
634638
635 .loop,639 .loop,
src/Liveness.zig+1
...@@ -297,6 +297,7 @@ fn analyzeInst(...@@ -297,6 +297,7 @@ fn analyzeInst(
297 .array_to_slice,297 .array_to_slice,
298 .float_to_int,298 .float_to_int,
299 .int_to_float,299 .int_to_float,
300 .get_union_tag,
300 => {301 => {
301 const o = inst_datas[inst].ty_op;302 const o = inst_datas[inst].ty_op;
302 return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });303 return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
src/Sema.zig+72-10
...@@ -1349,7 +1349,13 @@ fn zirUnionDecl(...@@ -1349,7 +1349,13 @@ fn zirUnionDecl(
1349 errdefer new_decl_arena.deinit();1349 errdefer new_decl_arena.deinit();
13501350
1351 const union_obj = try new_decl_arena.allocator.create(Module.Union);1351 const union_obj = try new_decl_arena.allocator.create(Module.Union);
1352 const union_ty = try Type.Tag.@"union".create(&new_decl_arena.allocator, union_obj);1352 const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union";
1353 const union_payload = try new_decl_arena.allocator.create(Type.Payload.Union);
1354 union_payload.* = .{
1355 .base = .{ .tag = type_tag },
1356 .data = union_obj,
1357 };
1358 const union_ty = Type.initPayload(&union_payload.base);
1353 const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty);1359 const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty);
1354 const type_name = try sema.createTypeName(block, small.name_strategy);1360 const type_name = try sema.createTypeName(block, small.name_strategy);
1355 const new_decl = try sema.mod.createAnonymousDeclNamed(&block.base, .{1361 const new_decl = try sema.mod.createAnonymousDeclNamed(&block.base, .{
...@@ -6477,10 +6483,11 @@ fn zirCmpEq(...@@ -6477,10 +6483,11 @@ fn zirCmpEq(
6477 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;6483 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;
6478 return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});6484 return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
6479 }6485 }
6480 if (((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or6486 if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) {
6481 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))6487 return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op);
6482 {6488 }
6483 return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});6489 if (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union) {
6490 return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op);
6484 }6491 }
6485 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {6492 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
6486 const runtime_src: LazySrcLoc = src: {6493 const runtime_src: LazySrcLoc = src: {
...@@ -6521,6 +6528,28 @@ fn zirCmpEq(...@@ -6521,6 +6528,28 @@ fn zirCmpEq(
6521 return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true);6528 return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true);
6522}6529}
65236530
6531fn analyzeCmpUnionTag(
6532 sema: *Sema,
6533 block: *Scope.Block,
6534 un: Air.Inst.Ref,
6535 un_src: LazySrcLoc,
6536 tag: Air.Inst.Ref,
6537 tag_src: LazySrcLoc,
6538 op: std.math.CompareOperator,
6539) CompileError!Air.Inst.Ref {
6540 const union_ty = sema.typeOf(un);
6541 const union_tag_ty = union_ty.unionTagType() orelse {
6542 // TODO note at declaration site that says "union foo is not tagged"
6543 return sema.mod.fail(&block.base, un_src, "comparison of union and enum literal is only valid for tagged union types", .{});
6544 };
6545 // Coerce both the union and the tag to the union's tag type, and then execute the
6546 // enum comparison codepath.
6547 const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src);
6548 const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src);
6549
6550 return sema.cmpSelf(block, coerced_union, coerced_tag, op, un_src, tag_src);
6551}
6552
6524/// Only called for non-equality operators. See also `zirCmpEq`.6553/// Only called for non-equality operators. See also `zirCmpEq`.
6525fn zirCmp(6554fn zirCmp(
6526 sema: *Sema,6555 sema: *Sema,
...@@ -6567,10 +6596,21 @@ fn analyzeCmp(...@@ -6567,10 +6596,21 @@ fn analyzeCmp(
6567 @tagName(op), resolved_type,6596 @tagName(op), resolved_type,
6568 });6597 });
6569 }6598 }
6570
6571 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);6599 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
6572 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);6600 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
6601 return sema.cmpSelf(block, casted_lhs, casted_rhs, op, lhs_src, rhs_src);
6602}
65736603
6604fn cmpSelf(
6605 sema: *Sema,
6606 block: *Scope.Block,
6607 casted_lhs: Air.Inst.Ref,
6608 casted_rhs: Air.Inst.Ref,
6609 op: std.math.CompareOperator,
6610 lhs_src: LazySrcLoc,
6611 rhs_src: LazySrcLoc,
6612) CompileError!Air.Inst.Ref {
6613 const resolved_type = sema.typeOf(casted_lhs);
6574 const runtime_src: LazySrcLoc = src: {6614 const runtime_src: LazySrcLoc = src: {
6575 if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {6615 if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
6576 if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type);6616 if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type);
...@@ -9919,9 +9959,9 @@ fn coerce(...@@ -9919,9 +9959,9 @@ fn coerce(
9919 }9959 }
9920 }9960 }
9921 },9961 },
9922 .Enum => {9962 .Enum => switch (inst_ty.zigTypeTag()) {
9923 // enum literal to enum9963 .EnumLiteral => {
9924 if (inst_ty.zigTypeTag() == .EnumLiteral) {9964 // enum literal to enum
9925 const val = try sema.resolveConstValue(block, inst_src, inst);9965 const val = try sema.resolveConstValue(block, inst_src, inst);
9926 const bytes = val.castTag(.enum_literal).?.data;9966 const bytes = val.castTag(.enum_literal).?.data;
9927 const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type);9967 const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type);
...@@ -9948,7 +9988,15 @@ fn coerce(...@@ -9948,7 +9988,15 @@ fn coerce(
9948 resolved_dest_type,9988 resolved_dest_type,
9949 try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)),9989 try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)),
9950 );9990 );
9951 }9991 },
9992 .Union => blk: {
9993 // union to its own tag type
9994 const union_tag_ty = inst_ty.unionTagType() orelse break :blk;
9995 if (union_tag_ty.eql(dest_type)) {
9996 return sema.unionToTag(block, dest_type, inst, inst_src);
9997 }
9998 },
9999 else => {},
9952 },10000 },
9953 .ErrorUnion => {10001 .ErrorUnion => {
9954 // T to E!T or E to E!T10002 // T to E!T or E to E!T
...@@ -10802,6 +10850,20 @@ fn wrapErrorUnion(...@@ -10802,6 +10850,20 @@ fn wrapErrorUnion(
10802 }10850 }
10803}10851}
1080410852
10853fn unionToTag(
10854 sema: *Sema,
10855 block: *Scope.Block,
10856 dest_type: Type,
10857 un: Air.Inst.Ref,
10858 un_src: LazySrcLoc,
10859) !Air.Inst.Ref {
10860 if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| {
10861 return sema.addConstant(dest_type, un_val.unionTag());
10862 }
10863 try sema.requireRuntimeBlock(block, un_src);
10864 return block.addTyOp(.get_union_tag, dest_type, un);
10865}
10866
10805fn resolvePeerTypes(10867fn resolvePeerTypes(
10806 sema: *Sema,10868 sema: *Sema,
10807 block: *Scope.Block,10869 block: *Scope.Block,
src/codegen.zig+9
...@@ -890,6 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -890,6 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
890 .memcpy => try self.airMemcpy(inst),890 .memcpy => try self.airMemcpy(inst),
891 .memset => try self.airMemset(inst),891 .memset => try self.airMemset(inst),
892 .set_union_tag => try self.airSetUnionTag(inst),892 .set_union_tag => try self.airSetUnionTag(inst),
893 .get_union_tag => try self.airGetUnionTag(inst),
893894
894 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),895 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
895 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),896 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
...@@ -1552,6 +1553,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1552,6 +1553,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1552 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });1553 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1553 }1554 }
15541555
1556 fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
1557 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1558 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1559 else => return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch}),
1560 };
1561 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1562 }
1563
1555 fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {1564 fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
1556 if (!self.liveness.operandDies(inst, op_index))1565 if (!self.liveness.operandDies(inst, op_index))
1557 return false;1566 return false;
src/codegen/c.zig+17
...@@ -956,6 +956,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO...@@ -956,6 +956,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
956 .memset => try airMemset(f, inst),956 .memset => try airMemset(f, inst),
957 .memcpy => try airMemcpy(f, inst),957 .memcpy => try airMemcpy(f, inst),
958 .set_union_tag => try airSetUnionTag(f, inst),958 .set_union_tag => try airSetUnionTag(f, inst),
959 .get_union_tag => try airGetUnionTag(f, inst),
959960
960 .int_to_float,961 .int_to_float,
961 .float_to_int,962 .float_to_int,
...@@ -2096,6 +2097,22 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -2096,6 +2097,22 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
2096 return CValue.none;2097 return CValue.none;
2097}2098}
20982099
2100fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
2101 if (f.liveness.isUnused(inst))
2102 return CValue.none;
2103
2104 const inst_ty = f.air.typeOfIndex(inst);
2105 const local = try f.allocLocal(inst_ty, .Const);
2106 const ty_op = f.air.instructions.items(.data)[inst].ty_op;
2107 const writer = f.object.writer();
2108 const operand = try f.resolveInst(ty_op.operand);
2109
2110 try writer.writeAll("get_union_tag(");
2111 try f.writeCValue(writer, operand);
2112 try writer.writeAll(");\n");
2113 return local;
2114}
2115
2099fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {2116fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
2100 return switch (order) {2117 return switch (order) {
2101 .Unordered => "memory_order_relaxed",2118 .Unordered => "memory_order_relaxed",
src/codegen/llvm.zig+13
...@@ -1304,6 +1304,7 @@ pub const FuncGen = struct {...@@ -1304,6 +1304,7 @@ pub const FuncGen = struct {
1304 .memset => try self.airMemset(inst),1304 .memset => try self.airMemset(inst),
1305 .memcpy => try self.airMemcpy(inst),1305 .memcpy => try self.airMemcpy(inst),
1306 .set_union_tag => try self.airSetUnionTag(inst),1306 .set_union_tag => try self.airSetUnionTag(inst),
1307 .get_union_tag => try self.airGetUnionTag(inst),
13071308
1308 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),1309 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
1309 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),1310 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
...@@ -2557,6 +2558,18 @@ pub const FuncGen = struct {...@@ -2557,6 +2558,18 @@ pub const FuncGen = struct {
2557 return null;2558 return null;
2558 }2559 }
25592560
2561 fn airGetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2562 if (self.liveness.isUnused(inst))
2563 return null;
2564
2565 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2566 const un_ty = self.air.typeOf(ty_op.operand);
2567 const un = try self.resolveInst(ty_op.operand);
2568
2569 _ = un_ty; // TODO handle when onlyTagHasCodegenBits() == true and other union forms
2570 return self.builder.buildExtractValue(un, 1, "");
2571 }
2572
2560 fn fieldPtr(2573 fn fieldPtr(
2561 self: *FuncGen,2574 self: *FuncGen,
2562 inst: Air.Inst.Index,2575 inst: Air.Inst.Index,
src/print_air.zig+1
...@@ -179,6 +179,7 @@ const Writer = struct {...@@ -179,6 +179,7 @@ const Writer = struct {
179 .array_to_slice,179 .array_to_slice,
180 .int_to_float,180 .int_to_float,
181 .float_to_int,181 .float_to_int,
182 .get_union_tag,
182 => try w.writeTyOp(s, inst),183 => try w.writeTyOp(s, inst),
183184
184 .block,185 .block,
src/type.zig+8
...@@ -2487,6 +2487,12 @@ pub const Type = extern union {...@@ -2487,6 +2487,12 @@ pub const Type = extern union {
2487 };2487 };
2488 }2488 }
24892489
2490 pub fn unionFieldType(ty: Type, enum_tag: Value) Type {
2491 const union_obj = ty.cast(Payload.Union).?.data;
2492 const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag).?;
2493 return union_obj.fields.values()[index].ty;
2494 }
2495
2490 /// Asserts that the type is an error union.2496 /// Asserts that the type is an error union.
2491 pub fn errorUnionPayload(self: Type) Type {2497 pub fn errorUnionPayload(self: Type) Type {
2492 return switch (self.tag()) {2498 return switch (self.tag()) {
...@@ -3801,6 +3807,8 @@ pub const Type = extern union {...@@ -3801,6 +3807,8 @@ pub const Type = extern union {
3801 };3807 };
3802 };3808 };
38033809
3810 pub const @"bool" = initTag(.bool);
3811
3804 pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {3812 pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
3805 assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);3813 assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
38063814
src/value.zig+14-1
...@@ -1275,7 +1275,12 @@ pub const Value = extern union {...@@ -1275,7 +1275,12 @@ pub const Value = extern union {
1275 }1275 }
1276 },1276 },
1277 .Union => {1277 .Union => {
1278 @panic("TODO implement hashing union values");1278 const union_obj = val.castTag(.@"union").?.data;
1279 if (ty.unionTagType()) |tag_ty| {
1280 union_obj.tag.hash(tag_ty, hasher);
1281 }
1282 const active_field_ty = ty.unionFieldType(union_obj.tag);
1283 union_obj.val.hash(active_field_ty, hasher);
1279 },1284 },
1280 .Fn => {1285 .Fn => {
1281 @panic("TODO implement hashing function values");1286 @panic("TODO implement hashing function values");
...@@ -1431,6 +1436,14 @@ pub const Value = extern union {...@@ -1431,6 +1436,14 @@ pub const Value = extern union {
1431 }1436 }
1432 }1437 }
14331438
1439 pub fn unionTag(val: Value) Value {
1440 switch (val.tag()) {
1441 .undef => return val,
1442 .@"union" => return val.castTag(.@"union").?.data.tag,
1443 else => unreachable,
1444 }
1445 }
1446
1434 /// Returns a pointer to the element value at the index.1447 /// Returns a pointer to the element value at the index.
1435 pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {1448 pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
1436 if (self.castTag(.elem_ptr)) |elem_ptr| {1449 if (self.castTag(.elem_ptr)) |elem_ptr| {
test/behavior/union.zig+18
...@@ -14,3 +14,21 @@ test "basic unions" {...@@ -14,3 +14,21 @@ test "basic unions" {
14 foo = Foo{ .float = 12.34 };14 foo = Foo{ .float = 12.34 };
15 try expect(foo.float == 12.34);15 try expect(foo.float == 12.34);
16}16}
17
18test "init union with runtime value" {
19 var foo: Foo = undefined;
20
21 setFloat(&foo, 12.34);
22 try expect(foo.float == 12.34);
23
24 setInt(&foo, 42);
25 try expect(foo.int == 42);
26}
27
28fn setFloat(foo: *Foo, x: f64) void {
29 foo.* = Foo{ .float = x };
30}
31
32fn setInt(foo: *Foo, x: i32) void {
33 foo.* = Foo{ .int = x };
34}
test/behavior/union_stage1.zig+5-22
...@@ -49,24 +49,6 @@ test "comptime union field access" {...@@ -49,24 +49,6 @@ test "comptime union field access" {
49 }49 }
50}50}
5151
52test "init union with runtime value" {
53 var foo: Foo = undefined;
54
55 setFloat(&foo, 12.34);
56 try expect(foo.float == 12.34);
57
58 setInt(&foo, 42);
59 try expect(foo.int == 42);
60}
61
62fn setFloat(foo: *Foo, x: f64) void {
63 foo.* = Foo{ .float = x };
64}
65
66fn setInt(foo: *Foo, x: i32) void {
67 foo.* = Foo{ .int = x };
68}
69
70const FooExtern = extern union {52const FooExtern = extern union {
71 float: f64,53 float: f64,
72 int: i32,54 int: i32,
...@@ -185,12 +167,13 @@ test "union field access gives the enum values" {...@@ -185,12 +167,13 @@ test "union field access gives the enum values" {
185}167}
186168
187test "cast union to tag type of union" {169test "cast union to tag type of union" {
188 try testCastUnionToTag(TheUnion{ .B = 1234 });170 try testCastUnionToTag();
189 comptime try testCastUnionToTag(TheUnion{ .B = 1234 });171 comptime try testCastUnionToTag();
190}172}
191173
192fn testCastUnionToTag(x: TheUnion) !void {174fn testCastUnionToTag() !void {
193 try expect(@as(TheTag, x) == TheTag.B);175 var u = TheUnion{ .B = 1234 };
176 try expect(@as(TheTag, u) == TheTag.B);
194}177}
195178
196test "cast tag type of union to union" {179test "cast tag type of union to union" {