@@ -69,17 +69,21 @@ private func removeTempRValues(in function: Function, keepDebugInfo: Bool, _ con
6969private func tryEliminate( copy: CopyLikeInstruction , keepDebugInfo: Bool , _ context: FunctionPassContext ) {
7070
7171 guard let ( allocStack, lastUseOfAllocStack) =
72- getRemovableAllocStackDestination ( of: copy, keepDebugInfo: keepDebugInfo, context) else {
72+ getRemovableAllocStackDestination ( of: copy, keepDebugInfo: keepDebugInfo, context) ,
73+ let needHoistDestroys = needHoistDestroys ( of: allocStack, after: lastUseOfAllocStack, copy: copy, context)
74+ else {
7375 return
7476 }
7577
7678 guard extendAccessScopes ( beyond: lastUseOfAllocStack, copy: copy, context) else {
7779 return
7880 }
7981
80- if copy. isTakeOfSource {
82+ if needHoistDestroys {
83+ // Hoist destroy_addrs after the last use, because between the last use and the original destroy
84+ // the source is modified.
8185 hoistDestroy ( of: allocStack, after: lastUseOfAllocStack, context)
82- } else {
86+ } else if !copy . isTakeOfSource {
8387 removeDestroys ( of: allocStack, context)
8488 }
8589
@@ -140,21 +144,6 @@ private func getRemovableAllocStackDestination(
140144 return nil
141145 }
142146
143- // We cannot insert the destroy of `copy.source` after `lastUseOfAllocStack` if `copy.source` is
144- // re-initialized by exactly this instruction.
145- // This is a corner case, but can happen if `lastUseOfAllocStack` is a `copy_addr`. Example:
146- // ```
147- // copy_addr [take] %src to [init] %allocStack // `copy`
148- // copy_addr [take] %allocStack to [init] %src // `lastUseOfAllocStack`
149- // ```
150- if copy. isTakeOfSource,
151- lastUseOfAllocStack != copy,
152- !( lastUseOfAllocStack is DestroyAddrInst ) ,
153- lastUseOfAllocStack. mayWrite ( toAddress: copy. sourceAddress, context. aliasAnalysis)
154- {
155- return nil
156- }
157-
158147 // Bail if in non-OSSA the `allocStack` is destroyed in a non-obvious way, e.g. by
159148 // ```
160149 // %x = load %allocStack // looks like a load, but is a `load [take]`
@@ -171,6 +160,91 @@ private func getRemovableAllocStackDestination(
171160 return ( allocStack, lastUseOfAllocStack)
172161}
173162
163+ /// Returns true if the final `destroy_addr`s need to be hoisted to the last use of the `allocStack`.
164+ /// This is required if the copy-source is re-initialized inbetween, e.g.
165+ /// ```
166+ /// copy_addr %source, %allocStack
167+ /// ...
168+ /// last_use(%allocStack)
169+ /// ... <- the destroy_addr needs to be moved here
170+ /// store %newValue to [init] %source
171+ /// ...
172+ /// destroy_addr %allocStack
173+ /// ```
174+ /// Returns nil if hoisting is needed but not possible.
175+ private func needHoistDestroys( of allocStack: AllocStackInst ,
176+ after lastUseOfAllocStack: Instruction ,
177+ copy: CopyLikeInstruction ,
178+ _ context: FunctionPassContext
179+ ) -> Bool ? {
180+ guard copy. isTakeOfSource else {
181+ return false
182+ }
183+ if lastUseOfAllocStack is DestroyAddrInst {
184+ assert ( !copy. parentFunction. hasOwnership, " should not treat destroy_addr as uses in OSSA " )
185+ return false
186+ }
187+
188+ // We cannot insert the destroy of `copy.source` after `lastUseOfAllocStack` if `copy.source` is
189+ // re-initialized by exactly this instruction.
190+ // This is a corner case, but can happen if `lastUseOfAllocStack` is a `copy_addr`. Example:
191+ // ```
192+ // copy_addr [take] %src to [init] %allocStack // `copy`
193+ // copy_addr [take] %allocStack to [init] %src // `lastUseOfAllocStack`
194+ // ```
195+ if lastUseOfAllocStack != copy, lastUseOfAllocStack. mayWrite ( toAddress: copy. sourceAddress, context. aliasAnalysis) {
196+ return nil
197+ }
198+
199+ if mayWrite ( to: copy. sourceAddress, after: lastUseOfAllocStack, beforeDestroysOf: allocStack, context) {
200+ if hasDestroyBarrier ( after: lastUseOfAllocStack, beforeDestroysOf: allocStack, context) {
201+ return nil
202+ }
203+ return true
204+ }
205+ return false
206+ }
207+
208+ private func mayWrite( to source: Value ,
209+ after lastUse: Instruction ,
210+ beforeDestroysOf allocStack: AllocStackInst ,
211+ _ context: FunctionPassContext
212+ ) -> Bool {
213+ return visitInstructions ( after: lastUse, beforeDestroysOf: allocStack, context) { inst in
214+ inst. mayWrite ( toAddress: source, context. aliasAnalysis)
215+ }
216+ }
217+
218+ private func hasDestroyBarrier( after lastUse: Instruction ,
219+ beforeDestroysOf allocStack: AllocStackInst ,
220+ _ context: FunctionPassContext
221+ ) -> Bool {
222+ return visitInstructions ( after: lastUse, beforeDestroysOf: allocStack, context) { inst in
223+ inst. isBarrierForDestroy ( of: allocStack. type, context)
224+ }
225+ }
226+
227+ private func visitInstructions( after lastUse: Instruction ,
228+ beforeDestroysOf allocStack: AllocStackInst ,
229+ _ context: FunctionPassContext ,
230+ _ predicate: ( Instruction ) -> Bool
231+ ) -> Bool {
232+ var worklist = InstructionWorklist ( context)
233+ defer { worklist. deinitialize ( ) }
234+
235+ for destroy in allocStack. uses. users ( ofType: DestroyAddrInst . self) {
236+ worklist. pushPredecessors ( of: destroy, ignoring: lastUse)
237+ }
238+
239+ while let inst = worklist. pop ( ) {
240+ if predicate ( inst) {
241+ return true
242+ }
243+ worklist. pushPredecessors ( of: inst, ignoring: lastUse)
244+ }
245+ return false
246+ }
247+
174248// We need to hoist the destroy of the stack location up to its last use because the source of the copy
175249// could be re-initialized between the last use and the destroy.
176250private func hoistDestroy( of allocStack: AllocStackInst , after lastUse: Instruction , _ context: FunctionPassContext ) {
0 commit comments