Skip to content

Commit a1f4a43

Browse files
committed
[ownership] Teach the ownership verifier how to verify that guaranteed yielded values are only used within the coroutine's lifetime.
I think validating this was an oversight from the bringup of coroutines. I discovered this while writing test cases for coroutine lifetime extension. I realized it was possible to write a test case that should have triggered this but was not. I added some tests to make sure that we continue to flag this in the future. rdar://69597888
1 parent bb954f9 commit a1f4a43

File tree

2 files changed

+123
-15
lines changed

2 files changed

+123
-15
lines changed

lib/SIL/Verifier/SILOwnershipVerifier.cpp

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define DEBUG_TYPE "sil-ownership-verifier"
1414

1515
#include "LinearLifetimeCheckerPrivate.h"
16+
1617
#include "swift/AST/ASTContext.h"
1718
#include "swift/AST/AnyFunctionRef.h"
1819
#include "swift/AST/Decl.h"
@@ -32,16 +33,20 @@
3233
#include "swift/SIL/SILBuiltinVisitor.h"
3334
#include "swift/SIL/SILDebugScope.h"
3435
#include "swift/SIL/SILFunction.h"
36+
#include "swift/SIL/SILInstruction.h"
3537
#include "swift/SIL/SILModule.h"
3638
#include "swift/SIL/SILOpenedArchetypesTracker.h"
3739
#include "swift/SIL/SILVTable.h"
3840
#include "swift/SIL/SILVisitor.h"
3941
#include "swift/SIL/TypeLowering.h"
42+
4043
#include "llvm/ADT/DenseSet.h"
4144
#include "llvm/ADT/PostOrderIterator.h"
45+
#include "llvm/ADT/SmallVector.h"
4246
#include "llvm/ADT/StringSet.h"
4347
#include "llvm/Support/CommandLine.h"
4448
#include "llvm/Support/Debug.h"
49+
4550
#include <algorithm>
4651

