diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp index 6c04dcf2fbe09..7f2fbef0d54e7 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp @@ -149,6 +149,13 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, if (SubstCalleeTy->hasArchetype() || ConvertCalleeTy->hasArchetype()) return nullptr; + // If we converted from a non-throwing to a throwing function which is + // try_apply'd, rewriting would require changing the CFG. Bail for now. + if (!ConvertCalleeTy->hasErrorResult() && isa(AI)) { + assert(SubstCalleeTy->hasErrorResult()); + return nullptr; + } + // Ok, we can now perform our transformation. Grab AI's operands and the // relevant types from the ConvertFunction function type and AI. Builder.setCurrentDebugScope(AI.getDebugScope()); @@ -166,7 +173,9 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, auto newOpParamTypes = convertConventions.getParameterSILTypes(context); llvm::SmallVector Args; - auto convertOp = [&](SILValue Op, SILType OldOpType, SILType NewOpType) { + llvm::SmallVector Borrows; + auto convertOp = [&](SILValue Op, SILType OldOpType, SILType NewOpType, + OperandOwnership ownership) { // Convert function takes refs to refs, address to addresses, and leaves // other types alone. if (OldOpType.isAddress()) { @@ -174,6 +183,12 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, auto UAC = Builder.createUncheckedAddrCast(AI.getLoc(), Op, NewOpType); Args.push_back(UAC); } else if (OldOpType.getASTType() != NewOpType.getASTType()) { + if (Op->getOwnershipKind() == OwnershipKind::Owned && + !ownership.getOwnershipConstraint().isConsuming()) { + auto borrow = Builder.createBeginBorrow(AI.getLoc(), Op); + Op = borrow; + Borrows.push_back(borrow); + } auto URC = Builder.createUncheckedForwardingCast(AI.getLoc(), Op, NewOpType); Args.push_back(URC); @@ -187,14 +202,21 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, auto newRetI = newOpRetTypes.begin(); auto oldRetI = oldOpRetTypes.begin(); + auto getCurrentOperand = [&OpI, &AI]() -> Operand & { + return AI.getInstruction() + ->getAllOperands()[OpI + ApplyInst::getArgumentOperandNumber()]; + }; + for (auto e = newOpRetTypes.end(); newRetI != e; ++OpI, ++newRetI, ++oldRetI) { - convertOp(Ops[OpI], *oldRetI, *newRetI); + convertOp(Ops[OpI], *oldRetI, *newRetI, + getCurrentOperand().getOperandOwnership()); } if (oldIndirectErrorResultType) { assert(newIndirectErrorResultType); - convertOp(Ops[OpI], oldIndirectErrorResultType, newIndirectErrorResultType); + convertOp(Ops[OpI], oldIndirectErrorResultType, newIndirectErrorResultType, + getCurrentOperand().getOperandOwnership()); ++OpI; } @@ -202,7 +224,8 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, auto oldParamI = oldOpParamTypes.begin(); for (auto e = newOpParamTypes.end(); newParamI != e; ++OpI, ++newParamI, ++oldParamI) { - convertOp(Ops[OpI], *oldParamI, *newParamI); + convertOp(Ops[OpI], *oldParamI, *newParamI, + getCurrentOperand().getOperandOwnership()); } // Convert the direct results if they changed. @@ -240,10 +263,22 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, Builder.createBranch(AI.getLoc(), TAI->getNormalBB(), branchArgs); } - - return Builder.createTryApply(AI.getLoc(), funcOper, SubstitutionMap(), Args, - normalBB, TAI->getErrorBB(), - TAI->getApplyOptions()); + + Builder.setInsertionPoint(AI.getInstruction()); + auto *result = Builder.createTryApply( + AI.getLoc(), funcOper, SubstitutionMap(), Args, normalBB, + TAI->getErrorBB(), TAI->getApplyOptions()); + if (!Borrows.empty()) { + Builder.setInsertionPoint(&TAI->getErrorBB()->front()); + for (auto *borrow : Borrows) { + Builder.createEndBorrow(AI.getLoc(), borrow); + } + Builder.setInsertionPoint(&TAI->getNormalBB()->front()); + for (auto *borrow : Borrows) { + Builder.createEndBorrow(AI.getLoc(), borrow); + } + } + return result; } // Match the throwing bit of the underlying function_ref. We assume that if @@ -262,6 +297,11 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, Builder.createUncheckedForwardingCast(AI.getLoc(), NAI, oldResultTy); } + Builder.setInsertionPoint(AI->getNextInstruction()); + for (auto *borrow : Borrows) { + Builder.createEndBorrow(AI.getLoc(), borrow); + } + return result; } @@ -1623,6 +1663,10 @@ SILInstruction *SILCombiner::visitTryApplyInst(TryApplyInst *AI) { if (isa(AI->getCallee())) return nullptr; + if (auto *CFI = dyn_cast(AI->getCallee())) { + return optimizeApplyOfConvertFunctionInst(AI, CFI); + } + // Optimize readonly functions with no meaningful users. SILFunction *Fn = AI->getReferencedFunctionOrNull(); if (Fn && Fn->getEffectsKind() < EffectsKind::ReleaseNone) { diff --git a/test/SILOptimizer/sil_combine.sil b/test/SILOptimizer/sil_combine.sil index 5807b021215df..808ffeaa8fc34 100644 --- a/test/SILOptimizer/sil_combine.sil +++ b/test/SILOptimizer/sil_combine.sil @@ -1420,7 +1420,9 @@ bb0(%0 : $*@callee_owned (@in ()) -> @out AnotherClass, %1 : $*AnotherClass): sil @convertible_result_with_error : $@convention(thin) () -> (@owned AnotherClass, @error Error) -// TODO +// CHECK-LABEL: sil @peephole_convert_function_result_change_with_error : {{.*}} { +// CHECK-NOT: convert_function +// CHECK-LABEL: } // end sil function 'peephole_convert_function_result_change_with_error' sil @peephole_convert_function_result_change_with_error : $@convention(thin) () -> () { entry: %f = function_ref @convertible_result_with_error : $@convention(thin) () -> (@owned AnotherClass, @error Error) diff --git a/test/SILOptimizer/sil_combine_apply_unit.sil b/test/SILOptimizer/sil_combine_apply_unit.sil index 078e44155cd13..9f0b4dcc0f45f 100644 --- a/test/SILOptimizer/sil_combine_apply_unit.sil +++ b/test/SILOptimizer/sil_combine_apply_unit.sil @@ -1,11 +1,28 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -test-runner | %FileCheck %s +// RUN: %target-sil-opt \ +// RUN: -test-runner %s \ +// RUN: -module-name Swift \ +// RUN: -enable-sil-verify-all \ +// RUN: | %FileCheck %s import Builtin +enum Optional { + case some(T) + case none +} + +protocol Error {} + +class C {} + struct Input {} struct Output {} enum Nunca {} +sil @borrowMaybeC : $@convention(thin) (@guaranteed Optional) -> () +sil @borrowMaybeC2 : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> () +sil @borrowMaybeCThrowing : $@convention(thin) (@guaranteed Optional) -> (@error Error) +sil @borrowMaybeC2Throwing : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> (@error Error) sil @rdar127452206_callee : $@convention(thin) @Sendable @substituted <τ_0_0, τ_0_1, τ_0_2> (@in_guaranteed τ_0_0) -> (@out τ_0_2, @error_indirect τ_0_1) for // CHECK-LABEL: sil @rdar127452206 : {{.*}} { @@ -31,4 +48,131 @@ entry(%input : $*Input): return %retval : $() } +// CHECK-LABEL: sil [ossa] @convert_function__to_optional__owned_as_guaranteed__1 : {{.*}} { +// CHECK: bb0([[C:%[^,]+]] : +// CHECK: [[BORROW_MAYBE_C:%[^,]+]] = function_ref @borrowMaybeC +// CHECK: [[B:%[^,]+]] = begin_borrow [[C]] +// CHECK: [[MAYBE_B:%[^,]+]] = unchecked_ref_cast [[B]] : $C to $Optional +// CHECK: apply [[BORROW_MAYBE_C]]([[MAYBE_B]]) +// CHECK: end_borrow [[B]] +// CHECK: destroy_value [[C]] +// CHECK-LABEL: } // end sil function 'convert_function__to_optional__owned_as_guaranteed__1' +sil [ossa] @convert_function__to_optional__owned_as_guaranteed__1 : $@convention(thin) (@owned C) -> () { +entry(%c : @owned $C): + %borrowMaybeC = function_ref @borrowMaybeC : $@convention(thin) (@guaranteed Optional) -> () + %borrowC = convert_function %borrowMaybeC : $@convention(thin) (@guaranteed Optional) -> () to $@convention(thin) (@guaranteed C) -> () + %void = apply %borrowC(%c) : $@convention(thin) (@guaranteed C) -> () + specify_test "sil_combine_instruction %void" + destroy_value %c : $C + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: sil [ossa] @convert_function__to_optional__owned_as_guaranteed__2 : {{.*}} { +// CHECK: bb0( +// CHECK-SAME: [[C:%[^,]+]] : +// CHECK-SAME: [[C2:%[^,]+]] : +// CHECK-SAME: ): +// CHECK: [[BORROW_MAYBE_C2:%[^,]+]] = function_ref @borrowMaybeC2 +// CHECK: [[B:%[^,]+]] = begin_borrow [[C]] +// CHECK: [[MAYBE_B:%[^,]+]] = unchecked_ref_cast [[B]] : $C to $Optional +// CHECK: [[B2:%[^,]+]] = begin_borrow [[C2]] +// CHECK: [[MAYBE_B2:%[^,]+]] = unchecked_ref_cast [[B2]] : $C to $Optional +// CHECK: apply [[BORROW_MAYBE_C2]]([[MAYBE_B]], [[MAYBE_B2]]) +// CHECK: end_borrow [[B]] +// CHECK: end_borrow [[B2]] +// CHECK: destroy_value [[C]] +// CHECK: destroy_value [[C2]] +// CHECK-LABEL: } // end sil function 'convert_function__to_optional__owned_as_guaranteed__2' +sil [ossa] @convert_function__to_optional__owned_as_guaranteed__2 : $@convention(thin) (@owned C, @owned C) -> () { +entry(%c : @owned $C, %c2 : @owned $C): + %borrowMaybeC2 = function_ref @borrowMaybeC2 : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> () + %borrowC2 = convert_function %borrowMaybeC2 : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> () to $@convention(thin) (@guaranteed C, @guaranteed C) -> () + %void = apply %borrowC2(%c, %c2) : $@convention(thin) (@guaranteed C, @guaranteed C) -> () + specify_test "sil_combine_instruction %void" + destroy_value %c : $C + destroy_value %c2 : $C + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: sil [ossa] @convert_function__to_optional__owned_as_guaranteed__3 : {{.*}} { +// CHECK: bb0([[C:%[^,]+]] : +// CHECK: [[BORROW_MAYBE_C:%[^,]+]] = function_ref @borrowMaybeCThrowing +// CHECK: [[B:%[^,]+]] = begin_borrow [[C]] +// CHECK: [[MAYBE_B:%[^,]+]] = unchecked_ref_cast [[B]] : $C to $Optional +// CHECK: try_apply [[BORROW_MAYBE_C]]([[MAYBE_B]]) +// CHECK: normal [[SUCCESS:bb[0-9]+]] +// CHECK: error [[FAILURE:bb[0-9]+]] +// CHECK: [[SUCCESS]] +// CHECK: end_borrow [[B]] +// CHECK: destroy_value [[C]] +// CHECK: [[FAILURE]]([[ERROR:%[^,]+]] : +// CHECK: end_borrow [[B]] +// CHECK: destroy_value [[C]] +// CHECK: throw [[ERROR]] +// CHECK-LABEL: } // end sil function 'convert_function__to_optional__owned_as_guaranteed__3' +sil [ossa] @convert_function__to_optional__owned_as_guaranteed__3 : $@convention(thin) (@owned C) -> (@error Error) { +entry(%c : @owned $C): + %borrowMaybeC = function_ref @borrowMaybeCThrowing : $@convention(thin) (@guaranteed Optional) -> (@error Error) + %borrowC = convert_function %borrowMaybeC : $@convention(thin) (@guaranteed Optional) -> (@error Error) to $@convention(thin) (@guaranteed C) -> (@error Error) + specify_test "sil_combine_instruction @instruction" + try_apply %borrowC(%c) : $@convention(thin) (@guaranteed C) -> (@error Error), + normal success, + error failure + +success(%void : $()): + destroy_value %c : $C + %retval = tuple () + return %retval : $() + +failure(%error : @owned $Error): + destroy_value %c : $C + throw %error : $Error +} +// CHECK-LABEL: sil [ossa] @convert_function__to_optional__owned_as_guaranteed__4 : {{.*}} { +// CHECK: bb0( +// CHECK-SAME: [[C:%[^,]+]] : +// CHECK-SAME: [[C2:%[^,]+]] : +// CHECK-SAME: ): +// CHECK: [[BORROW_MAYBE_C2:%[^,]+]] = function_ref @borrowMaybeC2Throwing +// CHECK: [[B:%[^,]+]] = begin_borrow [[C]] +// CHECK: [[MAYBE_B:%[^,]+]] = unchecked_ref_cast [[B]] : $C to $Optional +// CHECK: [[B2:%[^,]+]] = begin_borrow [[C2]] +// CHECK: [[MAYBE_B2:%[^,]+]] = unchecked_ref_cast [[B2]] : $C to $Optional +// CHECK: try_apply [[BORROW_MAYBE_C2]]([[MAYBE_B]], [[MAYBE_B2]]) +// CHECK: normal [[SUCCESS:bb[0-9]+]] +// CHECK: error [[FAILURE:bb[0-9]+]] +// CHECK: [[SUCCESS]] +// CHECK: end_borrow [[B]] +// CHECK: end_borrow [[B2]] +// CHECK: destroy_value [[C]] +// CHECK: destroy_value [[C2]] +// CHECK: [[FAILURE]]([[ERROR:%[^,]+]] : +// CHECK: end_borrow [[B]] +// CHECK: end_borrow [[B2]] +// CHECK: destroy_value [[C]] +// CHECK: destroy_value [[C2]] +// CHECK: throw [[ERROR]] +// CHECK-LABEL: } // end sil function 'convert_function__to_optional__owned_as_guaranteed__4' +sil [ossa] @convert_function__to_optional__owned_as_guaranteed__4 : $@convention(thin) (@owned C, @owned C) -> (@error Error) { +entry(%c : @owned $C, %c2 : @owned $C): + %borrowMaybeC2 = function_ref @borrowMaybeC2Throwing : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> (@error Error) + %borrowC2 = convert_function %borrowMaybeC2 : $@convention(thin) (@guaranteed Optional, @guaranteed Optional) -> (@error Error) to $@convention(thin) (@guaranteed C, @guaranteed C) -> (@error Error) + specify_test "sil_combine_instruction @instruction" + try_apply %borrowC2(%c, %c2) : $@convention(thin) (@guaranteed C, @guaranteed C) -> (@error Error), + normal success, + error failure + +success(%void : $()): + destroy_value %c : $C + destroy_value %c2 : $C + %retval = tuple () + return %retval : $() + +failure(%error : @owned $Error): + destroy_value %c : $C + destroy_value %c2 : $C + throw %error : $Error +} diff --git a/test/SILOptimizer/sil_combine_ossa.sil b/test/SILOptimizer/sil_combine_ossa.sil index 14fa757a86d86..72c7ed90ebddf 100644 --- a/test/SILOptimizer/sil_combine_ossa.sil +++ b/test/SILOptimizer/sil_combine_ossa.sil @@ -1745,7 +1745,9 @@ bb0(%0 : $*@callee_owned (@in ()) -> @out AnotherClass, %1 : $*AnotherClass): sil @convertible_result_with_error : $@convention(thin) () -> (@owned AnotherClass, @error Error) -// TODO +// CHECK-LABEL: sil [ossa] @peephole_convert_function_result_change_with_error : {{.*}} { +// CHECK-NOT: convert_function +// CHECK-LABEL: } // end sil function 'peephole_convert_function_result_change_with_error' sil [ossa] @peephole_convert_function_result_change_with_error : $@convention(thin) () -> () { entry: %f = function_ref @convertible_result_with_error : $@convention(thin) () -> (@owned AnotherClass, @error Error)