Skip to content

Commit 2d88482

Browse files
committed
EscapeUtils: add a computational limit to avoid quadratic complexity in some corner cases.
The `isEscaping` function is called a lot from ARCSequenceOpt and ReleaseHoisting. To avoid quadratic complexity for large functions, limit the amount of work what the EscapeUtils are allowed to to. This keeps the complexity linear. The arbitrary limit is good enough for almost all functions. It lets the EscapeUtils do several hundred up/down walks which is much more than needed in most cases. Fixes a compiler hang swiftlang#63846 rdar://105795976
1 parent 6a5c0d0 commit 2d88482

File tree

7 files changed

+229
-12
lines changed

7 files changed

+229
-12
lines changed

SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ struct AliasAnalysis {
9898
},
9999

100100
// isAddrVisibleFromObj
101-
{ (bridgedCtxt: BridgedPassContext, bridgedAddr: BridgedValue, bridgedObj: BridgedValue) -> Bool in
101+
{ (bridgedCtxt: BridgedPassContext, bridgedAddr: BridgedValue, bridgedObj: BridgedValue, complexityBudget: Int) -> Bool in
102102
let context = FunctionPassContext(_bridged: bridgedCtxt)
103103
let addr = bridgedAddr.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedAddr.value))
104104

105105
// This is similar to `canReferenceSameFieldFn`, except that all addresses of all objects are
106106
// considered which are transitively visible from `bridgedObj`.
107107
let anythingReachableFromObj = bridgedObj.value.at(SmallProjectionPath(.anything))
108-
return addr.canAddressAlias(with: anythingReachableFromObj, context)
108+
return addr.canAddressAlias(with: anythingReachableFromObj, complexityBudget: complexityBudget, context)
109109
},
110110

111111
// canReferenceSameFieldFn

