Skip to content

Commit 36b9f4e

Browse files
committed
RedundantLoadElimination: add a "mandatory" redundant load elimination pass
And make `eliminateRedundantLoads` callable from other optimizations
1 parent f043b36 commit 36b9f4e

File tree

5 files changed

+141
-177
lines changed

5 files changed

+141
-177
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,33 @@ import SIL
6363
///
6464
let redundantLoadElimination = FunctionPass(name: "redundant-load-elimination") {
6565
(function: Function, context: FunctionPassContext) in
66-
eliminateRedundantLoads(in: function, ignoreArrays: false, context)
66+
_ = eliminateRedundantLoads(in: function, variant: .regular, context)
6767
}
6868

6969
// Early RLE does not touch loads from Arrays. This is important because later array optimizations,
7070
// like ABCOpt, get confused if an array load in a loop is converted to a pattern with a phi argument.
7171
let earlyRedundantLoadElimination = FunctionPass(name: "early-redundant-load-elimination") {
7272
(function: Function, context: FunctionPassContext) in
73-
eliminateRedundantLoads(in: function, ignoreArrays: true, context)
73+
_ = eliminateRedundantLoads(in: function, variant: .early, context)
7474
}
7575

76-
private func eliminateRedundantLoads(in function: Function, ignoreArrays: Bool, _ context: FunctionPassContext) {
76+
let mandatoryRedundantLoadElimination = FunctionPass(name: "mandatory-redundant-load-elimination") {
77+
(function: Function, context: FunctionPassContext) in
78+
_ = eliminateRedundantLoads(in: function, variant: .mandatory, context)
79+
}
80+
81+
enum RedundantLoadEliminationVariant {
82+
case mandatory, mandatoryInGlobalInit, early, regular
83+
}
7784

85+
func eliminateRedundantLoads(in function: Function,
86+
variant: RedundantLoadEliminationVariant,
87+
_ context: FunctionPassContext) -> Bool
88+
{
7889
// Avoid quadratic complexity by limiting the number of visited instructions.
7990
// This limit is sufficient for most "real-world" functions, by far.
8091
var complexityBudget = 50_000
92+
var changed = false
8193

8294
for block in function.blocks.reversed() {
8395

@@ -89,50 +101,76 @@ private func eliminateRedundantLoads(in function: Function, ignoreArrays: Bool,
89101

90102
if let load = inst as? LoadInst {
91103
if !context.continueWithNextSubpassRun(for: load) {
92-
return
104+
return changed
93105
}
94-
if ignoreArrays,
95-
let nominal = load.type.nominal,
96-
nominal == context.swiftArrayDecl
97-
{
98-
continue
106+
if complexityBudget < 20 {
107+
complexityBudget = 20
99108
}
100-
// Check if the type can be expanded without a significant increase to
101-
// code size.
102-
// We block redundant load elimination because it might increase
103-
// register pressure for large values. Furthermore, this pass also
104-
// splits values into its projections (e.g
105-
// shrinkMemoryLifetimeAndSplit).
106-
if !load.type.shouldExpand(context) {
107-
continue
109+
if !load.isEligibleForElimination(in: variant, context) {
110+
continue;
108111
}
109-
tryEliminate(load: load, complexityBudget: &complexityBudget, context)
112+
changed = tryEliminate(load: load, complexityBudget: &complexityBudget, context) || changed
110113
}
111114
}
112115
}
116+
return changed
113117
}
114118

115-
private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context: FunctionPassContext) {
119+
private func tryEliminate(load: LoadInst, complexityBudget: inout Int, _ context: FunctionPassContext) -> Bool {
116120
switch load.isRedundant(complexityBudget: &complexityBudget, context) {
117121
case .notRedundant:
118-
break
122+
return false
119123
case .redundant(let availableValues):
120124
replace(load: load, with: availableValues, context)
125+
return true
121126
case .maybePartiallyRedundant(let subPath):
122127
// Check if the a partial load would really be redundant to avoid unnecessary splitting.
123128
switch load.isRedundant(at: subPath, complexityBudget: &complexityBudget, context) {
124129
case .notRedundant, .maybePartiallyRedundant:
125-
break
130+
return false
126131
case .redundant:
127132
// The new individual loads are inserted right before the current load and
128133
// will be optimized in the following loop iterations.
129-
load.trySplit(context)
134+
return load.trySplit(context)
130135
}
131136
}
132137
}
133138

134139
private extension LoadInst {
135140

141+
func isEligibleForElimination(in variant: RedundantLoadEliminationVariant, _ context: FunctionPassContext) -> Bool {
142+
switch variant {
143+
case .mandatory, .mandatoryInGlobalInit:
144+
if loadOwnership == .take {
145+
// load [take] would require to shrinkMemoryLifetime. But we don't want to do this in the mandatory
146+
// pipeline to not shrink or remove an alloc_stack which is relevant for debug info.
147+
return false
148+
}
149+
switch address.accessBase {
150+
case .box, .stack:
151+
break
152+
default:
153+
return false
154+
}
155+
case .early:
156+
// See the comment of `earlyRedundantLoadElimination`.
157+
if let nominal = self.type.nominal, nominal == context.swiftArrayDecl {
158+
return false
159+
}
160+
case .regular:
161+
break
162+
}
163+
// Check if the type can be expanded without a significant increase to code size.
164+
// We block redundant load elimination because it might increase register pressure for large values.
165+
// Furthermore, this pass also splits values into its projections (e.g shrinkMemoryLifetimeAndSplit).
166+
// But: it is required to remove loads, even of large structs, in global init functions to ensure
167+
// that globals (containing large structs) can be statically initialized.
168+
if variant != .mandatoryInGlobalInit, !self.type.shouldExpand(context) {
169+
return false
170+
}
171+
return true
172+
}
173+
136174
enum DataflowResult {
137175
case notRedundant
138176
case redundant([AvailableValue])

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ private func registerSwiftPasses() {
9191
registerPass(stripObjectHeadersPass, { stripObjectHeadersPass.run($0) })
9292
registerPass(deadStoreElimination, { deadStoreElimination.run($0) })
9393
registerPass(redundantLoadElimination, { redundantLoadElimination.run($0) })
94+
registerPass(mandatoryRedundantLoadElimination, { mandatoryRedundantLoadElimination.run($0) })
9495
registerPass(earlyRedundantLoadElimination, { earlyRedundantLoadElimination.run($0) })
9596
registerPass(deinitDevirtualizer, { deinitDevirtualizer.run($0) })
9697
registerPass(lifetimeDependenceDiagnosticsPass, { lifetimeDependenceDiagnosticsPass.run($0) })

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,15 +513,16 @@ extension StoreInst {
513513
}
514514

515515
extension LoadInst {
516-
func trySplit(_ context: FunctionPassContext) {
516+
@discardableResult
517+
func trySplit(_ context: FunctionPassContext) -> Bool {
517518
var elements = [Value]()
518519
let builder = Builder(before: self, context)
519520
if type.isStruct {
520521
if (type.nominal as! StructDecl).hasUnreferenceableStorage {
521-
return
522+
return false
522523
}
523524
guard let fields = type.getNominalFields(in: parentFunction) else {
524-
return
525+
return false
525526
}
526527
for idx in 0..<fields.count {
527528
let fieldAddr = builder.createStructElementAddr(structAddress: address, fieldIndex: idx)
@@ -530,6 +531,7 @@ extension LoadInst {
530531
}
531532
let newStruct = builder.createStruct(type: self.type, elements: elements)
532533
self.replace(with: newStruct, context)
534+
return true
533535
} else if type.isTuple {
534536
var elements = [Value]()
535537
let builder = Builder(before: self, context)
@@ -540,7 +542,9 @@ extension LoadInst {
540542
}
541543
let newTuple = builder.createTuple(type: self.type, elements: elements)
542544
self.replace(with: newTuple, context)
545+
return true
543546
}
547+
return false
544548
}
545549

546550
private func splitOwnership(for fieldValue: Value) -> LoadOwnership {

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ PASS(ARCSequenceOpts, "arc-sequence-opts",
252252
"ARC Sequence Optimization")
253253
PASS(ARCLoopOpts, "arc-loop-opts",
254254
"ARC Loop Optimization")
255+
SWIFT_FUNCTION_PASS(MandatoryRedundantLoadElimination, "mandatory-redundant-load-elimination",
256+
"Mandatory Redundant Load Elimination")
255257
SWIFT_FUNCTION_PASS(EarlyRedundantLoadElimination, "early-redundant-load-elimination",
256258
"Early Redundant Load Elimination")
257259
SWIFT_FUNCTION_PASS(RedundantLoadElimination, "redundant-load-elimination",

0 commit comments

Comments
 (0)