From fb28349349483c5e1c9523b19c2158cb465cf4e9 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 26 Aug 2020 12:34:41 +0300 Subject: [PATCH] stage2: astgen catch --- src-self-hosted/Module.zig | 12 ++++- src-self-hosted/astgen.zig | 94 ++++++++++++++++++++++++++++++++++-- src-self-hosted/zir.zig | 4 ++ src-self-hosted/zir_sema.zig | 32 ++++++------ 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 82029c1e9f..5b5ef656b2 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2570,7 +2570,17 @@ pub fn analyzeIsNull( operand: *Inst, invert_logic: bool, ) InnerError!*Inst { - return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{}); + if (operand.value()) |opt_val| { + const is_null = opt_val.isNull(); + return self.constBool(scope, src, invert_logic); + } + const b = try self.requireRuntimeBlock(scope, src); + const inst_tag: Inst.Tag = if (invert_logic) .isnonnull else .isnull; + return self.addUnOp(b, src, Type.initTag(.bool), inst_tag, operand); +} + +pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst { + return self.fail(scope, src, "TODO implement analysis of iserr", .{}); } /// Asserts that lhs and rhs types are both numeric. diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index b09ffc4894..737af24268 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -275,9 +275,9 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)), .For => return forExpr(mod, scope, rl, node.castTag(.For).?), .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?), + .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), - .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}), .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}), .OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}), .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), @@ -750,6 +750,93 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!* }); } +fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + + const err_union_ptr = try expr(mod, scope, .ref, node.lhs); + // TODO we could avoid an unnecessary copy if .iserr took a pointer + const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr); + const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union); + + var block_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer block_scope.instructions.deinit(mod.gpa); + + const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{ + .condition = cond, + .then_body = undefined, // populated below + .else_body = undefined, // populated below + }, .{}); + + const block = try addZIRInstBlock(mod, scope, src, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }); + + // Most result location types can be forwarded directly; however + // if we need to write to a pointer which has an inferred type, + // proper type inference requires peer type resolution on the if's + // branches. + const branch_rl: ResultLoc = switch (rl) { + .discard, .none, .ty, .ptr, .ref => rl, + .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, + }; + + var err_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer err_scope.instructions.deinit(mod.gpa); + + var err_val_scope: Scope.LocalVal = undefined; + const err_sub_scope = blk: { + const payload = node.payload orelse + break :blk &err_scope.base; + + const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken()); + if (mem.eql(u8, err_name, "_")) + break :blk &err_scope.base; + + const unwrapped_err_ptr = try addZIRUnOp(mod, &err_scope.base, src, .unwrap_err_code, err_union_ptr); + err_val_scope = .{ + .parent = &err_scope.base, + .gen_zir = &err_scope, + .name = err_name, + .inst = try addZIRUnOp(mod, &err_scope.base, src, .deref, unwrapped_err_ptr), + }; + break :blk &err_val_scope.base; + }; + + _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{ + .block = block, + .operand = try expr(mod, err_sub_scope, branch_rl, node.rhs), + }, .{}); + + var not_err_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer not_err_scope.instructions.deinit(mod.gpa); + + const unwrapped_payload = try addZIRUnOp(mod, ¬_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr); + _ = try addZIRInst(mod, ¬_err_scope.base, src, zir.Inst.Break, .{ + .block = block, + .operand = unwrapped_payload, + }, .{}); + + condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) }; + condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) }; + return rlWrap(mod, scope, rl, &block.base); +} + /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. /// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used. fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { @@ -1317,7 +1404,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = for_block }, }; - var index_scope: Scope.LocalPtr = undefined; + var index_scope: Scope.LocalVal = undefined; const then_sub_scope = blk: { const payload = for_node.payload.castTag(.PointerIndexPayload).?; const is_ptr = payload.ptr_token != null; @@ -1335,12 +1422,11 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) if (mem.eql(u8, index_name, "_")) { break :blk &then_scope.base; } - // TODO ensure this is const index_scope = .{ .parent = &then_scope.base, .gen_zir = &then_scope, .name = index_name, - .ptr = index_ptr, + .inst = index, }; break :blk &index_scope.base; }; diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index e06ce1fcca..4e8967f8dc 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -253,6 +253,8 @@ pub const Inst = struct { unwrap_err_safe, /// Same as previous, but without safety checks. Used for orelse, if and while unwrap_err_unsafe, + /// Gets the error code value of an error union + unwrap_err_code, /// Takes a *E!T and raises a compiler error if T != void ensure_err_payload_void, /// Enum literal @@ -298,6 +300,7 @@ pub const Inst = struct { .unwrap_optional_unsafe, .unwrap_err_safe, .unwrap_err_unsafe, + .unwrap_err_code, .ensure_err_payload_void, .anyframe_type, .bitnot, @@ -454,6 +457,7 @@ pub const Inst = struct { .unwrap_optional_unsafe, .unwrap_err_safe, .unwrap_err_unsafe, + .unwrap_err_code, .ptr_type, .ensure_err_payload_void, .enum_literal, diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index f7afb50e7a..056d6c2faa 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -120,6 +120,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false), .unwrap_err_safe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_safe).?, true), .unwrap_err_unsafe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_unsafe).?, false), + .unwrap_err_code => return analyzeInstUnwrapErrCode(mod, scope, old_inst.castTag(.unwrap_err_code).?), .ensure_err_payload_void => return analyzeInstEnsureErrPayloadVoid(mod, scope, old_inst.castTag(.ensure_err_payload_void).?), .array_type => return analyzeInstArrayType(mod, scope, old_inst.castTag(.array_type).?), .array_type_sentinel => return analyzeInstArrayTypeSentinel(mod, scope, old_inst.castTag(.array_type_sentinel).?), @@ -800,11 +801,12 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp const operand = try resolveInst(mod, scope, unwrap.positionals.operand); assert(operand.ty.zigTypeTag() == .Pointer); - if (operand.ty.elemType().zigTypeTag() != .Optional) { - return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()}); + const elem_type = operand.ty.elemType(); + if (elem_type.zigTypeTag() != .Optional) { + return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{elem_type}); } - const child_type = try operand.ty.elemType().optionalChildAlloc(scope.arena()); + const child_type = try elem_type.optionalChildAlloc(scope.arena()); const child_pointer = try mod.simplePtrType(scope, unwrap.base.src, child_type, operand.ty.isConstPtr(), .One); if (operand.value()) |val| { @@ -829,6 +831,10 @@ fn analyzeInstUnwrapErr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, saf return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstUnwrapErr", .{}); } +fn analyzeInstUnwrapErrCode(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstUnwrapErrCode", .{}); +} + fn analyzeInstEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst { return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstEnsureErrPayloadVoid", .{}); } @@ -964,7 +970,8 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr const entry = if (val.cast(Value.Payload.ErrorSet)) |payload| (payload.fields.getEntry(field_name) orelse return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).* - else try mod.getErrorValue(field_name); + else + try mod.getErrorValue(field_name); const error_payload = try scope.arena().create(Value.Payload.Error); error_payload.* = .{ @@ -1298,17 +1305,7 @@ fn analyzeInstCmp( { // comparing null with optionals const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; - if (opt_operand.value()) |opt_val| { - const is_null = opt_val.isNull(); - return mod.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); - } - const b = try mod.requireRuntimeBlock(scope, inst.base.src); - const inst_tag: Inst.Tag = switch (op) { - .eq => .isnull, - .neq => .isnonnull, - else => unreachable, - }; - return mod.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand); + return mod.analyzeIsNull(scope, inst.base.src, opt_operand, op == .neq); } else if (is_equality_cmp and ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) { @@ -1356,8 +1353,9 @@ fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, inver return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic); } -fn analyzeInstIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstIsErr", .{}); +fn analyzeInstIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + return mod.analyzeIsErr(scope, inst.base.src, operand); } fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {