Commit ef885a78d6

Andrew Kelley <andrew@ziglang.org>
2022-06-03 03:48:32
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.
1 parent 0224ad1
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),
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),
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),
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),
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,
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),
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),
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, "");
         }
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();
+        },
     }
 }
 
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(
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.
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);
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