Skip to content

Commit ce128e7

Browse files
committed
Update SIL verification for borrow accessors
1 parent 0bec28f commit ce128e7

File tree

6 files changed

+206
-8
lines changed

6 files changed

+206
-8
lines changed

SwiftCompilerSources/Sources/SIL/Utilities/Verifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private extension Instruction {
6161

6262
func checkGuaranteedResults() {
6363
for result in results where result.ownership == .guaranteed {
64-
require(BeginBorrowValue(result) != nil || self is ForwardingInstruction,
64+
require(BeginBorrowValue(result) != nil || self is ForwardingInstruction || result.isGuaranteedApplyResult,
6565
"\(result) must either be a BeginBorrowValue or a ForwardingInstruction")
6666
}
6767
}

include/swift/SIL/ApplySite.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,18 @@ class ApplySite {
562562
llvm_unreachable("covered switch");
563563
}
564564

565+
bool hasGuaranteedResult() const {
566+
switch (ApplySiteKind(Inst->getKind())) {
567+
case ApplySiteKind::ApplyInst:
568+
return cast<ApplyInst>(Inst)->hasGuaranteedResult();
569+
case ApplySiteKind::BeginApplyInst:
570+
case ApplySiteKind::TryApplyInst:
571+
case ApplySiteKind::PartialApplyInst:
572+
return false;
573+
}
574+
llvm_unreachable("covered switch");
575+
}
576+
565577
/// Returns true if \p op is an operand that passes an indirect
566578
/// result argument to the apply site.
567579
bool isIndirectResultOperand(const Operand &op) const;

include/swift/SIL/SILInstruction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,6 +3147,11 @@ class ApplyInst final
31473147
SILFunction &parentFunction,
31483148
const GenericSpecializationInformation *specializationInfo,
31493149
std::optional<ApplyIsolationCrossing> isolationCrossing);
3150+
3151+
public:
3152+
bool hasGuaranteedResult() const {
3153+
return getSubstCalleeConv().hasGuaranteedResults();
3154+
}
31503155
};
31513156

