SILGen: Emit a closure literal in a function conversion as the converted type.

Closure literals are sometimes type-checked as one type then immediately converted to another
type in the AST. One particular case of this is when a closure body never throws, but the closure
is used as an argument to a function that takes a parameter that `throws`. Emitting this naively,
by emitting the closure as its original type, then converting to throws, can be expensive for
async closures, since that takes a reabstraction thunk. Even for non-async functions, we still want
to get the benefit of reabstraction optimization for the closure literal through the conversion too.
So if the function conversion just add `throws`, emit the closure as throwing, and pass down the
context abstraction pattern when emitting the closure as well.
This commit is contained in:
Joe Groff
2022-05-03 15:17:14 -07:00
parent f66d9bdf9b
commit 482cc8ae24
8 changed files with 80 additions and 12 deletions

View File

@@ -1714,6 +1714,26 @@ static ManagedValue convertFunctionRepresentation(SILGenFunction &SGF,
llvm_unreachable("bad representation");
}
// Ideally our prolog/epilog emission would be able to handle all possible
// reabstractions and conversions. Until then, this returns true if a closure
// literal of type `literalType` can be directly emitted by SILGen as
// `convertedType`.
static bool canPeepholeLiteralClosureConversion(Type literalType,
Type convertedType) {
auto literalFnType = literalType->getAs<FunctionType>();
auto convertedFnType = convertedType->getAs<FunctionType>();
if (!literalFnType || !convertedFnType)
return false;
// Does the conversion only add `throws`?
if (literalFnType->isEqual(convertedFnType->getWithoutThrowing())) {
return true;
}
return false;
}
RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
SGFContext C)
{
@@ -1748,6 +1768,38 @@ RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
return RValue(SGF, e, result);
}
// If the function being converted is a closure literal, then the only use
// of the closure should be as the destination type of the conversion. Rather
// than emit the closure as is and convert it, see if we can emit the closure
// directly as the desired type.
//
// TODO: Move this up when we can emit closures directly with C calling
// convention.
auto subExpr = e->getSubExpr()->getSemanticsProvidingExpr();
if (isa<AbstractClosureExpr>(subExpr)
&& canPeepholeLiteralClosureConversion(subExpr->getType(),
e->getType())) {
// If we're emitting into a context with a preferred abstraction pattern
// already, carry that along.
auto origType = C.getAbstractionPattern();
// If not, use the conversion type as the desired abstraction pattern.
if (!origType) {
origType = AbstractionPattern(e->getType()->getCanonicalType());
}
auto substType = subExpr->getType()->getCanonicalType();
auto conversion = Conversion::getSubstToOrig(*origType, substType,
SGF.getLoweredType(*origType, substType));
ConvertingInitialization convertingInit(conversion, SGFContext());
auto closure = SGF.emitRValue(subExpr,
SGFContext(&convertingInit))
.getAsSingleValue(SGF, e);
closure = SGF.emitSubstToOrigValue(e, closure, *origType, substType);
return RValue(SGF, e, closure);
}
// Handle a reference to a "thin" native Swift function that only changes
// representation and refers to an inherently thin function reference.
if (destRepTy->getRepresentation() == FunctionTypeRepresentation::Thin) {

View File

@@ -552,13 +552,12 @@ func testTryApply(_ x: Float) -> Float {
// CHECK: bb0:
// CHECK: [ACTIVE] %0 = argument of bb0 : $Float
// CHECK: [NONE] // function_ref closure #1 in testTryApply(_:)
// CHECK: [NONE] %3 = thin_to_thick_function %2 : $@convention(thin) () -> () to $@noescape @callee_guaranteed () -> ()
// CHECK: [NONE] %4 = convert_function %3 : $@noescape @callee_guaranteed () -> () to $@noescape @callee_guaranteed () -> @error Error
// CHECK: [NONE] %3 = thin_to_thick_function %2 : $@convention(thin) () -> @error Error to $@noescape @callee_guaranteed () -> @error Error
// CHECK: [NONE] // function_ref rethrowing(_:)
// CHECK: bb1:
// CHECK: [NONE] %7 = argument of bb1 : $()
// CHECK: [NONE] %6 = argument of bb1 : $()
// CHECK: bb2:
// CHECK: [NONE] %9 = argument of bb2 : $Error
// CHECK: [NONE] %8 = argument of bb2 : $Error
//===----------------------------------------------------------------------===//
// Coroutine differentiation (`begin_apply`)

View File

@@ -35,7 +35,7 @@ public func mangle(s: [UnicodeScalar]) -> [UnicodeScalar] {
// Do we care to expose these via lldb?
// CHECK: define {{.*}}@"$s11patternvars6mangle1sSayAA13UnicodeScalarVGAF_tFA2EXEfU_"
// CHECK: %[[VAL:[0-9]+]] = call swiftcc i32 @"$s11patternvars13UnicodeScalarV5values6UInt32Vvg"(i32 %0)
// CHECK: %[[VAL:[0-9]+]] = call swiftcc i32 @"$s11patternvars13UnicodeScalarV5values6UInt32Vvg"(
// CHECK: {{[0-9]+}}:
// CHECK-NOT: call void @llvm.dbg.value
// CHECK-NOT: call void asm sideeffect "", "r"

View File

@@ -203,7 +203,7 @@ public func testGetFunc() {
// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} hidden swiftcc void @"$s22big_types_corner_cases7TestBigC4testyyF"(%T22big_types_corner_cases7TestBigC* swiftself %0)
// CHECK: [[CALL1:%.*]] = call {{.*}} @__swift_instantiateConcreteTypeFromMangledName({{.*}} @"$sSayy22big_types_corner_cases9BigStructVcSgGMD"
// CHECK: [[CALL2:%.*]] = call i8** @"$sSayy22big_types_corner_cases9BigStructVcSgGSayxGSlsWl
// CHECK: call swiftcc void @"$sSlsE10firstIndex5where0B0QzSgSb7ElementQzKXE_tKF"(%swift.opaque* noalias nocapture sret({{.*}}) %{{[0-9]+}}, i8* bitcast ({{.*}}* @"$s22big_types_corner_cases9BigStruct{{.*}}_TRTA{{(\.ptrauth)?}}" to i8*), %swift.opaque* %{{[0-9]+}}, %swift.type* %{{[0-9]+}}, i8** [[CALL2]]
// CHECK: call swiftcc void @"$sSlsE10firstIndex5where0B0QzSgSb7ElementQzKXE_tKF"(%swift.opaque* noalias nocapture sret({{.*}}) %{{[0-9]+}}, i8* bitcast ({{.*}}* @"$s22big_types_corner_cases7TestBig{{.*}}" to i8*), %swift.opaque* null, %swift.type* %{{[0-9]+}}, i8** [[CALL2]]
class TestBig {
typealias Handler = (BigStruct) -> Void

View File

@@ -2,7 +2,8 @@
// CHECK-LABEL: closure #1 (Swift.Never) -> Swift.Never in coverage_closure_returns_never.closure_with_fatal_error(Swift.Array<Swift.Never>) -> ()
// CHECK: builtin "int_instrprof_increment"
// CHECK-NEXT: debug_value {{.*}} : $Never
// CHECK-NEXT: [[LOAD:%.*]] = load {{.*}} : $*Never
// CHECK-NEXT: debug_value [[LOAD]] : $Never
// CHECK-NEXT: unreachable
func closure_with_fatal_error(_ arr: [Never]) {

View File

@@ -0,0 +1,16 @@
// RUN: %target-swift-frontend -emit-silgen %s -disable-availability-checking | %FileCheck %s
// REQUIRES: concurrency
@_silgen_name("takeThrowingAsyncClosure")
func takeThrowingAsyncClosure<T>(_: () async throws -> T)
// CHECK-LABEL: sil {{.*}} @{{.*}}34passNonthrowingAsyncClosureLiteral
func passNonthrowingAsyncClosureLiteral() {
// Check that the literal closure was emitted directly with an error return,
// without a reabstraction thunk to convert from nonthrowing.
// CHECK: [[INVOKE_FN:%.*]] = function_ref
// CHECK: [[CLOSURE:%.*]] = thin_to_thick_function [[INVOKE_FN]]
// CHECK: [[CALLEE:%.*]] = function_ref @takeThrowingAsyncClosure
// CHECK: apply [[CALLEE]]<Int>([[CLOSURE]])
takeThrowingAsyncClosure { return 42 }
}

View File

@@ -70,11 +70,10 @@ func test2() {
}
// CHECK-LABEL: sil hidden @$s8rethrows5test3yyF : $@convention(thin) () -> () {
// CHECK: [[CLOSURE:%.*]] = function_ref @$s8rethrows5test3yyFSiyXEfU_ : $@convention(thin) () -> Int
// CHECK: [[CLOSURE:%.*]] = function_ref @$s8rethrows5test3yyFSiyXEfU_ :
// CHECK: [[T0:%.*]] = thin_to_thick_function [[CLOSURE]]
// CHECK: [[T1:%.*]] = convert_function [[T0]] : $@noescape @callee_guaranteed () -> Int to $@noescape @callee_guaranteed () -> (Int, @error Error)
// CHECK: [[RETHROWER:%.*]] = function_ref @$s8rethrows9rethroweryS2iyKXEKF : $@convention(thin) (@noescape @callee_guaranteed () -> (Int, @error Error)) -> (Int, @error Error)
// CHECK: try_apply [[RETHROWER]]([[T1]]) : $@convention(thin) (@noescape @callee_guaranteed () -> (Int, @error Error)) -> (Int, @error Error), normal [[NORMAL:bb1]], error [[ERROR:bb2]]
// CHECK: [[RETHROWER:%.*]] = function_ref @$s8rethrows9rethroweryS2iyKXEKF :
// CHECK: try_apply [[RETHROWER]]([[T0]])
// CHECK: [[NORMAL]]({{%.*}} : $Int):
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]]

View File

@@ -64,7 +64,8 @@ struct S4<Base> where Base : P1, Base.Element: P1 {
// CHECK-LABEL: {{^}}sil {{.*}} @${{.*}}2S4{{.*}}3foo{{.*}}F :
// CHECK: @callee_guaranteed @substituted <τ_0_0, τ_0_1 where τ_0_0 : P1, τ_0_0 == τ_0_1, τ_0_0.Element : P1> (@in_guaranteed S3<τ_0_0>) -> () for <Base, Base>
func foo(index: S3<Base>?) {
_ = index.map({ _ = $0 })
let f: (S3<Base>) -> () = { _ = $0 }
_ = index.map(f)
}
}