@@ -77,45 +77,19 @@ private func tryEliminate(copy: CopyLikeInstruction, keepDebugInfo: Bool, _ cont
7777 return
7878 }
7979
80- if needToInsertDestroy ( copy: copy , lastUseOfAllocStack : lastUseOfAllocStack ) {
81- Builder . insert ( after: lastUseOfAllocStack, context) { builder in
82- builder . createDestroyAddr ( address : copy . sourceAddress )
83- }
80+ if copy. isTakeOfSource {
81+ hoistDestroy ( of : allocStack , after: lastUseOfAllocStack, context)
82+ } else {
83+ removeDestroys ( of : allocStack , context )
8484 }
8585
86- for use in allocStack. uses {
87- switch use. instruction {
88- case is DestroyAddrInst , is DeallocStackInst :
89- context. erase ( instruction: use. instruction)
90- case let cai as CopyAddrInst where cai != copy:
91- if cai. isTakeOfSource, !copy. isTakeOfSource {
92- // If the original copy is not taking its source, a `copy_addr` from the `allocStack` must
93- // not take its source either.
94- assert ( cai == lastUseOfAllocStack && cai. source == allocStack)
95- cai. set ( isTakeOfSource: false , context)
96- }
97- use. set ( to: copy. sourceAddress, context)
98- case let load as LoadInst :
99- if load. loadOwnership == . take, !copy. isTakeOfSource {
100- // If the original copy is not taking its source, a `load` from the `allocStack` must
101- // not take its source either.
102- assert ( load == lastUseOfAllocStack)
103- load. set ( ownership: . copy, context)
104- }
105- use. set ( to: copy. sourceAddress, context) ;
86+ allocStack. uses. ignoreUses ( ofType: DeallocStackInst . self) . replaceAll ( with: copy. sourceAddress, context)
10687
107- default :
108- // Note that no operations that may be handled by this default clause can destroy the `allocStack`.
109- // This includes operations that load the value from memory and release it or cast the address
110- // before destroying it.
111- use. set ( to: copy. sourceAddress, context) ;
112- }
113- }
114- context. erase ( instruction: allocStack)
11588 if keepDebugInfo {
11689 Builder ( before: copy, context) . createDebugStep ( )
11790 }
11891 context. erase ( instructionIncludingAllUsers: copy. loadingInstruction)
92+ context. erase ( instructionIncludingAllUsers: allocStack)
11993}
12094
12195/// Checks if the `copy` is copying into an `alloc_stack` which is removable:
@@ -193,32 +167,60 @@ private func getRemovableAllocStackDestination(
193167 return ( allocStack, lastUseOfAllocStack)
194168}
195169
196- /// We need to insert a final destroy if the original `copy` is taking the source but the
197- /// `lastUseOfAllocStack` is not taking the `alloc_stack`.
198- private func needToInsertDestroy( copy: CopyLikeInstruction , lastUseOfAllocStack: Instruction ) -> Bool {
199- if !copy. isTakeOfSource {
200- return false
170+ // We need to hoist the destroy of the stack location up to its last use because the source of the copy
171+ // could be re-initialized between the last use and the destroy.
172+ private func hoistDestroy( of allocStack: AllocStackInst , after lastUse: Instruction , _ context: FunctionPassContext ) {
173+ // Check if the last use already takes the stack location,
174+ switch lastUse {
175+ case let cai as CopyAddrInst where cai. source == allocStack && cai. isTakeOfSource:
176+ return
177+ case let li as LoadInst where li. loadOwnership == . take:
178+ assert ( li. address == allocStack, " load must be not take a projected address " )
179+ return
180+ default :
181+ break
201182 }
202- switch lastUseOfAllocStack {
203- case copy:
204- return true
205- case let cai as CopyAddrInst :
206- if cai. isTakeOfSource {
207- assert ( cai. source == copy. destinationAddress, " copy_addr must be not take a projected address " )
208- return false
209- }
210- return true
211- case let li as LoadInst :
212- if li. loadOwnership == . take {
213- assert ( li. address == copy. destinationAddress, " load must be not take a projected address " )
214- return false
183+ if allocStack. uses. users ( ofType: DestroyAddrInst . self) . isEmpty {
184+ // The stack location is not destroyed at all! This can happen if it contains a non-copyable
185+ // value which has only trivial fields.
186+ return
187+ }
188+
189+ // Note that there can be multiple destroy_addr because they don't need to be in the same block
190+ // as the alloc_stack, e.g.
191+ // %1 = alloc_stack
192+ // cond_br %c, bb1, bb2
193+ // bb1:
194+ // destroy_addr %1
195+ // bb2:
196+ // destroy_addr %1
197+ context. erase ( instructions: allocStack. uses. users ( ofType: DestroyAddrInst . self) )
198+
199+ Builder . insert ( after: lastUse, context) { builder in
200+ builder. createDestroyAddr ( address: allocStack)
201+ }
202+ }
203+
204+ private func removeDestroys( of allocStack: AllocStackInst , _ context: FunctionPassContext ) {
205+ for use in allocStack. uses {
206+ switch use. instruction {
207+ case is DestroyAddrInst :
208+ context. erase ( instruction: use. instruction)
209+ case let cai as CopyAddrInst where cai. isTakeOfSource:
210+ assert ( cai. source == allocStack, " partial takes of the stack location are not supported " )
211+ cai. set ( isTakeOfSource: false , context)
212+ case let load as LoadInst where load. loadOwnership == . take:
213+ assert ( load. address == allocStack, " partial takes of the stack location are not supported " )
214+ load. set ( ownership: . copy, context)
215+ default :
216+ // Note that no operations other than the cases above can destroy the `allocStack` (we checked
217+ // this in the `UseCollector`).
218+ break
215219 }
216- return true
217- default :
218- return true
219220 }
220221}
221222
223+
222224private extension AllocStackInst {
223225 func hasUses( before instruction: Instruction , _ context: FunctionPassContext ) -> Bool {
224226 var useSet = InstructionSet ( context)
0 commit comments