1212
1313import SIL
1414
15- /// Replaces redundant load instructions with already available values.
15+ /// Replaces redundant ` load` or `copy_addr` instructions with already available values.
1616///
1717/// A load is redundant if the loaded value is already available at that point.
1818/// This can be via a preceding store to the same address:
@@ -52,6 +52,9 @@ import SIL
5252/// %f2 = load %fa2
5353/// %2 = struct (%f1, %f2)
5454///
55+ /// This works in a similar fashion for `copy_addr`. If the source value of the `copy_addr` is
56+ /// already available, the `copy_addr` is replaced by a `store` of the available value.
57+ ///
5558/// The algorithm is a data flow analysis which starts at the original load and searches
5659/// for preceding stores or loads by following the control flow in backward direction.
5760/// The preceding stores and loads provide the "available values" with which the original
@@ -99,7 +102,7 @@ func eliminateRedundantLoads(in function: Function,
99102 while let i = inst {
100103 defer { inst = i. previous }
101104
102- if let load = inst as? LoadInst {
105+ if let load = inst as? LoadingInstruction {
103106 if !context. continueWithNextSubpassRun ( for: load) {
104107 return changed
105108 }
@@ -116,7 +119,59 @@ func eliminateRedundantLoads(in function: Function,
116119 return changed
117120}
118121
119- private func tryEliminate( load: LoadInst , complexityBudget: inout Int , _ context: FunctionPassContext ) -> Bool {
122+ /// Either a `load` or a `copy_addr` (which is equivalent to a load+store).
123+ private protocol LoadingInstruction : Instruction {
124+ var address : Value { get }
125+ var type : Type { get }
126+ var ownership : Ownership { get }
127+ var loadOwnership : LoadInst . LoadOwnership { get }
128+ var canLoadValue : Bool { get }
129+ func trySplit( _ context: FunctionPassContext ) -> Bool
130+ func materializeLoadForReplacement( _ context: FunctionPassContext ) -> LoadInst
131+ }
132+
133+ extension LoadInst : LoadingInstruction {
134+ // We know that the type is loadable because - well - this is a load.
135+ var canLoadValue : Bool { true }
136+
137+ // Nothing to materialize, because this is already a `load`.
138+ func materializeLoadForReplacement( _ context: FunctionPassContext ) -> LoadInst { return self }
139+ }
140+
141+ extension CopyAddrInst : LoadingInstruction {
142+ var address : Value { source }
143+ var type : Type { address. type. objectType }
144+ var typeIsLoadable : Bool { type. isLoadable ( in: parentFunction) }
145+
146+ var ownership : Ownership {
147+ if !parentFunction. hasOwnership || type. isTrivial ( in: parentFunction) {
148+ return . none
149+ }
150+ // Regardless of if the copy is taking or copying, the loaded value is an owned value.
151+ return . owned
152+ }
153+
154+ var canLoadValue : Bool {
155+ if !source. type. isLoadable ( in: parentFunction) {
156+ // Although the original load's type is loadable (obviously), it can be projected-out
157+ // from the copy_addr's type which might be not loadable.
158+ return false
159+ }
160+ if !parentFunction. hasOwnership {
161+ if !isTakeOfSrc || !isInitializationOfDest {
162+ // For simplicity, bail if we would have to insert compensating retains and releases.
163+ return false
164+ }
165+ }
166+ return true
167+ }
168+
169+ func materializeLoadForReplacement( _ context: FunctionPassContext ) -> LoadInst {
170+ return replaceWithLoadAndStore ( context) . load
171+ }
172+ }
173+
174+ private func tryEliminate( load: LoadingInstruction , complexityBudget: inout Int , _ context: FunctionPassContext ) -> Bool {
120175 switch load. isRedundant ( complexityBudget: & complexityBudget, context) {
121176 case . notRedundant:
122177 return false
@@ -136,9 +191,12 @@ private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context
136191 }
137192}
138193
139- private extension LoadInst {
194+ private extension LoadingInstruction {
140195
141196 func isEligibleForElimination( in variant: RedundantLoadEliminationVariant , _ context: FunctionPassContext ) -> Bool {
197+ if !canLoadValue {
198+ return false
199+ }
142200 switch variant {
143201 case . mandatory, . mandatoryInGlobalInit:
144202 if loadOwnership == . take {
@@ -171,20 +229,6 @@ private extension LoadInst {
171229 return true
172230 }
173231
174- enum DataflowResult {
175- case notRedundant
176- case redundant( [ AvailableValue ] )
177- case maybePartiallyRedundant( AccessPath )
178-
179- init ( notRedundantWith subPath: AccessPath ? ) {
180- if let subPath = subPath {
181- self = . maybePartiallyRedundant( subPath)
182- } else {
183- self = . notRedundant
184- }
185- }
186- }
187-
188232 func isRedundant( complexityBudget: inout Int , _ context: FunctionPassContext ) -> DataflowResult {
189233 return isRedundant ( at: address. constantAccessPath, complexityBudget: & complexityBudget, context)
190234 }
@@ -285,7 +329,7 @@ private extension LoadInst {
285329 }
286330}
287331
288- private func replace( load: LoadInst , with availableValues: [ AvailableValue ] , _ context: FunctionPassContext ) {
332+ private func replace( load: LoadingInstruction , with availableValues: [ AvailableValue ] , _ context: FunctionPassContext ) {
289333 var ssaUpdater = SSAUpdater ( function: load. parentFunction,
290334 type: load. type, ownership: load. ownership, context)
291335
@@ -318,14 +362,16 @@ private func replace(load: LoadInst, with availableValues: [AvailableValue], _ c
318362 newValue = ssaUpdater. getValue ( inMiddleOf: load. parentBlock)
319363 }
320364
365+ let originalLoad = load. materializeLoadForReplacement ( context)
366+
321367 // Make sure to keep dependencies valid after replacing the load
322- insertMarkDependencies ( for: load , context)
368+ insertMarkDependencies ( for: originalLoad , context)
323369
324- load . replace ( with: newValue, context)
370+ originalLoad . replace ( with: newValue, context)
325371}
326372
327373private func provideValue(
328- for load: LoadInst ,
374+ for load: LoadingInstruction ,
329375 from availableValue: AvailableValue ,
330376 _ context: FunctionPassContext
331377) -> Value {
@@ -341,9 +387,9 @@ private func provideValue(
341387 builder: availableValue. getBuilderForProjections ( context) )
342388 case . take:
343389 if projectionPath. isEmpty {
344- return shrinkMemoryLifetime ( from : load , to: availableValue, context)
390+ return shrinkMemoryLifetime ( to: availableValue, context)
345391 } else {
346- return shrinkMemoryLifetimeAndSplit ( from : load , to: availableValue, projectionPath: projectionPath, context)
392+ return shrinkMemoryLifetimeAndSplit ( to: availableValue, projectionPath: projectionPath, context)
347393 }
348394 }
349395}
@@ -366,7 +412,7 @@ private func insertMarkDependencies(for load: LoadInst, _ context: FunctionPassC
366412private struct MarkDependenceInserter : AddressUseDefWalker {
367413 let load : LoadInst
368414 let context : FunctionPassContext
369-
415+
370416 mutating func walkUp( address: Value , path: UnusedWalkingPath ) -> WalkResult {
371417 if let mdi = address as? MarkDependenceInst {
372418 let builder = Builder ( after: load, context)
@@ -375,7 +421,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
375421 }
376422 return walkUpDefault ( address: address, path: path)
377423 }
378-
424+
379425 mutating func rootDef( address: Value , path: UnusedWalkingPath ) -> WalkResult {
380426 return . continueWalk
381427 }
@@ -392,7 +438,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
392438/// ...
393439/// // replace %2 with %1
394440///
395- private func shrinkMemoryLifetime( from load : LoadInst , to availableValue: AvailableValue , _ context: FunctionPassContext ) -> Value {
441+ private func shrinkMemoryLifetime( to availableValue: AvailableValue , _ context: FunctionPassContext ) -> Value {
396442 switch availableValue {
397443 case . viaLoad( let availableLoad) :
398444 assert ( availableLoad. loadOwnership == . copy)
@@ -442,7 +488,7 @@ private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: Availa
442488/// ...
443489/// // replace %3 with %1
444490///
445- private func shrinkMemoryLifetimeAndSplit( from load : LoadInst , to availableValue: AvailableValue , projectionPath: SmallProjectionPath , _ context: FunctionPassContext ) -> Value {
491+ private func shrinkMemoryLifetimeAndSplit( to availableValue: AvailableValue , projectionPath: SmallProjectionPath , _ context: FunctionPassContext ) -> Value {
446492 switch availableValue {
447493 case . viaLoad( let availableLoad) :
448494 assert ( availableLoad. loadOwnership == . copy)
@@ -462,6 +508,20 @@ private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue
462508 }
463509}
464510
511+ private enum DataflowResult {
512+ case notRedundant
513+ case redundant( [ AvailableValue ] )
514+ case maybePartiallyRedundant( AccessPath )
515+
516+ init ( notRedundantWith subPath: AccessPath ? ) {
517+ if let subPath = subPath {
518+ self = . maybePartiallyRedundant( subPath)
519+ } else {
520+ self = . notRedundant
521+ }
522+ }
523+ }
524+
465525/// Either a `load` or `store` which is preceding the original load and provides the loaded value.
466526private enum AvailableValue {
467527 case viaLoad( LoadInst )
@@ -505,7 +565,7 @@ private extension Array where Element == AvailableValue {
505565 func replaceCopyAddrsWithLoadsAndStores( _ context: FunctionPassContext ) -> [ AvailableValue ] {
506566 return map {
507567 if case . viaCopyAddr( let copyAddr) = $0 {
508- return . viaStore( copyAddr. replaceWithLoadAndStore ( context) )
568+ return . viaStore( copyAddr. replaceWithLoadAndStore ( context) . store )
509569 } else {
510570 return $0
511571 }
@@ -514,15 +574,15 @@ private extension Array where Element == AvailableValue {
514574}
515575
516576private struct InstructionScanner {
517- private let load : LoadInst
577+ private let load : LoadingInstruction
518578 private let accessPath : AccessPath
519579 private let storageDefBlock : BasicBlock ?
520580 private let aliasAnalysis : AliasAnalysis
521581
522582 private( set) var potentiallyRedundantSubpath : AccessPath ? = nil
523583 private( set) var availableValues = Array < AvailableValue > ( )
524584
525- init ( load: LoadInst , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
585+ init ( load: LoadingInstruction , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
526586 self . load = load
527587 self . accessPath = accessPath
528588 self . storageDefBlock = accessPath. base. reference? . referenceRoot. parentBlock
@@ -616,7 +676,7 @@ private struct InstructionScanner {
616676 potentiallyRedundantSubpath = precedingStorePath
617677 }
618678
619- case let preceedingCopy as CopyAddrInst where preceedingCopy. canProvideValue :
679+ case let preceedingCopy as CopyAddrInst where preceedingCopy. canLoadValue :
620680 let copyPath = preceedingCopy. destination. constantAccessPath
621681 if copyPath. getMaterializableProjection ( to: accessPath) != nil {
622682 availableValues. append ( . viaCopyAddr( preceedingCopy) )
@@ -712,20 +772,3 @@ private struct Liverange {
712772 return false
713773 }
714774}
715-
716- private extension CopyAddrInst {
717- var canProvideValue : Bool {
718- if !source. type. isLoadable ( in: parentFunction) {
719- // Although the original load's type is loadable (obviously), it can be projected-out
720- // from the copy_addr's type which might be not loadable.
721- return false
722- }
723- if !parentFunction. hasOwnership {
724- if !isTakeOfSrc || !isInitializationOfDest {
725- // For simplicity, bail if we would have to insert compensating retains and releases.
726- return false
727- }
728- }
729- return true
730- }
731- }
0 commit comments