12
12
13
13
import SIL
14
14
15
- /// Replaces redundant load instructions with already available values.
15
+ /// Replaces redundant ` load` or `copy_addr` instructions with already available values.
16
16
///
17
17
/// A load is redundant if the loaded value is already available at that point.
18
18
/// This can be via a preceding store to the same address:
@@ -52,6 +52,9 @@ import SIL
52
52
/// %f2 = load %fa2
53
53
/// %2 = struct (%f1, %f2)
54
54
///
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
+ ///
55
58
/// The algorithm is a data flow analysis which starts at the original load and searches
56
59
/// for preceding stores or loads by following the control flow in backward direction.
57
60
/// The preceding stores and loads provide the "available values" with which the original
@@ -99,7 +102,7 @@ func eliminateRedundantLoads(in function: Function,
99
102
while let i = inst {
100
103
defer { inst = i. previous }
101
104
102
- if let load = inst as? LoadInst {
105
+ if let load = inst as? LoadingInstruction {
103
106
if !context. continueWithNextSubpassRun ( for: load) {
104
107
return changed
105
108
}
@@ -116,7 +119,59 @@ func eliminateRedundantLoads(in function: Function,
116
119
return changed
117
120
}
118
121
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 {
120
175
switch load. isRedundant ( complexityBudget: & complexityBudget, context) {
121
176
case . notRedundant:
122
177
return false
@@ -136,9 +191,12 @@ private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context
136
191
}
137
192
}
138
193
139
- private extension LoadInst {
194
+ private extension LoadingInstruction {
140
195
141
196
func isEligibleForElimination( in variant: RedundantLoadEliminationVariant , _ context: FunctionPassContext ) -> Bool {
197
+ if !canLoadValue {
198
+ return false
199
+ }
142
200
switch variant {
143
201
case . mandatory, . mandatoryInGlobalInit:
144
202
if loadOwnership == . take {
@@ -171,20 +229,6 @@ private extension LoadInst {
171
229
return true
172
230
}
173
231
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
-
188
232
func isRedundant( complexityBudget: inout Int , _ context: FunctionPassContext ) -> DataflowResult {
189
233
return isRedundant ( at: address. constantAccessPath, complexityBudget: & complexityBudget, context)
190
234
}
@@ -285,7 +329,7 @@ private extension LoadInst {
285
329
}
286
330
}
287
331
288
- private func replace( load: LoadInst , with availableValues: [ AvailableValue ] , _ context: FunctionPassContext ) {
332
+ private func replace( load: LoadingInstruction , with availableValues: [ AvailableValue ] , _ context: FunctionPassContext ) {
289
333
var ssaUpdater = SSAUpdater ( function: load. parentFunction,
290
334
type: load. type, ownership: load. ownership, context)
291
335
@@ -318,14 +362,16 @@ private func replace(load: LoadInst, with availableValues: [AvailableValue], _ c
318
362
newValue = ssaUpdater. getValue ( inMiddleOf: load. parentBlock)
319
363
}
320
364
365
+ let originalLoad = load. materializeLoadForReplacement ( context)
366
+
321
367
// Make sure to keep dependencies valid after replacing the load
322
- insertMarkDependencies ( for: load , context)
368
+ insertMarkDependencies ( for: originalLoad , context)
323
369
324
- load . replace ( with: newValue, context)
370
+ originalLoad . replace ( with: newValue, context)
325
371
}
326
372
327
373
private func provideValue(
328
- for load: LoadInst ,
374
+ for load: LoadingInstruction ,
329
375
from availableValue: AvailableValue ,
330
376
_ context: FunctionPassContext
331
377
) -> Value {
@@ -341,9 +387,9 @@ private func provideValue(
341
387
builder: availableValue. getBuilderForProjections ( context) )
342
388
case . take:
343
389
if projectionPath. isEmpty {
344
- return shrinkMemoryLifetime ( from : load , to: availableValue, context)
390
+ return shrinkMemoryLifetime ( to: availableValue, context)
345
391
} else {
346
- return shrinkMemoryLifetimeAndSplit ( from : load , to: availableValue, projectionPath: projectionPath, context)
392
+ return shrinkMemoryLifetimeAndSplit ( to: availableValue, projectionPath: projectionPath, context)
347
393
}
348
394
}
349
395
}
@@ -366,7 +412,7 @@ private func insertMarkDependencies(for load: LoadInst, _ context: FunctionPassC
366
412
private struct MarkDependenceInserter : AddressUseDefWalker {
367
413
let load : LoadInst
368
414
let context : FunctionPassContext
369
-
415
+
370
416
mutating func walkUp( address: Value , path: UnusedWalkingPath ) -> WalkResult {
371
417
if let mdi = address as? MarkDependenceInst {
372
418
let builder = Builder ( after: load, context)
@@ -375,7 +421,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
375
421
}
376
422
return walkUpDefault ( address: address, path: path)
377
423
}
378
-
424
+
379
425
mutating func rootDef( address: Value , path: UnusedWalkingPath ) -> WalkResult {
380
426
return . continueWalk
381
427
}
@@ -392,7 +438,7 @@ private struct MarkDependenceInserter : AddressUseDefWalker {
392
438
/// ...
393
439
/// // replace %2 with %1
394
440
///
395
- private func shrinkMemoryLifetime( from load : LoadInst , to availableValue: AvailableValue , _ context: FunctionPassContext ) -> Value {
441
+ private func shrinkMemoryLifetime( to availableValue: AvailableValue , _ context: FunctionPassContext ) -> Value {
396
442
switch availableValue {
397
443
case . viaLoad( let availableLoad) :
398
444
assert ( availableLoad. loadOwnership == . copy)
@@ -442,7 +488,7 @@ private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: Availa
442
488
/// ...
443
489
/// // replace %3 with %1
444
490
///
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 {
446
492
switch availableValue {
447
493
case . viaLoad( let availableLoad) :
448
494
assert ( availableLoad. loadOwnership == . copy)
@@ -462,6 +508,20 @@ private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue
462
508
}
463
509
}
464
510
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
+
465
525
/// Either a `load` or `store` which is preceding the original load and provides the loaded value.
466
526
private enum AvailableValue {
467
527
case viaLoad( LoadInst )
@@ -505,7 +565,7 @@ private extension Array where Element == AvailableValue {
505
565
func replaceCopyAddrsWithLoadsAndStores( _ context: FunctionPassContext ) -> [ AvailableValue ] {
506
566
return map {
507
567
if case . viaCopyAddr( let copyAddr) = $0 {
508
- return . viaStore( copyAddr. replaceWithLoadAndStore ( context) )
568
+ return . viaStore( copyAddr. replaceWithLoadAndStore ( context) . store )
509
569
} else {
510
570
return $0
511
571
}
@@ -514,15 +574,15 @@ private extension Array where Element == AvailableValue {
514
574
}
515
575
516
576
private struct InstructionScanner {
517
- private let load : LoadInst
577
+ private let load : LoadingInstruction
518
578
private let accessPath : AccessPath
519
579
private let storageDefBlock : BasicBlock ?
520
580
private let aliasAnalysis : AliasAnalysis
521
581
522
582
private( set) var potentiallyRedundantSubpath : AccessPath ? = nil
523
583
private( set) var availableValues = Array < AvailableValue > ( )
524
584
525
- init ( load: LoadInst , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
585
+ init ( load: LoadingInstruction , accessPath: AccessPath , _ aliasAnalysis: AliasAnalysis ) {
526
586
self . load = load
527
587
self . accessPath = accessPath
528
588
self . storageDefBlock = accessPath. base. reference? . referenceRoot. parentBlock
@@ -616,7 +676,7 @@ private struct InstructionScanner {
616
676
potentiallyRedundantSubpath = precedingStorePath
617
677
}
618
678
619
- case let preceedingCopy as CopyAddrInst where preceedingCopy. canProvideValue :
679
+ case let preceedingCopy as CopyAddrInst where preceedingCopy. canLoadValue :
620
680
let copyPath = preceedingCopy. destination. constantAccessPath
621
681
if copyPath. getMaterializableProjection ( to: accessPath) != nil {
622
682
availableValues. append ( . viaCopyAddr( preceedingCopy) )
@@ -712,20 +772,3 @@ private struct Liverange {
712
772
return false
713
773
}
714
774
}
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