authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-07 20:34:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-07 20:34:28-07:00
log799fedf612aa8742c446b015c12d21707a1dbec0
tree1d9efbc4cb3f6dda9631160784a074e58d6bd479
parentf81b2531cb4904064446f84a06f6e09e4120e28a

stage2: pass some error union tests

* Value: rename `error_union` to `eu_payload` and clarify the intended usage in the doc comments. The way error unions is represented with Value is fixed to not have ambiguous values. * Fix codegen for error union constants in all the backends. * Implement the AIR instructions having to do with error unions in the LLVM backend.

9 files changed, 155 insertions(+), 117 deletions(-)

src/Sema.zig+7-9
......@@ -3468,7 +3468,7 @@ fn zirErrUnionPayload(
34683468 if (val.getError()) |name| {
34693469 return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
34703470 }
3471 const data = val.castTag(.error_union).?.data;
3471 const data = val.castTag(.eu_payload).?.data;
34723472 const result_ty = operand_ty.errorUnionPayload();
34733473 return sema.addConstant(result_ty, data);
34743474 }
......@@ -3539,8 +3539,7 @@ fn zirErrUnionCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi
35393539
35403540 if (try sema.resolveDefinedValue(block, src, operand)) |val| {
35413541 assert(val.getError() != null);
3542 const data = val.castTag(.error_union).?.data;
3543 return sema.addConstant(result_ty, data);
3542 return sema.addConstant(result_ty, val);
35443543 }
35453544
35463545 try sema.requireRuntimeBlock(block, src);
......@@ -3566,8 +3565,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
35663565 if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
35673566 if (try pointer_val.pointerDeref(sema.arena)) |val| {
35683567 assert(val.getError() != null);
3569 const data = val.castTag(.error_union).?.data;
3570 return sema.addConstant(result_ty, data);
3568 return sema.addConstant(result_ty, val);
35713569 }
35723570 }
35733571
......@@ -8900,7 +8898,9 @@ fn wrapErrorUnion(
89008898 if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
89018899 if (inst_ty.zigTypeTag() != .ErrorSet) {
89028900 _ = try sema.coerce(block, dest_payload_ty, inst, inst_src);
8903 } else switch (dest_err_set_ty.tag()) {
8901 return sema.addConstant(dest_type, try Value.Tag.eu_payload.create(sema.arena, val));
8902 }
8903 switch (dest_err_set_ty.tag()) {
89048904 .anyerror => {},
89058905 .error_set_single => {
89068906 const expected_name = val.castTag(.@"error").?.data.name;
......@@ -8946,9 +8946,7 @@ fn wrapErrorUnion(
89468946 },
89478947 else => unreachable,
89488948 }
8949
8950 // Create a SubValue for the error_union payload.
8951 return sema.addConstant(dest_type, try Value.Tag.error_union.create(sema.arena, val));
8949 return sema.addConstant(dest_type, val);
89528950 }
89538951
89548952 try sema.requireRuntimeBlock(block, inst_src);
src/codegen.zig+1-1
......@@ -4815,7 +4815,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
48154815 .ErrorUnion => {
48164816 const error_type = typed_value.ty.errorUnionSet();
48174817 const payload_type = typed_value.ty.errorUnionPayload();
4818 const sub_val = typed_value.val.castTag(.error_union).?.data;
4818 const sub_val = typed_value.val.castTag(.eu_payload).?.data;
48194819
48204820 if (!payload_type.hasCodeGenBits()) {
48214821 // We use the error type directly as the type.
src/codegen/c.zig+9-16
......@@ -350,32 +350,25 @@ pub const DeclGen = struct {
350350 .ErrorUnion => {
351351 const error_type = t.errorUnionSet();
352352 const payload_type = t.errorUnionPayload();
353 const sub_val = val.castTag(.error_union).?.data;
354353
355354 if (!payload_type.hasCodeGenBits()) {
356355 // We use the error type directly as the type.
357 return dg.renderValue(writer, error_type, sub_val);
356 const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val;
357 return dg.renderValue(writer, error_type, err_val);
358358 }
359359
360360 try writer.writeByte('(');
361361 try dg.renderType(writer, t);
362362 try writer.writeAll("){");
363 if (val.getError()) |_| {
364 try writer.writeAll(" .error = ");
365 try dg.renderValue(
366 writer,
367 error_type,
368 sub_val,
369 );
370 try writer.writeAll(" }");
371 } else {
363 if (val.castTag(.eu_payload)) |pl| {
364 const payload_val = pl.data;
372365 try writer.writeAll(" .payload = ");
373 try dg.renderValue(
374 writer,
375 payload_type,
376 sub_val,
377 );
366 try dg.renderValue(writer, payload_type, payload_val);
378367 try writer.writeAll(", .error = 0 }");
368 } else {
369 try writer.writeAll(" .error = ");
370 try dg.renderValue(writer, error_type, val);
371 try writer.writeAll(" }");
379372 }
380373 },
381374 .Enum => {
src/codegen/llvm.zig+51-27
......@@ -593,7 +593,7 @@ pub const DeclGen = struct {
593593 try self.llvmType(ptr_type),
594594 try self.llvmType(Type.initTag(.usize)),
595595 };
596 return self.context.structType(&fields, 2, .False);
596 return self.context.structType(&fields, fields.len, .False);
597597 } else {
598598 const elem_type = try self.llvmType(t.elemType());
599599 return elem_type.pointerType(0);
......@@ -621,10 +621,14 @@ pub const DeclGen = struct {
621621 .ErrorUnion => {
622622 const error_type = t.errorUnionSet();
623623 const payload_type = t.errorUnionPayload();
624 const llvm_error_type = try self.llvmType(error_type);
624625 if (!payload_type.hasCodeGenBits()) {
625 return self.llvmType(error_type);
626 return llvm_error_type;
626627 }
627 return self.todo("implement llvmType for error unions", .{});
628 const llvm_payload_type = try self.llvmType(payload_type);
629
630 const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type };
631 return self.context.structType(&fields, fields.len, .False);
628632 },
629633 .ErrorSet => {
630634 return self.context.intType(16);
......@@ -846,14 +850,25 @@ pub const DeclGen = struct {
846850 .ErrorUnion => {
847851 const error_type = tv.ty.errorUnionSet();
848852 const payload_type = tv.ty.errorUnionPayload();
849 const sub_val = tv.val.castTag(.error_union).?.data;
853 const is_pl = tv.val.errorUnionIsPayload();
850854
851855 if (!payload_type.hasCodeGenBits()) {
852856 // We use the error type directly as the type.
853 return self.genTypedValue(.{ .ty = error_type, .val = sub_val });
857 const err_val = if (!is_pl) tv.val else Value.initTag(.zero);
858 return self.genTypedValue(.{ .ty = error_type, .val = err_val });
854859 }
855860
856 return self.todo("implement error union const of type '{}'", .{tv.ty});
861 const fields: [2]*const llvm.Value = .{
862 try self.genTypedValue(.{
863 .ty = error_type,
864 .val = if (is_pl) Value.initTag(.zero) else tv.val,
865 }),
866 try self.genTypedValue(.{
867 .ty = payload_type,
868 .val = if (tv.val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef),
869 }),
870 };
871 return self.context.constStruct(&fields, fields.len, .False);
857872 },
858873 .Struct => {
859874 const fields_len = tv.ty.structFieldCount();
......@@ -984,10 +999,10 @@ pub const FuncGen = struct {
984999 .is_non_null_ptr => try self.airIsNonNull(inst, true),
9851000 .is_null => try self.airIsNull(inst, false),
9861001 .is_null_ptr => try self.airIsNull(inst, true),
987 .is_non_err => try self.airIsErr(inst, true, false),
988 .is_non_err_ptr => try self.airIsErr(inst, true, true),
989 .is_err => try self.airIsErr(inst, false, false),
990 .is_err_ptr => try self.airIsErr(inst, false, true),
1002 .is_non_err => try self.airIsErr(inst, .EQ, false),
1003 .is_non_err_ptr => try self.airIsErr(inst, .EQ, true),
1004 .is_err => try self.airIsErr(inst, .NE, false),
1005 .is_err_ptr => try self.airIsErr(inst, .NE, true),
9911006
9921007 .alloc => try self.airAlloc(inst),
9931008 .arg => try self.airArg(inst),
......@@ -1098,7 +1113,7 @@ pub const FuncGen = struct {
10981113 const inst_ty = self.air.typeOfIndex(inst);
10991114
11001115 switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) {
1101 .Int, .Bool, .Pointer => {
1116 .Int, .Bool, .Pointer, .ErrorSet => {
11021117 const is_signed = inst_ty.isSignedInt();
11031118 const operation = switch (op) {
11041119 .eq => .EQ,
......@@ -1256,12 +1271,7 @@ pub const FuncGen = struct {
12561271 const rhs = try self.resolveInst(bin_op.rhs);
12571272
12581273 const base_ptr = ptr: {
1259 const index_type = self.context.intType(32);
1260 const indices: [2]*const llvm.Value = .{
1261 index_type.constNull(),
1262 index_type.constInt(0, .False),
1263 };
1264 const ptr_field_ptr = self.builder.buildInBoundsGEP(lhs, &indices, 2, "");
1274 const ptr_field_ptr = self.builder.buildStructGEP(lhs, 0, "");
12651275 break :ptr self.builder.buildLoad(ptr_field_ptr, "");
12661276 };
12671277
......@@ -1472,7 +1482,7 @@ pub const FuncGen = struct {
14721482 index_type.constInt(1, .False),
14731483 };
14741484
1475 return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), "");
1485 return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""), "");
14761486 } else {
14771487 return self.builder.buildExtractValue(operand, 1, "");
14781488 }
......@@ -1488,7 +1498,7 @@ pub const FuncGen = struct {
14881498 fn airIsErr(
14891499 self: *FuncGen,
14901500 inst: Air.Inst.Index,
1491 invert_logic: bool,
1501 op: llvm.IntPredicate,
14921502 operand_is_ptr: bool,
14931503 ) !?*const llvm.Value {
14941504 if (self.liveness.isUnused(inst))
......@@ -1498,16 +1508,22 @@ pub const FuncGen = struct {
14981508 const operand = try self.resolveInst(un_op);
14991509 const err_union_ty = self.air.typeOf(un_op);
15001510 const payload_ty = err_union_ty.errorUnionPayload();
1511 const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror));
1512 const zero = err_set_ty.constNull();
15011513
15021514 if (!payload_ty.hasCodeGenBits()) {
15031515 const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand;
1504 const op: llvm.IntPredicate = if (invert_logic) .EQ else .NE;
1505 const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror));
1506 const zero = err_set_ty.constNull();
15071516 return self.builder.buildICmp(op, loaded, zero, "");
15081517 }
15091518
1510 return self.todo("implement 'airIsErr' for error unions with nonzero payload", .{});
1519 if (operand_is_ptr) {
1520 const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
1521 const loaded = self.builder.buildLoad(err_field_ptr, "");
1522 return self.builder.buildICmp(op, loaded, zero, "");
1523 }
1524
1525 const loaded = self.builder.buildExtractValue(operand, 0, "");
1526 return self.builder.buildICmp(op, loaded, zero, "");
15111527 }
15121528
15131529 fn airOptionalPayload(
......@@ -1552,9 +1568,11 @@ pub const FuncGen = struct {
15521568 return null;
15531569 }
15541570
1555 _ = operand;
1556 _ = operand_is_ptr;
1557 return self.todo("implement llvm codegen for 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)});
1571 if (operand_is_ptr) {
1572 return self.builder.buildStructGEP(operand, 1, "");
1573 }
1574
1575 return self.builder.buildExtractValue(operand, 1, "");
15581576 }
15591577
15601578 fn airErrUnionErr(
......@@ -1574,7 +1592,13 @@ pub const FuncGen = struct {
15741592 if (!operand_is_ptr) return operand;
15751593 return self.builder.buildLoad(operand, "");
15761594 }
1577 return self.todo("implement llvm codegen for 'airErrUnionErr'", .{});
1595
1596 if (operand_is_ptr) {
1597 const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
1598 return self.builder.buildLoad(err_field_ptr, "");
1599 }
1600
1601 return self.builder.buildExtractValue(operand, 0, "");
15781602 }
15791603
15801604 fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
src/codegen/wasm.zig+9-9
......@@ -1167,12 +1167,18 @@ pub const Context = struct {
11671167 try leb.writeULEB128(writer, error_index);
11681168 },
11691169 .ErrorUnion => {
1170 const data = val.castTag(.error_union).?.data;
11711170 const error_type = ty.errorUnionSet();
11721171 const payload_type = ty.errorUnionPayload();
1173 if (val.getError()) |_| {
1172 if (val.castTag(.eu_payload)) |pl| {
1173 const payload_val = pl.data;
1174 // no error, so write a '0' const
1175 try writer.writeByte(wasm.opcode(.i32_const));
1176 try leb.writeULEB128(writer, @as(u32, 0));
1177 // after the error code, we emit the payload
1178 try self.emitConstant(payload_val, payload_type);
1179 } else {
11741180 // write the error val
1175 try self.emitConstant(data, error_type);
1181 try self.emitConstant(val, error_type);
11761182
11771183 // no payload, so write a '0' const
11781184 const opcode: wasm.Opcode = buildOpcode(.{
......@@ -1181,12 +1187,6 @@ pub const Context = struct {
11811187 });
11821188 try writer.writeByte(wasm.opcode(opcode));
11831189 try leb.writeULEB128(writer, @as(u32, 0));
1184 } else {
1185 // no error, so write a '0' const
1186 try writer.writeByte(wasm.opcode(.i32_const));
1187 try leb.writeULEB128(writer, @as(u32, 0));
1188 // after the error code, we emit the payload
1189 try self.emitConstant(data, payload_type);
11901190 }
11911191 },
11921192 .Optional => {
src/value.zig+31-12
......@@ -129,7 +129,13 @@ pub const Value = extern union {
129129 /// A specific enum tag, indicated by the field index (declaration order).
130130 enum_field_index,
131131 @"error",
132 error_union,
132 /// When the type is error union:
133 /// * If the tag is `.@"error"`, the error union is an error.
134 /// * If the tag is `.eu_payload`, the error union is a payload.
135 /// * A nested error such as `((anyerror!T1)!T2)` in which the the outer error union
136 /// is non-error, but the inner error union is an error, is represented as
137 /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`.
138 eu_payload,
133139 /// A pointer to the payload of an error union, based on a pointer to an error union.
134140 eu_payload_ptr,
135141 /// An instance of a struct.
......@@ -228,7 +234,7 @@ pub const Value = extern union {
228234 => Payload.Decl,
229235
230236 .repeated,
231 .error_union,
237 .eu_payload,
232238 .eu_payload_ptr,
233239 => Payload.SubValue,
234240
......@@ -450,7 +456,7 @@ pub const Value = extern union {
450456 return Value{ .ptr_otherwise = &new_payload.base };
451457 },
452458 .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
453 .repeated, .error_union, .eu_payload_ptr => {
459 .repeated, .eu_payload, .eu_payload_ptr => {
454460 const payload = self.cast(Payload.SubValue).?;
455461 const new_payload = try allocator.create(Payload.SubValue);
456462 new_payload.* = .{
......@@ -642,7 +648,10 @@ pub const Value = extern union {
642648 .float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}),
643649 .@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}),
644650 // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that
645 .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}),
651 .eu_payload => {
652 try out_stream.writeAll("(eu_payload) ");
653 val = val.castTag(.eu_payload).?.data;
654 },
646655 .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
647656 .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"),
648657 .eu_payload_ptr => {
......@@ -1241,7 +1250,7 @@ pub const Value = extern union {
12411250 .eu_payload_ptr => blk: {
12421251 const err_union_ptr = self.castTag(.eu_payload_ptr).?.data;
12431252 const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null;
1244 break :blk err_union_val.castTag(.error_union).?.data;
1253 break :blk err_union_val.castTag(.eu_payload).?.data;
12451254 },
12461255
12471256 .zero,
......@@ -1351,16 +1360,16 @@ pub const Value = extern union {
13511360 }
13521361
13531362 /// Valid for all types. Asserts the value is not undefined and not unreachable.
1363 /// Prefer `errorUnionIsPayload` to find out whether something is an error or not
1364 /// because it works without having to figure out the string.
13541365 pub fn getError(self: Value) ?[]const u8 {
13551366 return switch (self.tag()) {
1356 .error_union => {
1357 const data = self.castTag(.error_union).?.data;
1358 return if (data.tag() == .@"error")
1359 data.castTag(.@"error").?.data.name
1360 else
1361 null;
1362 },
13631367 .@"error" => self.castTag(.@"error").?.data.name,
1368 .int_u64 => @panic("TODO"),
1369 .int_i64 => @panic("TODO"),
1370 .int_big_positive => @panic("TODO"),
1371 .int_big_negative => @panic("TODO"),
1372 .one => @panic("TODO"),
13641373 .undef => unreachable,
13651374 .unreachable_value => unreachable,
13661375 .inferred_alloc => unreachable,
......@@ -1369,6 +1378,16 @@ pub const Value = extern union {
13691378 else => null,
13701379 };
13711380 }
1381
1382 /// Assumes the type is an error union. Returns true if and only if the value is
1383 /// the error union payload, not an error.
1384 pub fn errorUnionIsPayload(val: Value) bool {
1385 return switch (val.tag()) {
1386 .eu_payload => true,
1387 else => false,
1388 };
1389 }
1390
13721391 /// Valid for all types. Asserts the value is not undefined.
13731392 pub fn isFloat(self: Value) bool {
13741393 return switch (self.tag()) {
test/behavior.zig+2-1
......@@ -7,6 +7,7 @@ test {
77 _ = @import("behavior/generics.zig");
88 _ = @import("behavior/eval.zig");
99 _ = @import("behavior/pointers.zig");
10 _ = @import("behavior/if.zig");
1011
1112 if (!builtin.zig_is_stage2) {
1213 // Tests that only pass for stage1.
......@@ -100,7 +101,7 @@ test {
100101 _ = @import("behavior/generics_stage1.zig");
101102 _ = @import("behavior/hasdecl.zig");
102103 _ = @import("behavior/hasfield.zig");
103 _ = @import("behavior/if.zig");
104 _ = @import("behavior/if_stage1.zig");
104105 _ = @import("behavior/import.zig");
105106 _ = @import("behavior/incomplete_struct_param_tld.zig");
106107 _ = @import("behavior/inttoptr.zig");
test/behavior/if.zig-42
......@@ -65,45 +65,3 @@ test "labeled break inside comptime if inside runtime if" {
6565 }
6666 try expect(answer == 42);
6767}
68
69test "const result loc, runtime if cond, else unreachable" {
70 const Num = enum {
71 One,
72 Two,
73 };
74
75 var t = true;
76 const x = if (t) Num.Two else unreachable;
77 try expect(x == .Two);
78}
79
80test "if prongs cast to expected type instead of peer type resolution" {
81 const S = struct {
82 fn doTheTest(f: bool) !void {
83 var x: i32 = 0;
84 x = if (f) 1 else 2;
85 try expect(x == 2);
86
87 var b = true;
88 const y: i32 = if (b) 1 else 2;
89 try expect(y == 1);
90 }
91 };
92 try S.doTheTest(false);
93 comptime try S.doTheTest(false);
94}
95
96test "while copies its payload" {
97 const S = struct {
98 fn doTheTest() !void {
99 var tmp: ?i32 = 10;
100 if (tmp) |value| {
101 // Modify the original variable
102 tmp = null;
103 try expectEqual(@as(i32, 10), value);
104 } else unreachable;
105 }
106 };
107 try S.doTheTest();
108 comptime try S.doTheTest();
109}
test/behavior/if_stage1.zig created+45
......@@ -0,0 +1,45 @@
1const std = @import("std");
2const expect = std.testing.expect;
3const expectEqual = std.testing.expectEqual;
4
5test "const result loc, runtime if cond, else unreachable" {
6 const Num = enum {
7 One,
8 Two,
9 };
10
11 var t = true;
12 const x = if (t) Num.Two else unreachable;
13 try expect(x == .Two);
14}
15
16test "if prongs cast to expected type instead of peer type resolution" {
17 const S = struct {
18 fn doTheTest(f: bool) !void {
19 var x: i32 = 0;
20 x = if (f) 1 else 2;
21 try expect(x == 2);
22
23 var b = true;
24 const y: i32 = if (b) 1 else 2;
25 try expect(y == 1);
26 }
27 };
28 try S.doTheTest(false);
29 comptime try S.doTheTest(false);
30}
31
32test "while copies its payload" {
33 const S = struct {
34 fn doTheTest() !void {
35 var tmp: ?i32 = 10;
36 if (tmp) |value| {
37 // Modify the original variable
38 tmp = null;
39 try expectEqual(@as(i32, 10), value);
40 } else unreachable;
41 }
42 };
43 try S.doTheTest();
44 comptime try S.doTheTest();
45}