| author | |
| committer | |
| log | 09e1f37cb6a8e0df4c521f4b76eab07f0c811852 |
| tree | cce1b86a1cd273841a660e5438f714cfbaea74a7 |
| parent | c2a7542df5e9e93289a5d487ba3bbc37c12ffc11 |
* 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 | 290 | /// Result type is always void. |
| 291 | 291 | /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value. |
| 292 | 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 | 296 | /// Given a slice value, return the length. |
| 294 | 297 | /// Result type is always usize. |
| 295 | 298 | /// Uses the `ty_op` field. |
| ... | ... | @@ -630,6 +633,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 630 | 633 | .array_to_slice, |
| 631 | 634 | .float_to_int, |
| 632 | 635 | .int_to_float, |
| 636 | .get_union_tag, | |
| 633 | 637 | => return air.getRefType(datas[inst].ty_op.ty), |
| 634 | 638 | |
| 635 | 639 | .loop, |
src/Liveness.zig+1| ... | ... | @@ -297,6 +297,7 @@ fn analyzeInst( |
| 297 | 297 | .array_to_slice, |
| 298 | 298 | .float_to_int, |
| 299 | 299 | .int_to_float, |
| 300 | .get_union_tag, | |
| 300 | 301 | => { |
| 301 | 302 | const o = inst_datas[inst].ty_op; |
| 302 | 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 | 1349 | errdefer new_decl_arena.deinit(); |
| 1350 | 1350 | |
| 1351 | 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 | 1359 | const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty); |
| 1354 | 1360 | const type_name = try sema.createTypeName(block, small.name_strategy); |
| 1355 | 1361 | const new_decl = try sema.mod.createAnonymousDeclNamed(&block.base, .{ |
| ... | ... | @@ -6477,10 +6483,11 @@ fn zirCmpEq( |
| 6477 | 6483 | const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty; |
| 6478 | 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) or | |
| 6481 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) | |
| 6482 | { | |
| 6483 | return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); | |
| 6486 | if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) { | |
| 6487 | return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op); | |
| 6488 | } | |
| 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 | 6492 | if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { |
| 6486 | 6493 | const runtime_src: LazySrcLoc = src: { |
| ... | ... | @@ -6521,6 +6528,28 @@ fn zirCmpEq( |
| 6521 | 6528 | return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true); |
| 6522 | 6529 | } |
| 6523 | 6530 | |
| 6531 | fn 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 | 6553 | /// Only called for non-equality operators. See also `zirCmpEq`. |
| 6525 | 6554 | fn zirCmp( |
| 6526 | 6555 | sema: *Sema, |
| ... | ... | @@ -6567,10 +6596,21 @@ fn analyzeCmp( |
| 6567 | 6596 | @tagName(op), resolved_type, |
| 6568 | 6597 | }); |
| 6569 | 6598 | } |
| 6570 | ||
| 6571 | 6599 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| 6572 | 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 | } | |
| 6573 | 6603 | |
| 6604 | fn 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 | 6614 | const runtime_src: LazySrcLoc = src: { |
| 6575 | 6615 | if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { |
| 6576 | 6616 | if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type); |
| ... | ... | @@ -9919,9 +9959,9 @@ fn coerce( |
| 9919 | 9959 | } |
| 9920 | 9960 | } |
| 9921 | 9961 | }, |
| 9922 | .Enum => { | |
| 9923 | // enum literal to enum | |
| 9924 | if (inst_ty.zigTypeTag() == .EnumLiteral) { | |
| 9962 | .Enum => switch (inst_ty.zigTypeTag()) { | |
| 9963 | .EnumLiteral => { | |
| 9964 | // enum literal to enum | |
| 9925 | 9965 | const val = try sema.resolveConstValue(block, inst_src, inst); |
| 9926 | 9966 | const bytes = val.castTag(.enum_literal).?.data; |
| 9927 | 9967 | const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type); |
| ... | ... | @@ -9948,7 +9988,15 @@ fn coerce( |
| 9948 | 9988 | resolved_dest_type, |
| 9949 | 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 | 10001 | .ErrorUnion => { |
| 9954 | 10002 | // T to E!T or E to E!T |
| ... | ... | @@ -10802,6 +10850,20 @@ fn wrapErrorUnion( |
| 10802 | 10850 | } |
| 10803 | 10851 | } |
| 10804 | 10852 | |
| 10853 | fn 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 | ||
| 10805 | 10867 | fn resolvePeerTypes( |
| 10806 | 10868 | sema: *Sema, |
| 10807 | 10869 | block: *Scope.Block, |
src/codegen.zig+9| ... | ... | @@ -890,6 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 890 | 890 | .memcpy => try self.airMemcpy(inst), |
| 891 | 891 | .memset => try self.airMemset(inst), |
| 892 | 892 | .set_union_tag => try self.airSetUnionTag(inst), |
| 893 | .get_union_tag => try self.airGetUnionTag(inst), | |
| 893 | 894 | |
| 894 | 895 | .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), |
| 895 | 896 | .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), |
| ... | ... | @@ -1552,6 +1553,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1552 | 1553 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1553 | 1554 | } |
| 1554 | 1555 | |
| 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 | 1564 | fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { |
| 1556 | 1565 | if (!self.liveness.operandDies(inst, op_index)) |
| 1557 | 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 | 956 | .memset => try airMemset(f, inst), |
| 957 | 957 | .memcpy => try airMemcpy(f, inst), |
| 958 | 958 | .set_union_tag => try airSetUnionTag(f, inst), |
| 959 | .get_union_tag => try airGetUnionTag(f, inst), | |
| 959 | 960 | |
| 960 | 961 | .int_to_float, |
| 961 | 962 | .float_to_int, |
| ... | ... | @@ -2096,6 +2097,22 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2096 | 2097 | return CValue.none; |
| 2097 | 2098 | } |
| 2098 | 2099 | |
| 2100 | fn 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 | ||
| 2099 | 2116 | fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { |
| 2100 | 2117 | return switch (order) { |
| 2101 | 2118 | .Unordered => "memory_order_relaxed", |
src/codegen/llvm.zig+13| ... | ... | @@ -1304,6 +1304,7 @@ pub const FuncGen = struct { |
| 1304 | 1304 | .memset => try self.airMemset(inst), |
| 1305 | 1305 | .memcpy => try self.airMemcpy(inst), |
| 1306 | 1306 | .set_union_tag => try self.airSetUnionTag(inst), |
| 1307 | .get_union_tag => try self.airGetUnionTag(inst), | |
| 1307 | 1308 | |
| 1308 | 1309 | .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), |
| 1309 | 1310 | .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), |
| ... | ... | @@ -2557,6 +2558,18 @@ pub const FuncGen = struct { |
| 2557 | 2558 | return null; |
| 2558 | 2559 | } |
| 2559 | 2560 | |
| 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 | 2573 | fn fieldPtr( |
| 2561 | 2574 | self: *FuncGen, |
| 2562 | 2575 | inst: Air.Inst.Index, |
src/print_air.zig+1| ... | ... | @@ -179,6 +179,7 @@ const Writer = struct { |
| 179 | 179 | .array_to_slice, |
| 180 | 180 | .int_to_float, |
| 181 | 181 | .float_to_int, |
| 182 | .get_union_tag, | |
| 182 | 183 | => try w.writeTyOp(s, inst), |
| 183 | 184 | |
| 184 | 185 | .block, |
src/type.zig+8| ... | ... | @@ -2487,6 +2487,12 @@ pub const Type = extern union { |
| 2487 | 2487 | }; |
| 2488 | 2488 | } |
| 2489 | 2489 | |
| 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 | 2496 | /// Asserts that the type is an error union. |
| 2491 | 2497 | pub fn errorUnionPayload(self: Type) Type { |
| 2492 | 2498 | return switch (self.tag()) { |
| ... | ... | @@ -3801,6 +3807,8 @@ pub const Type = extern union { |
| 3801 | 3807 | }; |
| 3802 | 3808 | }; |
| 3803 | 3809 | |
| 3810 | pub const @"bool" = initTag(.bool); | |
| 3811 | ||
| 3804 | 3812 | pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { |
| 3805 | 3813 | assert(d.host_size == 0 or d.bit_offset < d.host_size * 8); |
| 3806 | 3814 |
src/value.zig+14-1| ... | ... | @@ -1275,7 +1275,12 @@ pub const Value = extern union { |
| 1275 | 1275 | } |
| 1276 | 1276 | }, |
| 1277 | 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 | 1285 | .Fn => { |
| 1281 | 1286 | @panic("TODO implement hashing function values"); |
| ... | ... | @@ -1431,6 +1436,14 @@ pub const Value = extern union { |
| 1431 | 1436 | } |
| 1432 | 1437 | } |
| 1433 | 1438 | |
| 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 | 1447 | /// Returns a pointer to the element value at the index. |
| 1435 | 1448 | pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value { |
| 1436 | 1449 | if (self.castTag(.elem_ptr)) |elem_ptr| { |
test/behavior/union.zig+18| ... | ... | @@ -14,3 +14,21 @@ test "basic unions" { |
| 14 | 14 | foo = Foo{ .float = 12.34 }; |
| 15 | 15 | try expect(foo.float == 12.34); |
| 16 | 16 | } |
| 17 | ||
| 18 | test "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 | ||
| 28 | fn setFloat(foo: *Foo, x: f64) void { | |
| 29 | foo.* = Foo{ .float = x }; | |
| 30 | } | |
| 31 | ||
| 32 | fn 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 | 49 | } |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | test "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 | ||
| 62 | fn setFloat(foo: *Foo, x: f64) void { | |
| 63 | foo.* = Foo{ .float = x }; | |
| 64 | } | |
| 65 | ||
| 66 | fn setInt(foo: *Foo, x: i32) void { | |
| 67 | foo.* = Foo{ .int = x }; | |
| 68 | } | |
| 69 | ||
| 70 | 52 | const FooExtern = extern union { |
| 71 | 53 | float: f64, |
| 72 | 54 | int: i32, |
| ... | ... | @@ -185,12 +167,13 @@ test "union field access gives the enum values" { |
| 185 | 167 | } |
| 186 | 168 | |
| 187 | 169 | test "cast union to tag type of union" { |
| 188 | try testCastUnionToTag(TheUnion{ .B = 1234 }); | |
| 189 | comptime try testCastUnionToTag(TheUnion{ .B = 1234 }); | |
| 170 | try testCastUnionToTag(); | |
| 171 | comptime try testCastUnionToTag(); | |
| 190 | 172 | } |
| 191 | 173 | |
| 192 | fn testCastUnionToTag(x: TheUnion) !void { | |
| 193 | try expect(@as(TheTag, x) == TheTag.B); | |
| 174 | fn testCastUnionToTag() !void { | |
| 175 | var u = TheUnion{ .B = 1234 }; | |
| 176 | try expect(@as(TheTag, u) == TheTag.B); | |
| 194 | 177 | } |
| 195 | 178 | |
| 196 | 179 | test "cast tag type of union to union" { |