Skip to content

Commit 5ae7399

Browse files
authored
Merge pull request #67227 from eeckstein/dead-store-elimination-followup
DeadStoreElimination: some refactoring and improvements
2 parents ddeab73 + 9cb83c3 commit 5ae7399

File tree

9 files changed

+173
-93
lines changed

9 files changed

+173
-93
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeSideEffects.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private struct CollectedEffects {
113113

114114
case let store as StoreInst:
115115
addEffects(.write, to: store.destination)
116-
if store.destinationOwnership == .assign {
116+
if store.storeOwnership == .assign {
117117
addDestroyEffects(ofAddress: store.destination)
118118
}
119119

@@ -454,7 +454,7 @@ private struct ArgumentEscapingWalker : ValueDefUseWalker, AddressDefUseWalker {
454454
case let load as LoadInst:
455455
if !address.value.hasTrivialType &&
456456
// In non-ossa SIL we don't know if a load is taking.
457-
(!function.hasOwnership || load.ownership == .take) {
457+
(!function.hasOwnership || load.loadOwnership == .take) {
458458
foundTakingLoad = true
459459
}
460460
return .continueWalk

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeadStoreElimination.swift

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SIL
1414

1515
/// Eliminates dead store instructions.
1616
///
17-
/// A store is dead if after the store has occurred:
17+
/// A store is dead if, after the store has occurred:
1818
///
1919
/// 1. The value in memory is not read until the memory object is deallocated:
2020
///
@@ -47,6 +47,9 @@ import SIL
4747
/// ... // no reads from %1
4848
/// store %7 to %3
4949
///
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+
///
5053
let deadStoreElimination = FunctionPass(name: "dead-store-elimination") {
5154
(function: Function, context: FunctionPassContext) in
5255

@@ -85,7 +88,7 @@ private func tryEliminate(store: StoreInst, _ context: FunctionPassContext) {
8588
case .dead:
8689
// The new individual stores are inserted right after the current store and
8790
// will be optimized in the following loop iterations.
88-
store.trySplit(context)
91+
store.split(context)
8992
}
9093
}
9194
}
@@ -111,18 +114,24 @@ private extension StoreInst {
111114
}
112115

113116
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
116119

117-
worklist.pushIfNotVisited(self.next!)
120+
switch scanner.scan(instructions: InstructionList(first: self.next)) {
121+
case .dead:
122+
return .dead
118123

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() {
121134

122-
while let startInstInBlock = worklist.pop() {
123-
let block = startInstInBlock.parentBlock
124-
switch scanner.scan(instructions: InstructionList(first: startInstInBlock)) {
125-
case .transparent:
126135
// Abort if we find the storage definition of the access in case of a loop, e.g.
127136
//
128137
// bb1:
@@ -133,43 +142,45 @@ private extension StoreInst {
133142
//
134143
// The storage root is different in each loop iteration. Therefore the store of a
135144
// 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:
138154
return DataflowResult(aliveWith: scanner.potentiallyDeadSubpath)
139155
}
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)
145156
}
157+
return .dead
146158
}
147-
return .dead
148159
}
149160

150-
func trySplit(_ context: FunctionPassContext) {
161+
func split(_ context: FunctionPassContext) {
162+
let builder = Builder(after: self, context)
151163
let type = source.type
152164
if type.isStruct {
153-
let builder = Builder(after: self, context)
154165
for idx in 0..<type.getNominalFields(in: parentFunction).count {
155166
let srcField = builder.createStructExtract(struct: source, fieldIndex: idx)
156167
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)
158169
}
159-
context.erase(instruction: self)
160170
} else if type.isTuple {
161-
let builder = Builder(after: self, context)
162171
for idx in 0..<type.tupleElements.count {
163172
let srcField = builder.createTupleExtract(tuple: source, elementIndex: idx)
164173
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)
166175
}
167-
context.erase(instruction: self)
176+
} else {
177+
fatalError("a materializable projection path should only contain struct and tuple projections")
168178
}
179+
context.erase(instruction: self)
169180
}
170181

171182
var hasValidOwnershipForDeadStoreElimination: Bool {
172-
switch destinationOwnership {
183+
switch storeOwnership {
173184
case .unqualified, .trivial:
174185
return true
175186
case .initialize, .assign:
@@ -181,11 +192,11 @@ private extension StoreInst {
181192
}
182193

183194
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
187198

188-
var potentiallyDeadSubpath: AccessPath? = nil
199+
private(set) var potentiallyDeadSubpath: AccessPath? = nil
189200

190201
// Avoid quadratic complexity by limiting the number of visited instructions for each store.
191202
// The limit of 1000 instructions is not reached by far in "real-world" functions.
@@ -208,14 +219,16 @@ private struct InstructionScanner {
208219
switch inst {
209220
case let successiveStore as StoreInst:
210221
let successivePath = successiveStore.destination.accessPath
211-
if successivePath.isEqualOrOverlaps(storePath) {
222+
if successivePath.isEqualOrContains(storePath) {
212223
return .dead
213224
}
214-
if storePath.isEqualOrOverlaps(successivePath),
215-
potentiallyDeadSubpath == nil {
225+
if potentiallyDeadSubpath == nil,
226+
storePath.getMaterializableProjection(to: successivePath) != nil {
216227
// Storing to a sub-field of the original store doesn't make the original store dead.
217228
// But when we split the original store, then one of the new individual stores might be
218229
// overwritten by this store.
230+
// Requiring that the projection to the partial store path is materializable guarantees
231+
// that we can split the store.
219232
potentiallyDeadSubpath = successivePath
220233
}
221234
case is DeallocRefInst, is DeallocStackRefInst, is DeallocBoxInst:
@@ -241,6 +254,8 @@ private struct InstructionScanner {
241254
if inst.mayRead(fromAddress: storeAddress, aliasAnalysis) {
242255
return .alive
243256
}
257+
// TODO: We might detect that this is a partial read of the original store which potentially
258+
// enables partial dead store elimination.
244259
}
245260
}
246261
return .transparent

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyInitEnumDataAddr.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension InitEnumDataAddrInst : OnoneSimplifyable {
4343

4444
let builder = Builder(before: store, context)
4545
let enumInst = builder.createEnum(caseIndex: self.caseIndex, payload: store.source, enumType: self.enum.type.objectType)
46-
let storeOwnership = StoreInst.Ownership(for: self.enum.type, in: parentFunction, initialize: true)
46+
let storeOwnership = StoreInst.StoreOwnership(for: self.enum.type, in: parentFunction, initialize: true)
4747
builder.createStore(source: enumInst, destination: self.enum, ownership: storeOwnership)
4848
context.erase(instruction: store)
4949
context.erase(instruction: inject)

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable {
4545

4646
operand.set(to: uac.fromAddress, context)
4747
let builder = Builder(before: self, context)
48-
let newLoad = builder.createLoad(fromAddress: uac.fromAddress, ownership: ownership)
48+
let newLoad = builder.createLoad(fromAddress: uac.fromAddress, ownership: loadOwnership)
4949
let cast = builder.createUpcast(from: newLoad, to: type)
5050
uses.replaceAll(with: cast, context)
5151
context.erase(instruction: self)
@@ -184,7 +184,7 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable {
184184

185185
/// Removes the `load [copy]` if the loaded value is just destroyed.
186186
private func removeIfDead(_ context: SimplifyContext) {
187-
if ownership == .copy,
187+
if loadOwnership == .copy,
188188
loadedValueIsDead(context) {
189189
for use in uses {
190190
context.erase(instruction: use.instruction)

SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ enum AccessBase : CustomStringConvertible, Hashable {
162162
}
163163
}
164164

165-
// Returns true if it's guaranteed that this access has the same base address as the `other` access.
165+
/// Returns true if it's guaranteed that this access has the same base address as the `other` access.
166+
///
167+
/// `isEqual` abstracts away the projection instructions that are included as part of the AccessBase:
168+
/// multiple `project_box` and `ref_element_addr` instructions are equivalent bases as long as they
169+
/// refer to the same variable or class property.
166170
func isEqual(to other: AccessBase) -> Bool {
167171
switch (self, other) {
168172
case (.box(let pb1), .box(let pb2)):
@@ -275,11 +279,52 @@ struct AccessPath : CustomStringConvertible {
275279
return false
276280
}
277281

278-
func isEqualOrOverlaps(_ other: AccessPath) -> Bool {
279-
return base.isEqual(to: other.base) &&
280-
// Note: an access with a smaller path overlaps an access with larger path, e.g.
281-
// `s0` overlaps `s0.s1`
282-
projectionPath.isSubPath(of: other.projectionPath)
282+
/// Returns true if this access addresses the same memory location as `other` or if `other`
283+
/// is a sub-field of this access.
284+
285+
/// Note that this access _contains_ `other` if `other` has a _larger_ projection path than this acccess.
286+
/// For example:
287+
/// `%value.s0` contains `%value.s0.s1`
288+
func isEqualOrContains(_ other: AccessPath) -> Bool {
289+
return getProjection(to: other) != nil
290+
}
291+
292+
/// Returns the projection path to `other` if this access path is equal or contains `other`.
293+
///
294+
/// For example,
295+
/// `%value.s0`.getProjection(to: `%value.s0.s1`)
296+
/// yields
297+
/// `s1`
298+
func getProjection(to other: AccessPath) -> SmallProjectionPath? {
299+
if !base.isEqual(to: other.base) {
300+
return nil
301+
}
302+
return projectionPath.subtract(from: other.projectionPath)
303+
}
304+
305+
/// Like `getProjection`, but also requires that the resulting projection path is materializable.
306+
func getMaterializableProjection(to other: AccessPath) -> SmallProjectionPath? {
307+
if let projectionPath = getProjection(to: other),
308+
projectionPath.isMaterializable {
309+
return projectionPath
310+
}
311+
return nil
312+
}
313+
}
314+
315+
private extension SmallProjectionPath {
316+
/// Returns true if the path only contains projections which can be materialized as
317+
/// SIL struct or tuple projection instructions - for values or addresses.
318+
var isMaterializable: Bool {
319+
let (kind, _, subPath) = pop()
320+
switch kind {
321+
case .root:
322+
return true
323+
case .structField, .tupleField:
324+
return subPath.isMaterializable
325+
default:
326+
return false
327+
}
283328
}
284329
}
285330

SwiftCompilerSources/Sources/Optimizer/Utilities/EscapeUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
435435
case is StoreInst, is StoreWeakInst, is StoreUnownedInst:
436436
let store = instruction as! StoringInstruction
437437
assert(operand == store.destinationOperand)
438-
if let si = store as? StoreInst, si.destinationOwnership == .assign {
438+
if let si = store as? StoreInst, si.storeOwnership == .assign {
439439
if handleDestroy(of: operand.value, path: path.with(knownType: nil)) == .abortWalk {
440440
return .abortWalk
441441
}

SwiftCompilerSources/Sources/SIL/Builder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public struct Builder {
113113
return notifyNew(cast.getAs(UpcastInst.self))
114114
}
115115

116-
public func createLoad(fromAddress: Value, ownership: LoadInst.Ownership) -> LoadInst {
116+
public func createLoad(fromAddress: Value, ownership: LoadInst.LoadOwnership) -> LoadInst {
117117
let load = bridged.createLoad(fromAddress.bridged, ownership.rawValue)
118118
return notifyNew(load.getAs(LoadInst.self))
119119
}
@@ -274,7 +274,7 @@ public struct Builder {
274274
}
275275

276276
@discardableResult
277-
public func createStore(source: Value, destination: Value, ownership: StoreInst.Ownership) -> StoreInst {
277+
public func createStore(source: Value, destination: Value, ownership: StoreInst.StoreOwnership) -> StoreInst {
278278
let store = bridged.createStore(source.bridged, destination.bridged, ownership.rawValue)
279279
return notifyNew(store.getAs(StoreInst.self))
280280
}

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ extension StoringInstruction {
228228

229229
final public class StoreInst : Instruction, StoringInstruction {
230230
// must match with enum class StoreOwnershipQualifier
231-
public enum Ownership: Int {
231+
public enum StoreOwnership: Int {
232232
case unqualified = 0, initialize = 1, assign = 2, trivial = 3
233233

234234
public init(for type: Type, in function: Function, initialize: Bool) {
@@ -243,8 +243,8 @@ final public class StoreInst : Instruction, StoringInstruction {
243243
}
244244
}
245245
}
246-
public var destinationOwnership: Ownership {
247-
Ownership(rawValue: bridged.StoreInst_getStoreOwnership())!
246+
public var storeOwnership: StoreOwnership {
247+
StoreOwnership(rawValue: bridged.StoreInst_getStoreOwnership())!
248248
}
249249
}
250250

@@ -387,11 +387,11 @@ final public class LoadInst : SingleValueInstruction, UnaryInstruction {
387387
public var address: Value { operand.value }
388388

389389
// must match with enum class LoadOwnershipQualifier
390-
public enum Ownership: Int {
390+
public enum LoadOwnership: Int {
391391
case unqualified = 0, take = 1, copy = 2, trivial = 3
392392
}
393-
public var ownership: Ownership {
394-
Ownership(rawValue: bridged.LoadInst_getLoadOwnership())!
393+
public var loadOwnership: LoadOwnership {
394+
LoadOwnership(rawValue: bridged.LoadInst_getLoadOwnership())!
395395
}
396396
}
397397

0 commit comments

Comments
 (0)