diff --git a/src/Air.zig b/src/Air.zig index 5571fc63597d..53421b647534 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -320,6 +320,20 @@ pub const Inst = struct { /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. switch_br, + /// Given an operand which is an error union, splits control flow. In + /// case of error, control flow goes into the block that is part of this + /// instruction, which is guaranteed to end with a return instruction + /// and never breaks out of the block. + /// In the case of non-error, control flow proceeds to the next instruction + /// after the `try`, with the result of this instruction being the unwrapped + /// payload value, as if `unwrap_errunion_payload` was executed on the operand. + /// Uses the `pl_op` field. Payload is `Try`. + @"try", + /// Same as `try` except the operand is a pointer to an error union, and the + /// result is a pointer to the payload. Result is as if `unwrap_errunion_payload_ptr` + /// was executed on the operand. + /// Uses the `ty_pl` field. Payload is `TryPtr`. + try_ptr, /// A comptime-known value. Uses the `ty_pl` field, payload is index of /// `values` array. constant, @@ -780,6 +794,19 @@ pub const SwitchBr = struct { }; }; +/// This data is stored inside extra. Trailing: +/// 0. body: Inst.Index // for each body_len +pub const Try = struct { + body_len: u32, +}; + +/// This data is stored inside extra. Trailing: +/// 0. body: Inst.Index // for each body_len +pub const TryPtr = struct { + ptr: Inst.Ref, + body_len: u32, +}; + pub const StructField = struct { /// Whether this is a pointer or byval is determined by the AIR tag. struct_operand: Inst.Ref, @@ -991,6 +1018,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .shl_with_overflow, .ptr_add, .ptr_sub, + .try_ptr, => return air.getRefType(datas[inst].ty_pl.ty), .not, @@ -1102,6 +1130,11 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { const extra = air.extraData(Air.Bin, datas[inst].pl_op.payload).data; return air.typeOf(extra.lhs); }, + + .@"try" => { + const err_union_ty = air.typeOf(datas[inst].pl_op.operand); + return err_union_ty.errorUnionPayload(); + }, } } diff --git a/src/AstGen.zig b/src/AstGen.zig index ab5befa4ba40..adb1223b7177 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2425,6 +2425,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .param_type, .ret_ptr, .ret_type, + .@"try", + .try_ptr, + //.try_inline, + //.try_ptr_inline, => break :b false, .extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) { @@ -4871,68 +4875,43 @@ fn tryExpr( if (parent_gz.in_defer) return astgen.failNode(node, "'try' not allowed inside defer expression", .{}); - var block_scope = parent_gz.makeSubBlock(scope); - block_scope.setBreakResultLoc(rl); - defer block_scope.unstack(); - - const operand_rl: ResultLoc = switch (block_scope.break_result_loc) { + const operand_rl: ResultLoc = switch (rl) { .ref => .ref, else => .none, }; - const err_ops = switch (operand_rl) { - // zig fmt: off - .ref => [3]Zir.Inst.Tag{ .is_non_err_ptr, .err_union_code_ptr, .err_union_payload_unsafe_ptr }, - else => [3]Zir.Inst.Tag{ .is_non_err, .err_union_code, .err_union_payload_unsafe }, - // zig fmt: on - }; - // This could be a pointer or value depending on the `operand_rl` parameter. - // We cannot use `block_scope.break_result_loc` because that has the bare - // type, whereas this expression has the optional type. Later we make - // up for this fact by calling rvalue on the else branch. - const operand = try expr(&block_scope, &block_scope.base, operand_rl, operand_node); - const cond = try block_scope.addUnNode(err_ops[0], operand, node); - const condbr = try block_scope.addCondBr(.condbr, node); - - const block = try parent_gz.makeBlockInst(.block, node); - try block_scope.setBlockBody(block); - // block_scope unstacked now, can add new instructions to parent_gz - try parent_gz.instructions.append(astgen.gpa, block); - - var then_scope = parent_gz.makeSubBlock(scope); - defer then_scope.unstack(); - - block_scope.break_count += 1; - // This could be a pointer or value depending on `err_ops[2]`. - const unwrapped_payload = try then_scope.addUnNode(err_ops[2], operand, node); - const then_result = switch (rl) { - .ref => unwrapped_payload, - else => try rvalue(&then_scope, block_scope.break_result_loc, unwrapped_payload, node), + // This could be a pointer or value depending on the `rl` parameter. + const operand = try expr(parent_gz, scope, operand_rl, operand_node); + const is_inline = parent_gz.force_comptime; + const is_inline_bit = @as(u2, @boolToInt(is_inline)); + const is_ptr_bit = @as(u2, @boolToInt(operand_rl == .ref)) << 1; + const block_tag: Zir.Inst.Tag = switch (is_inline_bit | is_ptr_bit) { + 0b00 => .@"try", + 0b01 => .@"try", + //0b01 => .try_inline, + 0b10 => .try_ptr, + 0b11 => .try_ptr, + //0b11 => .try_ptr_inline, }; + const try_inst = try parent_gz.makeBlockInst(block_tag, node); + try parent_gz.instructions.append(astgen.gpa, try_inst); - // else_scope will be stacked on then_scope as both are stacked on parent_gz var else_scope = parent_gz.makeSubBlock(scope); defer else_scope.unstack(); - const err_code = try else_scope.addUnNode(err_ops[1], operand, node); + const err_tag = switch (rl) { + .ref => Zir.Inst.Tag.err_union_code_ptr, + else => Zir.Inst.Tag.err_union_code, + }; + const err_code = try else_scope.addUnNode(err_tag, operand, node); try genDefers(&else_scope, &fn_block.base, scope, .{ .both = err_code }); - const else_result = try else_scope.addUnNode(.ret_node, err_code, node); + _ = try else_scope.addUnNode(.ret_node, err_code, node); - const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break"; - return finishThenElseBlock( - parent_gz, - rl, - node, - &block_scope, - &then_scope, - &else_scope, - condbr, - cond, - then_result, - else_result, - block, - block, - break_tag, - ); + try else_scope.setTryBody(try_inst, operand); + const result = indexToRef(try_inst); + switch (rl) { + .ref => return result, + else => return rvalue(parent_gz, rl, result, node), + } } fn orelseCatchExpr( @@ -10011,6 +9990,22 @@ const GenZir = struct { gz.unstack(); } + /// Assumes nothing stacked on `gz`. Unstacks `gz`. + fn setTryBody(gz: *GenZir, inst: Zir.Inst.Index, operand: Zir.Inst.Ref) !void { + const gpa = gz.astgen.gpa; + const body = gz.instructionsSlice(); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Try).Struct.fields.len + body.len); + const zir_datas = gz.astgen.instructions.items(.data); + zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity( + Zir.Inst.Try{ + .operand = operand, + .body_len = @intCast(u32, body.len), + }, + ); + gz.astgen.extra.appendSliceAssumeCapacity(body); + gz.unstack(); + } + /// Must be called with the following stack set up: /// * gz (bottom) /// * align_gz diff --git a/src/Liveness.zig b/src/Liveness.zig index b4576c0f1801..ecb755ae0a20 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -478,6 +478,12 @@ pub fn categorizeOperand( .block => { return .complex; }, + .@"try" => { + return .complex; + }, + .try_ptr => { + return .complex; + }, .loop => { return .complex; }, @@ -1019,6 +1025,19 @@ fn analyzeInst( try analyzeWithContext(a, new_set, body); return; // Loop has no operands and it is always unreferenced. }, + .@"try" => { + const pl_op = inst_datas[inst].pl_op; + const extra = a.air.extraData(Air.Try, pl_op.payload); + const body = a.air.extra[extra.end..][0..extra.data.body_len]; + try analyzeWithContext(a, new_set, body); + return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none }); + }, + .try_ptr => { + const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload); + const body = a.air.extra[extra.end..][0..extra.data.body_len]; + try analyzeWithContext(a, new_set, body); + return trackOperands(a, new_set, inst, main_tomb, .{ extra.data.ptr, .none, .none }); + }, .cond_br => { // Each death that occurs inside one branch, but not the other, needs // to be added as a death immediately upon entering the other branch. diff --git a/src/Sema.zig b/src/Sema.zig index 593b299833d8..78d025d54894 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1322,6 +1322,106 @@ fn analyzeBodyInner( break break_data.inst; } }, + .@"try" => blk: { + if (!block.is_comptime) break :blk try sema.zirTry(block, inst); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const err_union = try sema.resolveInst(extra.data.operand); + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + assert(is_non_err != .none); + const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + if (is_non_err_tv.val.toBool()) { + const err_union_ty = sema.typeOf(err_union); + break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false); + } + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + break break_data.inst; + } + }, + //.try_inline => blk: { + // const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + // const src = inst_data.src(); + // const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + // const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + // const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + // const operand = try sema.resolveInst(extra.data.operand); + // const operand_ty = sema.typeOf(operand); + // const is_ptr = operand_ty.zigTypeTag() == .Pointer; + // const err_union = if (is_ptr) + // try sema.analyzeLoad(block, src, operand, operand_src) + // else + // operand; + // const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + // assert(is_non_err != .none); + // const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + // if (is_non_err_tv.val.toBool()) { + // if (is_ptr) { + // break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + // } else { + // const err_union_ty = sema.typeOf(err_union); + // break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, false); + // } + // } + // const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + // break always_noreturn; + // if (inst == break_data.block_inst) { + // break :blk try sema.resolveInst(break_data.operand); + // } else { + // break break_data.inst; + // } + //}, + .try_ptr => blk: { + if (!block.is_comptime) break :blk try sema.zirTryPtr(block, inst); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const operand = try sema.resolveInst(extra.data.operand); + const err_union = try sema.analyzeLoad(block, src, operand, operand_src); + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + assert(is_non_err != .none); + const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + if (is_non_err_tv.val.toBool()) { + break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + } + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + break break_data.inst; + } + }, + //.try_ptr_inline => blk: { + // const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + // const src = inst_data.src(); + // const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + // const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + // const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + // const operand = try sema.resolveInst(extra.data.operand); + // const err_union = try sema.analyzeLoad(block, src, operand, operand_src); + // const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + // assert(is_non_err != .none); + // const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + // if (is_non_err_tv.val.toBool()) { + // break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + // } + // const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + // break always_noreturn; + // if (inst == break_data.block_inst) { + // break :blk try sema.resolveInst(break_data.operand); + // } else { + // break break_data.inst; + // } + //}, }; if (sema.typeOf(air_inst).isNoReturn()) break always_noreturn; @@ -6415,32 +6515,43 @@ fn zirErrUnionPayload( const src = inst_data.src(); const operand = try sema.resolveInst(inst_data.operand); const operand_src = src; - const operand_ty = sema.typeOf(operand); - if (operand_ty.zigTypeTag() != .ErrorUnion) { + const err_union_ty = sema.typeOf(operand); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { return sema.fail(block, operand_src, "expected error union type, found '{}'", .{ - operand_ty.fmt(sema.mod), + err_union_ty.fmt(sema.mod), }); } + return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, safety_check); +} - const result_ty = operand_ty.errorUnionPayload(); - if (try sema.resolveDefinedValue(block, src, operand)) |val| { +fn analyzeErrUnionPayload( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + err_union_ty: Type, + operand: Zir.Inst.Ref, + operand_src: LazySrcLoc, + safety_check: bool, +) CompileError!Air.Inst.Ref { + const payload_ty = err_union_ty.errorUnionPayload(); + if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| { if (val.getError()) |name| { return sema.fail(block, src, "caught unexpected error '{s}'", .{name}); } const data = val.castTag(.eu_payload).?.data; - return sema.addConstant(result_ty, data); + return sema.addConstant(payload_ty, data); } try sema.requireRuntimeBlock(block, src); // If the error set has no fields then no safety check is needed. if (safety_check and block.wantSafety() and - operand_ty.errorUnionSet().errorSetCardinality() != .zero) + err_union_ty.errorUnionSet().errorSetCardinality() != .zero) { try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err); } - return block.addTyOp(.unwrap_errunion_payload, result_ty, operand); + return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand); } /// Pointer in, pointer out. @@ -12958,6 +13069,108 @@ fn zirCondbr( return always_noreturn; } +fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const err_union = try sema.resolveInst(extra.data.operand); + const err_union_ty = sema.typeOf(err_union); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(parent_block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union); + if (is_non_err != .none) { + const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?; + if (is_non_err_val.toBool()) { + return sema.analyzeErrUnionPayload(parent_block, src, err_union_ty, err_union, operand_src, false); + } + // We can analyze the body directly in the parent block because we know there are + // no breaks from the body possible, and that the body is noreturn. + return sema.resolveBody(parent_block, body, inst); + } + + var sub_block = parent_block.makeSubBlock(); + defer sub_block.instructions.deinit(sema.gpa); + + // This body is guaranteed to end with noreturn and has no breaks. + _ = try sema.analyzeBodyInner(&sub_block, body); + + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Try).Struct.fields.len + + sub_block.instructions.items.len); + const try_inst = try parent_block.addInst(.{ + .tag = .@"try", + .data = .{ .pl_op = .{ + .operand = err_union, + .payload = sema.addExtraAssumeCapacity(Air.Try{ + .body_len = @intCast(u32, sub_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendSliceAssumeCapacity(sub_block.instructions.items); + return try_inst; +} + +fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const operand = try sema.resolveInst(extra.data.operand); + const err_union = try sema.analyzeLoad(parent_block, src, operand, operand_src); + const err_union_ty = sema.typeOf(err_union); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(parent_block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union); + if (is_non_err != .none) { + const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?; + if (is_non_err_val.toBool()) { + return sema.analyzeErrUnionPayloadPtr(parent_block, src, operand, false, false); + } + // We can analyze the body directly in the parent block because we know there are + // no breaks from the body possible, and that the body is noreturn. + return sema.resolveBody(parent_block, body, inst); + } + + var sub_block = parent_block.makeSubBlock(); + defer sub_block.instructions.deinit(sema.gpa); + + // This body is guaranteed to end with noreturn and has no breaks. + _ = try sema.analyzeBodyInner(&sub_block, body); + + const operand_ty = sema.typeOf(operand); + const ptr_info = operand_ty.ptrInfo().data; + const res_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = err_union_ty.errorUnionPayload(), + .@"addrspace" = ptr_info.@"addrspace", + .mutable = ptr_info.mutable, + .@"allowzero" = ptr_info.@"allowzero", + .@"volatile" = ptr_info.@"volatile", + }); + const res_ty_ref = try sema.addType(res_ty); + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.TryPtr).Struct.fields.len + + sub_block.instructions.items.len); + const try_inst = try parent_block.addInst(.{ + .tag = .try_ptr, + .data = .{ .ty_pl = .{ + .ty = res_ty_ref, + .payload = sema.addExtraAssumeCapacity(Air.TryPtr{ + .ptr = operand, + .body_len = @intCast(u32, sub_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendSliceAssumeCapacity(sub_block.instructions.items); + return try_inst; +} + // A `break` statement is inside a runtime condition, but trying to // break from an inline loop. In such case we must convert it to // a runtime break. @@ -21586,7 +21799,7 @@ fn analyzeIsNull( return block.addUnOp(air_tag, operand); } -fn analyzeIsNonErr( +fn analyzeIsNonErrComptimeOnly( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -21638,8 +21851,22 @@ fn analyzeIsNonErr( return Air.Inst.Ref.bool_false; } } - try sema.requireRuntimeBlock(block, src); - return block.addUnOp(.is_non_err, operand); + return Air.Inst.Ref.none; +} + +fn analyzeIsNonErr( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + operand: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const result = try sema.analyzeIsNonErrComptimeOnly(block, src, operand); + if (result == .none) { + try sema.requireRuntimeBlock(block, src); + return block.addUnOp(.is_non_err, operand); + } else { + return result; + } } fn analyzeSlice( diff --git a/src/Zir.zig b/src/Zir.zig index f09f2015e037..02f9a9715516 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -319,6 +319,23 @@ pub const Inst = struct { /// only the taken branch is analyzed. The then block and else block must /// terminate with an "inline" variant of a noreturn instruction. condbr_inline, + /// Given an operand which is an error union, splits control flow. In + /// case of error, control flow goes into the block that is part of this + /// instruction, which is guaranteed to end with a return instruction + /// and never breaks out of the block. + /// In the case of non-error, control flow proceeds to the next instruction + /// after the `try`, with the result of this instruction being the unwrapped + /// payload value, as if `err_union_payload_unsafe` was executed on the operand. + /// Uses the `pl_node` union field. Payload is `Try`. + @"try", + ///// Same as `try` except the operand is coerced to a comptime value, and + ///// only the taken branch is analyzed. The block must terminate with an "inline" + ///// variant of a noreturn instruction. + //try_inline, + /// Same as `try` except the operand is a pointer and the result is a pointer. + try_ptr, + ///// Same as `try_inline` except the operand is a pointer and the result is a pointer. + //try_ptr_inline, /// An error set type definition. Contains a list of field names. /// Uses the `pl_node` union field. Payload is `ErrorSetDecl`. error_set_decl, @@ -1231,6 +1248,10 @@ pub const Inst = struct { .closure_capture, .ret_ptr, .ret_type, + .@"try", + .try_ptr, + //.try_inline, + //.try_ptr_inline, => false, .@"break", @@ -1509,6 +1530,10 @@ pub const Inst = struct { .repeat, .repeat_inline, .panic, + .@"try", + .try_ptr, + //.try_inline, + //.try_ptr_inline, => false, .extended => switch (data.extended.opcode) { @@ -1569,6 +1594,10 @@ pub const Inst = struct { .coerce_result_ptr = .bin, .condbr = .pl_node, .condbr_inline = .pl_node, + .@"try" = .pl_node, + .try_ptr = .pl_node, + //.try_inline = .pl_node, + //.try_ptr_inline = .pl_node, .error_set_decl = .pl_node, .error_set_decl_anon = .pl_node, .error_set_decl_func = .pl_node, @@ -2803,6 +2832,14 @@ pub const Inst = struct { else_body_len: u32, }; + /// This data is stored inside extra, trailed by: + /// * 0. body: Index // for each `body_len`. + pub const Try = struct { + /// The error union to unwrap. + operand: Ref, + body_len: u32, + }; + /// Stored in extra. Depending on the flags in Data, there will be up to 5 /// trailing Ref fields: /// 0. sentinel: Ref // if `has_sentinel` flag is set @@ -3739,6 +3776,12 @@ fn findDeclsInner( try zir.findDeclsBody(list, then_body); try zir.findDeclsBody(list, else_body); }, + .@"try", .try_ptr => { + const inst_data = datas[inst].pl_node; + const extra = zir.extraData(Inst.Try, inst_data.payload_index); + const body = zir.extra[extra.end..][0..extra.data.body_len]; + try zir.findDeclsBody(list, body); + }, .switch_block => return findDeclsSwitch(zir, list, inst), .suspend_block => @panic("TODO iterate suspend block"), diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index c85c280fddcd..2875bda7e979 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -665,6 +665,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .prefetch => try self.airPrefetch(inst), .mul_add => try self.airMulAdd(inst), + .@"try" => try self.airTry(inst), + .try_ptr => try self.airTryPtr(inst), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), @@ -2305,27 +2308,70 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } +/// Given an error union, returns the error +fn errUnionErr(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCValue { + const err_ty = error_union_ty.errorUnionSet(); + const payload_ty = error_union_ty.errorUnionPayload(); + if (err_ty.errorSetCardinality() == .zero) { + return MCValue{ .immediate = 0 }; + } + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + return error_union_mcv; + } + + const err_offset = @intCast(u32, errUnionErrorOffset(payload_ty, self.target.*)); + switch (error_union_mcv) { + .register => return self.fail("TODO errUnionErr for registers", .{}), + .stack_offset => |off| { + return MCValue{ .stack_offset = off - err_offset }; + }, + .memory => |addr| { + return MCValue{ .memory = addr + err_offset }; + }, + else => unreachable, // invalid MCValue for an error union + } +} + 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 result: { const error_union_ty = self.air.typeOf(ty_op.operand); - const payload_ty = error_union_ty.errorUnionPayload(); const mcv = try self.resolveInst(ty_op.operand); - if (!payload_ty.hasRuntimeBits()) break :result mcv; - - return self.fail("TODO implement unwrap error union error for non-empty payloads", .{}); + break :result try self.errUnionErr(mcv, error_union_ty); }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } +/// Given an error union, returns the payload +fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCValue { + const err_ty = error_union_ty.errorUnionSet(); + const payload_ty = error_union_ty.errorUnionPayload(); + if (err_ty.errorSetCardinality() == .zero) { + return error_union_mcv; + } + if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { + return MCValue.none; + } + + const payload_offset = @intCast(u32, errUnionPayloadOffset(payload_ty, self.target.*)); + switch (error_union_mcv) { + .register => return self.fail("TODO errUnionPayload for registers", .{}), + .stack_offset => |off| { + return MCValue{ .stack_offset = off - payload_offset }; + }, + .memory => |addr| { + return MCValue{ .memory = addr + payload_offset }; + }, + else => unreachable, // invalid MCValue for an error union + } +} + 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 result: { const error_union_ty = self.air.typeOf(ty_op.operand); - const payload_ty = error_union_ty.errorUnionPayload(); - if (!payload_ty.hasRuntimeBits()) break :result MCValue.none; - - return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{}); + const error_union = try self.resolveInst(ty_op.operand); + break :result try self.errUnionPayload(error_union, error_union_ty); }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -3386,45 +3432,38 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ operand, .none, .none }); } -fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[inst].pl_op; - const cond = try self.resolveInst(pl_op.operand); - const extra = self.air.extraData(Air.CondBr, pl_op.payload); - const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; - const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; - const liveness_condbr = self.liveness.getCondBr(inst); - - const reloc: Mir.Inst.Index = switch (cond) { +fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index { + switch (condition) { .compare_flags_signed, .compare_flags_unsigned, - => try self.addInst(.{ + => return try self.addInst(.{ .tag = .b_cond, .data = .{ .inst_cond = .{ .inst = undefined, // populated later through performReloc - .cond = switch (cond) { + .cond = switch (condition) { .compare_flags_signed => |cmp_op| blk: { // Here we map to the opposite condition because the jump is to the false branch. - const condition = Instruction.Condition.fromCompareOperatorSigned(cmp_op); - break :blk condition.negate(); + const condition_code = Instruction.Condition.fromCompareOperatorSigned(cmp_op); + break :blk condition_code.negate(); }, .compare_flags_unsigned => |cmp_op| blk: { // Here we map to the opposite condition because the jump is to the false branch. - const condition = Instruction.Condition.fromCompareOperatorUnsigned(cmp_op); - break :blk condition.negate(); + const condition_code = Instruction.Condition.fromCompareOperatorUnsigned(cmp_op); + break :blk condition_code.negate(); }, else => unreachable, }, }, }, }), - else => blk: { - const reg = switch (cond) { + else => { + const reg = switch (condition) { .register => |r| r, - else => try self.copyToTmpRegister(Type.bool, cond), + else => try self.copyToTmpRegister(Type.bool, condition), }; - break :blk try self.addInst(.{ + return try self.addInst(.{ .tag = .cbz, .data = .{ .r_inst = .{ @@ -3434,7 +3473,18 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { }, }); }, - }; + } +} + +fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const cond = try self.resolveInst(pl_op.operand); + const extra = self.air.extraData(Air.CondBr, pl_op.payload); + const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; + const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; + const liveness_condbr = self.liveness.getCondBr(inst); + + const reloc = try self.condBr(cond); // If the condition dies here in this condbr instruction, process // that death now instead of later as this has an effect on @@ -4466,6 +4516,33 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand }); } +fn airTry(self: *Self, inst: Air.Inst.Index) !void { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Try, pl_op.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const result: MCValue = result: { + const error_union_ty = self.air.typeOf(pl_op.operand); + const error_union = try self.resolveInst(pl_op.operand); + const is_err_result = try self.isErr(error_union_ty, error_union); + const reloc = try self.condBr(is_err_result); + + try self.genBody(body); + + try self.performReloc(reloc); + break :result try self.errUnionPayload(error_union, error_union_ty); + }; + return self.finishAir(inst, result, .{ pl_op.operand, .none, .none }); +} + +fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + _ = body; + return self.fail("TODO implement airTryPtr for arm", .{}); + // return self.finishAir(inst, result, .{ extra.data.ptr, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index a0f9c12c345a..01f3f1ff6ac6 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -677,6 +677,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .prefetch => try self.airPrefetch(inst), .mul_add => try self.airMulAdd(inst), + .@"try" => try self.airTry(inst), + .try_ptr => try self.airTryPtr(inst), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), @@ -1834,8 +1837,8 @@ 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 result: { const error_union_ty = self.air.typeOf(ty_op.operand); - const mcv = try self.resolveInst(ty_op.operand); - break :result try self.errUnionPayload(mcv, error_union_ty); + const error_union = try self.resolveInst(ty_op.operand); + break :result try self.errUnionPayload(error_union, error_union_ty); }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -3702,6 +3705,42 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ operand, .none, .none }); } +/// Given a boolean condition, emit a jump that is taken when that +/// condition is false. +fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index { + const condition_code: Condition = switch (condition) { + .cpsr_flags => |cond| cond.negate(), + else => blk: { + const reg = switch (condition) { + .register => |r| r, + else => try self.copyToTmpRegister(Type.bool, condition), + }; + + try self.spillCompareFlagsIfOccupied(); + + // cmp reg, 1 + // bne ... + _ = try self.addInst(.{ + .tag = .cmp, + .cond = .al, + .data = .{ .rr_op = .{ + .rd = .r0, + .rn = reg, + .op = Instruction.Operand.imm(1, 0), + } }, + }); + + break :blk .ne; + }, + }; + + return try self.addInst(.{ + .tag = .b, + .cond = condition_code, + .data = .{ .inst = undefined }, // populated later through performReloc + }); +} + fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const cond_inst = try self.resolveInst(pl_op.operand); @@ -3710,39 +3749,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const liveness_condbr = self.liveness.getCondBr(inst); - const reloc: Mir.Inst.Index = reloc: { - const condition: Condition = switch (cond_inst) { - .cpsr_flags => |cond| cond.negate(), - else => blk: { - const reg = switch (cond_inst) { - .register => |r| r, - else => try self.copyToTmpRegister(Type.bool, cond_inst), - }; - - try self.spillCompareFlagsIfOccupied(); - - // cmp reg, 1 - // bne ... - _ = try self.addInst(.{ - .tag = .cmp, - .cond = .al, - .data = .{ .rr_op = .{ - .rd = .r0, - .rn = reg, - .op = Instruction.Operand.imm(1, 0), - } }, - }); - - break :blk .ne; - }, - }; - - break :reloc try self.addInst(.{ - .tag = .b, - .cond = condition, - .data = .{ .inst = undefined }, // populated later through performReloc - }); - }; + const reloc: Mir.Inst.Index = try self.condBr(cond_inst); // If the condition dies here in this condbr instruction, process // that death now instead of later as this has an effect on @@ -4154,13 +4161,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { .lhs = condition, .rhs = item, } }; - const cmp_result = try self.cmp(operands, condition_ty, .neq); - - relocs[0] = try self.addInst(.{ - .tag = .b, - .cond = cmp_result.cpsr_flags, - .data = .{ .inst = undefined }, // populated later through performReloc - }); + const cmp_result = try self.cmp(operands, condition_ty, .eq); + relocs[0] = try self.condBr(cmp_result); } else { return self.fail("TODO switch with multiple items", .{}); } @@ -5145,6 +5147,33 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand }); } +fn airTry(self: *Self, inst: Air.Inst.Index) !void { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Try, pl_op.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const result: MCValue = result: { + const error_union_ty = self.air.typeOf(pl_op.operand); + const error_union = try self.resolveInst(pl_op.operand); + const is_err_result = try self.isErr(error_union_ty, error_union); + const reloc = try self.condBr(is_err_result); + + try self.genBody(body); + + try self.performReloc(reloc); + break :result try self.errUnionPayload(error_union, error_union_ty); + }; + return self.finishAir(inst, result, .{ pl_op.operand, .none, .none }); +} + +fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + _ = body; + return self.fail("TODO implement airTryPtr for arm", .{}); + // return self.finishAir(inst, result, .{ extra.data.ptr, .none, .none }); +} + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c94b4de378df..5d6d50fd09cd 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -604,6 +604,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .prefetch => try self.airPrefetch(inst), .mul_add => try self.airMulAdd(inst), + .@"try" => @panic("TODO"), + .try_ptr => @panic("TODO"), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index eb9d9a4ad9c2..780f01cdd447 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -604,6 +604,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .prefetch => @panic("TODO try self.airPrefetch(inst)"), .mul_add => @panic("TODO try self.airMulAdd(inst)"), + .@"try" => @panic("TODO try self.airTry(inst)"), + .try_ptr => @panic("TODO try self.airTryPtr(inst)"), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 944ef162940b..039d62b05ca6 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1490,6 +1490,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .int_to_float => self.airIntToFloat(inst), .get_union_tag => self.airGetUnionTag(inst), + .@"try" => self.airTry(inst), + .try_ptr => self.airTryPtr(inst), + // TODO .dbg_inline_begin, .dbg_inline_end, @@ -4623,3 +4626,68 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !WValue { } }); return WValue{ .none = {} }; } + +fn airTry(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const err_union = try self.resolveInst(pl_op.operand); + const extra = self.air.extraData(Air.Try, pl_op.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(pl_op.operand); + return lowerTry(self, err_union, body, err_union_ty, false); +} + +fn airTryPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); + const err_union_ptr = try self.resolveInst(extra.data.ptr); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(extra.data.ptr).childType(); + return lowerTry(self, err_union_ptr, body, err_union_ty, true); +} + +fn lowerTry( + self: *Self, + err_union: WValue, + body: []const Air.Inst.Index, + err_union_ty: Type, + operand_is_ptr: bool, +) InnerError!WValue { + if (operand_is_ptr) { + return self.fail("TODO: lowerTry for pointers", .{}); + } + + if (err_union_ty.errorUnionSet().errorSetCardinality() == .zero) { + return err_union; + } + + const pl_ty = err_union_ty.errorUnionPayload(); + const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(); + + // Block we can jump out of when error is not set + try self.startBlock(.block, wasm.block_empty); + + // check if the error tag is set for the error union. + try self.emitWValue(err_union); + if (pl_has_bits) { + const err_offset = @intCast(u32, errUnionErrorOffset(pl_ty, self.target)); + try self.addMemArg(.i32_load16_u, .{ + .offset = err_union.offset() + err_offset, + .alignment = Type.anyerror.abiAlignment(self.target), + }); + } + try self.addTag(.i32_eqz); + try self.addLabel(.br_if, 0); // jump out of block when error is '0' + try self.genBody(body); + try self.endBlock(); + + // if we reach here it means error was not set, and we want the payload + if (!pl_has_bits) { + return WValue{ .none = {} }; + } + + const pl_offset = @intCast(u32, errUnionPayloadOffset(pl_ty, self.target)); + if (isByRef(pl_ty, self.target)) { + return buildPointerOffset(self, err_union, pl_offset, .new); + } + return self.load(err_union, pl_ty, pl_offset); +} diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 33734eda308a..e3ca7c318419 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -681,6 +681,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .prefetch => try self.airPrefetch(inst), .mul_add => try self.airMulAdd(inst), + .@"try" => try self.airTry(inst), + .try_ptr => try self.airTryPtr(inst), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), @@ -1804,14 +1807,24 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); } const err_union_ty = self.air.typeOf(ty_op.operand); + const operand = try self.resolveInst(ty_op.operand); + const result = try self.genUnwrapErrorUnionPayloadMir(inst, err_union_ty, operand); + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); +} + +fn genUnwrapErrorUnionPayloadMir( + self: *Self, + maybe_inst: ?Air.Inst.Index, + err_union_ty: Type, + err_union: MCValue, +) !MCValue { const payload_ty = err_union_ty.errorUnionPayload(); const err_ty = err_union_ty.errorUnionSet(); - const operand = try self.resolveInst(ty_op.operand); const result: MCValue = result: { if (err_ty.errorSetCardinality() == .zero) { // TODO check if we can reuse - break :result operand; + break :result err_union; } if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { @@ -1819,7 +1832,7 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { } const payload_off = errUnionPayloadOffset(payload_ty, self.target.*); - switch (operand) { + switch (err_union) { .stack_offset => |off| { const offset = off - @intCast(i32, payload_off); break :result MCValue{ .stack_offset = offset }; @@ -1828,19 +1841,23 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void { // TODO reuse operand const lock = self.register_manager.lockRegAssumeUnused(reg); defer self.register_manager.unlockReg(lock); - const result = try self.copyToRegisterWithInstTracking(inst, err_union_ty, operand); + const result_reg: Register = if (maybe_inst) |inst| + (try self.copyToRegisterWithInstTracking(inst, err_union_ty, err_union)).register + else + try self.copyToTmpRegister(err_union_ty, err_union); if (payload_off > 0) { const shift = @intCast(u6, payload_off * 8); - try self.genShiftBinOpMir(.shr, err_union_ty, result.register, .{ .immediate = shift }); + try self.genShiftBinOpMir(.shr, err_union_ty, result_reg, .{ .immediate = shift }); } else { - try self.truncateRegister(payload_ty, result.register); + try self.truncateRegister(payload_ty, result_reg); } - break :result result; + break :result MCValue{ .register = result_reg }; }, - else => return self.fail("TODO implement unwrap_err_payload for {}", .{operand}), + else => return self.fail("TODO implement genUnwrapErrorUnionPayloadMir for {}", .{err_union}), } }; - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + + return result; } // *(E!T) -> E @@ -4228,6 +4245,45 @@ fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ un_op, .none, .none }); } +fn airTry(self: *Self, inst: Air.Inst.Index) !void { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Try, pl_op.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(pl_op.operand); + const err_union = try self.resolveInst(pl_op.operand); + const result = try self.genTry(inst, err_union, body, err_union_ty, false); + return self.finishAir(inst, result, .{ pl_op.operand, .none, .none }); +} + +fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(extra.data.ptr).childType(); + const err_union_ptr = try self.resolveInst(extra.data.ptr); + const result = try self.genTry(inst, err_union_ptr, body, err_union_ty, true); + return self.finishAir(inst, result, .{ extra.data.ptr, .none, .none }); +} + +fn genTry( + self: *Self, + inst: Air.Inst.Index, + err_union: MCValue, + body: []const Air.Inst.Index, + err_union_ty: Type, + operand_is_ptr: bool, +) !MCValue { + if (operand_is_ptr) { + return self.fail("TODO genTry for pointers", .{}); + } + const is_err_mcv = try self.isErr(null, err_union_ty, err_union); + const reloc = try self.genCondBrMir(Type.anyerror, is_err_mcv); + try self.genBody(body); + try self.performReloc(reloc); + const result = try self.genUnwrapErrorUnionPayloadMir(inst, err_union_ty, err_union); + return result; +} + fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; const payload = try self.addExtra(Mir.DbgLineColumn{ @@ -4593,7 +4649,7 @@ fn isNonNull(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCV return MCValue{ .eflags = is_null_res.eflags.negate() }; } -fn isErr(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { +fn isErr(self: *Self, maybe_inst: ?Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { const err_type = ty.errorUnionSet(); if (err_type.errorSetCardinality() == .zero) { @@ -4601,7 +4657,9 @@ fn isErr(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue } try self.spillEflagsIfOccupied(); - self.eflags_inst = inst; + if (maybe_inst) |inst| { + self.eflags_inst = inst; + } const err_off = errUnionErrorOffset(ty.errorUnionPayload(), self.target.*); switch (operand) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1e4509064817..4c2239b3062d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1875,6 +1875,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .union_init => try airUnionInit(f, inst), .prefetch => try airPrefetch(f, inst), + .@"try" => try airTry(f, inst), + .try_ptr => try airTryPtr(f, inst), + .dbg_var_ptr, .dbg_var_val, => try airDbgVar(f, inst), @@ -2861,6 +2864,91 @@ fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue { return result; } +fn airTry(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const err_union = try f.resolveInst(pl_op.operand); + const extra = f.air.extraData(Air.Try, pl_op.payload); + const body = f.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = f.air.typeOf(pl_op.operand); + const result_ty = f.air.typeOfIndex(inst); + return lowerTry(f, err_union, body, err_union_ty, false, result_ty); +} + +fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.TryPtr, ty_pl.payload); + const err_union_ptr = try f.resolveInst(extra.data.ptr); + const body = f.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = f.air.typeOf(extra.data.ptr).childType(); + const result_ty = f.air.typeOfIndex(inst); + return lowerTry(f, err_union_ptr, body, err_union_ty, true, result_ty); +} + +fn lowerTry( + f: *Function, + err_union: CValue, + body: []const Air.Inst.Index, + err_union_ty: Type, + operand_is_ptr: bool, + result_ty: Type, +) !CValue { + if (err_union_ty.errorUnionSet().errorSetCardinality() == .zero) { + // If the error set has no fields, then the payload and the error + // union are the same value. + return err_union; + } + + const payload_ty = err_union_ty.errorUnionPayload(); + const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(); + + const writer = f.object.writer(); + + err: { + if (!payload_has_bits) { + if (operand_is_ptr) { + try writer.writeAll("if(*"); + } else { + try writer.writeAll("if("); + } + try f.writeCValue(writer, err_union); + try writer.writeAll(")"); + break :err; + } + if (operand_is_ptr or isByRef(err_union_ty)) { + try writer.writeAll("if("); + try f.writeCValue(writer, err_union); + try writer.writeAll("->error)"); + break :err; + } + try writer.writeAll("if("); + try f.writeCValue(writer, err_union); + try writer.writeAll(".error)"); + } + + try genBody(f, body); + try f.object.indent_writer.insertNewline(); + + if (!payload_has_bits) { + if (!operand_is_ptr) { + return CValue.none; + } else { + return err_union; + } + } + + const local = try f.allocLocal(result_ty, .Const); + if (operand_is_ptr or isByRef(payload_ty)) { + try writer.writeAll(" = &"); + try f.writeCValue(writer, err_union); + try writer.writeAll("->payload;\n"); + } else { + try writer.writeAll(" = "); + try f.writeCValue(writer, err_union); + try writer.writeAll(".payload;\n"); + } + return local; +} + fn airBr(f: *Function, inst: Air.Inst.Index) !CValue { const branch = f.air.instructions.items(.data)[inst].br; const block = f.blocks.get(branch.block_inst).?; @@ -4221,3 +4309,8 @@ fn loweredFnRetTyHasBits(fn_ty: Type) bool { } return false; } + +fn isByRef(ty: Type) bool { + _ = ty; + return false; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index aab2dfe51ad2..9e9dd2cb0c2d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4040,6 +4040,8 @@ pub const FuncGen = struct { .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), .cond_br => try self.airCondBr(inst), + .@"try" => try self.airTry(inst), + .try_ptr => try self.airTryPtr(inst), .intcast => try self.airIntCast(inst), .trunc => try self.airTrunc(inst), .fptrunc => try self.airFptrunc(inst), @@ -4731,6 +4733,75 @@ pub const FuncGen = struct { return null; } + fn airTry(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const err_union = try self.resolveInst(pl_op.operand); + const extra = self.air.extraData(Air.Try, pl_op.payload); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(pl_op.operand); + const result_ty = self.air.typeOfIndex(inst); + return lowerTry(self, err_union, body, err_union_ty, false, result_ty); + } + + fn airTryPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); + const err_union_ptr = try self.resolveInst(extra.data.ptr); + const body = self.air.extra[extra.end..][0..extra.data.body_len]; + const err_union_ty = self.air.typeOf(extra.data.ptr).childType(); + const result_ty = self.air.typeOfIndex(inst); + return lowerTry(self, err_union_ptr, body, err_union_ty, true, result_ty); + } + + fn lowerTry(fg: *FuncGen, err_union: *const llvm.Value, body: []const Air.Inst.Index, err_union_ty: Type, operand_is_ptr: bool, result_ty: Type) !?*const llvm.Value { + if (err_union_ty.errorUnionSet().errorSetCardinality() == .zero) { + // If the error set has no fields, then the payload and the error + // union are the same value. + return err_union; + } + + const payload_ty = err_union_ty.errorUnionPayload(); + const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(); + const target = fg.dg.module.getTarget(); + const is_err = err: { + const err_set_ty = try fg.dg.lowerType(Type.anyerror); + const zero = err_set_ty.constNull(); + if (!payload_has_bits) { + const loaded = if (operand_is_ptr) fg.builder.buildLoad(err_union, "") else err_union; + break :err fg.builder.buildICmp(.NE, loaded, zero, ""); + } + const err_field_index = errUnionErrorOffset(payload_ty, target); + if (operand_is_ptr or isByRef(err_union_ty)) { + const err_field_ptr = fg.builder.buildStructGEP(err_union, err_field_index, ""); + const loaded = fg.builder.buildLoad(err_field_ptr, ""); + break :err fg.builder.buildICmp(.NE, loaded, zero, ""); + } + const loaded = fg.builder.buildExtractValue(err_union, err_field_index, ""); + break :err fg.builder.buildICmp(.NE, loaded, zero, ""); + }; + + const return_block = fg.context.appendBasicBlock(fg.llvm_func, "TryRet"); + const continue_block = fg.context.appendBasicBlock(fg.llvm_func, "TryCont"); + _ = fg.builder.buildCondBr(is_err, return_block, continue_block); + + fg.builder.positionBuilderAtEnd(return_block); + try fg.genBody(body); + + fg.builder.positionBuilderAtEnd(continue_block); + if (!payload_has_bits) { + if (!operand_is_ptr) return null; + + // TODO once we update to LLVM 14 this bitcast won't be necessary. + const res_ptr_ty = try fg.dg.lowerType(result_ty); + return fg.builder.buildBitCast(err_union, res_ptr_ty, ""); + } + const offset = errUnionPayloadOffset(payload_ty, target); + if (operand_is_ptr or isByRef(payload_ty)) { + return fg.builder.buildStructGEP(err_union, offset, ""); + } + return fg.builder.buildExtractValue(err_union, offset, ""); + } + fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const cond = try self.resolveInst(pl_op.operand); @@ -5673,15 +5744,14 @@ pub const FuncGen = struct { const operand = try self.resolveInst(ty_op.operand); const operand_ty = self.air.typeOf(ty_op.operand); const error_union_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty; - // If the error set has no fields, then the payload and the error - // union are the same value. if (error_union_ty.errorUnionSet().errorSetCardinality() == .zero) { + // If the error set has no fields, then the payload and the error + // union are the same value. return operand; } const result_ty = self.air.typeOfIndex(inst); const payload_ty = if (operand_is_ptr) result_ty.childType() else result_ty; const target = self.dg.module.getTarget(); - const offset = errUnionPayloadOffset(payload_ty, target); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { if (!operand_is_ptr) return null; @@ -5690,6 +5760,7 @@ pub const FuncGen = struct { const res_ptr_ty = try self.dg.lowerType(result_ty); return self.builder.buildBitCast(operand, res_ptr_ty, ""); } + const offset = errUnionPayloadOffset(payload_ty, target); if (operand_is_ptr or isByRef(payload_ty)) { return self.builder.buildStructGEP(operand, offset, ""); } diff --git a/src/print_air.zig b/src/print_air.zig index e62ca806b717..af1bcb8cfbfa 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -258,6 +258,8 @@ const Writer = struct { .union_init => try w.writeUnionInit(s, inst), .br => try w.writeBr(s, inst), .cond_br => try w.writeCondBr(s, inst), + .@"try" => try w.writeTry(s, inst), + .try_ptr => try w.writeTryPtr(s, inst), .switch_br => try w.writeSwitchBr(s, inst), .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst), .fence => try w.writeFence(s, inst), @@ -624,6 +626,36 @@ const Writer = struct { try w.writeOperand(s, inst, 0, br.operand); } + fn writeTry(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.Try, pl_op.payload); + const body = w.air.extra[extra.end..][0..extra.data.body_len]; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", {\n"); + const old_indent = w.indent; + w.indent += 2; + try w.writeBody(s, body); + w.indent = old_indent; + try s.writeByteNTimes(' ', w.indent); + try s.writeAll("}"); + } + + fn writeTryPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; + const extra = w.air.extraData(Air.TryPtr, ty_pl.payload); + const body = w.air.extra[extra.end..][0..extra.data.body_len]; + + try w.writeOperand(s, inst, 0, extra.data.ptr); + try s.print(", {}, {{\n", .{w.air.getRefType(ty_pl.ty).fmtDebug()}); + const old_indent = w.indent; + w.indent += 2; + try w.writeBody(s, body); + w.indent = old_indent; + try s.writeByteNTimes(' ', w.indent); + try s.writeAll("}"); + } + fn writeCondBr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const pl_op = w.air.instructions.items(.data)[inst].pl_op; const extra = w.air.extraData(Air.CondBr, pl_op.payload); diff --git a/src/print_zir.zig b/src/print_zir.zig index 8081a940933b..3257a3cb58bd 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -374,17 +374,21 @@ const Writer = struct { .validate_array_init_comptime, .c_import, .typeof_builtin, - => try self.writePlNodeBlock(stream, inst), + => try self.writeBlock(stream, inst), .condbr, .condbr_inline, - => try self.writePlNodeCondBr(stream, inst), + => try self.writeCondBr(stream, inst), + + .@"try", + .try_ptr, + => try self.writeTry(stream, inst), .error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent), .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon), .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func), - .switch_block => try self.writePlNodeSwitchBlock(stream, inst), + .switch_block => try self.writeSwitchBlock(stream, inst), .field_ptr, .field_val, @@ -1171,7 +1175,7 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + fn writeBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; try self.writePlNodeBlockWithoutSrc(stream, inst); try self.writeSrc(stream, inst_data.src()); @@ -1185,7 +1189,7 @@ const Writer = struct { try stream.writeAll(") "); } - fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + fn writeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.CondBr, inst_data.payload_index); const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len]; @@ -1199,6 +1203,17 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeTry(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; + try self.writeInstRef(stream, extra.data.operand); + try stream.writeAll(", "); + try self.writeBracedBody(stream, body); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeStructDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); @@ -1746,7 +1761,7 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + fn writeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);