@@ -74,6 +74,11 @@ let earlyRedundantLoadElimination = FunctionPass(name: "early-redundant-load-eli
74
74
}
75
75
76
76
private func eliminateRedundantLoads( in function: Function , ignoreArrays: Bool , _ context: FunctionPassContext ) {
77
+
78
+ // Avoid quadratic complexity by limiting the number of visited instructions.
79
+ // This limit is sufficient for most "real-world" functions, by far.
80
+ var complexityBudget = 50_000
81
+
77
82
for block in function. blocks. reversed ( ) {
78
83
79
84
// We cannot use for-in iteration here because if the load is split, the new
@@ -89,21 +94,21 @@ private func eliminateRedundantLoads(in function: Function, ignoreArrays: Bool,
89
94
if ignoreArrays && load. type. isNominal && load. type. nominal == context. swiftArrayDecl {
90
95
continue
91
96
}
92
- tryEliminate ( load: load, context)
97
+ tryEliminate ( load: load, complexityBudget : & complexityBudget , context)
93
98
}
94
99
}
95
100
}
96
101
}
97
102
98
- private func tryEliminate( load: LoadInst , _ context: FunctionPassContext ) {
99
- switch load. isRedundant ( context) {
103
+ private func tryEliminate( load: LoadInst , complexityBudget : inout Int , _ context: FunctionPassContext ) {
104
+ switch load. isRedundant ( complexityBudget : & complexityBudget , context) {
100
105
case . notRedundant:
101
106
break
102
107
case . redundant( let availableValues) :
103
108
replace ( load: load, with: availableValues, context)
104
109
case . maybePartiallyRedundant( let subPath) :
105
110
// Check if the a partial load would really be redundant to avoid unnecessary splitting.
106
- switch load. isRedundant ( at: subPath, context) {
111
+ switch load. isRedundant ( at: subPath, complexityBudget : & complexityBudget , context) {
107
112
case . notRedundant, . maybePartiallyRedundant:
108
113
break
109
114
case . redundant:
@@ -130,25 +135,29 @@ private extension LoadInst {
130
135
}
131
136
}
132
137
133
- func isRedundant( _ context: FunctionPassContext ) -> DataflowResult {
134
- return isRedundant ( at: address. accessPath, context)
138
+ func isRedundant( complexityBudget : inout Int , _ context: FunctionPassContext ) -> DataflowResult {
139
+ return isRedundant ( at: address. accessPath, complexityBudget : & complexityBudget , context)
135
140
}
136
141
137
- func isRedundant( at accessPath: AccessPath , _ context: FunctionPassContext ) -> DataflowResult {
142
+ func isRedundant( at accessPath: AccessPath , complexityBudget : inout Int , _ context: FunctionPassContext ) -> DataflowResult {
138
143
var scanner = InstructionScanner ( load: self , accessPath: accessPath, context. aliasAnalysis)
139
144
140
- switch scanner. scan ( instructions: ReverseInstructionList ( first: self . previous) , in: parentBlock) {
145
+ switch scanner. scan ( instructions: ReverseInstructionList ( first: self . previous) ,
146
+ in: parentBlock,
147
+ complexityBudget: & complexityBudget)
148
+ {
141
149
case . overwritten:
142
150
return DataflowResult ( notRedundantWith: scanner. potentiallyRedundantSubpath)
143
151
case . available:
144
152
return . redundant( scanner. availableValues)
145
153
case . transparent:
146
- return self . isRedundantInPredecessorBlocks ( scanner: & scanner, context)
154
+ return self . isRedundantInPredecessorBlocks ( scanner: & scanner, complexityBudget : & complexityBudget , context)
147
155
}
148
156
}
149
157
150
158
private func isRedundantInPredecessorBlocks(
151
159
scanner: inout InstructionScanner ,
160
+ complexityBudget: inout Int ,
152
161
_ context: FunctionPassContext
153
162
) -> DataflowResult {
154
163
@@ -157,7 +166,10 @@ private extension LoadInst {
157
166
liferange. pushPredecessors ( of: self . parentBlock)
158
167
159
168
while let block = liferange. pop ( ) {
160
- switch scanner. scan ( instructions: block. instructions. reversed ( ) , in: block) {
169
+ switch scanner. scan ( instructions: block. instructions. reversed ( ) ,
170
+ in: block,
171
+ complexityBudget: & complexityBudget)
172
+ {
161
173
case . overwritten:
162
174
return DataflowResult ( notRedundantWith: scanner. potentiallyRedundantSubpath)
163
175
case . available:
@@ -402,10 +414,6 @@ private struct InstructionScanner {
402
414
private( set) var potentiallyRedundantSubpath : AccessPath ? = nil
403
415
private( set) var availableValues = Array < AvailableValue > ( )
404
416
405
- // Avoid quadratic complexity by limiting the number of visited instructions for each store.
406
- // The limit of 1000 instructions is not reached by far in "real-world" functions.
407
- private var budget = 1000
408
-
409
417
init ( load: LoadInst , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
410
418
self . load = load
411
419
self . accessPath = accessPath
@@ -419,8 +427,16 @@ private struct InstructionScanner {
419
427
case transparent
420
428
}
421
429
422
- mutating func scan( instructions: ReverseInstructionList , in block: BasicBlock ) -> ScanResult {
430
+ mutating func scan( instructions: ReverseInstructionList ,
431
+ in block: BasicBlock ,
432
+ complexityBudget: inout Int ) -> ScanResult
433
+ {
423
434
for inst in instructions {
435
+ complexityBudget -= 1
436
+ if complexityBudget <= 0 {
437
+ return . overwritten
438
+ }
439
+
424
440
switch visit ( instruction: inst) {
425
441
case . available: return . available
426
442
case . overwritten: return . overwritten
@@ -490,10 +506,6 @@ private struct InstructionScanner {
490
506
default :
491
507
break
492
508
}
493
- budget -= 1
494
- if budget == 0 {
495
- return . overwritten
496
- }
497
509
if load. loadOwnership == . take {
498
510
// In case of `take`, don't allow reading instructions in the liferange.
499
511
// Otherwise we cannot shrink the memory liferange afterwards.
0 commit comments