@@ -14,7 +14,7 @@ import SIL
14
14
15
15
/// Eliminates dead store instructions.
16
16
///
17
- /// A store is dead if after the store has occurred:
17
+ /// A store is dead if, after the store has occurred:
18
18
///
19
19
/// 1. The value in memory is not read until the memory object is deallocated:
20
20
///
@@ -47,6 +47,9 @@ import SIL
47
47
/// ... // no reads from %1
48
48
/// store %7 to %3
49
49
///
50
+ /// The algorithm is a data flow analysis which starts at the original store and searches
51
+ /// for successive stores by following the control flow in forward direction.
52
+ ///
50
53
let deadStoreElimination = FunctionPass ( name: " dead-store-elimination " ) {
51
54
( function: Function , context: FunctionPassContext ) in
52
55
@@ -85,7 +88,7 @@ private func tryEliminate(store: StoreInst, _ context: FunctionPassContext) {
85
88
case . dead:
86
89
// The new individual stores are inserted right after the current store and
87
90
// will be optimized in the following loop iterations.
88
- store. trySplit ( context)
91
+ store. split ( context)
89
92
}
90
93
}
91
94
}
@@ -111,18 +114,24 @@ private extension StoreInst {
111
114
}
112
115
113
116
func isDead( at accessPath: AccessPath , _ context: FunctionPassContext ) -> DataflowResult {
114
- var worklist = InstructionWorklist ( context)
115
- defer { worklist . deinitialize ( ) }
117
+ var scanner = InstructionScanner ( storePath : accessPath , storeAddress : self . destination , context. aliasAnalysis )
118
+ let storageDefBlock = accessPath . base . reference ? . referenceRoot . parentBlock
116
119
117
- worklist. pushIfNotVisited ( self . next!)
120
+ switch scanner. scan ( instructions: InstructionList ( first: self . next) ) {
121
+ case . dead:
122
+ return . dead
118
123
119
- let storageDefBlock = accessPath. base. reference? . referenceRoot. parentBlock
120
- var scanner = InstructionScanner ( storePath: accessPath, storeAddress: self . destination, context. aliasAnalysis)
124
+ case . alive:
125
+ return DataflowResult ( aliveWith: scanner. potentiallyDeadSubpath)
126
+
127
+ case . transparent:
128
+ // Continue with iterative data flow analysis starting at the block's successors.
129
+ var worklist = BasicBlockWorklist ( context)
130
+ defer { worklist. deinitialize ( ) }
131
+ worklist. pushIfNotVisited ( contentsOf: self . parentBlock. successors)
132
+
133
+ while let block = worklist. pop ( ) {
121
134
122
- while let startInstInBlock = worklist. pop ( ) {
123
- let block = startInstInBlock. parentBlock
124
- switch scanner. scan ( instructions: InstructionList ( first: startInstInBlock) ) {
125
- case . transparent:
126
135
// Abort if we find the storage definition of the access in case of a loop, e.g.
127
136
//
128
137
// bb1:
@@ -133,43 +142,45 @@ private extension StoreInst {
133
142
//
134
143
// The storage root is different in each loop iteration. Therefore the store of a
135
144
// successive loop iteration does not overwrite the store of the previous iteration.
136
- if let storageDefBlock = storageDefBlock,
137
- block. successors. contains ( storageDefBlock) {
145
+ if let storageDefBlock = storageDefBlock, block == storageDefBlock {
146
+ return DataflowResult ( aliveWith: scanner. potentiallyDeadSubpath)
147
+ }
148
+ switch scanner. scan ( instructions: block. instructions) {
149
+ case . transparent:
150
+ worklist. pushIfNotVisited ( contentsOf: block. successors)
151
+ case . dead:
152
+ break
153
+ case . alive:
138
154
return DataflowResult ( aliveWith: scanner. potentiallyDeadSubpath)
139
155
}
140
- worklist. pushIfNotVisited ( contentsOf: block. successors. lazy. map { $0. instructions. first! } )
141
- case . dead:
142
- break
143
- case . alive:
144
- return DataflowResult ( aliveWith: scanner. potentiallyDeadSubpath)
145
156
}
157
+ return . dead
146
158
}
147
- return . dead
148
159
}
149
160
150
- func trySplit( _ context: FunctionPassContext ) {
161
+ func split( _ context: FunctionPassContext ) {
162
+ let builder = Builder ( after: self , context)
151
163
let type = source. type
152
164
if type. isStruct {
153
- let builder = Builder ( after: self , context)
154
165
for idx in 0 ..< type. getNominalFields ( in: parentFunction) . count {
155
166
let srcField = builder. createStructExtract ( struct: source, fieldIndex: idx)
156
167
let destFieldAddr = builder. createStructElementAddr ( structAddress: destination, fieldIndex: idx)
157
- builder. createStore ( source: srcField, destination: destFieldAddr, ownership: destinationOwnership )
168
+ builder. createStore ( source: srcField, destination: destFieldAddr, ownership: storeOwnership )
158
169
}
159
- context. erase ( instruction: self )
160
170
} else if type. isTuple {
161
- let builder = Builder ( after: self , context)
162
171
for idx in 0 ..< type. tupleElements. count {
163
172
let srcField = builder. createTupleExtract ( tuple: source, elementIndex: idx)
164
173
let destFieldAddr = builder. createTupleElementAddr ( tupleAddress: destination, elementIndex: idx)
165
- builder. createStore ( source: srcField, destination: destFieldAddr, ownership: destinationOwnership )
174
+ builder. createStore ( source: srcField, destination: destFieldAddr, ownership: storeOwnership )
166
175
}
167
- context. erase ( instruction: self )
176
+ } else {
177
+ fatalError ( " a materializable projection path should only contain struct and tuple projections " )
168
178
}
179
+ context. erase ( instruction: self )
169
180
}
170
181
171
182
var hasValidOwnershipForDeadStoreElimination : Bool {
172
- switch destinationOwnership {
183
+ switch storeOwnership {
173
184
case . unqualified, . trivial:
174
185
return true
175
186
case . initialize, . assign:
@@ -181,11 +192,11 @@ private extension StoreInst {
181
192
}
182
193
183
194
private struct InstructionScanner {
184
- let storePath : AccessPath
185
- let storeAddress : Value
186
- let aliasAnalysis : AliasAnalysis
195
+ private let storePath : AccessPath
196
+ private let storeAddress : Value
197
+ private let aliasAnalysis : AliasAnalysis
187
198
188
- var potentiallyDeadSubpath : AccessPath ? = nil
199
+ private ( set ) var potentiallyDeadSubpath : AccessPath ? = nil
189
200
190
201
// Avoid quadratic complexity by limiting the number of visited instructions for each store.
191
202
// The limit of 1000 instructions is not reached by far in "real-world" functions.
@@ -208,14 +219,16 @@ private struct InstructionScanner {
208
219
switch inst {
209
220
case let successiveStore as StoreInst :
210
221
let successivePath = successiveStore. destination. accessPath
211
- if successivePath. isEqualOrOverlaps ( storePath) {
222
+ if successivePath. isEqualOrContains ( storePath) {
212
223
return . dead
213
224
}
214
- if storePath . isEqualOrOverlaps ( successivePath ) ,
215
- potentiallyDeadSubpath = = nil {
225
+ if potentiallyDeadSubpath == nil ,
226
+ storePath . getMaterializableProjection ( to : successivePath ) ! = nil {
216
227
// Storing to a sub-field of the original store doesn't make the original store dead.
217
228
// But when we split the original store, then one of the new individual stores might be
218
229
// overwritten by this store.
230
+ // Requiring that the projection to the partial store path is materializable guarantees
231
+ // that we can split the store.
219
232
potentiallyDeadSubpath = successivePath
220
233
}
221
234
case is DeallocRefInst , is DeallocStackRefInst , is DeallocBoxInst :
@@ -241,6 +254,8 @@ private struct InstructionScanner {
241
254
if inst. mayRead ( fromAddress: storeAddress, aliasAnalysis) {
242
255
return . alive
243
256
}
257
+ // TODO: We might detect that this is a partial read of the original store which potentially
258
+ // enables partial dead store elimination.
244
259
}
245
260
}
246
261
return . transparent
0 commit comments