@@ -39,126 +39,150 @@ let releaseDevirtualizerPass = FunctionPass(name: "release-devirtualizer") {
39
39
var lastRelease : RefCountingInst ?
40
40
41
41
for instruction in block. instructions {
42
- if let release = lastRelease {
43
- // We only do the optimization for stack promoted object, because for
44
- // these we know that they don't have associated objects, which are
45
- // _not_ released by the deinit method.
46
- if let deallocStackRef = instruction as? DeallocStackRefInst {
47
- if !context. continueWithNextSubpassRun ( for: release) {
42
+ switch instruction {
43
+ case let dealloc as DeallocStackRefInst :
44
+ if let lastRel = lastRelease {
45
+ // We only do the optimization for stack promoted object, because for
46
+ // these we know that they don't have associated objects, which are
47
+ // _not_ released by the deinit method.
48
+ if !context. continueWithNextSubpassRun ( for: lastRel) {
48
49
return
49
50
}
50
- tryDevirtualizeReleaseOfObject ( context , release , deallocStackRef )
51
+ tryDevirtualizeRelease ( of : dealloc . allocRef , lastRelease : lastRel , context )
51
52
lastRelease = nil
52
- continue
53
53
}
54
- }
55
-
56
- switch instruction {
57
- case is ReleaseValueInst , is StrongReleaseInst :
58
- lastRelease = instruction as? RefCountingInst
59
- case is DeallocRefInst , is SetDeallocatingInst :
54
+ case let strongRelease as StrongReleaseInst :
55
+ lastRelease = strongRelease
56
+ case let releaseValue as ReleaseValueInst where releaseValue. value. type. containsSingleReference ( in: function) :
57
+ lastRelease = releaseValue
58
+ case is DeallocRefInst , is BeginDeallocRefInst :
59
+ lastRelease = nil
60
+ default :
61
+ if instruction. mayRelease {
60
62
lastRelease = nil
61
- default :
62
- if instruction. mayRelease {
63
- lastRelease = nil
64
- }
63
+ }
65
64
}
66
65
}
67
66
}
68
67
}
69
68
70
69
/// Tries to de-virtualize the final release of a stack-promoted object.
71
- private func tryDevirtualizeReleaseOfObject (
72
- _ context : FunctionPassContext ,
73
- _ release : RefCountingInst ,
74
- _ deallocStackRef : DeallocStackRefInst
70
+ private func tryDevirtualizeRelease (
71
+ of allocRef : AllocRefInstBase ,
72
+ lastRelease : RefCountingInst ,
73
+ _ context : FunctionPassContext
75
74
) {
76
- let allocRefInstruction = deallocStackRef. allocRef
75
+ var downWalker = FindReleaseWalker ( release: lastRelease)
76
+ guard let pathToRelease = downWalker. getPathToRelease ( from: allocRef) else {
77
+ return
78
+ }
79
+
80
+ if !pathToRelease. isMaterializable {
81
+ return
82
+ }
77
83
78
- // Check if the release instruction right before the `dealloc_stack_ref` really releases
79
- // the allocated object (and not something else).
80
- var finder = FindAllocationOfRelease ( allocation: allocRefInstruction)
81
- if !finder. allocationIsRoot ( of: release. operand. value) {
84
+ var upWalker = FindAllocationWalker ( allocation: allocRef)
85
+ if upWalker. walkUp ( value: lastRelease. operand. value, path: pathToRelease) == . abortWalk {
82
86
return
83
87
}
84
88
85
- let type = allocRefInstruction . type
89
+ let type = allocRef . type
86
90
87
91
guard let dealloc = context. calleeAnalysis. getDestructor ( ofExactType: type) else {
88
92
return
89
93
}
90
94
91
- let builder = Builder ( before: release , location: release . location, context)
95
+ let builder = Builder ( before: lastRelease , location: lastRelease . location, context)
92
96
93
- var object : Value = allocRefInstruction
97
+ var object = lastRelease . operand . value . createProjection ( path : pathToRelease , builder : builder )
94
98
if object. type != type {
95
99
object = builder. createUncheckedRefCast ( from: object, to: type)
96
100
}
97
101
98
102
// Do what a release would do before calling the deallocator: set the object
99
103
// in deallocating state, which means set the RC_DEALLOCATING_FLAG flag.
100
- builder. createSetDeallocating ( operand : object, isAtomic : release . isAtomic )
104
+ let beginDealloc = builder. createBeginDeallocRef ( reference : object, allocation : allocRef )
101
105
102
106
// Create the call to the destructor with the allocated object as self
103
107
// argument.
104
108
let functionRef = builder. createFunctionRef ( dealloc)
105
109
106
110
let substitutionMap = context. getContextSubstitutionMap ( for: type)
107
- builder. createApply ( function: functionRef, substitutionMap, arguments: [ object] )
108
- context. erase ( instruction: release)
111
+ builder. createApply ( function: functionRef, substitutionMap, arguments: [ beginDealloc] )
112
+ context. erase ( instruction: lastRelease)
113
+ }
114
+
115
+ private struct FindReleaseWalker : ValueDefUseWalker {
116
+ private let release : RefCountingInst
117
+ private var result : SmallProjectionPath ? = nil
118
+
119
+ var walkDownCache = WalkerCache < SmallProjectionPath > ( )
120
+
121
+ init ( release: RefCountingInst ) {
122
+ self . release = release
123
+ }
124
+
125
+ mutating func getPathToRelease( from allocRef: AllocRefInstBase ) -> SmallProjectionPath ? {
126
+ if walkDownUses ( ofValue: allocRef, path: SmallProjectionPath ( ) ) == . continueWalk {
127
+ return result
128
+ }
129
+ return nil
130
+ }
131
+
132
+ mutating func leafUse( value: Operand , path: SmallProjectionPath ) -> WalkResult {
133
+ if value. instruction == release {
134
+ if let existingResult = result {
135
+ result = existingResult. merge ( with: path)
136
+ } else {
137
+ result = path
138
+ }
139
+ }
140
+ return . continueWalk
141
+ }
109
142
}
110
143
111
144
// Up-walker to find the root of a release instruction.
112
- private struct FindAllocationOfRelease : ValueUseDefWalker {
145
+ private struct FindAllocationWalker : ValueUseDefWalker {
113
146
private let allocInst : AllocRefInstBase
114
- private var allocFound = false
115
147
116
- var walkUpCache = WalkerCache < UnusedWalkingPath > ( )
148
+ var walkUpCache = WalkerCache < SmallProjectionPath > ( )
117
149
118
150
init ( allocation: AllocRefInstBase ) { allocInst = allocation }
119
151
120
- /// The top-level entry point: returns true if the root of `value` is the `allocInst`.
121
- mutating func allocationIsRoot( of value: Value ) -> Bool {
122
- return walkUp ( value: value, path: UnusedWalkingPath ( ) ) != . abortWalk &&
123
- allocFound
152
+ mutating func rootDef( value: Value , path: SmallProjectionPath ) -> WalkResult {
153
+ return value == allocInst && path. isEmpty ? . continueWalk : . abortWalk
124
154
}
155
+ }
125
156
126
- mutating func rootDef( value: Value , path: UnusedWalkingPath ) -> WalkResult {
127
- if value == allocInst {
128
- allocFound = true
129
- return . continueWalk
157
+ private extension Type {
158
+ func containsSingleReference( in function: Function ) -> Bool {
159
+ if isClass {
160
+ return true
161
+ }
162
+ if isStruct {
163
+ return getNominalFields ( in: function) . containsSingleReference ( in: function)
164
+ } else if isTuple {
165
+ return tupleElements. containsSingleReference ( in: function)
166
+ } else {
167
+ return false
130
168
}
131
- return . abortWalk
132
169
}
170
+ }
133
171
134
- // This function is called for `struct` and `tuple` instructions in case the `path` doesn't select
135
- // a specific operand but all operands.
136
- mutating func walkUpAllOperands( of def: Instruction , path: UnusedWalkingPath ) -> WalkResult {
137
-
138
- // Instead of walking up _all_ operands (which would be the default behavior), require that only a
139
- // _single_ operand is not trivial and walk up that operand.
140
- // We need to check this because the released value must not contain multiple copies of the
141
- // allocated object. We can only replace a _single_ release with the destructor call and not
142
- // multiple releases of the same object. E.g.
143
- //
144
- // %x = alloc_ref [stack] $X
145
- // strong_retain %x
146
- // %t = tuple (%x, %x)
147
- // release_value %t // -> releases %x two times!
148
- // dealloc_stack_ref %x
149
- //
150
- var nonTrivialOperandFound = false
151
- for operand in def. operands {
152
- if !operand. value. hasTrivialType {
153
- if nonTrivialOperandFound {
154
- return . abortWalk
172
+ private extension Collection where Element == Type {
173
+ func containsSingleReference( in function: Function ) -> Bool {
174
+ var nonTrivialFieldFound = false
175
+ for elementTy in self {
176
+ if !elementTy. isTrivial ( in: function) {
177
+ if nonTrivialFieldFound {
178
+ return false
155
179
}
156
- nonTrivialOperandFound = true
157
- if walkUp ( value: operand. value, path: path) == . abortWalk {
158
- return . abortWalk
180
+ if !elementTy. containsSingleReference ( in: function) {
181
+ return false
159
182
}
183
+ nonTrivialFieldFound = true
160
184
}
161
185
}
162
- return . continueWalk
186
+ return nonTrivialFieldFound
163
187
}
164
188
}
0 commit comments