@@ -74,6 +74,11 @@ let earlyRedundantLoadElimination = FunctionPass(name: "early-redundant-load-eli
7474}
7575
7676private 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+
7782 for block in function. blocks. reversed ( ) {
7883
7984 // 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,
8994 if ignoreArrays && load. type. isNominal && load. type. nominal == context. swiftArrayDecl {
9095 continue
9196 }
92- tryEliminate ( load: load, context)
97+ tryEliminate ( load: load, complexityBudget : & complexityBudget , context)
9398 }
9499 }
95100 }
96101}
97102
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) {
100105 case . notRedundant:
101106 break
102107 case . redundant( let availableValues) :
103108 replace ( load: load, with: availableValues, context)
104109 case . maybePartiallyRedundant( let subPath) :
105110 // 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) {
107112 case . notRedundant, . maybePartiallyRedundant:
108113 break
109114 case . redundant:
@@ -130,25 +135,29 @@ private extension LoadInst {
130135 }
131136 }
132137
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)
135140 }
136141
137- func isRedundant( at accessPath: AccessPath , _ context: FunctionPassContext ) -> DataflowResult {
142+ func isRedundant( at accessPath: AccessPath , complexityBudget : inout Int , _ context: FunctionPassContext ) -> DataflowResult {
138143 var scanner = InstructionScanner ( load: self , accessPath: accessPath, context. aliasAnalysis)
139144
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+ {
141149 case . overwritten:
142150 return DataflowResult ( notRedundantWith: scanner. potentiallyRedundantSubpath)
143151 case . available:
144152 return . redundant( scanner. availableValues)
145153 case . transparent:
146- return self . isRedundantInPredecessorBlocks ( scanner: & scanner, context)
154+ return self . isRedundantInPredecessorBlocks ( scanner: & scanner, complexityBudget : & complexityBudget , context)
147155 }
148156 }
149157
150158 private func isRedundantInPredecessorBlocks(
151159 scanner: inout InstructionScanner ,
160+ complexityBudget: inout Int ,
152161 _ context: FunctionPassContext
153162 ) -> DataflowResult {
154163
@@ -157,7 +166,10 @@ private extension LoadInst {
157166 liferange. pushPredecessors ( of: self . parentBlock)
158167
159168 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+ {
161173 case . overwritten:
162174 return DataflowResult ( notRedundantWith: scanner. potentiallyRedundantSubpath)
163175 case . available:
@@ -402,10 +414,6 @@ private struct InstructionScanner {
402414 private( set) var potentiallyRedundantSubpath : AccessPath ? = nil
403415 private( set) var availableValues = Array < AvailableValue > ( )
404416
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-
409417 init ( load: LoadInst , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
410418 self . load = load
411419 self . accessPath = accessPath
@@ -419,8 +427,16 @@ private struct InstructionScanner {
419427 case transparent
420428 }
421429
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+ {
423434 for inst in instructions {
435+ complexityBudget -= 1
436+ if complexityBudget <= 0 {
437+ return . overwritten
438+ }
439+
424440 switch visit ( instruction: inst) {
425441 case . available: return . available
426442 case . overwritten: return . overwritten
@@ -490,10 +506,6 @@ private struct InstructionScanner {
490506 default :
491507 break
492508 }
493- budget -= 1
494- if budget == 0 {
495- return . overwritten
496- }
497509 if load. loadOwnership == . take {
498510 // In case of `take`, don't allow reading instructions in the liferange.
499511 // Otherwise we cannot shrink the memory liferange afterwards.
0 commit comments