From e9f069f53627f1e647a200da7bb75e7663705c5f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 3 Jan 2022 17:05:56 +0100 Subject: [PATCH] stage2: implement isErr/isNonErr and unwrap error --- src/arch/x86_64/CodeGen.zig | 105 ++++++++++++++++++++++-------------- test/stage2/x86_64.zig | 23 ++++++++ 2 files changed, 89 insertions(+), 39 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 0d4ca4306e..6a01d01968 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -415,9 +415,14 @@ fn gen(self: *Self) InnerError!void { try self.genBody(self.air.getMainBody()); - if (self.exitlude_jump_relocs.items.len == 1) { - self.mir_instructions.len -= 1; - } else for (self.exitlude_jump_relocs.items) |jmp_reloc| { + // TODO can single exitlude jump reloc be elided? What if it is not at the end of the code? + // Example: + // pub fn main() void { + // maybeErr() catch return; + // unreachable; + // } + // Eliding the reloc will cause a miscompilation in this case. + for (self.exitlude_jump_relocs.items) |jmp_reloc| { self.mir_instructions.items(.data)[jmp_reloc].inst = @intCast(u32, self.mir_instructions.len); } @@ -1180,19 +1185,24 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) - .dead - else - return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const err_union_ty = self.air.typeOf(ty_op.operand); + const payload_ty = err_union_ty.errorUnionPayload(); + const mcv = try self.resolveInst(ty_op.operand); + if (!payload_ty.hasCodeGenBits()) break :result mcv; + return self.fail("TODO implement unwrap error union error for non-empty payloads", .{}); + }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) - .dead - else - return self.fail("TODO implement unwrap error union payload for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const err_union_ty = self.air.typeOf(ty_op.operand); + const payload_ty = err_union_ty.errorUnionPayload(); + if (!payload_ty.hasCodeGenBits()) break :result MCValue.none; + return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{}); + }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -2396,19 +2406,35 @@ fn isNonNull(self: *Self, ty: Type, operand: MCValue) !MCValue { } fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue { - _ = ty; - _ = operand; - // Here you can specialize this instruction if it makes sense to, otherwise the default - // will call isNonErr and invert the result. - return self.fail("TODO call isNonErr and invert the result", .{}); + const err_type = ty.errorUnionSet(); + const payload_type = ty.errorUnionPayload(); + if (!err_type.hasCodeGenBits()) { + return MCValue{ .immediate = 0 }; // always false + } else if (!payload_type.hasCodeGenBits()) { + if (err_type.abiSize(self.target.*) <= 8) { + try self.genBinMathOpMir(.cmp, err_type, operand, MCValue{ .immediate = 0 }); + return MCValue{ .compare_flags_unsigned = .gt }; + } else { + return self.fail("TODO isErr for errors with size larger than register size", .{}); + } + } else { + return self.fail("TODO isErr for non-empty payloads", .{}); + } } fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue { - _ = ty; - _ = operand; - // Here you can specialize this instruction if it makes sense to, otherwise the default - // will call isErr and invert the result. - return self.fail("TODO call isErr and invert the result", .{}); + const is_err_res = try self.isErr(ty, operand); + switch (is_err_res) { + .compare_flags_unsigned => |op| { + assert(op == .gt); + return MCValue{ .compare_flags_unsigned = .lte }; + }, + .immediate => |imm| { + assert(imm == 0); + return MCValue{ .immediate = 1 }; + }, + else => unreachable, + } } fn airIsNull(self: *Self, inst: Air.Inst.Index) !void { @@ -3435,31 +3461,32 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { } }, .ErrorSet => { - switch (typed_value.val.tag()) { - .@"error" => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - else => { - // In this case we are rendering an error union which has a 0 bits payload. - return MCValue{ .immediate = 0 }; - }, - } + const err_name = typed_value.val.castTag(.@"error").?.data.name; + const module = self.bin_file.options.module.?; + const global_error_set = module.global_error_set; + const error_index = global_error_set.get(err_name).?; + return MCValue{ .immediate = error_index }; }, .ErrorUnion => { const error_type = typed_value.ty.errorUnionSet(); const payload_type = typed_value.ty.errorUnionPayload(); - const sub_val = typed_value.val.castTag(.eu_payload).?.data; - if (!payload_type.hasCodeGenBits()) { - // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); + if (typed_value.val.castTag(.eu_payload)) |pl| { + if (!payload_type.hasCodeGenBits()) { + // We use the error type directly as the type. + return MCValue{ .immediate = 0 }; + } + + _ = pl; + return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty}); + } else { + if (!payload_type.hasCodeGenBits()) { + // We use the error type directly as the type. + return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val }); + } } - return self.fail("TODO implement error union const of type '{}'", .{typed_value.ty}); + return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty}); }, else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}), } diff --git a/test/stage2/x86_64.zig b/test/stage2/x86_64.zig index 9f90a18194..7abbbad255 100644 --- a/test/stage2/x86_64.zig +++ b/test/stage2/x86_64.zig @@ -1738,6 +1738,29 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exe("unwrap error union - simple errors", target); + case.addCompareOutput( + \\pub fn main() void { + \\ maybeErr() catch unreachable; + \\} + \\ + \\fn maybeErr() !void { + \\ return; + \\} + , ""); + case.addCompareOutput( + \\pub fn main() void { + \\ maybeErr() catch return; + \\ unreachable; + \\} + \\ + \\fn maybeErr() !void { + \\ return error.NoWay; + \\} + , ""); + } } }