SwiftCompilerSources/Sources/Optimizer/Utilities/EscapeUtils.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ extension ProjectedValue {
7575
/// the walk. See `EscapeVisitor` for details.
7676
///
7777
func isEscaping(using visitor: some EscapeVisitor = DefaultVisitor(),
78+
complexityBudget: Int = Int.max,
7879
_ context: some Context) -> Bool {
79-
var walker = EscapeWalker(visitor: visitor, context)
80+
var walker = EscapeWalker(visitor: visitor, complexityBudget: complexityBudget, context)
8081
return walker.walkUp(addressOrValue: value, path: path.escapePath) == .abortWalk
8182
}
8283

@@ -287,9 +288,10 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
287288
AddressUseDefWalker {
288289
typealias Path = EscapeUtilityTypes.EscapePath
289290

290-
init(visitor: V, _ context: some Context) {
291+
init(visitor: V, complexityBudget: Int = Int.max, _ context: some Context) {
291292
self.calleeAnalysis = context.calleeAnalysis
292293
self.visitor = visitor
294+
self.complexityBudget = complexityBudget
293295
}
294296

295297
//===--------------------------------------------------------------------===//
@@ -313,6 +315,9 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
313315
}
314316

315317
mutating func walkDown(value: Operand, path: Path) -> WalkResult {
318+
if complexityBudgetExceeded(value.value) {
319+
return .abortWalk
320+
}
316321
if hasRelevantType(value.value, at: path.projectionPath) {
317322
switch visitor.visitUse(operand: value, path: path) {
318323
case .continueWalk:
@@ -409,6 +414,9 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
409414
}
410415

411416
mutating func walkDown(address: Operand, path: Path) -> WalkResult {
417+
if complexityBudgetExceeded(address.value) {
418+
return .abortWalk
419+
}
412420
if hasRelevantType(address.value, at: path.projectionPath) {
413421
switch visitor.visitUse(operand: address, path: path) {
414422
case .continueWalk:
@@ -508,7 +516,7 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
508516
return .continueWalk
509517
}
510518
if !visitor.followLoads && p.matches(pattern: SmallProjectionPath(.anyValueFields).push(.anyClassField)) {
511-
// Any address of a class property of the object to destroy cannot esacpe the destructor.
519+
// Any address of a class property of the object to destroy cannot escape the destructor.
512520
// (Whereas a value stored in such a property could escape.)
513521
return .continueWalk
514522
}
@@ -644,6 +652,9 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
644652
}
645653

646654
mutating func walkUp(value: Value, path: Path) -> WalkResult {
655+
if complexityBudgetExceeded(value) {
656+
return .abortWalk
657+
}
647658
if hasRelevantType(value, at: path.projectionPath) {
648659
switch visitor.visitDef(def: value, path: path) {
649660
case .continueWalkUp:
@@ -697,6 +708,9 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
697708
}
698709

699710
mutating func walkUp(address: Value, path: Path) -> WalkResult {
711+
if complexityBudgetExceeded(address) {
712+
return .abortWalk
713+
}
700714
if hasRelevantType(address, at: path.projectionPath) {
701715
switch visitor.visitDef(def: address, path: path) {
702716
case .continueWalkUp:
@@ -785,6 +799,10 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
785799
var walkDownCache = WalkerCache<Path>()
786800
var walkUpCache = WalkerCache<Path>()
787801

802+
// Only this number of up/and down walks are done until the walk aborts.
803+
// Used to avoid quadratic complexity in some scenarios.
804+
var complexityBudget: Int
805+
788806
private let calleeAnalysis: CalleeAnalysis
789807

790808
//===--------------------------------------------------------------------===//
@@ -825,6 +843,14 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
825843
return path.popLastClassAndValuesFromTail()
826844
}
827845

846+
private mutating func complexityBudgetExceeded(_ v: Value) -> Bool {
847+
if complexityBudget <= 0 {
848+
return true
849+
}
850+
complexityBudget = complexityBudget &- 1
851+
return false
852+
}
853+
828854
// Set a breakpoint here to debug when a value is escaping.
829855
private var isEscaping: WalkResult { .abortWalk }
830856
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,15 @@ extension ProjectedValue {
221221
/// `%s`.canAddressAlias(with: `%2`) -> true
222222
/// `%1`.canAddressAlias(with: `%2`) -> false
223223
///
224-
func canAddressAlias(with rhs: ProjectedValue, _ context: some Context) -> Bool {
224+
func canAddressAlias(with rhs: ProjectedValue, complexityBudget: Int = Int.max, _ context: some Context) -> Bool {
225225
// self -> rhs will succeed (= return false) if self is a non-escaping "local" object,
226226
// but not necessarily rhs.
227-
if !isEscaping(using: EscapesToValueVisitor(target: rhs), context) {
227+
if !isEscaping(using: EscapesToValueVisitor(target: rhs), complexityBudget: complexityBudget, context) {
228228
return false
229229
}
230230
// The other way round: rhs -> self will succeed if rhs is a non-escaping "local" object,
231231
// but not necessarily self.
232-
if !rhs.isEscaping(using: EscapesToValueVisitor(target: self), context) {
232+
if !rhs.isEscaping(using: EscapesToValueVisitor(target: self), complexityBudget: complexityBudget, context) {
233233
return false
234234
}
235235
return true

include/swift/SILOptimizer/Analysis/AliasAnalysis.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ class AliasAnalysis {
105105
/// scopes.
106106
llvm::SmallPtrSet<SILInstruction *, 16> immutableScopeComputed;
107107

108+
/// Used to limit complexity.
109+
/// The side is computed lazily. Therefore the actual value depends on what
110+
/// SIL modifications an optimization pass already performed when the size
111+
/// is requested.
112+
int estimatedFunctionSize = -1;
113+
108114
AliasResult aliasAddressProjection(SILValue V1, SILValue V2,
109115
SILValue O1, SILValue O2);
110116

@@ -209,6 +215,8 @@ class AliasAnalysis {
209215

210216
/// Returns true if `lhs` can reference the same field as `rhs`.
211217
bool canReferenceSameField(SILValue lhs, SILValue rhs);
218+
219+
int getEstimatedFunctionSize(SILValue valueInFunction);
212220
};
213221

214222

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,12 @@ typedef bool (* _Nonnull AliasAnalysisEscaping2InstFn)(
133133
BridgedPassContext context, BridgedValue, BridgedInstruction);
134134
typedef bool (* _Nonnull AliasAnalysisEscaping2ValFn)(
135135
BridgedPassContext context, BridgedValue, BridgedValue);
136+
typedef bool (* _Nonnull AliasAnalysisEscaping2ValIntFn)(
137+
BridgedPassContext context, BridgedValue, BridgedValue, SwiftInt);
136138

137139
void AliasAnalysis_register(AliasAnalysisGetMemEffectFn getMemEffectsFn,
138140
AliasAnalysisEscaping2InstFn isObjReleasedFn,
139-
AliasAnalysisEscaping2ValFn isAddrVisibleFromObjFn,
141+
AliasAnalysisEscaping2ValIntFn isAddrVisibleFromObjFn,
140142
AliasAnalysisEscaping2ValFn mayPointToSameAddrFn);
141143

142144
BridgedSlab PassContext_getNextSlab(BridgedSlab slab);

lib/SILOptimizer/Analysis/AliasAnalysis.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ bool AliasAnalysis::typesMayAlias(SILType T1, SILType T2,
524524
// Bridging functions.
525525
static AliasAnalysisGetMemEffectFn getMemEffectsFunction = nullptr;
526526
static AliasAnalysisEscaping2InstFn isObjReleasedFunction = nullptr;
527-
static AliasAnalysisEscaping2ValFn isAddrVisibleFromObjFunction = nullptr;
527+
static AliasAnalysisEscaping2ValIntFn isAddrVisibleFromObjFunction = nullptr;
528528
static AliasAnalysisEscaping2ValFn canReferenceSameFieldFunction = nullptr;
529529

530530
/// The main AA entry point. Performs various analyses on V1, V2 in an attempt
@@ -716,7 +716,7 @@ BridgedMemoryBehavior AliasAnalysis_getMemBehavior(BridgedAliasAnalysis aa,
716716

717717
void AliasAnalysis_register(AliasAnalysisGetMemEffectFn getMemEffectsFn,
718718
AliasAnalysisEscaping2InstFn isObjReleasedFn,
719-
AliasAnalysisEscaping2ValFn isAddrVisibleFromObjFn,
719+
AliasAnalysisEscaping2ValIntFn isAddrVisibleFromObjFn,
720720
AliasAnalysisEscaping2ValFn canReferenceSameFieldFn) {
721721
getMemEffectsFunction = getMemEffectsFn;
722722
isObjReleasedFunction = isObjReleasedFn;
@@ -742,7 +742,16 @@ bool AliasAnalysis::isObjectReleasedByInst(SILValue obj, SILInstruction *inst) {
742742

743743
bool AliasAnalysis::isAddrVisibleFromObject(SILValue addr, SILValue obj) {
744744
if (isAddrVisibleFromObjFunction) {
745-
return isAddrVisibleFromObjFunction({PM->getSwiftPassInvocation()}, {addr}, {obj}) != 0;
745+
// This function is called a lot from ARCSequenceOpt and ReleaseHoisting.
746+
// To avoid quadratic complexity for large functions, we limit the amount
747+
// of work what the EscapeUtils are allowed to to.
748+
// This keeps the complexity linear.
749+
//
750+
// This arbitrary limit is good enough for almost all functions. It lets
751+
// the EscapeUtils do several hundred up/down walks which is much more than
752+
// needed in most cases.
753+
SwiftInt complexityLimit = 1000000 / getEstimatedFunctionSize(addr);
754+
return isAddrVisibleFromObjFunction({PM->getSwiftPassInvocation()}, {addr}, {obj}, complexityLimit) != 0;
746755
}
747756
return true;
748757
}
@@ -753,3 +762,15 @@ bool AliasAnalysis::canReferenceSameField(SILValue lhs, SILValue rhs) {
753762
}
754763
return true;
755764
}
765+
766+
int AliasAnalysis::getEstimatedFunctionSize(SILValue valueInFunction) {
767+
if (estimatedFunctionSize < 0) {
768+
int numInsts = 0;
769+
SILFunction *f = valueInFunction->getFunction();
770+
for (SILBasicBlock &block : *f) {
771+
numInsts += std::distance(block.begin(), block.end());
772+
}
773+
estimatedFunctionSize = numInsts;
774+
}
775+
return estimatedFunctionSize;
776+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// The compiler should finish in less than 5 seconds. To give some slack, specify a timeout of 30 seconds.
2+
// If the compiler needs more than that, there is probably a real problem.
3+
// So please don't just increase the timeout in case this fails.
4+
5+
// RUN: %{python} %S/../../test/Inputs/timeout.py 30 %target-swift-frontend -O -parse-as-library -sil-verify-none %s -emit-sil | %FileCheck %s
6+
7+
// REQUIRES: tools-release,no_asan
8+
9+
public var gg = false
10+
11+
enum SomeError : Error {
12+
case E
13+
}
14+
15+
public class X {
16+
@inline(never)
17+
init() throws {
18+
if gg {
19+
throw SomeError.E
20+
}
21+
}
22+
}
23+
24+
// CHECK-LABEL: testit
25+
public func testit(_ i: Int) throws -> (Int, X) {
26+
let arr: [(Int, X)] = [
27+
(0, try X()),
28+
(1, try X()),
29+
(2, try X()),
30+
(3, try X()),
31+
(4, try X()),
32+
(5, try X()),
33+
(6, try X()),
34+
(7, try X()),
35+
(8, try X()),
36+
(9, try X()),
37+
(10, try X()),
38+
(11, try X()),
39+
(12, try X()),
40+
(13, try X()),
41+
(14, try X()),
42+
(15, try X()),
43+
(16, try X()),
44+
(17, try X()),
45+
(18, try X()),
46+
(19, try X()),
47+
(20, try X()),
48+
(21, try X()),
49+
(22, try X()),
50+
(23, try X()),
51+
(24, try X()),
52+
(25, try X()),
53+
(26, try X()),
54+
(27, try X()),
55+
(28, try X()),
56+
(29, try X()),
57+
(30, try X()),
58+
(31, try X()),
59+
(32, try X()),
60+
(33, try X()),
61+
(34, try X()),
62+
(35, try X()),
63+
(36, try X()),
64+
(37, try X()),
65+
(38, try X()),
66+
(39, try X()),
67+
(40, try X()),
68+
(41, try X()),
69+
(42, try X()),
70+
(43, try X()),
71+
(44, try X()),
72+
(45, try X()),
73+
(46, try X()),
74+
(47, try X()),
75+
(48, try X()),
76+
(49, try X()),
77+
(50, try X()),
78+
(51, try X()),
79+
(52, try X()),
80+
(53, try X()),
81+
(54, try X()),
82+
(55, try X()),
83+
(56, try X()),
84+
(57, try X()),
85+
(58, try X()),
86+
(59, try X()),
87+
(60, try X()),
88+
(61, try X()),
89+
(62, try X()),
90+
(63, try X()),
91+
(64, try X()),
92+
(65, try X()),
93+
(66, try X()),
94+
(67, try X()),
95+
(68, try X()),
96+
(69, try X()),
97+
(70, try X()),
98+
(71, try X()),
99+
(72, try X()),
100+
(73, try X()),
101+
(74, try X()),
102+
(75, try X()),
103+
(76, try X()),
104+
(77, try X()),
105+
(78, try X()),
106+
(79, try X()),
107+
(80, try X()),
108+
(81, try X()),
109+
(82, try X()),
110+
(83, try X()),
111+
(84, try X()),
112+
(85, try X()),
113+
(86, try X()),
114+
(87, try X()),
115+
(88, try X()),
116+
(89, try X()),
117+
(90, try X()),
118+
(91, try X()),
119+
(92, try X()),
120+
(93, try X()),
121+
(94, try X()),
122+
(95, try X()),
123+
(96, try X()),
124+
(97, try X()),
125+
(98, try X()),
126+
(99, try X()),
127+
(100, try X()),
128+
(101, try X()),
129+
(102, try X()),
130+
(103, try X()),
131+
(104, try X()),
132+
(105, try X()),
133+
(106, try X()),
134+
(107, try X()),
135+
(108, try X()),
136+
(109, try X()),
137+
(110, try X()),
138+
(111, try X()),
139+
(112, try X()),
140+
(113, try X()),
141+
(114, try X()),
142+
(115, try X()),
143+
(116, try X()),
144+
(117, try X()),
145+
(118, try X()),
146+
(119, try X()),
147+
(120, try X()),
148+
(121, try X()),
149+
(122, try X()),
150+
(123, try X()),
151+
(124, try X()),
152+
(125, try X()),
153+
(126, try X()),
154+
(127, try X()),
155+
(128, try X()),
156+
]
157+
158+
return arr[i]
159+
}
160+

0 commit comments

Comments
 (0)