@@ -34,54 +34,26 @@ import SIL
3434let releaseDevirtualizerPass = FunctionPass ( name: " release-devirtualizer " ) {
3535 ( function: Function , context: FunctionPassContext ) in
3636
37- for block in function. blocks {
38- // The last `release_value`` or `strong_release`` instruction before the
39- // deallocation.
40- var lastRelease : RefCountingInst ?
41-
42- for instruction in block. instructions {
43- switch instruction {
44- case let dealloc as DeallocStackRefInst :
45- if let lastRel = lastRelease {
46- // We only do the optimization for stack promoted object, because for
47- // these we know that they don't have associated objects, which are
48- // _not_ released by the deinit method.
49- if !context. continueWithNextSubpassRun ( for: lastRel) {
50- return
51- }
52- tryDevirtualizeRelease ( of: dealloc. allocRef, lastRelease: lastRel, context)
53- lastRelease = nil
54- }
55- case let strongRelease as StrongReleaseInst :
56- lastRelease = strongRelease
57- case let releaseValue as ReleaseValueInst where releaseValue. value. type. containsSingleReference ( in: function) :
58- lastRelease = releaseValue
59- case is DeallocRefInst , is BeginDeallocRefInst :
60- lastRelease = nil
61- default :
62- if instruction. mayRelease {
63- lastRelease = nil
64- }
37+ for inst in function. instructions {
38+ if let dealloc = inst as? DeallocStackRefInst {
39+ if !context. continueWithNextSubpassRun ( for: dealloc) {
40+ return
6541 }
42+ tryDevirtualizeRelease ( of: dealloc, context)
6643 }
6744 }
6845}
6946
70- /// Tries to de-virtualize the final release of a stack-promoted object.
71- private func tryDevirtualizeRelease(
72- of allocRef: AllocRefInstBase ,
73- lastRelease: RefCountingInst ,
74- _ context: FunctionPassContext
75- ) {
76- var downWalker = FindReleaseWalker ( release: lastRelease)
77- guard let pathToRelease = downWalker. getPathToRelease ( from: allocRef) else {
47+ private func tryDevirtualizeRelease( of dealloc: DeallocStackRefInst , _ context: FunctionPassContext ) {
48+ guard let ( lastRelease, pathToRelease) = findLastRelease ( of: dealloc, context) else {
7849 return
7950 }
8051
8152 if !pathToRelease. isMaterializable {
8253 return
8354 }
8455
56+ let allocRef = dealloc. allocRef
8557 var upWalker = FindAllocationWalker ( allocation: allocRef)
8658 if upWalker. walkUp ( value: lastRelease. operand. value, path: pathToRelease) == . abortWalk {
8759 return
@@ -120,23 +92,62 @@ private func tryDevirtualizeRelease(
12092 context. erase ( instruction: lastRelease)
12193}
12294
95+ private func findLastRelease(
96+ of dealloc: DeallocStackRefInst ,
97+ _ context: FunctionPassContext
98+ ) -> ( lastRelease: RefCountingInst , pathToRelease: SmallProjectionPath ) ? {
99+ let allocRef = dealloc. allocRef
100+
101+ // Search for the final release in the same basic block of the dealloc.
102+ for instruction in ReverseInstructionList ( first: dealloc. previous) {
103+ switch instruction {
104+ case let strongRelease as StrongReleaseInst :
105+ if let pathToRelease = getPathToRelease ( from: allocRef, to: strongRelease) {
106+ return ( strongRelease, pathToRelease)
107+ }
108+ case let releaseValue as ReleaseValueInst :
109+ if releaseValue. value. type. containsSingleReference ( in: dealloc. parentFunction) {
110+ if let pathToRelease = getPathToRelease ( from: allocRef, to: releaseValue) {
111+ return ( releaseValue, pathToRelease)
112+ }
113+ }
114+ case is BeginDeallocRefInst , is DeallocRefInst :
115+ // Check if the last release was already de-virtualized.
116+ if allocRef. escapes ( to: instruction, context) {
117+ return nil
118+ }
119+ default :
120+ break
121+ }
122+ if instruction. mayRelease && allocRef. escapes ( to: instruction, context) {
123+ // This instruction may release the allocRef, which means that any release we find
124+ // earlier in the block is not guaranteed to be the final release.
125+ return nil
126+ }
127+ }
128+ return nil
129+ }
130+
131+ // If the release is a release_value it might release a struct which _contains_ the allocated object.
132+ // Return a projection path to the contained object in this case.
133+ private func getPathToRelease( from allocRef: AllocRefInstBase , to release: RefCountingInst ) -> SmallProjectionPath ? {
134+ var downWalker = FindReleaseWalker ( release: release)
135+ if downWalker. walkDownUses ( ofValue: allocRef, path: SmallProjectionPath ( ) ) == . continueWalk {
136+ return downWalker. result
137+ }
138+ return nil
139+ }
140+
123141private struct FindReleaseWalker : ValueDefUseWalker {
124142 private let release : RefCountingInst
125- private var result : SmallProjectionPath ? = nil
143+ private( set ) var result : SmallProjectionPath ? = nil
126144
127145 var walkDownCache = WalkerCache < SmallProjectionPath > ( )
128146
129147 init ( release: RefCountingInst ) {
130148 self . release = release
131149 }
132150
133- mutating func getPathToRelease( from allocRef: AllocRefInstBase ) -> SmallProjectionPath ? {
134- if walkDownUses ( ofValue: allocRef, path: SmallProjectionPath ( ) ) == . continueWalk {
135- return result
136- }
137- return nil
138- }
139-
140151 mutating func leafUse( value: Operand , path: SmallProjectionPath ) -> WalkResult {
141152 if value. instruction == release {
142153 if let existingResult = result {
@@ -149,6 +160,23 @@ private struct FindReleaseWalker : ValueDefUseWalker {
149160 }
150161}
151162
163+ private extension AllocRefInstBase {
164+ func escapes( to instruction: Instruction , _ context: FunctionPassContext ) -> Bool {
165+ return self . isEscaping ( using: EscapesToInstructionVisitor ( target: instruction) , context)
166+ }
167+ }
168+
169+ private struct EscapesToInstructionVisitor : EscapeVisitor {
170+ let target : Instruction
171+
172+ mutating func visitUse( operand: Operand , path: EscapePath ) -> UseResult {
173+ if operand. instruction == target {
174+ return . abort
175+ }
176+ return . continueWalk
177+ }
178+ }
179+
152180// Up-walker to find the root of a release instruction.
153181private struct FindAllocationWalker : ValueUseDefWalker {
154182 private let allocInst : AllocRefInstBase
0 commit comments