Skip to content

Commit 6d76534

Browse files
authored
Merge pull request #77228 from eeckstein/improve-rle
RedundantLoadElimination: support redundant loads of array elements with non-constant index
2 parents de91bb1 + 7786596 commit 6d76534

File tree

11 files changed

+232
-30
lines changed

11 files changed

+232
-30
lines changed

SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ struct AliasAnalysis {
334334
case .stack, .global, .argument, .storeBorrow:
335335
// Those access bases cannot be interior pointers of a borrowed value
336336
return .noEffects
337-
case .pointer, .unidentified, .yield:
337+
case .pointer, .index, .unidentified, .yield:
338338
// We don't know anything about this address -> get the conservative effects
339339
return defaultEffects(of: endBorrow, on: memLoc)
340340
case .box, .class, .tail:

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ private struct LifetimeVariable {
337337
case .pointer(let ptrToAddr):
338338
self.varDecl = nil
339339
self.sourceLoc = ptrToAddr.location.sourceLoc
340-
case .unidentified:
340+
case .index, .unidentified:
341341
self.varDecl = nil
342342
self.sourceLoc = nil
343343
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private extension LoadInst {
142142
}
143143

144144
func isRedundant(complexityBudget: inout Int, _ context: FunctionPassContext) -> DataflowResult {
145-
return isRedundant(at: address.accessPath, complexityBudget: &complexityBudget, context)
145+
return isRedundant(at: address.constantAccessPath, complexityBudget: &complexityBudget, context)
146146
}
147147

148148
func isRedundant(at accessPath: AccessPath, complexityBudget: inout Int, _ context: FunctionPassContext) -> DataflowResult {
@@ -282,7 +282,7 @@ private func provideValue(
282282
from availableValue: AvailableValue,
283283
_ context: FunctionPassContext
284284
) -> Value {
285-
let projectionPath = availableValue.address.accessPath.getMaterializableProjection(to: load.address.accessPath)!
285+
let projectionPath = availableValue.address.constantAccessPath.getMaterializableProjection(to: load.address.constantAccessPath)!
286286

287287
switch load.loadOwnership {
288288
case .unqualified:
@@ -483,7 +483,7 @@ private struct InstructionScanner {
483483
// This happens if the load is in a loop.
484484
return .available
485485
}
486-
let precedingLoadPath = precedingLoad.address.accessPath
486+
let precedingLoadPath = precedingLoad.address.constantAccessPath
487487
if precedingLoadPath.getMaterializableProjection(to: accessPath) != nil {
488488
availableValues.append(.viaLoad(precedingLoad))
489489
return .available
@@ -500,7 +500,7 @@ private struct InstructionScanner {
500500
if precedingStore.source is Undef {
501501
return .overwritten
502502
}
503-
let precedingStorePath = precedingStore.destination.accessPath
503+
let precedingStorePath = precedingStore.destination.constantAccessPath
504504
if precedingStorePath.getMaterializableProjection(to: accessPath) != nil {
505505
availableValues.append(.viaStore(precedingStore))
506506
return .available

SwiftCompilerSources/Sources/Optimizer/ModulePasses/StackProtection.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ private extension AccessBase {
467467
return .decidedInCaller(arg)
468468
case .yield, .pointer:
469469
return .unknown
470-
case .unidentified:
470+
case .index, .unidentified:
471471
// In the rare case of an unidentified access, just ignore it.
472472
// This should not happen in regular SIL, anyway.
473473
return .no

SwiftCompilerSources/Sources/Optimizer/TestPasses/AccessDumper.swift

+38-8
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,16 @@ private func printAccessInfo(address: Value) {
6767
print(" Scope: base")
6868
}
6969

70-
print(" Base: \(ap.base)")
71-
print(" Path: \"\(ap.projectionPath)\"")
70+
let constAp = address.constantAccessPath
71+
if constAp == ap {
72+
print(" Base: \(ap.base)")
73+
print(" Path: \"\(ap.projectionPath)\"")
74+
} else {
75+
print(" nonconst-base: \(ap.base)")
76+
print(" nonconst-path: \"\(ap.projectionPath)\"")
77+
print(" const-base: \(constAp.base)")
78+
print(" const-path: \"\(constAp.projectionPath)\"")
79+
}
7280

7381
var arw = AccessStoragePathVisitor()
7482
if !arw.visitAccessStorageRoots(of: ap) {
@@ -79,18 +87,40 @@ private func printAccessInfo(address: Value) {
7987
private func checkAliasInfo(forArgumentsOf apply: ApplyInst, expectDistinct: Bool) {
8088
let address1 = apply.arguments[0]
8189
let address2 = apply.arguments[1]
82-
let path1 = address1.accessPath
83-
let path2 = address2.accessPath
8490

91+
checkIsDistinct(path1: address1.accessPath,
92+
path2: address2.accessPath,
93+
expectDistinct: expectDistinct,
94+
instruction: apply)
95+
96+
if !expectDistinct {
97+
// Also check all combinations with the constant variant of access paths.
98+
// Note: we can't do that for "isDistinct" because "isDistinct" might be more conservative in one of the variants.
99+
checkIsDistinct(path1: address1.constantAccessPath,
100+
path2: address2.constantAccessPath,
101+
expectDistinct: false,
102+
instruction: apply)
103+
checkIsDistinct(path1: address1.accessPath,
104+
path2: address2.constantAccessPath,
105+
expectDistinct: false,
106+
instruction: apply)
107+
checkIsDistinct(path1: address1.constantAccessPath,
108+
path2: address2.accessPath,
109+
expectDistinct: false,
110+
instruction: apply)
111+
}
112+
}
113+
114+
private func checkIsDistinct(path1: AccessPath, path2: AccessPath, expectDistinct: Bool, instruction: Instruction) {
85115
if path1.isDistinct(from: path2) != expectDistinct {
86-
print("wrong isDistinct result of \(apply)")
116+
print("wrong isDistinct result of \(instruction)")
87117
} else if path2.isDistinct(from: path1) != expectDistinct {
88-
print("wrong reverse isDistinct result of \(apply)")
118+
print("wrong reverse isDistinct result of \(instruction)")
89119
} else {
90120
return
91121
}
92-
122+
93123
print("in function")
94-
print(apply.parentFunction)
124+
print(instruction.parentFunction)
95125
fatalError()
96126
}

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ enum AddressOwnershipLiveRange : CustomStringConvertible {
464464
}
465465
case .storeBorrow(let sb):
466466
return computeValueLiveRange(of: sb.source, context)
467-
case .pointer, .unidentified:
467+
case .pointer, .index, .unidentified:
468468
return nil
469469
}
470470
}

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ extension LifetimeDependence.Scope {
366366
return nil
367367
}
368368
self = scope
369-
case .pointer, .unidentified:
369+
case .pointer, .index, .unidentified:
370370
self = .unknown(address)
371371
}
372372
}
@@ -1094,7 +1094,7 @@ extension LifetimeDependenceDefUseWalker {
10941094
break
10951095
case .yield:
10961096
return storeToYieldDependence(address: address, of: operand)
1097-
case .global, .class, .tail, .storeBorrow, .pointer, .unidentified:
1097+
case .global, .class, .tail, .storeBorrow, .pointer, .index, .unidentified:
10981098
// An address produced by .storeBorrow should never be stored into.
10991099
//
11001100
// TODO: allow storing an immortal value into a global.

SwiftCompilerSources/Sources/SIL/Utilities/AccessUtils.swift

+46-10
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public enum AccessBase : CustomStringConvertible, Hashable {
7878
/// An address which is derived from a `Builtin.RawPointer`.
7979
case pointer(PointerToAddressInst)
8080

81+
// The result of an `index_addr` with a non-constant index.
82+
// This can only occur in access paths returned by `Value.constantAccessPath`.
83+
// In "regular" access paths such `index_addr` projections are contained in the `projectionPath` (`i*`).
84+
case index(IndexAddrInst)
85+
8186
/// The access base is some SIL pattern which does not fit into any other case.
8287
/// This should be a very rare situation.
8388
case unidentified
@@ -115,6 +120,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
115120
case .yield(let result): return "yield - \(result)"
116121
case .storeBorrow(let sb): return "storeBorrow - \(sb)"
117122
case .pointer(let p): return "pointer - \(p)"
123+
case .index(let ia): return "index - \(ia)"
118124
}
119125
}
120126

@@ -123,7 +129,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
123129
switch self {
124130
case .class, .tail:
125131
return true
126-
case .box, .stack, .global, .argument, .yield, .storeBorrow, .pointer, .unidentified:
132+
case .box, .stack, .global, .argument, .yield, .storeBorrow, .pointer, .index, .unidentified:
127133
return false
128134
}
129135
}
@@ -134,7 +140,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
134140
case .box(let pbi): return pbi.box
135141
case .class(let rea): return rea.instance
136142
case .tail(let rta): return rta.instance
137-
case .stack, .global, .argument, .yield, .storeBorrow, .pointer, .unidentified:
143+
case .stack, .global, .argument, .yield, .storeBorrow, .pointer, .index, .unidentified:
138144
return nil
139145
}
140146
}
@@ -162,7 +168,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
162168
switch self {
163169
case .class(let rea): return rea.fieldIsLet
164170
case .global(let g): return g.isLet
165-
case .box, .stack, .tail, .argument, .yield, .storeBorrow, .pointer, .unidentified:
171+
case .box, .stack, .tail, .argument, .yield, .storeBorrow, .pointer, .index, .unidentified:
166172
return false
167173
}
168174
}
@@ -174,7 +180,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
174180
case .class(let rea): return rea.instance.referenceRoot is AllocRefInstBase
175181
case .tail(let rta): return rta.instance.referenceRoot is AllocRefInstBase
176182
case .stack, .storeBorrow: return true
177-
case .global, .argument, .yield, .pointer, .unidentified:
183+
case .global, .argument, .yield, .pointer, .index, .unidentified:
178184
return false
179185
}
180186
}
@@ -184,7 +190,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
184190
switch self {
185191
case .box, .class, .tail, .stack, .storeBorrow, .global:
186192
return true
187-
case .argument, .yield, .pointer, .unidentified:
193+
case .argument, .yield, .pointer, .index, .unidentified:
188194
return false
189195
}
190196
}
@@ -216,6 +222,8 @@ public enum AccessBase : CustomStringConvertible, Hashable {
216222
return sb1 == sb2
217223
case (.pointer(let p1), .pointer(let p2)):
218224
return p1 == p2
225+
case (.index(let ia1), .index(let ia2)):
226+
return ia1 == ia2
219227
default:
220228
return false
221229
}
@@ -258,7 +266,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
258266

