From ef885a78d606693c73641159731274cc57f6ea98 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Jun 2022 18:48:32 -0700 Subject: [PATCH] stage2: implement the new "try" ZIR/AIR instruction Implements semantic analysis for the new try/try_inline ZIR instruction. Adds the new try/try_ptr AIR instructions and implements them for the LLVM backend. Fixes not calling rvalue() for tryExpr in AstGen. This is part of an effort to implement #11772. --- src/Air.zig | 33 ++++++++++++++++ src/AstGen.zig | 3 +- src/Liveness.zig | 19 +++++++++ src/Sema.zig | 52 ++++++++++++++++++++++-- src/arch/aarch64/CodeGen.zig | 3 ++ src/arch/arm/CodeGen.zig | 3 ++ src/arch/riscv64/CodeGen.zig | 3 ++ src/arch/sparc64/CodeGen.zig | 3 ++ src/arch/wasm/CodeGen.zig | 3 ++ src/arch/x86_64/CodeGen.zig | 3 ++ src/codegen/c.zig | 3 ++ src/codegen/llvm.zig | 77 ++++++++++++++++++++++++++++++++++-- src/print_air.zig | 32 +++++++++++++++ 13 files changed, 229 insertions(+), 8 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 5571fc6359..efaa7f9b6b 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, @@ -1028,6 +1055,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .popcount, .byte_swap, .bit_reverse, + .try_ptr, => return air.getRefType(datas[inst].ty_op.ty), .loop, @@ -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 86ac6633ba..7874ed8218 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -4896,7 +4896,8 @@ fn tryExpr( _ = try else_scope.addUnNode(.ret_node, err_code, node); try else_scope.setTryBody(try_inst, operand); - return indexToRef(try_inst); + const result = indexToRef(try_inst); + return rvalue(parent_gz, rl, result, node); } fn orelseCatchExpr( diff --git a/src/Liveness.zig b/src/Liveness.zig index b4576c0f18..ecb755ae0a 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 8331aed409..de4b6c4236 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12983,7 +12983,8 @@ fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError! 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 is_ptr = sema.typeOf(operand).zigTypeTag() == .Pointer; + const operand_ty = sema.typeOf(operand); + const is_ptr = operand_ty.zigTypeTag() == .Pointer; const err_union = if (is_ptr) try sema.analyzeLoad(parent_block, src, operand, operand_src) else @@ -13008,9 +13009,52 @@ fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError! // no breaks from the body possible, and that the body is noreturn. return sema.resolveBody(parent_block, body, inst); } - _ = body; - _ = is_non_err; - @panic("TODO"); + + 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); + + if (is_ptr) { + 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.Try).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; + } + + 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 = operand, + .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; } // A `break` statement is inside a runtime condition, but trying to diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index c85c280fdd..4574469920 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" => @panic("TODO"), + .try_ptr => @panic("TODO"), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index a0f9c12c34..16e4b6e07b 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" => @panic("TODO"), + .try_ptr => @panic("TODO"), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c94b4de378..5d6d50fd09 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 eb9d9a4ad9..780f01cdd4 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 944ef16294..d889babb58 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" => @panic("TODO"), + .try_ptr => @panic("TODO"), + // TODO .dbg_inline_begin, .dbg_inline_end, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 33734eda30..7d2babed3e 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" => @panic("TODO"), + .try_ptr => @panic("TODO"), + .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1e45090648..597b3b0f6b 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" => @panic("TODO"), + .try_ptr => @panic("TODO"), + .dbg_var_ptr, .dbg_var_val, => try airDbgVar(f, inst), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index aab2dfe51a..9e9dd2cb0c 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 e62ca806b7..af1bcb8cfb 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);