mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Explanation: Unowned result conventions do not work well with OSSA. Retain the result right after the call when we come out of OSSA so we can treat the returned value as if it was owned when we do optimizations. This fix a miscompilation due to the DestroyAddrHoisting pass hoisting destroys above copies with unowned sources. When the destroyed object was the last reference to the pointed memory the copy is happening too late resulting in a use after free. Issues: rdar://160462854 Original PRs: #84612 Risk: We change where retaining of the unowned return values happen in the optimization pipeline. It is hard to anticipate all the possible effects but it should make the optimizer more correct. Testing: Added a compiler test. Reviewers: @eeckstein, @atrick
1003 lines
36 KiB
C++
1003 lines
36 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.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/SIL/SILValue.h"
|
|
#define DEBUG_TYPE "sil-ownership-model-eliminator"
|
|
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/BlotSetVector.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/Projection.h"
|
|
#include "swift/SIL/SILBuilder.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILVisitor.h"
|
|
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
|
|
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/StackNesting.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/ErrorHandling.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;
|
|
|
|
/// Construct an OME visitor for eliminating ownership from \p fn.
|
|
OwnershipModelEliminatorVisitor(SILFunction &fn)
|
|
: trackingList(), instructionsToSimplify(),
|
|
builderCtx(fn.getModule(), &trackingList) {
|
|
}
|
|
|
|
/// 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 *inst) {
|
|
// Make sure this wasn't a forwarding instruction in case someone adds a new
|
|
// forwarding instruction but does not update this code.
|
|
if (ForwardingInstruction::isa(inst)) {
|
|
llvm::errs() << "Found unhandled forwarding inst: " << *inst;
|
|
llvm_unreachable("standard error handler");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool visitLoadInst(LoadInst *li);
|
|
bool visitStoreInst(StoreInst *si);
|
|
bool visitStoreBorrowInst(StoreBorrowInst *si);
|
|
bool visitCopyValueInst(CopyValueInst *cvi);
|
|
bool visitExplicitCopyValueInst(ExplicitCopyValueInst *cvi);
|
|
bool visitExplicitCopyAddrInst(ExplicitCopyAddrInst *cai);
|
|
bool visitApplyInst(ApplyInst *ai);
|
|
|
|
bool visitIgnoredUseInst(IgnoredUseInst *iui) {
|
|
eraseInstruction(iui);
|
|
return true;
|
|
}
|
|
|
|
void splitDestroy(DestroyValueInst *destroy);
|
|
bool peepholeTupleConstructorUser(DestructureTupleInst *dti);
|
|
bool visitDestroyValueInst(DestroyValueInst *dvi);
|
|
bool visitDeallocBoxInst(DeallocBoxInst *dbi) {
|
|
if (!dbi->isDeadEnd())
|
|
return false;
|
|
|
|
// dead_end instructions are required for complete OSSA lifetimes but should
|
|
// not exist post-OSSA.
|
|
eraseInstruction(dbi);
|
|
return true;
|
|
}
|
|
bool visitMergeIsolationRegionInst(MergeIsolationRegionInst *mir) {
|
|
eraseInstruction(mir);
|
|
return true;
|
|
}
|
|
|
|
bool visitLoadBorrowInst(LoadBorrowInst *lbi);
|
|
bool visitMoveValueInst(MoveValueInst *mvi) {
|
|
eraseInstructionAndRAUW(mvi, mvi->getOperand());
|
|
return true;
|
|
}
|
|
bool visitDropDeinitInst(DropDeinitInst *ddi) {
|
|
instructionsToSimplify.insert(ddi);
|
|
return false;
|
|
}
|
|
bool visitBeginBorrowInst(BeginBorrowInst *bbi) {
|
|
eraseInstructionAndRAUW(bbi, bbi->getOperand());
|
|
return true;
|
|
}
|
|
bool visitBorrowedFromInst(BorrowedFromInst *bfi) {
|
|
eraseInstructionAndRAUW(bfi, bfi->getBorrowedValue());
|
|
return true;
|
|
}
|
|
bool visitEndBorrowInst(EndBorrowInst *ebi) {
|
|
eraseInstruction(ebi);
|
|
return true;
|
|
}
|
|
bool visitEndLifetimeInst(EndLifetimeInst *eli) {
|
|
eraseInstruction(eli);
|
|
return true;
|
|
}
|
|
bool visitExtendLifetimeInst(ExtendLifetimeInst *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;
|
|
});
|
|
}
|
|
|
|
bool visitPartialApplyInst(PartialApplyInst *pai);
|
|
|
|
void splitDestructure(SILInstruction *destructure,
|
|
SILValue destructureOperand);
|
|
|
|
#define HANDLE_FORWARDING_INST(Cls) \
|
|
bool visit##Cls##Inst(Cls##Inst *i) { \
|
|
ForwardingInstruction::get(i)->setForwardingOwnershipKind( \
|
|
OwnershipKind::None); \
|
|
return true; \
|
|
}
|
|
HANDLE_FORWARDING_INST(ConvertFunction)
|
|
HANDLE_FORWARDING_INST(MoveOnlyWrapperToCopyableValue)
|
|
HANDLE_FORWARDING_INST(MoveOnlyWrapperToCopyableBox)
|
|
HANDLE_FORWARDING_INST(Upcast)
|
|
HANDLE_FORWARDING_INST(UncheckedRefCast)
|
|
HANDLE_FORWARDING_INST(RefToBridgeObject)
|
|
HANDLE_FORWARDING_INST(BridgeObjectToRef)
|
|
HANDLE_FORWARDING_INST(ThinToThickFunction)
|
|
HANDLE_FORWARDING_INST(UnconditionalCheckedCast)
|
|
HANDLE_FORWARDING_INST(Struct)
|
|
HANDLE_FORWARDING_INST(Object)
|
|
HANDLE_FORWARDING_INST(Tuple)
|
|
HANDLE_FORWARDING_INST(Enum)
|
|
HANDLE_FORWARDING_INST(UncheckedEnumData)
|
|
HANDLE_FORWARDING_INST(OpenExistentialRef)
|
|
HANDLE_FORWARDING_INST(InitExistentialRef)
|
|
HANDLE_FORWARDING_INST(MarkDependence)
|
|
HANDLE_FORWARDING_INST(DifferentiableFunction)
|
|
HANDLE_FORWARDING_INST(LinearFunction)
|
|
HANDLE_FORWARDING_INST(StructExtract)
|
|
HANDLE_FORWARDING_INST(TupleExtract)
|
|
HANDLE_FORWARDING_INST(LinearFunctionExtract)
|
|
HANDLE_FORWARDING_INST(DifferentiableFunctionExtract)
|
|
HANDLE_FORWARDING_INST(MarkUninitialized)
|
|
HANDLE_FORWARDING_INST(FunctionExtractIsolation)
|
|
#undef HANDLE_FORWARDING_INST
|
|
};
|
|
|
|
} // 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 after RAUWing si with its dest. This
|
|
// ensures that any uses of the interior pointer result of the store_borrow
|
|
// are rewritten to be on the dest point.
|
|
si->replaceAllUsesWith(si->getDest());
|
|
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;
|
|
|
|
// Nonescaping closures are represented ultimately as trivial pointers to
|
|
// their context, but we use ownership to do borrow checking of their captures
|
|
// in OSSA. Now that we're eliminating ownership, fold away copies.
|
|
if (auto cvFnTy = cvi->getType().getAs<SILFunctionType>()) {
|
|
if (cvFnTy->isTrivialNoEscape()) {
|
|
// Erase any `destroy_value`s of this copy, so we don't mistake them for
|
|
// the end of the original value's lifetime after we RAUW.
|
|
SmallVector<DestroyValueInst *, 2> destroys;
|
|
for (auto user : cvi->getUsersOfType<DestroyValueInst>()) {
|
|
destroys.push_back(user);
|
|
}
|
|
for (auto destroy : destroys) {
|
|
eraseInstruction(destroy);
|
|
}
|
|
eraseInstructionAndRAUW(cvi, cvi->getOperand());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Now that we have set the unqualified ownership flag, emitCopyValueOperation
|
|
// 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::visitExplicitCopyValueInst(
|
|
ExplicitCopyValueInst *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::visitExplicitCopyAddrInst(
|
|
ExplicitCopyAddrInst *ecai) {
|
|
// Now that we have set the unqualified ownership flag, destroy value
|
|
// operation will delegate to the appropriate strong_release, etc.
|
|
withBuilder<void>(ecai, [&](SILBuilder &b, SILLocation loc) {
|
|
b.createCopyAddr(loc, ecai->getSrc(), ecai->getDest(), ecai->isTakeOfSrc(),
|
|
ecai->isInitializationOfDest());
|
|
});
|
|
eraseInstruction(ecai);
|
|
return true;
|
|
}
|
|
|
|
bool OwnershipModelEliminatorVisitor::visitApplyInst(ApplyInst *ai) {
|
|
auto callee = ai->getCallee();
|
|
|
|
if (!callee)
|
|
return false;
|
|
|
|
// Insert destroy_addr for @in_cxx arguments.
|
|
auto fnTy = callee->getType().castTo<SILFunctionType>();
|
|
SILFunctionConventions fnConv(fnTy, ai->getModule());
|
|
bool changed = false;
|
|
|
|
for (int i = fnConv.getSILArgIndexOfFirstParam(),
|
|
e = i + fnConv.getNumParameters();
|
|
i < e; ++i) {
|
|
auto paramInfo = fnConv.getParamInfoForSILArg(i);
|
|
if (!paramInfo.isIndirectInCXX())
|
|
continue;
|
|
auto arg = ai->getArgument(i);
|
|
SILBuilderWithScope builder(ai->getNextInstruction(), builderCtx);
|
|
builder.createDestroyAddr(ai->getLoc(), arg);
|
|
changed = true;
|
|
}
|
|
|
|
// Insert a retain for unowned results.
|
|
SILBuilderWithScope builder(ai->getNextInstruction(), builderCtx);
|
|
auto resultIt = fnConv.getDirectSILResults().begin();
|
|
auto copyValue = [&](unsigned idx, SILValue v) {
|
|
auto result = *resultIt;
|
|
if (result.getConvention() == ResultConvention::Unowned)
|
|
builder.emitCopyValueOperation(ai->getLoc(), v);
|
|
++resultIt;
|
|
};
|
|
if (fnConv.getNumDirectSILResults() == 1)
|
|
copyValue(0, ai);
|
|
else
|
|
builder.emitDestructureValueOperation(ai->getLoc(), ai, copyValue);
|
|
|
|
return changed;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Poison every debug variable associated with \p value.
|
|
static void injectDebugPoison(DestroyValueInst *destroy) {
|
|
// TODO: SILDebugVariable should define it's key. Until then, we try to be
|
|
// consistent with IRGen.
|
|
using StackSlotKey =
|
|
std::pair<unsigned, std::pair<const SILDebugScope *, StringRef>>;
|
|
// This DenseSet points to StringRef memory into the debug_value insts.
|
|
llvm::SmallDenseSet<StackSlotKey> poisonedVars;
|
|
|
|
SILValue destroyedValue = destroy->getOperand();
|
|
for (Operand *use : getDebugUses(destroyedValue)) {
|
|
auto debugVal = dyn_cast<DebugValueInst>(use->getUser());
|
|
if (!debugVal || debugVal->poisonRefs())
|
|
continue;
|
|
|
|
const SILDebugScope *scope = debugVal->getDebugScope();
|
|
auto loc = debugVal->getLoc();
|
|
|
|
std::optional<SILDebugVariable> varInfo = debugVal->getVarInfo();
|
|
if (!varInfo)
|
|
continue;
|
|
|
|
unsigned argNo = varInfo->ArgNo;
|
|
if (!poisonedVars.insert({argNo, {scope, varInfo->Name}}).second)
|
|
continue;
|
|
|
|
SILBuilder builder(destroy);
|
|
// The poison DebugValue's DebugLocation must be identical to the original
|
|
// DebugValue. The DebugScope is used to identify the variable's unique
|
|
// shadow copy. The SILLocation is used to determine the VarDecl, which is
|
|
// necessary in some cases to derive a unique variable name.
|
|
//
|
|
// This debug location is obviously inconsistent with surrounding code, but
|
|
// IRGen is responsible for fixing this.
|
|
builder.setCurrentDebugScope(scope);
|
|
auto *newDebugVal =
|
|
builder.createDebugValue(loc, destroyedValue, *varInfo, PoisonRefs);
|
|
assert(*(newDebugVal->getVarInfo()) == *varInfo && "lost in translation");
|
|
(void)newDebugVal;
|
|
}
|
|
}
|
|
|
|
bool OwnershipModelEliminatorVisitor::visitPartialApplyInst(
|
|
PartialApplyInst *inst) {
|
|
// Escaping closures don't need attention beyond what we already perform.
|
|
if (!inst->isOnStack())
|
|
return false;
|
|
|
|
// A nonescaping closure borrows its captures, but now that we've lowered
|
|
// those borrows away, we need to make those dependence relationships explicit
|
|
// so that the optimizer continues respecting them.
|
|
MarkDependenceInst *firstNewMDI = nullptr;
|
|
auto newValue = withBuilder<SILValue>(inst->getNextInstruction(),
|
|
[&](SILBuilder &b, SILLocation loc) {
|
|
SILValue newValue = inst;
|
|
for (auto op : inst->getArguments()) {
|
|
// Trivial types have infinite lifetimes already.
|
|
if (op->getType().isTrivial(*inst->getFunction())) {
|
|
break;
|
|
}
|
|
// Address operands should already have their dependence marked, since
|
|
// borrowing doesn't model values in memory.
|
|
if (op->getType().isAddress()) {
|
|
break;
|
|
}
|
|
|
|
// If this is a nontrivial value argument, insert the mark_dependence.
|
|
auto mdi = b.createMarkDependence(loc, newValue, op,
|
|
MarkDependenceKind::Escaping);
|
|
if (!firstNewMDI)
|
|
firstNewMDI = mdi;
|
|
newValue = mdi;
|
|
}
|
|
return newValue;
|
|
});
|
|
|
|
// Rewrite all uses other than the root of the new dependence chain, and a
|
|
// `dealloc_stack` of the partial_apply instruction we may have already
|
|
// created, to go through the dependence chain, if there is one.
|
|
if (firstNewMDI) {
|
|
while (!inst->use_empty()) {
|
|
auto opI = inst->use_begin();
|
|
while ((*opI)->getUser() == firstNewMDI
|
|
|| isa<DeallocStackInst>((*opI)->getUser())) {
|
|
++opI;
|
|
if (opI == inst->use_end()) {
|
|
goto done_rewriting;
|
|
}
|
|
}
|
|
(*opI)->set(newValue);
|
|
}
|
|
done_rewriting:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Destroy all nontrivial members of the struct or enum destroyed by \p destroy
|
|
// ignoring any user-defined deinit.
|
|
//
|
|
// See also splitDestructure().
|
|
void OwnershipModelEliminatorVisitor::splitDestroy(DestroyValueInst *destroy) {
|
|
SILModule &module = destroy->getModule();
|
|
SILFunction *function = destroy->getFunction();
|
|
auto loc = destroy->getLoc();
|
|
auto operand = destroy->getOperand();
|
|
auto operandTy = operand->getType();
|
|
NominalTypeDecl *nominalDecl = operandTy.getNominalOrBoundGenericNominal();
|
|
|
|
if (isa<StructDecl>(nominalDecl)) {
|
|
withBuilder<void>(destroy, [&](SILBuilder &builder, SILLocation loc) {
|
|
llvm::SmallVector<Projection, 8> projections;
|
|
Projection::getFirstLevelProjections(
|
|
operandTy, module, TypeExpansionContext(*function), projections);
|
|
for (Projection &projection : projections) {
|
|
auto *projectedValue =
|
|
projection.createObjectProjection(builder, loc, operand).get();
|
|
builder.emitDestroyValueOperation(loc, projectedValue);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// "Destructure" an enum.
|
|
auto *enumDecl = dyn_cast<EnumDecl>(nominalDecl);
|
|
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 8> caseCleanups;
|
|
auto *destroyBlock = destroy->getParent();
|
|
auto *contBlock = destroyBlock->split(std::next(destroy->getIterator()));
|
|
|
|
for (auto *enumElt : enumDecl->getAllElements()) {
|
|
auto *enumBlock = function->createBasicBlockBefore(contBlock);
|
|
SILBuilder builder(enumBlock, enumBlock->begin());
|
|
if (enumElt->hasAssociatedValues()) {
|
|
auto caseType = operandTy.getEnumElementType(enumElt, function);
|
|
auto *phiArg =
|
|
enumBlock->createPhiArgument(caseType, OwnershipKind::Owned);
|
|
SILBuilderWithScope(enumBlock, builderCtx, destroy->getDebugScope())
|
|
.emitDestroyValueOperation(loc, phiArg);
|
|
}
|
|
// Branch to the continue block.
|
|
builder.createBranch(loc, contBlock);
|
|
caseCleanups.emplace_back(enumElt, enumBlock);
|
|
}
|
|
SILBuilderWithScope switchBuilder(destroyBlock, builderCtx,
|
|
destroy->getDebugScope());
|
|
switchBuilder.createSwitchEnum(loc, operand, nullptr, caseCleanups);
|
|
}
|
|
|
|
bool OwnershipModelEliminatorVisitor::visitDestroyValueInst(
|
|
DestroyValueInst *dvi) {
|
|
if (dvi->isDeadEnd()) {
|
|
// dead_end instructions are required for complete OSSA lifetimes but should
|
|
// not exist post-OSSA.
|
|
eraseInstruction(dvi);
|
|
return true;
|
|
}
|
|
|
|
// Nonescaping closures are represented ultimately as trivial pointers to
|
|
// their context, but we use ownership to do borrow checking of their captures
|
|
// in OSSA. Now that we're eliminating ownership, fold away destroys.
|
|
auto operand = dvi->getOperand();
|
|
auto operandTy = operand->getType();
|
|
if (auto operandFnTy = operandTy.getAs<SILFunctionType>()){
|
|
if (operandFnTy->isTrivialNoEscape()) {
|
|
eraseInstruction(dvi);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// A drop_deinit eliminates any user-defined deinit. Its destroy does not
|
|
// lower to a release. If any members require deinitialization, they must be
|
|
// destructured and individually destroyed.
|
|
if (isa<DropDeinitInst>(lookThroughOwnershipInsts(operand))) {
|
|
splitDestroy(dvi);
|
|
eraseInstruction(dvi);
|
|
return true;
|
|
}
|
|
|
|
// Now that we have set the unqualified ownership flag,
|
|
// emitDestroyValueOperation will insert the appropriate instruction.
|
|
withBuilder<void>(dvi, [&](SILBuilder &b, SILLocation loc) {
|
|
b.emitDestroyValueOperation(loc, operand);
|
|
});
|
|
if (dvi->poisonRefs()) {
|
|
injectDebugPoison(dvi);
|
|
}
|
|
eraseInstruction(dvi);
|
|
return true;
|
|
}
|
|
|
|
bool OwnershipModelEliminatorVisitor::visitCheckedCastBranchInst(
|
|
CheckedCastBranchInst *cbi) {
|
|
cbi->setForwardingOwnershipKind(OwnershipKind::None);
|
|
|
|
// 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) {
|
|
swei->setForwardingOwnershipKind(OwnershipKind::None);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// See also splitDestroy().
|
|
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::peepholeTupleConstructorUser(DestructureTupleInst *dti) {
|
|
auto destructureResults = dti->getResults();
|
|
TupleInst *ti = nullptr;
|
|
for (unsigned index : indices(destructureResults)) {
|
|
SILValue result = destructureResults[index];
|
|
// We must have a single use of the destructure value.
|
|
auto *use = result->getSingleUse();
|
|
if (!use) {
|
|
ti = nullptr;
|
|
break;
|
|
}
|
|
// The user must be a single tuple constructor.
|
|
auto *tupleUsr = dyn_cast<TupleInst>(use->getUser());
|
|
if (!tupleUsr || (ti && ti != tupleUsr)) {
|
|
ti = nullptr;
|
|
break;
|
|
}
|
|
// Indices of destructure and tuple must match.
|
|
if (use->getOperandNumber() != index) {
|
|
ti = nullptr;
|
|
break;
|
|
}
|
|
ti = tupleUsr;
|
|
}
|
|
if (!ti)
|
|
return false;
|
|
|
|
if (ti->getType() != dti->getOperand()->getType())
|
|
return false;
|
|
|
|
ti->replaceAllUsesWith(dti->getOperand());
|
|
eraseInstruction(ti);
|
|
eraseInstruction(dti);
|
|
return true;
|
|
}
|
|
|
|
bool OwnershipModelEliminatorVisitor::visitDestructureTupleInst(
|
|
DestructureTupleInst *dti) {
|
|
|
|
// (tuple (destructure)) -> (id)
|
|
if (peepholeTupleConstructorUser(dti)) {
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
|
|
llvm::DenseMap<PartialApplyInst *, SmallVector<SILInstruction *>>
|
|
lifetimeEnds;
|
|
|
|
// Nonescaping closures are represented ultimately as trivial pointers to
|
|
// their context, but we use ownership to do borrow checking of their captures
|
|
// in OSSA. Now that we're eliminating ownership, we need to dealloc_stack the
|
|
// context at its lifetime ends.
|
|
// partial_apply's lifetime ends has to be gathered before we begin to leave
|
|
// OSSA, but no dealloc_stack can be emitted until after we leave OSSA.
|
|
for (auto &block : func) {
|
|
for (auto &ii : block) {
|
|
auto *pai = dyn_cast<PartialApplyInst>(&ii);
|
|
if (!pai || !pai->isOnStack()) {
|
|
continue;
|
|
}
|
|
pai->visitOnStackLifetimeEnds([&](Operand *op) {
|
|
lifetimeEnds[pai].push_back(op->getUser());
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Set F to have unqualified ownership.
|
|
func.setOwnershipEliminated();
|
|
|
|
// Now that we are in non-ossa, create dealloc_stack at partial_apply's
|
|
// lifetime ends
|
|
for (auto &it : lifetimeEnds) {
|
|
auto *pai = it.first;
|
|
for (auto *lifetimeEnd : it.second) {
|
|
SILBuilderWithScope(lifetimeEnd->getNextInstruction())
|
|
.createDeallocStack(lifetimeEnd->getLoc(), pai);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// This loop may erase instructions and split basic blocks.
|
|
for (auto ii = block.begin(); ii != block.end(); ++ii) {
|
|
SILInstruction *inst = &*ii;
|
|
if (inst->isDeleted())
|
|
continue;
|
|
|
|
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.has_value())
|
|
continue;
|
|
|
|
if (auto dropDeinit = dyn_cast<DropDeinitInst>(*value)) {
|
|
visitor.eraseInstructionAndRAUW(dropDeinit, dropDeinit->getOperand());
|
|
madeChange = true;
|
|
continue;
|
|
}
|
|
auto callbacks =
|
|
InstModCallbacks().onDelete([&](SILInstruction *instToErase) {
|
|
visitor.eraseInstruction(instToErase);
|
|
});
|
|
// We are no longer in OSSA, so we don't need to pass in a deBlocks.
|
|
simplifyAndReplaceAllSimplifiedUsesAndErase(*value, callbacks);
|
|
madeChange |= callbacks.hadCallbackInvocation();
|
|
}
|
|
|
|
if (madeChange) {
|
|
StackNesting::fixNesting(&func);
|
|
}
|
|
|
|
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(getAnalysis<BasicCalleeAnalysis>()->getCalleeCache());
|
|
getPassManager()->runSwiftFunctionVerification(f);
|
|
}
|
|
|
|
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*/);
|
|
}
|