259267
switch (self, other) {
260268

261-
// First handle all pairs of the same kind (except `yield` and `pointer`).
269+
// First handle all pairs of the same kind (except `yield`, `pointer` and `index`).
262270
case (.box(let pb), .box(let otherPb)):
263271
return pb.fieldIndex != otherPb.fieldIndex ||
264272
isDifferentAllocation(pb.box.referenceRoot, otherPb.box.referenceRoot) ||
@@ -303,7 +311,7 @@ public enum AccessBase : CustomStringConvertible, Hashable {
303311

304312
/// An `AccessPath` is a pair of a `base: AccessBase` and a `projectionPath: Path`
305313
/// which denotes the offset of the access from the base in terms of projections.
306-
public struct AccessPath : CustomStringConvertible {
314+
public struct AccessPath : CustomStringConvertible, Hashable {
307315
public let base: AccessBase
308316

309317
/// address projections only
@@ -475,6 +483,11 @@ public enum EnclosingScope {
475483
private struct AccessPathWalker : AddressUseDefWalker {
476484
var result = AccessPath.unidentified()
477485
var foundBeginAccess: BeginAccessInst?
486+
let enforceConstantProjectionPath: Bool
487+
488+
init(enforceConstantProjectionPath: Bool = false) {
489+
self.enforceConstantProjectionPath = enforceConstantProjectionPath
490+
}
478491

479492
mutating func walk(startAt address: Value, initialPath: SmallProjectionPath = SmallProjectionPath()) {
480493
if walkUp(address: address, path: Path(projectionPath: initialPath)) == .abortWalk {
@@ -533,9 +546,13 @@ private struct AccessPathWalker : AddressUseDefWalker {
533546
}
534547

535548
mutating func walkUp(address: Value, path: Path) -> WalkResult {
536-
if address is IndexAddrInst {
549+
if let indexAddr = address as? IndexAddrInst {
550+
if !(indexAddr.index is IntegerLiteralInst) && enforceConstantProjectionPath {
551+
self.result = AccessPath(base: .index(indexAddr), projectionPath: path.projectionPath)
552+
return .continueWalk
553+
}
537554
// Track that we crossed an `index_addr` during the walk-up
538-
return walkUpDefault(address: address, path: path.with(indexAddr: true))
555+
return walkUpDefault(address: indexAddr, path: path.with(indexAddr: true))
539556
} else if path.indexAddr && !canBeOperandOfIndexAddr(address) {
540557
// An `index_addr` instruction cannot be derived from an address
541558
// projection. Bail out
@@ -565,6 +582,25 @@ extension Value {
565582
return walker.result
566583
}
567584

585+
/// Like `accessPath`, but ensures that the projectionPath only contains "constant" elements.
586+
/// This means: if the access contains an `index_addr` projection with a non-constant index,
587+
/// the `projectionPath` does _not_ contain the `index_addr`.
588+
/// Instead, the `base` is an `AccessBase.index` which refers to the `index_addr`.
589+
/// For example:
590+
/// ```
591+
/// %1 = ref_tail_addr %some_reference
592+
/// %2 = index_addr %1, %some_non_const_value
593+
/// %3 = struct_element_addr %2, #field2
594+
/// ```
595+
/// `%3.accessPath` = base: tail(`%1`), projectionPath: `i*.s2`
596+
/// `%3.constantAccessPath` = base: index(`%2`), projectionPath: `s2`
597+
///
598+
public var constantAccessPath: AccessPath {
599+
var walker = AccessPathWalker(enforceConstantProjectionPath: true)
600+
walker.walk(startAt: self)
601+
return walker.result
602+
}
603+
568604
public func getAccessPath(fromInitialPath: SmallProjectionPath) -> AccessPath {
569605
var walker = AccessPathWalker()
570606
walker.walk(startAt: self, initialPath: fromInitialPath)
@@ -637,7 +673,7 @@ extension ValueUseDefWalker where Path == SmallProjectionPath {
637673
return walkUp(value: rea.instance, path: path.push(.classField, index: rea.fieldIndex)) != .abortWalk
638674
case .tail(let rta):
639675
return walkUp(value: rta.instance, path: path.push(.tailElements, index: 0)) != .abortWalk
640-
case .stack, .global, .argument, .yield, .storeBorrow, .pointer, .unidentified:
676+
case .stack, .global, .argument, .yield, .storeBorrow, .pointer, .index, .unidentified:
641677
return false
642678
}
643679
}

test/SILOptimizer/accessutils.sil

+62
Original file line numberDiff line numberDiff line change
@@ -626,3 +626,65 @@ bb0(%0 : @guaranteed $C):
626626
dealloc_stack %1 : $*C
627627
return %6 : $C
628628
}
629+
630+
// CHECK-LABEL: Accesses for indexAddr
631+
// CHECK: Value: %6 = index_addr %3 : $*Int64, %4 : $Builtin.Word
632+
// CHECK: Scope: base
633+
// CHECK: Base: tail - %0 = argument of bb0 : $C
634+
// CHECK: Path: "i4"
635+
// CHECK: Storage: %0 = argument of bb0 : $C
636+
// CHECK: Path: "ct.i4"
637+
// CHECK: Value: %7 = index_addr %3 : $*Int64, %5 : $Builtin.Word
638+
// CHECK: Scope: base
639+
// CHECK: Base: tail - %0 = argument of bb0 : $C
640+
// CHECK: Path: "i5"
641+
// CHECK: Storage: %0 = argument of bb0 : $C
642+
// CHECK: Path: "ct.i5"
643+
// CHECK: Value: %8 = index_addr %3 : $*Int64, %1 : $Builtin.Word
644+
// CHECK: Scope: base
645+
// CHECK: nonconst-base: tail - %0 = argument of bb0 : $C
646+
// CHECK: nonconst-path: "i*"
647+
// CHECK: const-base: index - %8 = index_addr %3 : $*Int64, %1 : $Builtin.Word
648+
// CHECK: const-path: ""
649+
// CHECK: Storage: %0 = argument of bb0 : $C
650+
// CHECK: Path: "ct.i*"
651+
// CHECK: Value: %10 = struct_element_addr %9 : $*Int64, #Int64._value
652+
// CHECK: Scope: base
653+
// CHECK: nonconst-base: tail - %0 = argument of bb0 : $C
654+
// CHECK: nonconst-path: "i*.s0"
655+
// CHECK: const-base: index - %9 = index_addr %3 : $*Int64, %2 : $Builtin.Word
656+
// CHECK: const-path: "s0"
657+
// CHECK: Storage: %0 = argument of bb0 : $C
658+
// CHECK: Path: "ct.i*.s0"
659+
// CHECK-LABEL: End accesses for indexAddr
660+
sil @indexAddr : $@convention(thin) (@guaranteed C, Builtin.Word, Builtin.Word) -> () {
661+
bb0(%0 : $C, %1 : $Builtin.Word, %2 : $Builtin.Word):
662+
%3 = ref_tail_addr %0 : $C, $Int64
663+
%4 = integer_literal $Builtin.Word, 4
664+
%5 = integer_literal $Builtin.Word, 5
665+
666+
%6 = index_addr %3 : $*Int64, %4 : $Builtin.Word
667+
%7 = index_addr %3 : $*Int64, %5 : $Builtin.Word
668+
%8 = index_addr %3 : $*Int64, %1 : $Builtin.Word
669+
%9 = index_addr %3 : $*Int64, %2 : $Builtin.Word
670+
%10 = struct_element_addr %9 : $*Int64, #Int64._value
671+
672+
%20 = load %6 : $*Int64
673+
%21 = load %7 : $*Int64
674+
%22 = load %8 : $*Int64
675+
%23 = load %10 : $*Builtin.Int64
676+
677+
%isDistinct = function_ref @_isDistinct : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
678+
%isNotDistinct = function_ref @_isNotDistinct : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
679+
680+
apply %isDistinct<Int64, Int64>(%6, %7) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
681+
apply %isNotDistinct<Int64, Int64>(%3, %6) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
682+
apply %isNotDistinct<Int64, Int64>(%3, %8) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
683+
apply %isNotDistinct<Int64, Builtin.Int64>(%3, %10) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
684+
apply %isNotDistinct<Int64, Int64>(%6, %8) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
685+
apply %isNotDistinct<Int64, Int64>(%8, %9) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
686+
apply %isNotDistinct<Int64, Builtin.Int64>(%8, %10) : $@convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @inout τ_0_1) -> ()
687+
688+
%200 = tuple ()
689+
return %200 : $()
690+
}

0 commit comments

Comments
 (0)