Skip to content

Commit 7493b7e

Browse files
committed
[CopyPropagation] Correctly recorded destroys.
Now that destory_values implicitly end borrow scopes for `partial_apply [on_stack]`s, they show up as users of values whose lifetimes are being canonicalized. Handle that properly by (1) only adding the `destroy_value`s whose operand is a transitive copy of the def to the set of destroys (2) not considering a `destroy_value` with another operand (i.e. one which does not destroy a transitive copy of the def) to be lifetime ending rdar://107198526
1 parent 0b09ed6 commit 7493b7e

File tree

2 files changed

+94
-14
lines changed

2 files changed

+94
-14
lines changed

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,22 @@ static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
101101
Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
102102
}
103103

104-
static DestroyValueInst *dynCastToDestroyOf(SILInstruction *instruction,
105-
SILValue def) {
104+
/// Is \p instruction a destroy_value whose operand is \p def, or its
105+
/// transitive copy.
106+
static bool isDestroyOfCopyOf(SILInstruction *instruction, SILValue def) {
106107
auto *destroy = dyn_cast<DestroyValueInst>(instruction);
107108
if (!destroy)
108-
return nullptr;
109-
auto originalDestroyedDef = destroy->getOperand();
110-
if (originalDestroyedDef == def)
111-
return destroy;
112-
auto underlyingDestroyedDef =
113-
CanonicalizeOSSALifetime::getCanonicalCopiedDef(originalDestroyedDef);
114-
if (underlyingDestroyedDef != def)
115-
return nullptr;
116-
return destroy;
109+
return false;
110+
auto destroyed = destroy->getOperand();
111+
while (true) {
112+
if (destroyed == def)
113+
return true;
114+
auto *copy = dyn_cast<CopyValueInst>(destroyed);
115+
if (!copy)
116+
break;
117+
destroyed = copy->getOperand();
118+
}
119+
return false;
117120
}
118121

119122
//===----------------------------------------------------------------------===//
@@ -172,11 +175,18 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
172175
liveness->updateForUse(user, /*lifetimeEnding*/ true);
173176
break;
174177
case OperandOwnership::DestroyingConsume:
175-
if (isa<DestroyValueInst>(user)) {
178+
if (isDestroyOfCopyOf(user, getCurrentDef())) {
176179
destroys.insert(user);
177180
} else {
178-
// destroy_value does not force pruned liveness (but store etc. does).
179-
liveness->updateForUse(user, /*lifetimeEnding*/ true);
181+
// destroy_value of a transitive copy of the currentDef does not
182+
// force pruned liveness (but store etc. does).
183+
184+
// Even though this instruction is a DestroyingConsume of its operand,
185+
// if it's a destroy_value whose operand is not a transitive copy of
186+
// currentDef, then it's just ending an implicit borrow of currentDef,
187+
// not consuming it.
188+
auto lifetimeEnding = !isa<DestroyValueInst>(user);
189+
liveness->updateForUse(user, lifetimeEnding);
180190
}
181191
recordConsumingUse(use);
182192
break;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %target-sil-opt -copy-propagation -enable-sil-verify-all -module-name Swift %s | %FileCheck %s --check-prefixes=CHECK,CHECK-OPT
2+
// RUN: %target-sil-opt -mandatory-copy-propagation -enable-sil-verify-all -module-name Swift %s | %FileCheck %s --check-prefixes=CHECK,CHECK-ONONE
3+
4+
// Runs CopyPropagation without borrow scope canonicalization.
5+
6+
sil_stage canonical
7+
8+
import Builtin
9+
10+
typealias AnyObject = Builtin.AnyObject
11+
12+
protocol Error {}
13+
14+
class B { }
15+
16+
class C {
17+
var a: Builtin.Int64
18+
}
19+
20+
struct NonTrivialStruct {
21+
@_hasStorage var val: C { get set }
22+
}
23+
24+
class CompileError : Error {}
25+
26+
// This test case used to have an invalid boundary extension.
27+
// CHECK-LABEL: sil [ossa] @canonicalize_borrow_of_copy_with_interesting_boundary : $@convention(thin) (@owned C) -> (@owned NonTrivialStruct, @error any Error) {
28+
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] :
29+
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
30+
// CHECK: destroy_value [[INSTANCE]]
31+
// CHECK: cond_br undef, [[SUCCESS:bb[0-9]+]], [[FAILURE:bb[0-9]+]]
32+
// CHECK: [[SUCCESS]]:
33+
// CHECK: [[BORROW:%[^,]+]] = begin_borrow [[COPY]]
34+
// CHECK: [[STRUCT:%[^,]+]] = struct $NonTrivialStruct ([[BORROW]] : $C)
35+
// CHECK: [[STRUCT_OUT:%[^,]+]] = copy_value [[STRUCT]]
36+
// CHECK: end_borrow [[BORROW]]
37+
// CHECK: destroy_value [[COPY]]
38+
// CHECK: return [[STRUCT_OUT]]
39+
// CHECK: [[FAILURE]]:
40+
// CHECK: destroy_value [[COPY]]
41+
// CHECK: [[BOX:%[^,]+]] = alloc_existential_box
42+
// CHECK: throw [[BOX]]
43+
// CHECK-LABEL: } // end sil function 'canonicalize_borrow_of_copy_with_interesting_boundary'
44+
sil [ossa] @canonicalize_borrow_of_copy_with_interesting_boundary : $@convention(thin) (@owned C) -> (@owned NonTrivialStruct, @error any Error) {
45+
bb0(%0 : @owned $C):
46+
%1 = copy_value %0 : $C
47+
%2 = copy_value %1 : $C
48+
cond_br undef, bb1, bb2
49+
bb1:
50+
destroy_value %0 : $C
51+
destroy_value %1 : $C
52+
%6 = begin_borrow %2 : $C
53+
%7 = struct $NonTrivialStruct (%6 : $C)
54+
%8 = copy_value %7 : $NonTrivialStruct
55+
end_borrow %6 : $C
56+
destroy_value %2 : $C
57+
return %8 : $NonTrivialStruct
58+
bb2:
59+
destroy_value %2 : $C
60+
%13 = begin_borrow %1 : $C
61+
%14 = struct $NonTrivialStruct (%13 : $C)
62+
%15 = copy_value %14 : $NonTrivialStruct
63+
end_borrow %13 : $C
64+
destroy_value %1 : $C
65+
destroy_value %15 : $NonTrivialStruct
66+
%19 = alloc_existential_box $any Error, $CompileError
67+
destroy_value %0 : $C
68+
%22 = builtin "willThrow"(%19 : $any Error) : $()
69+
throw %19 : $any Error
70+
}

0 commit comments

Comments
 (0)