Skip to content

Commit ea4906d

Browse files
authored
Merge pull request #81131 from eeckstein/copyaddr-in-rle-6.2
[6.2] RedundantLoadElimination: support replacing a redundant `copy_addr` with a `store`
2 parents 140abae + 78bfc9f commit ea4906d

File tree

8 files changed

+298
-74
lines changed

8 files changed

+298
-74
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import 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

327373
private 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
366412
private 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.
466526
private 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

516576
private 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-
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -941,27 +941,74 @@ extension CheckedCastAddrBranchInst {
941941

942942
extension CopyAddrInst {
943943
@discardableResult
944-
func replaceWithLoadAndStore(_ context: some MutatingContext) -> StoreInst {
945-
let loadOwnership: LoadInst.LoadOwnership
946-
let storeOwnership: StoreInst.StoreOwnership
947-
if parentFunction.hasOwnership {
948-
if source.type.isTrivial(in: parentFunction) {
949-
loadOwnership = .trivial
950-
storeOwnership = .trivial
951-
} else {
952-
loadOwnership = isTakeOfSrc ? .take : .copy
953-
storeOwnership = isInitializationOfDest ? .initialize : .assign
944+
func trySplit(_ context: FunctionPassContext) -> Bool {
945+
let builder = Builder(before: self, context)
946+
if source.type.isStruct {
947+
if (source.type.nominal as! StructDecl).hasUnreferenceableStorage {
948+
return false
954949
}
955-
} else {
956-
loadOwnership = .unqualified
957-
storeOwnership = .unqualified
950+
guard let fields = source.type.getNominalFields(in: parentFunction) else {
951+
return false
952+
}
953+
for idx in 0..<fields.count {
954+
let srcFieldAddr = builder.createStructElementAddr(structAddress: source, fieldIndex: idx)
955+
let destFieldAddr = builder.createStructElementAddr(structAddress: destination, fieldIndex: idx)
956+
builder.createCopyAddr(from: srcFieldAddr, to: destFieldAddr,
957+
takeSource: isTake(for: srcFieldAddr), initializeDest: isInitializationOfDest)
958+
}
959+
context.erase(instruction: self)
960+
return true
961+
} else if source.type.isTuple {
962+
let builder = Builder(before: self, context)
963+
for idx in 0..<source.type.tupleElements.count {
964+
let srcFieldAddr = builder.createTupleElementAddr(tupleAddress: source, elementIndex: idx)
965+
let destFieldAddr = builder.createTupleElementAddr(tupleAddress: destination, elementIndex: idx)
966+
builder.createCopyAddr(from: srcFieldAddr, to: destFieldAddr,
967+
takeSource: isTake(for: srcFieldAddr), initializeDest: isInitializationOfDest)
968+
}
969+
context.erase(instruction: self)
970+
return true
958971
}
959-
972+
return false
973+
}
974+
975+
private func isTake(for fieldValue: Value) -> Bool {
976+
return isTakeOfSrc && !fieldValue.type.objectType.isTrivial(in: parentFunction)
977+
}
978+
979+
@discardableResult
980+
func replaceWithLoadAndStore(_ context: some MutatingContext) -> (load: LoadInst, store: StoreInst) {
960981
let builder = Builder(before: self, context)
961-
let value = builder.createLoad(fromAddress: source, ownership: loadOwnership)
962-
let store = builder.createStore(source: value, destination: destination, ownership: storeOwnership)
982+
let load = builder.createLoad(fromAddress: source, ownership: loadOwnership)
983+
let store = builder.createStore(source: load, destination: destination, ownership: storeOwnership)
963984
context.erase(instruction: self)
964-
return store
985+
return (load, store)
986+
}
987+
988+
var loadOwnership: LoadInst.LoadOwnership {
989+
if !parentFunction.hasOwnership {
990+
return .unqualified
991+
}
992+
if type.isTrivial(in: parentFunction) {
993+
return .trivial
994+
}
995+
if isTakeOfSrc {
996+
return .take
997+
}
998+
return .copy
999+
}
1000+
1001+
var storeOwnership: StoreInst.StoreOwnership {
1002+
if !parentFunction.hasOwnership {
1003+
return .unqualified
1004+
}
1005+
if type.isTrivial(in: parentFunction) {
1006+
return .trivial
1007+
}
1008+
if isInitializationOfDest {
1009+
return .initialize
1010+
}
1011+
return .assign
9651012
}
9661013
}
9671014

test/AutoDiff/compiler_crashers_fixed/issue-62608-conflicting-debug-info.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public func pullback<T>(
3838

3939
// CHECK-LABEL: sil private @$s4main19testUpdateByCallingyyKF8fOfArrayL_5arraySdSaySdG_tFySdzcfU_TJpSUpSr :
4040
// CHECK: alloc_stack $Double, var, name "derivative of 'element' in scope at {{.*}} (scope #3)"
41-
// CHECK: debug_value %{{.*}} : $Builtin.FPIEEE64, var, (name "derivative of 'element' in scope at {{.*}} (scope #1)"
41+
// CHECK: debug_value %{{.*}} : $Builtin.FPIEEE64, var, (name "derivative of 'element' in scope at {{.*}} (scope #{{.*}})"
4242

4343
public extension Array where Element: Differentiable {
4444
@inlinable

test/SILGen/reference_bindings.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ func doSomething() {}
3535
// SIL: [[BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x"
3636
// SIL: [[INOUT_BOX:%.*]] = alloc_stack [var_decl] $Int, var, name "x2"
3737
// SIL: [[ACCESS:%.*]] = begin_access [modify] [static] [[BOX]]
38-
// SIL: copy_addr [take] [[ACCESS]] to [init] [[INOUT_BOX]]
38+
// SIL: store {{%.*}} to [[INOUT_BOX]]
3939
// SIL: [[FUNC:%.*]] = function_ref @$s18reference_bindings11doSomethingyyF : $@convention(thin) () -> ()
4040
// SIL: apply [[FUNC]]()
41-
// SIL: copy_addr [take] [[INOUT_BOX]] to [init] [[ACCESS]]
41+
// SIL: store {{%.*}} to [[ACCESS]]
4242
// SIL: end_access [[ACCESS]]
4343
// SIL: } // end sil function '$s18reference_bindings13testBindToVaryyF'
4444
func testBindToVar() {

0 commit comments

Comments
 (0)