Files
swift-mirror/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp
Michael Gottesman 0de00d1ce4 [sil-inst-opt] Improve performance of InstModCallbacks by eliminating indirect call along default callback path.
Specifically before this PR, if a caller did not customize a specific callback
of InstModCallbacks, we would store a static default std::function into
InstModCallbacks. This means that we always would have an indirect jump. That is
unfortunate since this code is often called in loops.

In this PR, I eliminate this problem by:

1. I made all of the actual callback std::function in InstModCallback private
   and gave them a "Func" postfix (e.x.: deleteInst -> deleteInstFunc).

2. I created public methods with the old callback names to actually call the
   callbacks. This ensured that as long as we are not escaping callbacks from
   InstModCallback, this PR would not result in the need for any source changes
   since we are changing a call of a std::function field to a call to a method.

3. I changed all of the places that were escaping inst mod's callbacks to take
   an InstModCallback. We shouldn't be doing that anyway.

4. I changed the default value of each callback in InstModCallbacks to be a
   nullptr and changed the public helper methods to check if a callback is
   null. If the callback is not null, it is called, otherwise the getter falls
   back to an inline default implementation of the operation.

All together this means that the cost of a plain InstModCallback is reduced and
one pays an indirect function cost price as one customizes it further which is
better scalability.

P.S. as a little extra thing, I added a madeChange field onto the
InstModCallback. Now that we have the helpers calling the callbacks, I can
easily insert instrumentation like this, allowing for users to pass in
InstModCallback and see if anything was RAUWed without needing to specify a
callback.
2021-01-04 12:51:55 -08:00

567 lines
21 KiB
C++

