Skip to content

Commit fb42d85

Browse files
committed
Verify @inout_aliasable captures.
This is currently only done in DiagnoseStaticExclusivity because AllocBoxToStack doesn't know how to set @NoEscape function types yet.
1 parent 209cd2e commit fb42d85

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

include/swift/SIL/InstructionUtils.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ SILValue stripExpectIntrinsic(SILValue V);
7777
/// ust return V.
7878
SILValue stripBorrow(SILValue V);
7979

80+
/// Return a non-null SingleValueInstruction if the given instruction merely
81+
/// copies a value, possibly changing its type or ownership state, but otherwise
82+
/// having no effect.
83+
///
84+
/// This is useful for checking all users of a value to verify that the value is
85+
/// only used in recognizable patterns without otherwise "escaping". These are
86+
/// instructions that the use-visitor can recurse into. Note that the value's
87+
/// type may be changed by a cast.
88+
SingleValueInstruction *getSingleValueCopyOrCast(SILInstruction *I);
89+
90+
/// Return true if the given instruction has no effect on it's operand values
91+
/// and produces no result. These are typically end-of scope markers.
92+
///
93+
/// This is useful for checking all users of a value to verify that the value is
94+
/// only used in recognizable patterns without otherwise "escaping".
95+
bool isIncidentalUse(SILInstruction *user);
96+
97+
/// Return true if the given `user` instruction modifies the value's refcount
98+
/// without propagating the value or having any other effect aside from
99+
/// potentially destroying the value itself (and executing associated cleanups).
100+
///
101+
/// This is useful for checking all users of a value to verify that the value is
102+
/// only used in recognizable patterns without otherwise "escaping".
103+
bool onlyAffectsRefCount(SILInstruction *user);
104+
80105
/// A utility class for evaluating whether a newly parsed or deserialized
81106
/// function has qualified or unqualified ownership.
82107
///

lib/SIL/InstructionUtils.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,53 @@ SILValue swift::stripBorrow(SILValue V) {
234234
return V;
235235
}
236236

