@@ -63,21 +63,33 @@ import SIL
63
63
///
64
64
let redundantLoadElimination = FunctionPass ( name: " redundant-load-elimination " ) {
65
65
( function: Function , context: FunctionPassContext ) in
66
- eliminateRedundantLoads ( in: function, ignoreArrays : false , context)
66
+ _ = eliminateRedundantLoads ( in: function, variant : . regular , context)
67
67
}
68
68
69
69
// Early RLE does not touch loads from Arrays. This is important because later array optimizations,
70
70
// like ABCOpt, get confused if an array load in a loop is converted to a pattern with a phi argument.
71
71
let earlyRedundantLoadElimination = FunctionPass ( name: " early-redundant-load-elimination " ) {
72
72
( function: Function , context: FunctionPassContext ) in
73
- eliminateRedundantLoads ( in: function, ignoreArrays : true , context)
73
+ _ = eliminateRedundantLoads ( in: function, variant : . early , context)
74
74
}
75
75
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
+ }
77
84
85
+ func eliminateRedundantLoads( in function: Function ,
86
+ variant: RedundantLoadEliminationVariant ,
87
+ _ context: FunctionPassContext ) -> Bool
88
+ {
78
89
// Avoid quadratic complexity by limiting the number of visited instructions.
79
90
// This limit is sufficient for most "real-world" functions, by far.
80
91
var complexityBudget = 50_000
92
+ var changed = false
81
93
82
94
for block in function. blocks. reversed ( ) {
83
95
@@ -89,50 +101,76 @@ private func eliminateRedundantLoads(in function: Function, ignoreArrays: Bool,
89
101
90
102
if let load = inst as? LoadInst {
91
103
if !context. continueWithNextSubpassRun ( for: load) {
92
- return
104
+ return changed
93
105
}
94
- if ignoreArrays,
95
- let nominal = load. type. nominal,
96
- nominal == context. swiftArrayDecl
97
- {
98
- continue
106
+ if complexityBudget < 20 {
107
+ complexityBudget = 20
99
108
}
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 ;
108
111
}
109
- tryEliminate ( load: load, complexityBudget: & complexityBudget, context)
112
+ changed = tryEliminate ( load: load, complexityBudget: & complexityBudget, context) || changed
110
113
}
111
114
}
112
115
}
116
+ return changed
113
117
}
114
118
115
- private func tryEliminate( load: LoadInst , complexityBudget: inout Int , _ context: FunctionPassContext ) {
119
+ private func tryEliminate( load: LoadInst , complexityBudget: inout Int , _ context: FunctionPassContext ) -> Bool {
116
120
switch load. isRedundant ( complexityBudget: & complexityBudget, context) {
117
121
case . notRedundant:
118
- break
122
+ return false
119
123
case . redundant( let availableValues) :
120
124
replace ( load: load, with: availableValues, context)
125
+ return true
121
126
case . maybePartiallyRedundant( let subPath) :
122
127
// Check if the a partial load would really be redundant to avoid unnecessary splitting.
123
128
switch load. isRedundant ( at: subPath, complexityBudget: & complexityBudget, context) {
124
129
case . notRedundant, . maybePartiallyRedundant:
125
- break
130
+ return false
126
131
case . redundant:
127
132
// The new individual loads are inserted right before the current load and
128
133
// will be optimized in the following loop iterations.
129
- load. trySplit ( context)
134
+ return load. trySplit ( context)
130
135
}
131
136
}
132
137
}
133
138
134
139
private extension LoadInst {
135
140
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
+
136
174
enum DataflowResult {
137
175
case notRedundant
138
176
case redundant( [ AvailableValue ] )
0 commit comments