//===--- OwnershipModelEliminator.cpp - Eliminate SILOwnership Instr. -----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This file contains a small pass that lowers SIL ownership instructions to
/// their constituent operations. This will enable us to separate
/// implementation
/// of Semantic ARC in SIL and SILGen from ensuring that all of the optimizer
/// passes respect Semantic ARC. This is done by running this pass right after
/// SILGen and as the pass pipeline is updated, moving this pass further and
/// further back in the pipeline.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-ownership-model-eliminator"
#include "swift/Basic/BlotSetVector.h"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "llvm/Support/CommandLine.h"
using namespace swift;
// Utility command line argument to dump the module before we eliminate
// ownership from it.
static llvm::cl::opt<std::string>
DumpBefore("sil-dump-before-ome-to-path", llvm::cl::Hidden);
//===----------------------------------------------------------------------===//
// Implementation
//===----------------------------------------------------------------------===//
namespace {
/// A high level SILInstruction visitor that lowers Ownership SSA from SIL.
///
/// NOTE: Erasing instructions must always be done by the method
/// eraseInstruction /and/ any instructions that are created in one visit must
/// not be deleted in the same visit since after each visit, we empty the
/// tracking list into the instructionsToSimplify array. We do this in order to
/// ensure that when we use inst-simplify on these instructions, we have
/// consistent non-ossa vs ossa code rather than an intermediate state.
struct OwnershipModelEliminatorVisitor
: SILInstructionVisitor<OwnershipModelEliminatorVisitor, bool> {
SmallVector<SILInstruction *, 8> trackingList;
SmallBlotSetVector<SILInstruction *, 8> instructionsToSimplify;
/// Points at either a user passed in SILBuilderContext or points at
/// builderCtxStorage.
SILBuilderContext builderCtx;
SILOpenedArchetypesTracker openedArchetypesTracker;
/// Construct an OME visitor for eliminating ownership from \p fn.
OwnershipModelEliminatorVisitor(SILFunction &fn)
: trackingList(), instructionsToSimplify(),
builderCtx(fn.getModule(), &trackingList),
openedArchetypesTracker(&fn) {
builderCtx.setOpenedArchetypesTracker(&openedArchetypesTracker);
}
/// A "syntactic" high level function that combines our insertPt with a
/// builder ctx.
///
/// Since this is syntactic and we assume that our caller is passing in a
/// lambda that if we inline will be eliminated, we mark this function always
/// inline.
template <typename ResultTy>
ResultTy LLVM_ATTRIBUTE_ALWAYS_INLINE
withBuilder(SILInstruction *insertPt,
llvm::function_ref<ResultTy(SILBuilder &, SILLocation)> visitor) {
SILBuilderWithScope builder(insertPt, builderCtx);
return visitor(builder, insertPt->getLoc());
}
void drainTrackingList() {
// Called before we visit a new instruction and before we ever erase an
// instruction. This ensures that we can post-process instructions that need
// simplification in a purely non-ossa world instead of an indeterminate
// state mid elimination.
while (!trackingList.empty()) {
instructionsToSimplify.insert(trackingList.pop_back_val());
}
}
void beforeVisit(SILInstruction *instToVisit) {
// Add any elements to the tracking list that we currently have in the
// tracking list that we haven't added yet.
drainTrackingList();
}
void eraseInstruction(SILInstruction *i) {
// Before we erase anything, drain the tracking list.
drainTrackingList();
// Make sure to blot our instruction.
instructionsToSimplify.erase(i);
i->eraseFromParent();
}
void eraseInstructionAndRAUW(SingleValueInstruction *i, SILValue newValue) {
// Make sure to blot our instruction.
i->replaceAllUsesWith(newValue);
eraseInstruction(i);
}
bool visitSILInstruction(SILInstruction *) { return false; }
bool visitLoadInst(LoadInst *li);
bool visitStoreInst(StoreInst *si);
bool visitStoreBorrowInst(StoreBorrowInst *si);
bool visitCopyValueInst(CopyValueInst *cvi);
bool visitDestroyValueInst(DestroyValueInst *dvi);
bool visitLoadBorrowInst(LoadBorrowInst *lbi);
bool visitBeginBorrowInst(BeginBorrowInst *bbi) {
eraseInstructionAndRAUW(bbi, bbi->getOperand());
return true;
}
bool visitEndBorrowInst(EndBorrowInst *ebi) {
eraseInstruction(ebi);
return true;
}
bool visitEndLifetimeInst(EndLifetimeInst *eli) {
eraseInstruction(eli);
return true;
}
bool visitUncheckedOwnershipConversionInst(
UncheckedOwnershipConversionInst *uoci) {
eraseInstructionAndRAUW(uoci, uoci->getOperand());
return true;
}
bool visitUnmanagedRetainValueInst(UnmanagedRetainValueInst *urvi);
bool visitUnmanagedReleaseValueInst(UnmanagedReleaseValueInst *urvi);
bool visitUnmanagedAutoreleaseValueInst(UnmanagedAutoreleaseValueInst *uavi);
bool visitCheckedCastBranchInst(CheckedCastBranchInst *cbi);
bool visitSwitchEnumInst(SwitchEnumInst *swi);
bool visitDestructureStructInst(DestructureStructInst *dsi);
bool visitDestructureTupleInst(DestructureTupleInst *dti);
// We lower this to unchecked_bitwise_cast losing our assumption of layout
// compatibility.
bool visitUncheckedValueCastInst(UncheckedValueCastInst *uvci) {
return withBuilder<bool>(uvci, [&](SILBuilder &b, SILLocation loc) {
auto *newVal = b.createUncheckedBitwiseCast(loc, uvci->getOperand(),
uvci->getType());
eraseInstructionAndRAUW(uvci, newVal);
return true;
});
}
void splitDestructure(SILInstruction *destructure,
SILValue destructureOperand);
};
} // end anonymous namespace
bool OwnershipModelEliminatorVisitor::visitLoadInst(LoadInst *li) {
auto qualifier = li->getOwnershipQualifier();
// If the qualifier is unqualified, there is nothing further to do
// here. Just return.
if (qualifier == LoadOwnershipQualifier::Unqualified)
return false;
auto result = withBuilder<SILValue>(li, [&](SILBuilder &b, SILLocation loc) {
return b.emitLoadValueOperation(loc, li->getOperand(),
li->getOwnershipQualifier());
});
// Then remove the qualified load and use the unqualified load as the def of
// all of LI's uses.
eraseInstructionAndRAUW(li, result);
return true;
}
bool OwnershipModelEliminatorVisitor::visitStoreInst(StoreInst *si) {
auto qualifier = si->getOwnershipQualifier();
// If the qualifier is unqualified, there is nothing further to do
// here. Just return.
if (qualifier == StoreOwnershipQualifier::Unqualified)
return false;
withBuilder<void>(si, [&](SILBuilder &b, SILLocation loc) {
b.emitStoreValueOperation(loc, si->getSrc(), si->getDest(),
si->getOwnershipQualifier());
});
// Then remove the qualified store.
eraseInstruction(si);
return true;
}
bool OwnershipModelEliminatorVisitor::visitStoreBorrowInst(
StoreBorrowInst *si) {
withBuilder<void>(si, [&](SILBuilder &b, SILLocation loc) {
b.emitStoreValueOperation(loc, si->getSrc(), si->getDest(),
StoreOwnershipQualifier::Unqualified);
});
// Then remove the qualified store.
eraseInstruction(si);
return true;
}
bool OwnershipModelEliminatorVisitor::visitLoadBorrowInst(LoadBorrowInst *lbi) {
// Break down the load borrow into an unqualified load.
auto newLoad =
withBuilder<SILValue>(lbi, [&](SILBuilder &b, SILLocation loc) {
return b.createLoad(loc, lbi->getOperand(),
LoadOwnershipQualifier::Unqualified);
});
// Then remove the qualified load and use the unqualified load as the def of
// all of LI's uses.
eraseInstructionAndRAUW(lbi, newLoad);
return true;
}
bool OwnershipModelEliminatorVisitor::visitCopyValueInst(CopyValueInst *cvi) {
// A copy_value of an address-only type cannot be replaced.
if (cvi->getType().isAddressOnly(*cvi->getFunction()))
return false;
// Now that we have set the unqualified ownership flag, destroy value
// operation will delegate to the appropriate strong_release, etc.
withBuilder<void>(cvi, [&](SILBuilder &b, SILLocation loc) {
b.emitCopyValueOperation(loc, cvi->getOperand());
});
eraseInstructionAndRAUW(cvi, cvi->getOperand());
return true;
}
bool OwnershipModelEliminatorVisitor::visitUnmanagedRetainValueInst(
UnmanagedRetainValueInst *urvi) {
// Now that we have set the unqualified ownership flag, destroy value
// operation will delegate to the appropriate strong_release, etc.
withBuilder<void>(urvi, [&](SILBuilder &b, SILLocation loc) {
b.emitCopyValueOperation(loc, urvi->getOperand());
});
eraseInstruction(urvi);
return true;
}
bool OwnershipModelEliminatorVisitor::visitUnmanagedReleaseValueInst(
UnmanagedReleaseValueInst *urvi) {
// Now that we have set the unqualified ownership flag, destroy value
// operation will delegate to the appropriate strong_release, etc.
withBuilder<void>(urvi, [&](SILBuilder &b, SILLocation loc) {
b.emitDestroyValueOperation(loc, urvi->getOperand());
});
eraseInstruction(urvi);
return true;
}
bool OwnershipModelEliminatorVisitor::visitUnmanagedAutoreleaseValueInst(
UnmanagedAutoreleaseValueInst *UAVI) {
// Now that we have set the unqualified ownership flag, destroy value
// operation will delegate to the appropriate strong_release, etc.
withBuilder<void>(UAVI, [&](SILBuilder &b, SILLocation loc) {
b.createAutoreleaseValue(loc, UAVI->getOperand(), UAVI->getAtomicity());
});
eraseInstruction(UAVI);
return true;
}
bool OwnershipModelEliminatorVisitor::visitDestroyValueInst(
DestroyValueInst *dvi) {
// A destroy_value of an address-only type cannot be replaced.
if (dvi->getOperand()->getType().isAddressOnly(*dvi->getFunction()))
return false;
// Now that we have set the unqualified ownership flag, destroy value
// operation will delegate to the appropriate strong_release, etc.
withBuilder<void>(dvi, [&](SILBuilder &b, SILLocation loc) {
b.emitDestroyValueOperation(loc, dvi->getOperand());
});
eraseInstruction(dvi);
return true;
}
bool OwnershipModelEliminatorVisitor::visitCheckedCastBranchInst(
CheckedCastBranchInst *cbi) {
// In ownership qualified SIL, checked_cast_br must pass its argument to the
// fail case so we can clean it up. In non-ownership qualified SIL, we expect
// no argument from the checked_cast_br in the default case. The way that we
// handle this transformation is that:
//
// 1. We replace all uses of the argument to the false block with a use of the
// checked cast branch's operand.
// 2. We delete the argument from the false block.
SILBasicBlock *failureBlock = cbi->getFailureBB();
if (failureBlock->getNumArguments() == 0)
return false;
failureBlock->getArgument(0)->replaceAllUsesWith(cbi->getOperand());
failureBlock->eraseArgument(0);
return true;
}
bool OwnershipModelEliminatorVisitor::visitSwitchEnumInst(
SwitchEnumInst *swei) {
// In ownership qualified SIL, switch_enum must pass its argument to the fail
// case so we can clean it up. In non-ownership qualified SIL, we expect no
// argument from the switch_enum in the default case. The way that we handle
// this transformation is that:
//
// 1. We replace all uses of the argument to the false block with a use of the
// checked cast branch's operand.
// 2. We delete the argument from the false block.
if (!swei->hasDefault())
return false;
SILBasicBlock *defaultBlock = swei->getDefaultBB();
if (defaultBlock->getNumArguments() == 0)
return false;
defaultBlock->getArgument(0)->replaceAllUsesWith(swei->getOperand());
defaultBlock->eraseArgument(0);
return true;
}
void OwnershipModelEliminatorVisitor::splitDestructure(
SILInstruction *destructureInst, SILValue destructureOperand) {
assert((isa<DestructureStructInst>(destructureInst) ||
isa<DestructureTupleInst>(destructureInst)) &&
"Only destructure operations can be passed to splitDestructure");
// First before we destructure anything, see if we can simplify any of our
// instruction operands.
SILModule &M = destructureInst->getModule();
SILType opType = destructureOperand->getType();
llvm::SmallVector<Projection, 8> projections;
Projection::getFirstLevelProjections(
opType, M, TypeExpansionContext(*destructureInst->getFunction()),
projections);
assert(projections.size() == destructureInst->getNumResults());
auto destructureResults = destructureInst->getResults();
for (unsigned index : indices(destructureResults)) {
SILValue result = destructureResults[index];
// If our result doesnt have any uses, do not emit instructions, just skip
// it.
if (result->use_empty())
continue;
// Otherwise, create a projection.
const auto &proj = projections[index];
auto *projInst = withBuilder<SingleValueInstruction *>(
destructureInst, [&](SILBuilder &b, SILLocation loc) {
return proj.createObjectProjection(b, loc, destructureOperand).get();
});
// First RAUW Result with ProjInst. This ensures that we have a complete IR
// before we perform any simplifications.
result->replaceAllUsesWith(projInst);
}
// Now that all of its uses have been eliminated, erase the destructure.
eraseInstruction(destructureInst);
}
bool OwnershipModelEliminatorVisitor::visitDestructureStructInst(
DestructureStructInst *dsi) {
splitDestructure(dsi, dsi->getOperand());
return true;
}
bool OwnershipModelEliminatorVisitor::visitDestructureTupleInst(
DestructureTupleInst *dti) {
splitDestructure(dti, dti->getOperand());
return true;
}
//===----------------------------------------------------------------------===//
// Top Level Entry Point
//===----------------------------------------------------------------------===//
static bool stripOwnership(SILFunction &func) {
// If F is an external declaration, do not process it.
if (func.isExternalDeclaration())
return false;
// Set F to have unqualified ownership.
func.setOwnershipEliminated();
bool madeChange = false;
SmallVector<SILInstruction *, 32> createdInsts;
OwnershipModelEliminatorVisitor visitor(func);
for (auto &block : func) {
// Change all arguments to have OwnershipKind::None.
for (auto *arg : block.getArguments()) {
arg->setOwnershipKind(OwnershipKind::None);
}
for (auto ii = block.begin(), ie = block.end(); ii != ie;) {
// Since we are going to be potentially removing instructions, we need
// to make sure to increment our iterator before we perform any
// visits.
SILInstruction *inst = &*ii;
++ii;
madeChange |= visitor.visit(inst);
}
}
// Once we have finished processing all instructions, we should be
// consistently in non-ossa form meaning that it is now safe for us to invoke
// utilities that assume that they are in a consistent ossa or non-ossa form
// such as inst simplify. Now go through any instructions and simplify using
// inst simplify!
//
// DISCUSSION: We want our utilities to be able to assume if f.hasOwnership()
// is false then the utility is allowed to assume the function the utility is
// invoked within is in non-ossa form structurally (e.x.: non-ossa does not
// have arguments on the default result of checked_cast_br).
while (!visitor.instructionsToSimplify.empty()) {
auto value = visitor.instructionsToSimplify.pop_back_val();
if (!value.hasValue())
continue;
if (SILValue newValue = simplifyInstruction(*value)) {
InstModCallbacks callbacks([&](SILInstruction *instToErase) {
visitor.eraseInstruction(instToErase);
});
replaceAllSimplifiedUsesAndErase(*value, newValue, callbacks);
madeChange = true;
}
}
return madeChange;
}
static void prepareNonTransparentSILFunctionForOptimization(ModuleDecl *,
SILFunction *f) {
if (!f->hasOwnership() || f->isTransparent())
return;
LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
<< f->getName() << "\n");
stripOwnership(*f);
}
static void prepareSILFunctionForOptimization(ModuleDecl *, SILFunction *f) {
if (!f->hasOwnership())
return;
LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
<< f->getName() << "\n");
stripOwnership(*f);
}
namespace {
struct OwnershipModelEliminator : SILFunctionTransform {
bool skipTransparent;
bool skipStdlibModule;
OwnershipModelEliminator(bool skipTransparent, bool skipStdlibModule)
: skipTransparent(skipTransparent), skipStdlibModule(skipStdlibModule) {}
void run() override {
if (DumpBefore.size()) {
getFunction()->dump(DumpBefore.c_str());
}
auto *f = getFunction();
auto &mod = getFunction()->getModule();
// If we are supposed to skip the stdlib module and we are in the stdlib
// module bail.
if (skipStdlibModule && mod.isStdlibModule()) {
return;
}
if (!f->hasOwnership())
return;
// If we were asked to not strip ownership from transparent functions in
// /our/ module, return.
if (skipTransparent && f->isTransparent())
return;
// Verify here to make sure ownership is correct before we strip.
{
// Add a pretty stack trace entry to tell users who see a verification
// failure triggered by this verification check that they need to re-run
// with -sil-verify-all to actually find the pass that introduced the
// verification error.
//
// DISCUSSION: This occurs due to the crash from the verification
// failure happening in the pass itself. This causes us to dump the
// SILFunction and emit a msg that this pass (OME) is the culprit. This
// is generally correct for most passes, but not for OME since we are
// verifying before we have even modified the function to ensure that
// all ownership invariants have been respected before we lower
// ownership from the function.
llvm::PrettyStackTraceString silVerifyAllMsgOnFailure(
"Found verification error when verifying before lowering "
"ownership. Please re-run with -sil-verify-all to identify the "
"actual pass that introduced the verification error.");
f->verify();
}
if (stripOwnership(*f)) {
auto InvalidKind = SILAnalysis::InvalidationKind::BranchesAndInstructions;
invalidateAnalysis(InvalidKind);
}
// If we were asked to strip transparent, we are at the beginning of the
// performance pipeline. In such a case, we register a handler so that all
// future things we deserialize have ownership stripped.
using NotificationHandlerTy =
FunctionBodyDeserializationNotificationHandler;
std::unique_ptr<DeserializationNotificationHandler> ptr;
if (skipTransparent) {
if (!mod.hasRegisteredDeserializationNotificationHandlerForNonTransparentFuncOME()) {
ptr.reset(new NotificationHandlerTy(
prepareNonTransparentSILFunctionForOptimization));
mod.registerDeserializationNotificationHandler(std::move(ptr));
mod.setRegisteredDeserializationNotificationHandlerForNonTransparentFuncOME();
}
} else {
if (!mod.hasRegisteredDeserializationNotificationHandlerForAllFuncOME()) {
ptr.reset(new NotificationHandlerTy(prepareSILFunctionForOptimization));
mod.registerDeserializationNotificationHandler(std::move(ptr));
mod.setRegisteredDeserializationNotificationHandlerForAllFuncOME();
}
}
}
};
} // end anonymous namespace
SILTransform *swift::createOwnershipModelEliminator() {
return new OwnershipModelEliminator(false /*skip transparent*/,
false /*ignore stdlib*/);
}
SILTransform *swift::createNonTransparentFunctionOwnershipModelEliminator() {
return new OwnershipModelEliminator(true /*skip transparent*/,
false /*ignore stdlib*/);
}
SILTransform *
swift::createNonStdlibNonTransparentFunctionOwnershipModelEliminator() {
return new OwnershipModelEliminator(true /*skip transparent*/,
true /*ignore stdlib*/);
}