Skip to content

Commit 22893a0

Browse files
authored
Merge pull request swiftlang#63890 from eeckstein/limit-escape-utils
EscapeUtils: add a computational limit to avoid quadratic complexity in some corner cases.
2 parents d103bf7 + 2d88482 commit 22893a0

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)