Skip to content

Verify noescape closures for exclusivity diagnostics. #13898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/swift/SIL/InstructionUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ SILValue stripExpectIntrinsic(SILValue V);
/// ust return V.
SILValue stripBorrow(SILValue V);

/// Return a non-null SingleValueInstruction if the given instruction merely
/// copies a value, possibly changing its type or ownership state, but otherwise
/// having no effect.
///
/// This is useful for checking all users of a value to verify that the value is
/// only used in recognizable patterns without otherwise "escaping". These are
/// instructions that the use-visitor can recurse into. Note that the value's
/// type may be changed by a cast.
SingleValueInstruction *getSingleValueCopyOrCast(SILInstruction *I);

/// Return true if the given instruction has no effect on it's operand values
/// and produces no result. These are typically end-of scope markers.
///
/// This is useful for checking all users of a value to verify that the value is
/// only used in recognizable patterns without otherwise "escaping".
bool isIncidentalUse(SILInstruction *user);

/// Return true if the given `user` instruction modifies the value's refcount
/// without propagating the value or having any other effect aside from
/// potentially destroying the value itself (and executing associated cleanups).
///
/// This is useful for checking all users of a value to verify that the value is
/// only used in recognizable patterns without otherwise "escaping".
bool onlyAffectsRefCount(SILInstruction *user);

/// A utility class for evaluating whether a newly parsed or deserialized
/// function has qualified or unqualified ownership.
///
Expand Down
47 changes: 47 additions & 0 deletions lib/SIL/InstructionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,53 @@ SILValue swift::stripBorrow(SILValue V) {
return V;
}

SingleValueInstruction *swift::getSingleValueCopyOrCast(SILInstruction *I) {
if (auto *convert = dyn_cast<ConversionInst>(I))
return convert;

switch (I->getKind()) {
default:
return nullptr;
case SILInstructionKind::CopyValueInst:
case SILInstructionKind::CopyBlockInst:
case SILInstructionKind::BeginBorrowInst:
case SILInstructionKind::BeginAccessInst:
return cast<SingleValueInstruction>(I);
}
}

bool swift::isIncidentalUse(SILInstruction *user) {
switch (user->getKind()) {
default:
return false;
case SILInstructionKind::DebugValueInst:
case SILInstructionKind::EndAccessInst:
case SILInstructionKind::EndBorrowInst:
case SILInstructionKind::EndLifetimeInst:
case SILInstructionKind::FixLifetimeInst:
return true;
}
}

bool swift::onlyAffectsRefCount(SILInstruction *user) {
switch (user->getKind()) {
default:
return false;
case SILInstructionKind::AutoreleaseValueInst:
case SILInstructionKind::DestroyValueInst:
case SILInstructionKind::ReleaseValueInst:
case SILInstructionKind::RetainValueInst:
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::StrongRetainInst:
case SILInstructionKind::UnmanagedAutoreleaseValueInst:
case SILInstructionKind::UnmanagedReleaseValueInst:
case SILInstructionKind::UnmanagedRetainValueInst:
case SILInstructionKind::UnownedReleaseInst:
case SILInstructionKind::UnownedRetainInst:
return true;
}
}

namespace {

enum class OwnershipQualifiedKind {
Expand Down
64 changes: 64 additions & 0 deletions lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "swift/Basic/SourceLoc.h"
#include "swift/Parse/Lexer.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/Projection.h"
Expand Down Expand Up @@ -1122,6 +1123,54 @@ static void checkForViolationsInNoEscapeClosures(
/*DiagnoseAsWarning=*/false);
}

#ifndef NDEBUG
// If a partial apply has @inout_aliasable arguments, it may only be used as
// a @noescape function type in a way that is recognized by
// DiagnoseStaticExclusivity.
static void checkNoEscapePartialApply(PartialApplyInst *PAI) {
SmallVector<Operand *, 8> uses(PAI->getUses());
while (!uses.empty()) {
Operand *oper = uses.pop_back_val();
SILInstruction *user = oper->getUser();

if (isIncidentalUse(user) || onlyAffectsRefCount(user))
continue;

if (SingleValueInstruction *copy = getSingleValueCopyOrCast(user)) {
uses.append(copy->getUses().begin(), copy->getUses().end());
continue;
}
if (auto apply = isa<ApplySite>(user)) {
SILValue arg = oper->get();
auto ArgumentFnType = arg->getType().getAs<SILFunctionType>();
if (ArgumentFnType && ArgumentFnType->isNoEscape())
continue;

llvm::dbgs() << "Argument must be @noescape function type: " << *arg;
llvm_unreachable("A partial_apply with @inout_aliasable may only be "
"used as a @noescape function type argument.");
}
auto *store = dyn_cast<StoreInst>(user);
if (store && oper->getOperandNumber() == StoreInst::Src) {
if (auto *PBSI = dyn_cast<ProjectBlockStorageInst>(store->getDest())) {
SILValue storageAddr = PBSI->getOperand();
// The closure is stored to block storage. Recursively visit all
// uses of any initialized block storage values derived from this
// storage address..
for (Operand *oper : storageAddr->getUses()) {
if (auto *IBS = dyn_cast<InitBlockStorageHeaderInst>(oper->getUser()))
uses.append(IBS->getUses().begin(), IBS->getUses().end());
}
continue;
}
}
llvm::dbgs() << "Unexpected partial_apply use: " << *user;
llvm_unreachable("A partial_apply with @inout_aliasable may only be "
"used as a @noescape function type argument.");
}
}
#endif

static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO,
AccessSummaryAnalysis *ASA) {
// The implementation relies on the following SIL invariants:
Expand Down Expand Up @@ -1229,6 +1278,21 @@ static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO,
ConflictingAccesses);
continue;
}
#ifndef NDEBUG
// FIXME: Once AllocBoxToStack is fixed to correctly set noescape
// closure types, move this PartialApply verification into the
// SILVerifier to better pinpoint the offending pass.
if (auto *PAI = dyn_cast<PartialApplyInst>(&I)) {
ApplySite apply(PAI);
if (llvm::any_of(range(apply.getNumArguments()),
[apply](unsigned argIdx) {
return apply.getArgumentConvention(argIdx)
== SILArgumentConvention::Indirect_InoutAliasable;
})) {
checkNoEscapePartialApply(PAI);
}
}
#endif
// Sanity check to make sure entries are properly removed.
assert((!isa<ReturnInst>(&I) || Accesses.size() == 0) &&
"Entries were not properly removed?!");
Expand Down
Loading