Skip to content

Commit fe76542

Browse files
authored
Merge pull request #84820 from eeckstein/fix-temp-rvalue-elimination
TempRValueElimination: fix a problem with non-copyable types
2 parents 1ba1521 + 5104c31 commit fe76542

File tree

2 files changed

+78
-53
lines changed

2 files changed

+78
-53
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
222224
private extension AllocStackInst {
223225
func hasUses(before instruction: Instruction, _ context: FunctionPassContext) -> Bool {
224226
var useSet = InstructionSet(context)

test/SILOptimizer/temp_rvalue_opt_ossa.sil

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public enum FakeOptional<T> {
4242

4343
struct MOS: ~Copyable {}
4444

45+
struct NCWithDeinit : ~Copyable {
46+
var x: Int
47+
48+
deinit
49+
}
50+
51+
4552
protocol P {
4653
func foo()
4754
}
@@ -1332,6 +1339,22 @@ sil [ossa] @take_from_original_copy_addr__final_use_apply__move_only : $() -> ()
13321339
return %tuple : $()
13331340
}
13341341

1342+
// CHECK-LABEL: sil [ossa] @missing_destroy :
1343+
// CHECK-NOT: destroy_addr
1344+
// CHECK-NOT: copy_addr
1345+
// CHECK: = drop_deinit %0
1346+
// CHECK-NEXT: = tuple ()
1347+
// CHECK-LABEL: } // end sil function 'missing_destroy'
1348+
sil [ossa] @missing_destroy : $@convention(thin) (@in NCWithDeinit) -> () {
1349+
bb0(%0 : $*NCWithDeinit):
1350+
%1 = alloc_stack $NCWithDeinit
1351+
copy_addr [take] %0 to [init] %1
1352+
%3 = drop_deinit %1
1353+
dealloc_stack %1
1354+
%5 = tuple ()
1355+
return %5
1356+
}
1357+
13351358
// CHECK-LABEL: sil [ossa] @test_temprvoborrowboundary1 :
13361359
// CHECK-NOT: alloc_stack
13371360
// CHECK-NOT: copy_addr

0 commit comments

Comments
 (0)