Skip to content

Commit 9c6792b

Browse files
committed
ClosureSpecializer: avoid an infinite optimization loop.
Avoid an infinite specialization loop caused by repeated runs of the ClosureSpecializer and CapturePropagation. CapturePropagation propagates constant function-literals. Such function specializations can then be optimized again by the ClosureSpecializer and so on. This happens if a closure argument is called _and_ referenced in another closure, which is passed to a recursive call. E.g. func foo(_ c: @escaping () -> ()) { c() foo({ c() }) } rdar://80752327
1 parent 41f8abf commit 9c6792b

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

lib/SILOptimizer/IPO/ClosureSpecializer.cpp

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
#define DEBUG_TYPE "closure-specialization"
5959
#include "swift/Basic/Range.h"
60+
#include "swift/Demangling/Demangler.h"
6061
#include "swift/SIL/InstructionUtils.h"
6162
#include "swift/SIL/SILCloner.h"
6263
#include "swift/SIL/SILFunction.h"
@@ -162,6 +163,14 @@ class ClosureSpecCloner : public SILClonerWithScopes<ClosureSpecCloner> {
162163
namespace {
163164
struct ClosureInfo;
164165

166+
static SILFunction *getClosureCallee(SILInstruction *inst) {
167+
if (auto *PAI = dyn_cast<PartialApplyInst>(inst))
168+
return cast<FunctionRefInst>(PAI->getCallee())->getReferencedFunction();
169+
170+
auto *TTTFI = cast<ThinToThickFunctionInst>(inst);
171+
return cast<FunctionRefInst>(TTTFI->getCallee())->getReferencedFunction();
172+
}
173+
165174
class CallSiteDescriptor {
166175
ClosureInfo *CInfo;
167176
FullApplySite AI;
@@ -188,11 +197,7 @@ class CallSiteDescriptor {
188197
}
189198

190199
SILFunction *getClosureCallee() const {
191-
if (auto *PAI = dyn_cast<PartialApplyInst>(getClosure()))
192-
return cast<FunctionRefInst>(PAI->getCallee())->getReferencedFunction();
193-
194-
auto *TTTFI = cast<ThinToThickFunctionInst>(getClosure());
195-
return cast<FunctionRefInst>(TTTFI->getCallee())->getReferencedFunction();
200+
return ::getClosureCallee(getClosure());
196201
}
197202

198203
bool closureHasRefSemanticContext() const {
@@ -1065,6 +1070,59 @@ static bool canSpecializeFullApplySite(FullApplySiteKind kind) {
10651070
llvm_unreachable("covered switch");
10661071
}
10671072

1073+
static int getSpecializationLevelRecursive(StringRef funcName, Demangler &parent) {
1074+
using namespace Demangle;
1075+
1076+
Demangler demangler;
1077+
demangler.providePreallocatedMemory(parent);
1078+
1079+
// Check for this kind of node tree:
1080+
//
1081+
// kind=Global
1082+
// kind=FunctionSignatureSpecialization
1083+
// kind=SpecializationPassID, index=1
1084+
// kind=FunctionSignatureSpecializationParam
1085+
// kind=FunctionSignatureSpecializationParamKind, index=5
1086+
// kind=FunctionSignatureSpecializationParamPayload, text="..."
1087+
//
1088+
Node *root = demangler.demangleSymbol(funcName);
1089+
if (!root)
1090+
return 0;
1091+
if (root->getKind() != Node::Kind::Global)
1092+
return 0;
1093+
Node *funcSpec = root->getFirstChild();
1094+
if (!funcSpec || funcSpec->getNumChildren() < 2)
1095+
return 0;
1096+
if (funcSpec->getKind() != Node::Kind::FunctionSignatureSpecialization)
1097+
return 0;
1098+
Node *param = funcSpec->getChild(1);
1099+
if (param->getKind() != Node::Kind::FunctionSignatureSpecializationParam)
1100+
return 0;
1101+
if (param->getNumChildren() < 2)
1102+
return 0;
1103+
Node *kindNd = param->getChild(0);
1104+
if (kindNd->getKind() != Node::Kind::FunctionSignatureSpecializationParamKind)
1105+
return 0;
1106+
auto kind = FunctionSigSpecializationParamKind(kindNd->getIndex());
1107+
if (kind != FunctionSigSpecializationParamKind::ConstantPropFunction)
1108+
return 0;
1109+
1110+
Node *payload = param->getChild(1);
1111+
if (payload->getKind() != Node::Kind::FunctionSignatureSpecializationParamPayload)
1112+
return 1;
1113+
// Check if the specialized function is a specialization itself.
1114+
return 1 + getSpecializationLevelRecursive(payload->getText(), demangler);
1115+
}
1116+
1117+
/// If \p function is a function-signature specialization for a constant-
1118+
/// propagated function argument, returns 1.
1119+
/// If \p function is a specialization of such a specialization, returns 2.
1120+
/// And so on.
1121+
static int getSpecializationLevel(SILFunction *f) {
1122+
Demangle::StackAllocatedDemangler<1024> demangler;
1123+
return getSpecializationLevelRecursive(f->getName(), demangler);
1124+
}
1125+
10681126
bool SILClosureSpecializerTransform::gatherCallSites(
10691127
SILFunction *Caller,
10701128
llvm::SmallVectorImpl<std::unique_ptr<ClosureInfo>> &ClosureCandidates,
@@ -1252,6 +1310,24 @@ bool SILClosureSpecializerTransform::gatherCallSites(
12521310
continue;
12531311
}
12541312

1313+
// Avoid an infinite specialization loop caused by repeated runs of
1314+
// ClosureSpecializer and CapturePropagation.
1315+
// CapturePropagation propagates constant function-literals. Such
1316+
// function specializations can then be optimized again by the
1317+
// ClosureSpecializer and so on.
1318+
// This happens if a closure argument is called _and_ referenced in
1319+
// another closure, which is passed to a recursive call. E.g.
1320+
//
1321+
// func foo(_ c: @escaping () -> ()) {
1322+
// c()
1323+
// foo({ c() })
1324+
// }
1325+
//
1326+
// A limit of 2 is good enough and will not be exceed in "regular"
1327+
// optimization scenarios.
1328+
if (getSpecializationLevel(getClosureCallee(ClosureInst)) > 2)
1329+
continue;
1330+
12551331
// Compute the final release points of the closure. We will insert
12561332
// release of the captured arguments here.
12571333
if (!CInfo)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %{python} %S/../Inputs/timeout.py 10 %target-swift-frontend -O -parse-as-library %s -emit-sil | %FileCheck %s
2+
3+
public func callit() {
4+
testit { false }
5+
}
6+
7+
// Check if the compiler terminates and does not full into an infinite optimization
8+
// loop between the ClosureSpecializer and CapturePropagation.
9+
10+
// CHECK-LABEL: sil @$s23closure_specialize_loop6testit1cySbyc_tF
11+
public func testit(c: @escaping () -> Bool) {
12+
if c() {
13+
testit { !c() }
14+
}
15+
}
16+

0 commit comments

Comments
 (0)