Files
swift-mirror/lib/SILOptimizer/Utils/SILInliner.cpp
2025-03-25 23:02:42 -07:00

1195 lines
47 KiB
C++

//===--- SILInliner.cpp - Inlines SIL functions ---------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-inliner"
#include "swift/SILOptimizer/Utils/SILInliner.h"
#include "swift/AST/Builtins.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/PrettyStackTrace.h"
#include "swift/SIL/SILDebugScope.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/TypeSubstCloner.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
using namespace swift;
bool SILInliner::canInlineBeginApply(BeginApplyInst *BA) {
// Don't inline if we have multiple resumption sites (i.e. end_apply or
// abort_apply instructions). The current implementation clones a single
// copy of the end_apply and abort_apply paths, so it can't handle values
// that might be live in the caller across different resumption sites. To
// handle this in general, we'd need to separately clone the resume/unwind
// paths into each end/abort.
bool hasEndApply = false, hasAbortApply = false;
for (auto *use : BA->getEndApplyUses()) {
auto *user = use->getUser();
if (isa<EndApplyInst>(user)) {
if (hasEndApply) return false;
hasEndApply = true;
} else if (isa<AbortApplyInst>(user)) {
if (hasAbortApply) return false;
hasAbortApply = true;
} else {
assert(isa<EndBorrowInst>(user));
}
}
// Don't inline a coroutine with multiple yields. The current
// implementation doesn't clone code from the caller, so it can't handle
// values that might be live in the callee across different yields.
// To handle this in general, we'd need to clone code in the caller,
// both between the begin_apply and the resumption site and then
// potentially after the resumption site when there are un-mergeable
// values alive across it.
bool hasYield = false;
for (auto &B : *BA->getReferencedFunctionOrNull()) {
if (isa<YieldInst>(B.getTerminator())) {
if (hasYield) return false;
hasYield = true;
}
}
// Note that zero yields is fine; it just means the begin_apply is
// basically noreturn.
return true;
}
bool SILInliner::canInlineApplySite(FullApplySite apply) {
if (!apply.canOptimize())
return false;
if (auto BA = dyn_cast<BeginApplyInst>(apply))
return canInlineBeginApply(BA);
return true;
}
namespace {
/// Utility class for rewiring control-flow of inlined begin_apply functions.
class BeginApplySite {
SILLocation Loc;
SILBuilder *Builder;
BeginApplyInst *BeginApply;
bool HasYield = false;
EndApplyInst *EndApply = nullptr;
SILBasicBlock *EndApplyBB = nullptr;
SILBasicBlock *EndApplyReturnBB = nullptr;
AbortApplyInst *AbortApply = nullptr;
SILBasicBlock *AbortApplyBB = nullptr;
SILBasicBlock *AbortApplyReturnBB = nullptr;
SmallVector<EndBorrowInst *, 2> EndBorrows;
public:
BeginApplySite(BeginApplyInst *BeginApply, SILLocation Loc,
SILBuilder *Builder)
: Loc(Loc), Builder(Builder), BeginApply(BeginApply) {}
static std::optional<BeginApplySite> get(FullApplySite AI, SILLocation Loc,
SILBuilder *Builder) {
auto *BeginApply = dyn_cast<BeginApplyInst>(AI);
if (!BeginApply)
return std::nullopt;
return BeginApplySite(BeginApply, Loc, Builder);
}
void preprocess(SILBasicBlock *returnToBB,
SmallVectorImpl<SILInstruction *> &endBorrowInsertPts) {
SmallVector<EndApplyInst *, 1> endApplyInsts;
SmallVector<AbortApplyInst *, 1> abortApplyInsts;
BeginApply->getCoroutineEndPoints(endApplyInsts, abortApplyInsts,
&EndBorrows);
while (!endApplyInsts.empty()) {
auto *endApply = endApplyInsts.pop_back_val();
collectEndApply(endApply);
endBorrowInsertPts.push_back(&*std::next(endApply->getIterator()));
}
while (!abortApplyInsts.empty()) {
auto *abortApply = abortApplyInsts.pop_back_val();
collectAbortApply(abortApply);
endBorrowInsertPts.push_back(&*std::next(abortApply->getIterator()));
}
}
// Split the basic block before the end/abort_apply. We will insert code
// to jump to the resume/unwind blocks depending on the integer token
// later. And the inlined resume/unwind return blocks will jump back to
// the merge blocks.
void collectEndApply(EndApplyInst *End) {
assert(!EndApply);
EndApply = End;
EndApplyBB = EndApply->getParent();
EndApplyReturnBB = EndApplyBB->split(SILBasicBlock::iterator(EndApply));
}
void collectAbortApply(AbortApplyInst *Abort) {
assert(!AbortApply);
AbortApply = Abort;
AbortApplyBB = AbortApply->getParent();
AbortApplyReturnBB = AbortApplyBB->split(SILBasicBlock::iterator(Abort));
}
/// Perform special processing for the given terminator if necessary.
///
/// \return false to use the normal inlining logic
bool processTerminator(
TermInst *terminator, SILBasicBlock *returnToBB,
llvm::function_ref<SILBasicBlock *(SILBasicBlock *)> remapBlock,
llvm::function_ref<SILValue(SILValue)> getMappedValue) {
// A yield branches to the begin_apply return block passing the yielded
// results as branch arguments. Collect the yields target block for
// resuming later. Pass an integer token to the begin_apply return block
// to mark the yield we came from.
if (auto *yield = dyn_cast<YieldInst>(terminator)) {
assert(!HasYield);
HasYield = true;
// Pairwise replace the yielded values of the BeginApply with the
// values that were yielded.
auto calleeYields = yield->getYieldedValues();
auto callerYields = BeginApply->getYieldedValues();
assert(calleeYields.size() == callerYields.size());
SmallVector<BeginBorrowInst *, 2> guaranteedYields;
for (auto i : indices(calleeYields)) {
auto remappedYield = getMappedValue(calleeYields[i]);
// When owned values are yielded @guaranteed, the mapped value must be
// borrowed and the result be substituted in place of the originally
// yielded value. Otherwise, there could be uses of the original value
// which require an @guaranteed operand into which we'd be attempting to
// substitute an @owned operand.
if (calleeYields[i]->getOwnershipKind() == OwnershipKind::Owned &&
!yield->getOperandRef(i).isConsuming() &&
Builder->getFunction().hasOwnership()) {
auto *bbi = Builder->createBeginBorrow(Loc, remappedYield);
guaranteedYields.push_back(bbi);
remappedYield = bbi;
}
callerYields[i]->replaceAllUsesWith(remappedYield);
}
Builder->createBranch(Loc, returnToBB);
// Add branches at the resumption sites to the resume/unwind block.
if (EndApply) {
SavedInsertionPointRAII savedIP(*Builder, EndApplyBB);
auto resumeBB = remapBlock(yield->getResumeBB());
for (auto *bbi : guaranteedYields) {
Builder->createEndBorrow(EndApply->getLoc(), bbi);
}
Builder->createBranch(EndApply->getLoc(), resumeBB);
}
if (AbortApply) {
SavedInsertionPointRAII savedIP(*Builder, AbortApplyBB);
auto unwindBB = remapBlock(yield->getUnwindBB());
for (auto *bbi : guaranteedYields) {
Builder->createEndBorrow(EndApply->getLoc(), bbi);
}
Builder->createBranch(AbortApply->getLoc(), unwindBB);
}
return true;
}
// 'return' and 'unwind' instructions turn into branches to the
// end_apply/abort_apply return blocks, respectively. If those blocks
// are null, it's because there weren't any of the corresponding
// instructions in the caller. That means this entire path is
// unreachable.
if (isa<ReturnInst>(terminator) || isa<UnwindInst>(terminator)) {
ReturnInst *retInst = dyn_cast<ReturnInst>(terminator);
auto *returnBB = retInst ? EndApplyReturnBB : AbortApplyReturnBB;
if (retInst && EndApply)
EndApply->replaceAllUsesWith(getMappedValue(retInst->getOperand()));
if (returnBB) {
Builder->createBranch(Loc, returnBB);
} else {
Builder->createUnreachable(Loc);
}
return true;
}
assert(!isa<ThrowInst>(terminator) &&
"Unexpected throw instruction in yield_once function");
// Otherwise, we just map the instruction normally.
return false;
}
/// Complete the begin_apply-specific inlining work. Delete vestiges of the
/// apply site except the callee value. Return a valid iterator after the
/// original begin_apply.
void complete() {
// If there was no yield in the coroutine, then control never reaches
// the end of the begin_apply, so all the downstream code is unreachable.
// Make sure the function is well-formed, since we otherwise rely on
// having visited a yield instruction.
if (!HasYield) {
// Make sure the split resumption blocks have terminators.
if (EndApplyBB) {
SavedInsertionPointRAII savedIP(*Builder, EndApplyBB);
Builder->createUnreachable(Loc);
}
if (AbortApplyBB) {
SavedInsertionPointRAII savedIP(*Builder, AbortApplyBB);
Builder->createUnreachable(Loc);
}
// Replace all the yielded values in the callee with undef.
for (auto calleeYield : BeginApply->getYieldedValues()) {
calleeYield->replaceAllUsesWith(SILUndef::get(calleeYield));
}
}
// Remove the resumption sites.
if (EndApply)
EndApply->eraseFromParent();
if (AbortApply)
AbortApply->eraseFromParent();
for (auto *EndBorrow : EndBorrows)
EndBorrow->eraseFromParent();
if (auto allocation = BeginApply->getCalleeAllocationResult()) {
SmallVector<SILInstruction *, 4> users(allocation->getUsers());
for (auto *user : users) {
auto *dsi = cast<DeallocStackInst>(user);
dsi->eraseFromParent();
}
}
assert(!BeginApply->hasUsesOfAnyResult());
}
};
} // end anonymous namespace
namespace swift {
class SILInlineCloner
: public TypeSubstCloner<SILInlineCloner, SILOptFunctionBuilder> {
friend class SILInstructionVisitor<SILInlineCloner>;
friend class SILCloner<SILInlineCloner>;
using SuperTy = TypeSubstCloner<SILInlineCloner, SILOptFunctionBuilder>;
using InlineKind = SILInliner::InlineKind;
SILOptFunctionBuilder &FuncBuilder;
InlineKind IKind;
// The original, noninlined apply site. These become invalid after fixUp,
// which is called as the last step in SILCloner::cloneFunctionBody.
FullApplySite Apply;
std::optional<BeginApplySite> BeginApply;
InstructionDeleter &deleter;
/// The location representing the inlined instructions.
///
/// This location wraps the call site AST node that is being inlined.
/// Alternatively, it can be the SIL file location of the call site (in case
/// of SIL-to-SIL transformations).
SILLocation Loc;
const SILDebugScope *CallSiteScope = nullptr;
llvm::SmallDenseMap<const SILDebugScope *, const SILDebugScope *, 8>
InlinedScopeCache;
// Block in the original caller serving as the successor of the inlined
// control path.
SILBasicBlock *ReturnToBB = nullptr;
public:
SILInlineCloner(SILFunction *CalleeFunction, FullApplySite Apply,
SILOptFunctionBuilder &FuncBuilder, InlineKind IKind,
SubstitutionMap ApplySubs,
InstructionDeleter &deleter);
SILFunction *getCalleeFunction() const { return &Original; }
void cloneInline(ArrayRef<SILValue> AppliedArgs);
protected:
SILValue borrowFunctionArgument(SILValue callArg, unsigned index);
SILValue moveFunctionArgument(SILValue callArg, unsigned index);
void visitDebugValueInst(DebugValueInst *Inst);
void visitHopToExecutorInst(HopToExecutorInst *Inst);
void visitTerminator(SILBasicBlock *BB);
/// This hook is called after either of the top-level visitors:
/// cloneReachableBlocks or cloneSILFunction.
///
/// After `preFixUp` is called `commonFixUp` will be called.
void preFixUp(SILFunction *calleeFunction);
/// After postFixUp, the SIL must be valid and semantically equivalent to the
/// SIL before cloning.
void postFixUp(SILFunction *calleeFunction);
const SILDebugScope *getOrCreateInlineScope(const SILDebugScope *DS);
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
// We just updated the debug scope information. Intentionally
// don't call SILClonerWithScopes<SILInlineCloner>::postProcess().
SILCloner<SILInlineCloner>::postProcess(Orig, Cloned);
}
SILLocation remapLocation(SILLocation InLoc) {
// For performance inlining return the original location.
if (IKind == InlineKind::PerformanceInline)
return InLoc;
// Inlined location wraps the call site that is being inlined, regardless of
// the input location.
return Loc;
}
const SILDebugScope *remapScope(const SILDebugScope *DS) {
if (IKind == InlineKind::MandatoryInline)
// Transparent functions are absorbed into the call
// site. No soup, err, debugging for you!
return CallSiteScope;
else
// Create an inlined version of the scope.
return getOrCreateInlineScope(DS);
}
};
} // namespace swift
SILBasicBlock *
SILInliner::inlineFunction(SILFunction *calleeFunction, FullApplySite apply,
ArrayRef<SILValue> appliedArgs) {
PrettyStackTraceSILFunction calleeTraceRAII("inlining", calleeFunction);
PrettyStackTraceSILFunction callerTraceRAII("...into", apply.getFunction());
assert(canInlineApplySite(apply)
&& "Asked to inline function that is unable to be inlined?!");
SILInlineCloner cloner(calleeFunction, apply, FuncBuilder, IKind, ApplySubs,
deleter);
cloner.cloneInline(appliedArgs);
return cloner.getLastClonedBB();
}
SILBasicBlock *
SILInliner::inlineFullApply(FullApplySite apply,
SILInliner::InlineKind inlineKind,
SILOptFunctionBuilder &funcBuilder,
InstructionDeleter &deleter) {
assert(apply.canOptimize());
assert(!apply.getFunction()->hasOwnership() ||
apply.getReferencedFunctionOrNull()->hasOwnership());
SmallVector<SILValue, 8> appliedArgs;
for (const auto arg : apply.getArguments())
appliedArgs.push_back(arg);
SILInliner Inliner(funcBuilder, deleter, inlineKind, apply.getSubstitutionMap());
return Inliner.inlineFunction(apply.getReferencedFunctionOrNull(), apply,
appliedArgs);
}
static SILLocation selectLoc(bool mandatory, SILLocation orig) {
// Compute the SILLocation which should be used by all the inlined
// instructions.
if (mandatory)
return MandatoryInlinedLocation(orig);
else {
return InlinedLocation(orig);
}
}
SILInlineCloner::SILInlineCloner(
SILFunction *calleeFunction, FullApplySite apply,
SILOptFunctionBuilder &funcBuilder, InlineKind inlineKind,
SubstitutionMap applySubs,
InstructionDeleter &deleter)
: SuperTy(*apply.getFunction(), *calleeFunction, applySubs,
/*DT=*/nullptr, /*Inlining=*/true),
FuncBuilder(funcBuilder), IKind(inlineKind), Apply(apply),
deleter(deleter),
Loc(selectLoc(inlineKind == InlineKind::MandatoryInline, apply.getLoc())) {
SILFunction &F = getBuilder().getFunction();
assert(apply.getFunction() && apply.getFunction() == &F
&& "Inliner called on apply instruction in wrong function?");
assert(((calleeFunction->getRepresentation()
!= SILFunctionTypeRepresentation::ObjCMethod
&& calleeFunction->getRepresentation()
!= SILFunctionTypeRepresentation::CFunctionPointer)
|| IKind == InlineKind::PerformanceInline)
&& "Cannot inline Objective-C methods or C functions in mandatory "
"inlining");
auto applyScope = apply.getDebugScope();
// FIXME: Turn this into an assertion instead.
if (!applyScope)
applyScope = apply.getFunction()->getDebugScope();
if (IKind == InlineKind::MandatoryInline) {
// Mandatory inlining: every instruction inherits scope/location
// from the call site.
CallSiteScope = applyScope;
} else {
// Performance inlining. Construct a proper inline scope pointing
// back to the call site.
CallSiteScope = new (F.getModule()) SILDebugScope(
apply.getLoc(), nullptr, applyScope, applyScope->InlinedCallSite);
}
assert(CallSiteScope && "call site has no scope");
assert(CallSiteScope->getParentFunction() == &F);
// Set up the coroutine-specific inliner if applicable.
BeginApply = BeginApplySite::get(apply, Loc, &getBuilder());
}
// Clone the entire callee function into the caller function at the apply site.
// Delete the original apply and all dead arguments except the callee.
void SILInlineCloner::cloneInline(ArrayRef<SILValue> AppliedArgs) {
assert(getCalleeFunction()->getArguments().size() == AppliedArgs.size()
&& "Unexpected number of callee arguments.");
getBuilder().setInsertionPoint(Apply.getInstruction());
SmallVector<SILValue, 4> entryArgs;
entryArgs.reserve(AppliedArgs.size());
auto calleeConv = getCalleeFunction()->getConventions();
SmallBitVector borrowedArgs(AppliedArgs.size());
SmallBitVector copiedArgs(AppliedArgs.size());
SmallBitVector inCxxArgs(AppliedArgs.size());
if (!Apply->getFunction()->hasOwnership()) {
for (auto p : llvm::enumerate(AppliedArgs)) {
SILValue callArg = p.value();
entryArgs.push_back(callArg);
}
} else {
for (auto p : llvm::enumerate(AppliedArgs)) {
SILValue callArg = p.value();
SWIFT_DEFER { entryArgs.push_back(callArg); };
unsigned idx = p.index();
if (idx >= calleeConv.getSILArgIndexOfFirstParam()) {
auto paramInfo = calleeConv.getParamInfoForSILArg(idx);
if (callArg->getType().isAddress()) {
// If lexical lifetimes are enabled, any alloc_stacks in the caller
// that are passed to the callee being inlined (except mutating
// exclusive accesses) need to be promoted to be lexical. Otherwise,
// destroy_addrs could be hoisted through the body of the newly
// inlined function without regard to the deinit barriers it contains.
//
// TODO: [begin_borrow_addr] Instead of marking the alloc_stack as a
// whole lexical, just mark the inlined range lexical via
// begin_borrow_addr [lexical]/end_borrow_addr just as is done
// with values.
auto &module = Apply.getFunction()->getModule();
auto enableLexicalLifetimes =
module.getASTContext().SILOpts.supportsLexicalLifetimes(module);
if (!enableLexicalLifetimes)
continue;
// Exclusive mutating accesses don't entail a lexical scope.
if (paramInfo.getConvention() == ParameterConvention::Indirect_Inout)
continue;
auto storage = AccessStorageWithBase::compute(callArg);
if (auto *asi = dyn_cast_or_null<AllocStackInst>(storage.base))
asi->setIsLexical();
} else {
// Insert begin/end borrow for guaranteed arguments.
if (paramInfo.isGuaranteedInCaller()) {
if (SILValue newValue = borrowFunctionArgument(callArg, idx)) {
callArg = newValue;
borrowedArgs[idx] = true;
}
} else if (paramInfo.isConsumedInCaller()) {
if (SILValue newValue = moveFunctionArgument(callArg, idx)) {
callArg = newValue;
}
}
}
if (paramInfo.getConvention() == ParameterConvention::Indirect_In_CXX)
inCxxArgs[idx] = true;
}
}
}
// Create the return block and set ReturnToBB for use in visitTerminator
// callbacks.
SILBasicBlock *callerBlock = Apply.getParent();
SmallVector<SILInstruction *, 1> endBorrowInsertPts;
switch (Apply.getKind()) {
case FullApplySiteKind::ApplyInst: {
auto *AI = dyn_cast<ApplyInst>(Apply);
// Split the BB and do NOT create a branch between the old and new
// BBs; we will create the appropriate terminator manually later.
ReturnToBB =
callerBlock->split(std::next(Apply.getInstruction()->getIterator()));
endBorrowInsertPts.push_back(&*ReturnToBB->begin());
// Create an argument on the return-to BB representing the returned value.
auto *retArg =
ReturnToBB->createPhiArgument(AI->getType(), OwnershipKind::Owned);
// Replace all uses of the ApplyInst with the new argument.
AI->replaceAllUsesWith(retArg);
break;
}
case FullApplySiteKind::BeginApplyInst: {
ReturnToBB =
callerBlock->split(std::next(Apply.getInstruction()->getIterator()));
// For begin_apply, we insert the end_borrow in the end_apply, abort_apply
// blocks to ensure that our borrowed values live over both the body and
// resume block of our coroutine.
BeginApply->preprocess(ReturnToBB, endBorrowInsertPts);
break;
}
case FullApplySiteKind::TryApplyInst: {
auto *tai = cast<TryApplyInst>(Apply);
ReturnToBB = tai->getNormalBB();
endBorrowInsertPts.push_back(&*ReturnToBB->begin());
endBorrowInsertPts.push_back(&*tai->getErrorBB()->begin());
break;
}
}
// Then insert end_borrow in our end borrow block and in the throw
// block if we have one.
if (borrowedArgs.any()) {
for (unsigned i : indices(AppliedArgs)) {
if (!borrowedArgs.test(i)) {
continue;
}
for (auto *insertPt : endBorrowInsertPts) {
SILBuilderWithScope returnBuilder(insertPt, getBuilder());
returnBuilder.createEndBorrow(Apply.getLoc(), entryArgs[i]);
}
}
}
if (inCxxArgs.any()) {
for (unsigned i : indices(AppliedArgs)) {
if (!inCxxArgs.test(i)) {
continue;
}
for (auto *insertPt : endBorrowInsertPts) {
SILBuilderWithScope returnBuilder(insertPt->getParent()->begin(),
getBuilder());
returnBuilder.emitDestroyOperation(Apply.getLoc(), entryArgs[i]);
}
}
}
// Visit original BBs in depth-first preorder, starting with the
// entry block, cloning all instructions and terminators.
cloneFunctionBody(getCalleeFunction(), callerBlock, entryArgs);
// For non-throwing applies, the inlined body now unconditionally branches to
// the returned-to-code, which was previously part of the call site's basic
// block. We could trivially merge these blocks now, however, this would be
// quadratic: O(num-calls-in-block * num-instructions-in-block). Also,
// guaranteeing that caller instructions following the inlined call are in a
// separate block gives the inliner control over revisiting only the inlined
// instructions.
//
// Once all calls in a function are inlined, unconditional branches are
// eliminated by mergeBlocks.
}
void SILInlineCloner::visitTerminator(SILBasicBlock *BB) {
TermInst *Terminator = BB->getTerminator();
// Coroutine terminators need special handling.
if (BeginApply) {
if (BeginApply->processTerminator(
Terminator, ReturnToBB,
[=](SILBasicBlock *Block) -> SILBasicBlock * {
return this->remapBasicBlock(Block);
},
[=](SILValue Val) -> SILValue {
return this->getMappedValue(Val);
}))
return;
}
// Modify return terminators to branch to the return-to BB, rather
// than trying to clone the ReturnInst. Because of that, the scope
// needs to be remapped manually.
getBuilder().setCurrentDebugScope(getOpScope(Terminator->getDebugScope()));
if (auto *RI = dyn_cast<ReturnInst>(Terminator)) {
auto returnedValue = getMappedValue(RI->getOperand());
getBuilder().createBranch(getOpLocation(RI->getLoc()), ReturnToBB,
returnedValue);
return;
}
// Modify throw terminators to branch to the error-return BB, rather than
// trying to clone the ThrowInst.
if (auto *TI = dyn_cast<ThrowInst>(Terminator)) {
SILLocation Loc = getOpLocation(TI->getLoc());
switch (Apply.getKind()) {
case FullApplySiteKind::ApplyInst:
assert(cast<ApplyInst>(Apply)->isNonThrowing()
&& "apply of a function with error result must be non-throwing");
getBuilder().createUnreachable(Loc);
return;
case FullApplySiteKind::BeginApplyInst:
assert(cast<BeginApplyInst>(Apply)->isNonThrowing()
&& "apply of a function with error result must be non-throwing");
getBuilder().createUnreachable(Loc);
return;
case FullApplySiteKind::TryApplyInst:
auto tryAI = cast<TryApplyInst>(Apply);
auto returnedValue = getMappedValue(TI->getOperand());
getBuilder().createBranch(Loc, tryAI->getErrorBB(), returnedValue);
return;
}
}
// Modify throw_addr terminators to branch to the error-return BB, rather than
// trying to clone the ThrowAddrInst.
if (auto *TAI = dyn_cast<ThrowAddrInst>(Terminator)) {
SILLocation Loc = getOpLocation(TAI->getLoc());
switch (Apply.getKind()) {
case FullApplySiteKind::ApplyInst:
assert(cast<ApplyInst>(Apply)->isNonThrowing()
&& "apply of a function with error result must be non-throwing");
getBuilder().createUnreachable(Loc);
return;
case FullApplySiteKind::BeginApplyInst:
assert(cast<BeginApplyInst>(Apply)->isNonThrowing()
&& "apply of a function with error result must be non-throwing");
getBuilder().createUnreachable(Loc);
return;
case FullApplySiteKind::TryApplyInst:
auto tryAI = cast<TryApplyInst>(Apply);
getBuilder().createBranch(Loc, tryAI->getErrorBB());
return;
}
}
// Otherwise use normal visitor, which clones the existing instruction
// but remaps basic blocks and values.
visit(Terminator);
}
void SILInlineCloner::preFixUp(SILFunction *calleeFunction) {
// "Completing" the BeginApply only fixes the end of the apply scope. The
// begin_apply itself lingers.
if (BeginApply)
BeginApply->complete();
}
void SILInlineCloner::postFixUp(SILFunction *calleeFunction) {
deleter.getCallbacks().notifyWillBeDeleted(Apply.getInstruction());
deleter.forceDelete(Apply.getInstruction());
}
namespace {
enum class Scope : uint8_t {
None,
Bare,
Lexical,
};
Scope scopeForArgument(Scope nonlexicalScope, SILValue callArg, unsigned index,
SILFunction *caller, SILFunction *callee) {
if (!caller->hasOwnership()) {
// The function isn't in OSSA. Borrows/moves are not meaningful.
return Scope::None;
}
auto &mod = caller->getModule();
auto enableLexicalLifetimes =
mod.getASTContext().SILOpts.supportsLexicalLifetimes(mod);
SILFunctionArgument *argument =
cast<SILFunctionArgument>(callee->getEntryBlock()->getArgument(index));
if (!enableLexicalLifetimes) {
// Lexical lifetimes are disabled. Use the non-lexical scope:
// - for borrows, do an ownership conversion.
// - for moves, do nothing.
return nonlexicalScope;
}
if (!argument->getLifetime().isLexical()) {
// The same applies if lexical lifetimes are enabled but the function
// argument is not lexical. There is no lexical lifetime to maintain. Use
// the non-lexical scope.
return nonlexicalScope;
}
// Lexical lifetimes are enabled and the function argument is lexical.
// During inlining, we need to ensure that the lifetime is maintained.
if (callArg->isLexical()) {
// The caller's value is already lexical. It will maintain the lifetime of
// the argument. Just do an ownership conversion if needed.
return nonlexicalScope;
}
// Lexical lifetimes are enabled, the function argument's lifetime is
// lexical, but the caller's value is not lexical. Extra care is required to
// maintain the function argument's lifetime. We need to add a lexical
// scope.
return Scope::Lexical;
}
} // anonymous namespace
SILValue SILInlineCloner::borrowFunctionArgument(SILValue callArg,
unsigned index) {
// The "minimal" borrow scope: Guaranteed values are valid operands to some
// instructions that owned values are not. If the caller's value is owned,
// it must be converted (via a "bare" begin_borrow) to a guaranteed value so
// that it can be used in place of the original guaranteed value in the
// instructions that are being inlined.
auto scopeForOwnership = callArg->getOwnershipKind() == OwnershipKind::Owned
? Scope::Bare
: Scope::None;
auto scope = scopeForArgument(scopeForOwnership, callArg, index,
Apply.getFunction(), getCalleeFunction());
IsLexical_t isLexical;
switch (scope) {
case Scope::None:
return SILValue();
case Scope::Bare:
isLexical = IsNotLexical;
break;
case Scope::Lexical:
isLexical = IsLexical;
break;
}
SILBuilderWithScope beginBuilder(Apply.getInstruction(), getBuilder());
return beginBuilder.createBeginBorrow(Apply.getLoc(), callArg, isLexical);
}
SILValue SILInlineCloner::moveFunctionArgument(SILValue callArg,
unsigned index) {
auto scope = scopeForArgument(Scope::None, callArg, index,
Apply.getFunction(), getCalleeFunction());
IsLexical_t isLexical;
switch (scope) {
case Scope::None:
return SILValue();
case Scope::Bare:
assert(false && "Non-lexical move produced during inlining!?");
isLexical = IsNotLexical;
break;
case Scope::Lexical:
isLexical = IsLexical;
break;
}
SILBuilderWithScope beginBuilder(Apply.getInstruction(), getBuilder());
return beginBuilder.createMoveValue(Apply.getLoc(), callArg, isLexical);
}
void SILInlineCloner::visitDebugValueInst(DebugValueInst *Inst) {
// The mandatory inliner drops debug_value instructions when inlining, as if
// it were a "nodebug" function in C.
if (IKind == InlineKind::MandatoryInline) return;
return SILCloner<SILInlineCloner>::visitDebugValueInst(Inst);
}
void SILInlineCloner::visitHopToExecutorInst(HopToExecutorInst *Inst) {
// Drop hop_to_executor in non async functions.
if (!Apply.getFunction()->isAsync()) {
assert(Apply.isNonAsync());
return;
}
return SILCloner<SILInlineCloner>::visitHopToExecutorInst(Inst);
}
const SILDebugScope *
SILInlineCloner::getOrCreateInlineScope(const SILDebugScope *CalleeScope) {
if (!CalleeScope)
return CallSiteScope;
auto it = InlinedScopeCache.find(CalleeScope);
if (it != InlinedScopeCache.end())
return it->second;
auto &M = getBuilder().getModule();
auto InlinedAt =
getOrCreateInlineScope(CalleeScope->InlinedCallSite);
auto *ParentFunction = CalleeScope->Parent.dyn_cast<SILFunction *>();
if (ParentFunction)
ParentFunction = remapParentFunction(
FuncBuilder, M, ParentFunction, SubsMap,
getCalleeFunction()->getLoweredFunctionType()
->getInvocationGenericSignature(),
ForInlining);
auto *ParentScope = CalleeScope->Parent.dyn_cast<const SILDebugScope *>();
auto *InlinedScope = new (M) SILDebugScope(
CalleeScope->Loc, ParentFunction,
ParentScope ? getOrCreateInlineScope(ParentScope) : nullptr, InlinedAt);
InlinedScopeCache.insert({CalleeScope, InlinedScope});
return InlinedScope;
}
template <typename... T, typename... U>
static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
U &&...args) {
Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
//===----------------------------------------------------------------------===//
// Cost Model
//===----------------------------------------------------------------------===//
static InlineCost getEnforcementCost(SILAccessEnforcement enforcement) {
switch (enforcement) {
case SILAccessEnforcement::Unknown:
llvm_unreachable("evaluating cost of access with unknown enforcement?");
case SILAccessEnforcement::Dynamic:
return InlineCost::Expensive;
case SILAccessEnforcement::Static:
case SILAccessEnforcement::Unsafe:
case SILAccessEnforcement::Signed:
return InlineCost::Free;
}
llvm_unreachable("bad enforcement");
}
/// For now just assume that every SIL instruction is one to one with an LLVM
/// instruction. This is of course very much so not true.
InlineCost swift::instructionInlineCost(SILInstruction &I) {
switch (I.getKind()) {
case SILInstructionKind::IntegerLiteralInst:
case SILInstructionKind::FloatLiteralInst:
case SILInstructionKind::DebugValueInst:
case SILInstructionKind::DebugStepInst:
case SILInstructionKind::StringLiteralInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::EndBorrowInst:
case SILInstructionKind::BeginBorrowInst:
case SILInstructionKind::BorrowedFromInst:
case SILInstructionKind::MarkDependenceInst:
case SILInstructionKind::MarkDependenceAddrInst:
case SILInstructionKind::MergeIsolationRegionInst:
case SILInstructionKind::PreviousDynamicFunctionRefInst:
case SILInstructionKind::DynamicFunctionRefInst:
case SILInstructionKind::FunctionRefInst:
case SILInstructionKind::AllocGlobalInst:
case SILInstructionKind::GlobalAddrInst:
case SILInstructionKind::BaseAddrForOffsetInst:
case SILInstructionKind::EndLifetimeInst:
case SILInstructionKind::ExtendLifetimeInst:
case SILInstructionKind::UncheckedOwnershipConversionInst:
case SILInstructionKind::BindMemoryInst:
case SILInstructionKind::RebindMemoryInst:
case SILInstructionKind::MoveValueInst:
case SILInstructionKind::DropDeinitInst:
case SILInstructionKind::MarkUnresolvedNonCopyableValueInst:
case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
case SILInstructionKind::MoveOnlyWrapperToCopyableValueInst:
case SILInstructionKind::SpecifyTestInst:
case SILInstructionKind::MoveOnlyWrapperToCopyableAddrInst:
case SILInstructionKind::CopyableToMoveOnlyWrapperAddrInst:
case SILInstructionKind::MoveOnlyWrapperToCopyableBoxInst:
case SILInstructionKind::IgnoredUseInst:
return InlineCost::Free;
// Typed GEPs are free.
case SILInstructionKind::TupleElementAddrInst:
case SILInstructionKind::StructElementAddrInst:
case SILInstructionKind::ProjectBlockStorageInst:
return InlineCost::Free;
// tuple_pack_element_addr is just a GEP, but getting the offset
// can require accessing metadata, so conservatively treat it as
// expensive.
case SILInstructionKind::TuplePackExtractInst:
case SILInstructionKind::TuplePackElementAddrInst:
return InlineCost::Expensive;
// pack_length and type_value is just a few adds, which is close enough to
// free.
case SILInstructionKind::PackLengthInst:
case SILInstructionKind::TypeValueInst:
return InlineCost::Free;
// dynamic_pack_index is free. The other pack-indexing instructions
// are just adds of values that should be trivially dynamically
// available; that's cheap enough to still consider free under the
// same principle as typed GEPs.
case SILInstructionKind::DynamicPackIndexInst:
case SILInstructionKind::PackPackIndexInst:
case SILInstructionKind::ScalarPackIndexInst:
return InlineCost::Free;
// Pack element get/set are a GEP plus a load/store.
// Cheap, but not free.
case SILInstructionKind::PackElementGetInst:
case SILInstructionKind::PackElementSetInst:
return InlineCost::Expensive;
// Aggregates are exploded at the IR level; these are effectively no-ops.
case SILInstructionKind::TupleInst:
case SILInstructionKind::StructInst:
case SILInstructionKind::StructExtractInst:
case SILInstructionKind::TupleExtractInst:
case SILInstructionKind::DestructureStructInst:
case SILInstructionKind::DestructureTupleInst:
return InlineCost::Free;
// Unchecked casts are free.
case SILInstructionKind::AddressToPointerInst:
case SILInstructionKind::PointerToAddressInst:
case SILInstructionKind::UncheckedRefCastInst:
case SILInstructionKind::UncheckedRefCastAddrInst:
case SILInstructionKind::UncheckedAddrCastInst:
case SILInstructionKind::UncheckedTrivialBitCastInst:
case SILInstructionKind::UncheckedBitwiseCastInst:
case SILInstructionKind::UncheckedValueCastInst:
case SILInstructionKind::RawPointerToRefInst:
case SILInstructionKind::RefToRawPointerInst:
case SILInstructionKind::UpcastInst:
case SILInstructionKind::EndInitLetRefInst:
case SILInstructionKind::ThinToThickFunctionInst:
case SILInstructionKind::ConvertFunctionInst:
case SILInstructionKind::ConvertEscapeToNoEscapeInst:
case SILInstructionKind::BridgeObjectToWordInst:
return InlineCost::Free;
// Access instructions are free unless we're dynamically enforcing them.
case SILInstructionKind::BeginAccessInst:
return getEnforcementCost(cast<BeginAccessInst>(I).getEnforcement());
case SILInstructionKind::EndAccessInst:
return getEnforcementCost(cast<EndAccessInst>(I).getBeginAccess()
->getEnforcement());
case SILInstructionKind::BeginUnpairedAccessInst:
return getEnforcementCost(cast<BeginUnpairedAccessInst>(I)
.getEnforcement());
case SILInstructionKind::EndUnpairedAccessInst:
return getEnforcementCost(cast<EndUnpairedAccessInst>(I)
.getEnforcement());
// TODO: These are free if the metatype is for a Swift class.
case SILInstructionKind::ThickToObjCMetatypeInst:
case SILInstructionKind::ObjCToThickMetatypeInst:
return InlineCost::Expensive;
// TODO: Bridge object conversions imply a masking operation that should be
// "hella cheap" but not really expensive.
case SILInstructionKind::BridgeObjectToRefInst:
case SILInstructionKind::RefToBridgeObjectInst:
case SILInstructionKind::ClassifyBridgeObjectInst:
case SILInstructionKind::ValueToBridgeObjectInst:
return InlineCost::Expensive;
case SILInstructionKind::MetatypeInst:
// Thin metatypes are always free.
if (cast<MetatypeInst>(I).getType().castTo<MetatypeType>()
->getRepresentation() == MetatypeRepresentation::Thin)
return InlineCost::Free;
// TODO: Thick metatypes are free if they don't require generic or lazy
// instantiation.
return InlineCost::Expensive;
// Protocol descriptor references are free.
case SILInstructionKind::ObjCProtocolInst:
return InlineCost::Free;
// Metatype-to-object conversions are free.
case SILInstructionKind::ObjCExistentialMetatypeToObjectInst:
case SILInstructionKind::ObjCMetatypeToObjectInst:
return InlineCost::Free;
// Return and unreachable are free.
case SILInstructionKind::UnreachableInst:
case SILInstructionKind::ReturnInst:
case SILInstructionKind::ThrowInst:
case SILInstructionKind::ThrowAddrInst:
case SILInstructionKind::UnwindInst:
case SILInstructionKind::YieldInst:
case SILInstructionKind::EndCOWMutationInst:
return InlineCost::Free;
// Turning the task reference into a continuation should be basically free.
// TODO(async): make sure this is true.
case SILInstructionKind::GetAsyncContinuationAddrInst:
case SILInstructionKind::GetAsyncContinuationInst:
return InlineCost::Free;
// Unconditional branch is free in empty blocks.
case SILInstructionKind::BranchInst:
return (I.getIterator() == I.getParent()->begin())
? InlineCost::Free : InlineCost::Expensive;
case SILInstructionKind::AbortApplyInst:
case SILInstructionKind::ApplyInst:
case SILInstructionKind::TryApplyInst:
case SILInstructionKind::AllocBoxInst:
case SILInstructionKind::AllocExistentialBoxInst:
case SILInstructionKind::AllocRefInst:
case SILInstructionKind::AllocRefDynamicInst:
case SILInstructionKind::AllocStackInst:
case SILInstructionKind::AllocPackInst:
case SILInstructionKind::AllocPackMetadataInst:
case SILInstructionKind::BeginApplyInst:
case SILInstructionKind::ValueMetatypeInst:
case SILInstructionKind::WitnessMethodInst:
case SILInstructionKind::AssignInst:
case SILInstructionKind::AssignByWrapperInst:
case SILInstructionKind::AssignOrInitInst:
case SILInstructionKind::CheckedCastBranchInst:
case SILInstructionKind::CheckedCastAddrBranchInst:
case SILInstructionKind::ClassMethodInst:
case SILInstructionKind::ObjCMethodInst:
case SILInstructionKind::CondBranchInst:
case SILInstructionKind::CondFailInst:
case SILInstructionKind::CopyBlockInst:
case SILInstructionKind::CopyBlockWithoutEscapingInst:
case SILInstructionKind::CopyAddrInst:
case SILInstructionKind::ExplicitCopyAddrInst:
case SILInstructionKind::TupleAddrConstructorInst:
case SILInstructionKind::MarkUnresolvedMoveAddrInst:
case SILInstructionKind::RetainValueInst:
case SILInstructionKind::RetainValueAddrInst:
case SILInstructionKind::UnmanagedRetainValueInst:
case SILInstructionKind::CopyValueInst:
case SILInstructionKind::ExplicitCopyValueInst:
case SILInstructionKind::DeallocBoxInst:
case SILInstructionKind::DeallocExistentialBoxInst:
case SILInstructionKind::DeallocStackRefInst:
case SILInstructionKind::DeallocRefInst:
case SILInstructionKind::DeallocPartialRefInst:
case SILInstructionKind::DeallocStackInst:
case SILInstructionKind::DeallocPackInst:
case SILInstructionKind::DeallocPackMetadataInst:
case SILInstructionKind::DeinitExistentialAddrInst:
case SILInstructionKind::DeinitExistentialValueInst:
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::EndApplyInst:
case SILInstructionKind::ProjectBoxInst:
case SILInstructionKind::ProjectExistentialBoxInst:
case SILInstructionKind::ReleaseValueInst:
case SILInstructionKind::ReleaseValueAddrInst:
case SILInstructionKind::UnmanagedReleaseValueInst:
case SILInstructionKind::DestroyValueInst:
case SILInstructionKind::AutoreleaseValueInst:
case SILInstructionKind::UnmanagedAutoreleaseValueInst:
case SILInstructionKind::DynamicMethodBranchInst:
case SILInstructionKind::EnumInst:
case SILInstructionKind::IndexAddrInst:
case SILInstructionKind::TailAddrInst:
case SILInstructionKind::IndexRawPointerInst:
case SILInstructionKind::InitEnumDataAddrInst:
case SILInstructionKind::InitExistentialAddrInst:
case SILInstructionKind::InitExistentialValueInst:
case SILInstructionKind::InitExistentialMetatypeInst:
case SILInstructionKind::InitExistentialRefInst:
case SILInstructionKind::InjectEnumAddrInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::LoadBorrowInst:
case SILInstructionKind::OpenExistentialAddrInst:
case SILInstructionKind::OpenExistentialBoxInst:
case SILInstructionKind::OpenExistentialBoxValueInst:
case SILInstructionKind::OpenExistentialMetatypeInst:
case SILInstructionKind::OpenExistentialRefInst:
case SILInstructionKind::OpenExistentialValueInst:
case SILInstructionKind::OpenPackElementInst:
case SILInstructionKind::PartialApplyInst:
case SILInstructionKind::ThunkInst:
case SILInstructionKind::ExistentialMetatypeInst:
case SILInstructionKind::RefElementAddrInst:
case SILInstructionKind::RefTailAddrInst:
case SILInstructionKind::StoreInst:
case SILInstructionKind::StoreBorrowInst:
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::BeginDeallocRefInst:
case SILInstructionKind::StrongRetainInst:
case SILInstructionKind::SuperMethodInst:
case SILInstructionKind::ObjCSuperMethodInst:
case SILInstructionKind::SwitchEnumAddrInst:
case SILInstructionKind::SwitchEnumInst:
case SILInstructionKind::SwitchValueInst:
case SILInstructionKind::UncheckedEnumDataInst:
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
case SILInstructionKind::UnconditionalCheckedCastInst:
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
case SILInstructionKind::DestroyNotEscapedClosureInst:
case SILInstructionKind::IsUniqueInst:
case SILInstructionKind::BeginCOWMutationInst:
case SILInstructionKind::InitBlockStorageHeaderInst:
case SILInstructionKind::SelectEnumAddrInst:
case SILInstructionKind::SelectEnumInst:
case SILInstructionKind::KeyPathInst:
case SILInstructionKind::GlobalValueInst:
case SILInstructionKind::DifferentiableFunctionInst:
case SILInstructionKind::LinearFunctionInst:
case SILInstructionKind::DifferentiableFunctionExtractInst:
case SILInstructionKind::LinearFunctionExtractInst:
case SILInstructionKind::DifferentiabilityWitnessFunctionInst:
case SILInstructionKind::IncrementProfilerCounterInst:
case SILInstructionKind::AwaitAsyncContinuationInst:
case SILInstructionKind::HopToExecutorInst:
case SILInstructionKind::ExtractExecutorInst:
case SILInstructionKind::FunctionExtractIsolationInst:
case SILInstructionKind::HasSymbolInst:
case SILInstructionKind::UnownedCopyValueInst:
case SILInstructionKind::WeakCopyValueInst:
#define COMMON_ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name) \
case SILInstructionKind::Name##ToRefInst: \
case SILInstructionKind::RefTo##Name##Inst: \
case SILInstructionKind::StrongCopy##Name##ValueInst:
#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
case SILInstructionKind::Load##Name##Inst: \
case SILInstructionKind::Store##Name##Inst: \
case SILInstructionKind::StrongCopy##Name##ValueInst:
#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
COMMON_ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name) \
case SILInstructionKind::Name##RetainInst: \
case SILInstructionKind::Name##ReleaseInst: \
case SILInstructionKind::StrongRetain##Name##Inst:
#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
case SILInstructionKind::Load##Name##Inst: \
case SILInstructionKind::Store##Name##Inst: \
ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, "...")
#define UNCHECKED_REF_STORAGE(Name, ...) \
COMMON_ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name)
#include "swift/AST/ReferenceStorage.def"
#undef COMMON_ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE
return InlineCost::Expensive;
case SILInstructionKind::BuiltinInst: {
auto *BI = cast<BuiltinInst>(&I);
// Expect intrinsics are 'free' instructions.
if (BI->getIntrinsicInfo().ID == llvm::Intrinsic::expect)
return InlineCost::Free;
if (BI->getBuiltinInfo().ID == BuiltinValueKind::OnFastPath)
return InlineCost::Free;
return InlineCost::Expensive;
}
case SILInstructionKind::MarkFunctionEscapeInst:
case SILInstructionKind::MarkUninitializedInst:
llvm_unreachable("not valid in canonical sil");
case SILInstructionKind::ObjectInst:
case SILInstructionKind::VectorInst:
llvm_unreachable("not valid in a function");
}
llvm_unreachable("Unhandled ValueKind in switch.");
}