@@ -63,21 +63,33 @@ import SIL
6363///
6464let 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.
7171let 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
134139private 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 ] )
0 commit comments