237+
SingleValueInstruction *swift::getSingleValueCopyOrCast(SILInstruction *I) {
238+
if (auto *convert = dyn_cast<ConversionInst>(I))
239+
return convert;
240+
241+
switch (I->getKind()) {
242+
default:
243+
return nullptr;
244+
case SILInstructionKind::CopyValueInst:
245+
case SILInstructionKind::CopyBlockInst:
246+
case SILInstructionKind::BeginBorrowInst:
247+
case SILInstructionKind::BeginAccessInst:
248+
return cast<SingleValueInstruction>(I);
249+
}
250+
}
251+
252+
bool swift::isIncidentalUse(SILInstruction *user) {
253+
switch (user->getKind()) {
254+
default:
255+
return false;
256+
case SILInstructionKind::DebugValueInst:
257+
case SILInstructionKind::EndAccessInst:
258+
case SILInstructionKind::EndBorrowInst:
259+
case SILInstructionKind::EndLifetimeInst:
260+
case SILInstructionKind::FixLifetimeInst:
261+
return true;
262+
}
263+
}
264+
265+
bool swift::onlyAffectsRefCount(SILInstruction *user) {
266+
switch (user->getKind()) {
267+
default:
268+
return false;
269+
case SILInstructionKind::AutoreleaseValueInst:
270+
case SILInstructionKind::DestroyValueInst:
271+
case SILInstructionKind::ReleaseValueInst:
272+
case SILInstructionKind::RetainValueInst:
273+
case SILInstructionKind::StrongReleaseInst:
274+
case SILInstructionKind::StrongRetainInst:
275+
case SILInstructionKind::UnmanagedAutoreleaseValueInst:
276+
case SILInstructionKind::UnmanagedReleaseValueInst:
277+
case SILInstructionKind::UnmanagedRetainValueInst:
278+
case SILInstructionKind::UnownedReleaseInst:
279+
case SILInstructionKind::UnownedRetainInst:
280+
return true;
281+
}
282+
}
283+
237284
namespace {
238285

239286
enum class OwnershipQualifiedKind {

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "swift/Basic/SourceLoc.h"
3333
#include "swift/Parse/Lexer.h"
3434
#include "swift/SIL/CFG.h"
35+
#include "swift/SIL/InstructionUtils.h"
3536
#include "swift/SIL/SILArgument.h"
3637
#include "swift/SIL/SILInstruction.h"
3738
#include "swift/SIL/Projection.h"
@@ -1122,6 +1123,54 @@ static void checkForViolationsInNoEscapeClosures(
11221123
/*DiagnoseAsWarning=*/false);
11231124
}
11241125

1126+
#ifndef NDEBUG
1127+
// If a partial apply has @inout_aliasable arguments, it may only be used as
1128+
// a @noescape function type in a way that is recognized by
1129+
// DiagnoseStaticExclusivity.
1130+
static void checkNoEscapePartialApply(PartialApplyInst *PAI) {
1131+
SmallVector<Operand *, 8> uses(PAI->getUses());
1132+
while (!uses.empty()) {
1133+
Operand *oper = uses.pop_back_val();
1134+
SILInstruction *user = oper->getUser();
1135+
1136+
if (isIncidentalUse(user) || onlyAffectsRefCount(user))
1137+
continue;
1138+
1139+
if (SingleValueInstruction *copy = getSingleValueCopyOrCast(user)) {
1140+
uses.append(copy->getUses().begin(), copy->getUses().end());
1141+
continue;
1142+
}
1143+
if (auto apply = isa<ApplySite>(user)) {
1144+
SILValue arg = oper->get();
1145+
auto ArgumentFnType = arg->getType().getAs<SILFunctionType>();
1146+
if (ArgumentFnType && ArgumentFnType->isNoEscape())
1147+
continue;
1148+
1149+
llvm::dbgs() << "Argument must be @noescape function type: " << *arg;
1150+
llvm_unreachable("A partial_apply with @inout_aliasable may only be "
1151+
"used as a @noescape function type argument.");
1152+
}
1153+
auto *store = dyn_cast<StoreInst>(user);
1154+
if (store && oper->getOperandNumber() == StoreInst::Src) {
1155+
if (auto *PBSI = dyn_cast<ProjectBlockStorageInst>(store->getDest())) {
1156+
SILValue storageAddr = PBSI->getOperand();
1157+
// The closure is stored to block storage. Recursively visit all
1158+
// uses of any initialized block storage values derived from this
1159+
// storage address..
1160+
for (Operand *oper : storageAddr->getUses()) {
1161+
if (auto *IBS = dyn_cast<InitBlockStorageHeaderInst>(oper->getUser()))
1162+
uses.append(IBS->getUses().begin(), IBS->getUses().end());
1163+
}
1164+
continue;
1165+
}
1166+
}
1167+
llvm::dbgs() << "Unexpected partial_apply use: " << *user;
1168+
llvm_unreachable("A partial_apply with @inout_aliasable may only be "
1169+
"used as a @noescape function type argument.");
1170+
}
1171+
}
1172+
#endif
1173+
11251174
static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO,
11261175
AccessSummaryAnalysis *ASA) {
11271176
// The implementation relies on the following SIL invariants:
@@ -1229,6 +1278,21 @@ static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO,
12291278
ConflictingAccesses);
12301279
continue;
12311280
}
1281+
#ifndef NDEBUG
1282+
// FIXME: Once AllocBoxToStack is fixed to correctly set noescape
1283+
// closure types, move this PartialApply verification into the
1284+
// SILVerifier to better pinpoint the offending pass.
1285+
if (auto *PAI = dyn_cast<PartialApplyInst>(&I)) {
1286+
ApplySite apply(PAI);
1287+
if (llvm::any_of(range(apply.getNumArguments()),
1288+
[apply](unsigned argIdx) {
1289+
return apply.getArgumentConvention(argIdx)
1290+
== SILArgumentConvention::Indirect_InoutAliasable;
1291+
})) {
1292+
checkNoEscapePartialApply(PAI);
1293+
}
1294+
}
1295+
#endif
12321296
// Sanity check to make sure entries are properly removed.
12331297
assert((!isa<ReturnInst>(&I) || Accesses.size() == 0) &&
12341298
"Entries were not properly removed?!");

0 commit comments

Comments
 (0)