4752
using namespace swift;
@@ -130,10 +135,11 @@ class SILValueOwnershipChecker {
130135
bool gatherNonGuaranteedUsers(SmallVectorImpl<Operand *> &lifetimeEndingUsers,
131136
SmallVectorImpl<Operand *> &regularUsers);
132137

133-
bool checkValueWithoutLifetimeEndingUses();
138+
bool checkValueWithoutLifetimeEndingUses(ArrayRef<Operand *> regularUsers);
134139

135140
bool checkFunctionArgWithoutLifetimeEndingUses(SILFunctionArgument *arg);
136-
bool checkYieldWithoutLifetimeEndingUses(BeginApplyResult *yield);
141+
bool checkYieldWithoutLifetimeEndingUses(BeginApplyResult *yield,
142+
ArrayRef<Operand *> regularUsers);
137143

138144
bool isGuaranteedFunctionArgWithLifetimeEndingUses(
139145
SILFunctionArgument *arg,
@@ -501,25 +507,56 @@ bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses(
501507
}
502508

503509
bool SILValueOwnershipChecker::checkYieldWithoutLifetimeEndingUses(
504-
BeginApplyResult *yield) {
510+
BeginApplyResult *yield, ArrayRef<Operand *> regularUses) {
505511
switch (yield->getOwnershipKind()) {
506-
case ValueOwnershipKind::Guaranteed:
507512
case ValueOwnershipKind::Unowned:
508513
case ValueOwnershipKind::None:
509514
return true;
510515
case ValueOwnershipKind::Owned:
516+
if (deadEndBlocks.isDeadEnd(yield->getParent()->getParent()))
517+
return true;
518+
519+
return !errorBuilder.handleMalformedSIL([&] {
520+
llvm::errs() << "Owned yield without life ending uses!\n"
521+
<< "Value: " << *yield << '\n';
522+
});
523+
case ValueOwnershipKind::Guaranteed:
524+
// NOTE: If we returned false here, we would catch any error caught below as
525+
// an out of lifetime use of the yielded value. That being said, that would
526+
// be confusing from a code perspective since we would be validating
527+
// something that did not have a /real/ lifetime ending use (one could
528+
// consider the end_apply to be a pseudo-lifetime ending uses) along a code
529+
// path that is explicitly trying to do that.
511530
break;
512531
}
513532

514-
if (deadEndBlocks.isDeadEnd(yield->getParent()->getParent()))
533+
// If we have a guaranteed value, make sure that all uses are before our
534+
// end_yield.
535+
SmallVector<Operand *, 4> coroutineEndUses;
536+
for (auto *use : yield->getParent()->getTokenResult()->getUses()) {
537+
coroutineEndUses.push_back(use);
538+
}
539+
540+
assert(visitedBlocks.empty());
541+
LinearLifetimeChecker checker(visitedBlocks, deadEndBlocks);
542+
auto linearLifetimeResult =
543+
checker.checkValue(yield, coroutineEndUses, regularUses, errorBuilder);
544+
if (linearLifetimeResult.getFoundError()) {
545+
// We return true here even if we find an error since we want to only emit
546+
// this error for the value rather than continue and go down the "has
547+
// consuming use" path. This is to work around any confusion that maybe
548+
// caused by end_apply/abort_apply acting as a pseudo-ending lifetime use.
549+
result = true;
515550
return true;
551+
}
516552

517-
return !errorBuilder.handleMalformedSIL([&] {
518-
llvm::errs() << "Owned yield without life ending uses!\n"
519-
<< "Value: " << *yield << '\n';
520-
});
553+
// Otherwise, we do not set result to have a value and return since all of our
554+
// guaranteed value's uses are appropriate.
555+
return true;
521556
}
522-
bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses() {
557+
558+
bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses(
559+
ArrayRef<Operand *> regularUses) {
523560
LLVM_DEBUG(llvm::dbgs() << "No lifetime ending users?! Bailing early.\n");
524561
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
525562
if (checkFunctionArgWithoutLifetimeEndingUses(arg)) {
@@ -528,9 +565,7 @@ bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses() {
528565
}
529566

530567
if (auto *yield = dyn_cast<BeginApplyResult>(value)) {
531-
if (checkYieldWithoutLifetimeEndingUses(yield)) {
532-
return true;
533-
}
568+
return checkYieldWithoutLifetimeEndingUses(yield, regularUses);
534569
}
535570

536571
// Check if we are a guaranteed subobject. In such a case, we should never
@@ -622,6 +657,7 @@ bool SILValueOwnershipChecker::checkUses() {
622657
// 1. A trivial typed value.
623658
// 2. An address type value.
624659
// 3. A guaranteed function argument.
660+
// 4. A yielded guaranteed value.
625661
//
626662
// In the first two cases, it is easy to see that there is nothing further to
627663
// do but return false.
@@ -630,8 +666,13 @@ bool SILValueOwnershipChecker::checkUses() {
630666
// more. Specifically, we should have /no/ lifetime ending uses of a
631667
// guaranteed function argument, since a guaranteed function argument should
632668
// outlive the current function always.
633-
if (lifetimeEndingUsers.empty() && checkValueWithoutLifetimeEndingUses()) {
634-
return false;
669+
//
670+
// In the case of a yielded guaranteed value, we need to validate that all
671+
// regular uses of the value are within the co
672+
if (lifetimeEndingUsers.empty()) {
673+
if (checkValueWithoutLifetimeEndingUses(regularUsers))
674+
return false;
675+
return true;
635676
}
636677

637678
LLVM_DEBUG(llvm::dbgs() << " Found lifetime ending users! Performing "
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: %target-sil-opt -sil-ownership-verifier-enable-testing -ownership-verifier-textual-error-dumper -enable-sil-verify-all=0 -o /dev/null %s 2>&1 | %FileCheck %s
2+
// REQUIRES: asserts
3+
4+
sil_stage canonical
5+
6+
import Builtin
7+
8+
class Klass {}
9+
10+
sil @guaranteed_yield_coroutine : $@yield_once @convention(thin) () -> @yields @guaranteed Klass
11+
sil @owned_yield_coroutine : $@yield_once @convention(thin) () -> @yields @owned Klass
12+
13+
sil @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
14+
15+
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'guaranteed_coroutine_caller'
16+
// CHECK: Found outside of lifetime use?!
17+
// CHECK: Value: (**%3**, %4) = begin_apply %0() : $@yield_once @convention(thin) () -> @yields @guaranteed Klass // user: %6
18+
// CHECK: Consuming User: end_apply %4 // id: %5
19+
// CHECK: Non Consuming User: %6 = apply %2(%3) : $@convention(thin) (@guaranteed Klass) -> ()
20+
// CHECK: Block: bb0
21+
// CHECK: Error#: 0. End Error in Function: 'guaranteed_coroutine_caller'
22+
23+
// CHECK-LABEL: Error#: 1. Begin Error in Function: 'guaranteed_coroutine_caller'
24+
// CHECK: Owned yield without life ending uses!
25+
// CHECK: Value: (**%7**, %8) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass // user: %10
26+
// CHECK: Error#: 1. End Error in Function: 'guaranteed_coroutine_caller'
27+
28+
// CHECK-LABEL: Error#: 2. Begin Error in Function: 'guaranteed_coroutine_caller'
29+
// CHECK: Found outside of lifetime use?!
30+
// CHECK: Value: (**%11**, %12) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass // users: %15, %13
31+
// CHECK: Consuming User: destroy_value %11 : $Klass // id: %13
32+
// CHECK: Non Consuming User: %15 = apply %2(%11) : $@convention(thin) (@guaranteed Klass) -> ()
33+
// CHECK: Block: bb0
34+
// CHECK: Error#: 2. End Error in Function: 'guaranteed_coroutine_caller'
35+
36+
// CHECK-LABEL: Error#: 3. Begin Error in Function: 'guaranteed_coroutine_caller'
37+
// CHECK: Owned yield without life ending uses!
38+
// CHECK: Value: (**%16**, %17) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass // user: %18
39+
// CHECK: Error#: 3. End Error in Function: 'guaranteed_coroutine_caller'
40+
41+
sil [ossa] @guaranteed_coroutine_caller : $@convention(thin) () -> () {
42+
bb0:
43+
%0 = function_ref @guaranteed_yield_coroutine : $@yield_once @convention(thin) () -> @yields @guaranteed Klass
44+
%1 = function_ref @owned_yield_coroutine : $@yield_once @convention(thin) () -> @yields @owned Klass
45+
46+
%user_func = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
47+
48+
(%0a, %0b) = begin_apply %0() : $@yield_once @convention(thin) () -> @yields @guaranteed Klass
49+
end_apply %0b
50+
apply %user_func(%0a) : $@convention(thin) (@guaranteed Klass) -> ()
51+
52+
(%val1, %tok1) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass
53+
end_apply %tok1
54+
apply %user_func(%val1) : $@convention(thin) (@guaranteed Klass) -> ()
55+
56+
(%val2, %tok2) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass
57+
destroy_value %val2 : $Klass
58+
end_apply %tok2
59+
apply %user_func(%val2) : $@convention(thin) (@guaranteed Klass) -> ()
60+
61+
(%val3, %tok3) = begin_apply %1() : $@yield_once @convention(thin) () -> @yields @owned Klass
62+
apply %user_func(%val3) : $@convention(thin) (@guaranteed Klass) -> ()
63+
end_apply %tok3
64+
65+
%9999 = tuple()
66+
return %9999 : $()
67+
}

0 commit comments

Comments
 (0)