31523157
/// PartialApplyInst - Represents the creation of a closure object by partial

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ OperandOwnershipClassifier::visitDropDeinitInst(DropDeinitInst *i) {
484484
// This does not apply to uses that begin an explicit borrow scope in the
485485
// caller, such as begin_apply.
486486
static OperandOwnership getFunctionArgOwnership(SILArgumentConvention argConv,
487-
bool hasScopeInCaller) {
487+
bool hasScopeInCaller,
488+
bool forwardsGuaranteedValues) {
488489

489490
switch (argConv) {
490491
case SILArgumentConvention::Indirect_In_CXX:
@@ -501,7 +502,6 @@ static OperandOwnership getFunctionArgOwnership(SILArgumentConvention argConv,
501502
// explicit borrow scope in the caller so we must treat arguments passed to it
502503
// as being borrowed for the entire region of coroutine execution.
503504
case SILArgumentConvention::Indirect_In_Guaranteed:
504-
case SILArgumentConvention::Direct_Guaranteed:
505505
case SILArgumentConvention::Pack_Guaranteed:
506506
case SILArgumentConvention::Pack_Inout:
507507
case SILArgumentConvention::Pack_Out:
@@ -510,6 +510,16 @@ static OperandOwnership getFunctionArgOwnership(SILArgumentConvention argConv,
510510
return hasScopeInCaller ? OperandOwnership::Borrow
511511
: OperandOwnership::InstantaneousUse;
512512

513+
case SILArgumentConvention::Direct_Guaranteed: {
514+
if (hasScopeInCaller) {
515+
return OperandOwnership::Borrow;
516+
}
517+
if (forwardsGuaranteedValues) {
518+
return OperandOwnership::GuaranteedForwarding;
519+
}
520+
return OperandOwnership::InstantaneousUse;
521+
}
522+
513523
case SILArgumentConvention::Direct_Unowned:
514524
return OperandOwnership::UnownedInstantaneousUse;
515525

@@ -541,7 +551,8 @@ OperandOwnershipClassifier::visitFullApply(FullApplySite apply) {
541551
}();
542552

543553
auto argOwnership = getFunctionArgOwnership(
544-
argConv, /*hasScopeInCaller*/ apply.beginsCoroutineEvaluation());
554+
argConv, /*hasScopeInCaller*/ apply.beginsCoroutineEvaluation(),
555+
/*forwardsGuaranteedValues*/ apply.hasGuaranteedResult());
545556

546557
// OSSA cleanup needs to handle each of these callee ownership cases.
547558
//
@@ -604,7 +615,8 @@ OperandOwnership OperandOwnershipClassifier::visitYieldInst(YieldInst *i) {
604615
auto fnType = i->getFunction()->getLoweredFunctionType();
605616
SILArgumentConvention argConv(
606617
fnType->getYields()[getOperandIndex()].getConvention());
607-
return getFunctionArgOwnership(argConv, /*hasScopeInCaller*/ false);
618+
return getFunctionArgOwnership(argConv, /*hasScopeInCaller*/ false,
619+
/*forwardsGuaranteedValues*/ false);
608620
}
609621

610622
OperandOwnership OperandOwnershipClassifier::visitReturnInst(ReturnInst *i) {

lib/SIL/IR/SILValue.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,15 @@ bool ValueBase::isGuaranteedForwarding() const {
192192
}
193193
// If not a phi, return false
194194
auto *phi = dyn_cast<SILPhiArgument>(this);
195-
if (!phi || !phi->isPhi()) {
196-
return false;
195+
if (phi && phi->isPhi()) {
196+
return phi->isGuaranteedForwarding();
197197
}
198198

199-
return phi->isGuaranteedForwarding();
199+
auto *applyInst = dyn_cast_or_null<ApplyInst>(getDefiningInstruction());
200+
if (!applyInst) {
201+
return false;
202+
}
203+
return applyInst->hasGuaranteedResult();
200204
}
201205

202206
bool ValueBase::isBeginApplyToken() const {
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// RUN: %target-sil-opt -disable-swift-verification -sil-ownership-verifier-enable-testing -ownership-verifier-textual-error-dumper -enable-sil-verify-all=0 %s -o /dev/null 2>&1 | %FileCheck %s
2+
// REQUIRES: asserts
3+
4+
class Klass {}
5+
6+
public struct Wrapper {
7+
@_hasStorage var _k: Klass { get set }
8+
var k: Klass
9+
}
10+
11+
public struct GenWrapper<T> {
12+
@_hasStorage var _prop: T { get set }
13+
public var prop: T
14+
}
15+
16+
sil [ossa] @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass {
17+
bb0(%0 : @guaranteed $Wrapper):
18+
%2 = struct_extract %0, #Wrapper._k
19+
return %2
20+
}
21+
22+
sil [ossa] @borrow_addressonly_prop : $@convention(method) <T> (@in_guaranteed GenWrapper<T>) -> @guaranteed_addr T {
23+
bb0(%0 : $*GenWrapper<T>):
24+
%2 = struct_element_addr %0, #GenWrapper._prop
25+
return %2
26+
}
27+
28+
sil @get_wrapper : $@convention(thin) () -> @owned Klass
29+
sil @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
30+
sil @use_T : $@convention(thin) <T> (@in_guaranteed T) -> ()
31+
32+
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'test_end_borrow_on_guaranteed_return_value'
33+
// CHECK: Invalid End Borrow!
34+
// CHECK: Original Value: %1 = begin_borrow %0 : $Wrapper
35+
// CHECK: End Borrow: end_borrow %3 : $Klass
36+
// CHECK-LABEL: Error#: 0. End Error in Function: 'test_end_borrow_on_guaranteed_return_value'
37+
// CHECK-LABEL: Error#: 1. Begin Error in Function: 'test_end_borrow_on_guaranteed_return_value'
38+
// CHECK: Subobject projection with lifetime ending uses!
39+
// CHECK: Value: %3 = apply %2(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
40+
// CHECK: Lifetime Ending User: end_borrow %3 : $Klass
41+
// CHECK-LABEL: Error#: 1. End Error in Function: 'test_end_borrow_on_guaranteed_return_value'
42+
sil [ossa] @test_end_borrow_on_guaranteed_return_value : $@convention(thin) (@owned Wrapper) -> () {
43+
bb0(%0 : @owned $Wrapper):
44+
%1 = begin_borrow %0
45+
%2 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
46+
%3 = apply %2(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
47+
end_borrow %3
48+
end_borrow %1
49+
destroy_value %0
50+
%10 = tuple ()
51+
return %10
52+
}
53+
54+
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'test_inconsistent_guaranteed_phi1'
55+
// CHECK: Invalid End Borrow!
56+
// CHECK: Original Value: %1 = begin_borrow %0 : $Wrapper
57+
// CHECK: End Borrow: br bb3(%4 : $Klass)
58+
// CHECK-LABEL: Error#: 0. End Error in Function: 'test_inconsistent_guaranteed_phi1'
59+
// CHECK-LABEL: Error#: 1. Begin Error in Function: 'test_inconsistent_guaranteed_phi1'
60+
// CHECK: Subobject projection with lifetime ending uses!
61+
// CHECK: Value: %4 = apply %3(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
62+
// CHECK: Lifetime Ending User: br bb3(%4 : $Klass)
63+
// CHECK-LABEL: Error#: 1. End Error in Function: 'test_inconsistent_guaranteed_phi1'
64+
sil [ossa] @test_inconsistent_guaranteed_phi1 : $@convention(thin) (@owned Wrapper) -> () {
65+
bb0(%0 : @owned $Wrapper):
66+
%1 = begin_borrow %0
67+
cond_br undef, bb1, bb2
68+
69+
bb1:
70+
%3 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
71+
%4 = apply %3(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
72+
br bb3(%4)
73+
74+
bb2:
75+
%6 = struct_extract %1, #Wrapper._k
76+
%7 = begin_borrow %6
77+
br bb3(%7)
78+
79+
bb3(%9 : @reborrow $Klass):
80+
%10 = borrowed %9 from (%1)
81+
end_borrow %10
82+
end_borrow %1
83+
destroy_value %0
84+
%14 = tuple ()
85+
return %14
86+
}
87+
88+
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'test_inconsistent_guaranteed_phi2'
89+
// CHECK: Non trivial values, non address values, and non guaranteed function args must have at least one lifetime ending use?!
90+
// CHECK: Value: %7 = begin_borrow %6 : $Klass
91+
// CHECK-LABEL: Error#: 0. End Error in Function: 'test_inconsistent_guaranteed_phi2'
92+
sil [ossa] @test_inconsistent_guaranteed_phi2 : $@convention(thin) (@owned Wrapper) -> () {
93+
bb0(%0 : @owned $Wrapper):
94+
%1 = begin_borrow %0
95+
cond_br undef, bb1, bb2
96+
97+
bb1:
98+
%3 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
99+
%4 = apply %3(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
100+
br bb3(%4)
101+
102+
bb2:
103+
%6 = struct_extract %1, #Wrapper._k
104+
%7 = begin_borrow %6
105+
br bb3(%7)
106+
107+
bb3(%9 : @guaranteed $Klass):
108+
end_borrow %1
109+
destroy_value %0
110+
%12 = tuple ()
111+
return %12
112+
}
113+
114+
// CHECK: Error#: 0. Begin Error in Function: 'test_use_after_free_loadable1'
115+
// CHECK: Have operand with incompatible ownership?!
116+
// CHECK: Value: %0 = argument of bb0 : $Wrapper
117+
// CHECK: User: %2 = apply %1(%0) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
118+
// CHECK: Operand Number: 1
119+
// CHECK: Conv: owned
120+
// CHECK: Constraint:
121+
// CHECK: <Constraint Kind:guaranteed LifetimeConstraint:NonLifetimeEnding>
122+
// CHECK: Error#: 0. End Error in Function: 'test_use_after_free_loadable1'
123+
sil [ossa] @test_use_after_free_loadable1 : $@convention(thin) (@owned Wrapper) -> () {
124+
bb0(%0 : @owned $Wrapper):
125+
%1 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
126+
%2 = apply %1(%0) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
127+
destroy_value %0
128+
%4 = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
129+
%5 = apply %4(%2) : $@convention(thin) (@guaranteed Klass) -> ()
130+
%6 = tuple ()
131+
return %6
132+
}
133+
134+
// CHECK: Error#: 0. Begin Error in Function: 'test_use_after_free_loadable2'
135+
// CHECK: Found outside of lifetime use?!
136+
// CHECK: Value: %1 = begin_borrow %0 : $Wrapper
137+
// CHECK: Consuming User: end_borrow %1 : $Wrapper
138+
// CHECK: Non Consuming User: %6 = apply %5(%3) : $@convention(thin) (@guaranteed Klass) -> ()
139+
// CHECK: Block: bb0
140+
// CHECK: Error#: 0. End Error in Function: 'test_use_after_free_loadable2'
141+
sil [ossa] @test_use_after_free_loadable2 : $@convention(thin) (@owned Wrapper) -> () {
142+
bb0(%0 : @owned $Wrapper):
143+
%1 = begin_borrow %0
144+
%2 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
145+
%3 = apply %2(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
146+
end_borrow %1
147+
%4 = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
148+
%5 = apply %4(%3) : $@convention(thin) (@guaranteed Klass) -> ()
149+
destroy_value %0
150+
%6 = tuple ()
151+
return %6
152+
}
153+
154+
// TODO: Add verification support in MemoryLifetimeVerifier
155+
sil [ossa] @test_use_after_free_address_only : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
156+
bb0(%0 : $*GenWrapper<T>):
157+
%1 = function_ref @borrow_addressonly_prop : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_addr τ_0_0
158+
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_addr τ_0_0
159+
%3 = function_ref @use_T : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
160+
destroy_addr %0
161+
%5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
162+
%6 = tuple ()
163+
return %6
164+
}
165+

0 commit comments

Comments
 (0)