blob: 4c352f33219d642dcd1bcd9fdff3bdda47ab2927 [file] [log] [blame]
//===--- InOutDeshadowing.cpp - Remove non-escaping inout shadows ---------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// SILGen produces shadow variables for "inout" arguments to provide proper
// semantics for when the inout argument is closed over. However, this shadow
// value is *only* needed when the argument is closed over (and when that
// closure isn't inlined). This pass looks for shadow allocations and removes
// them.
//
// This is a guaranteed optimization pass, because adding additional references
// can cause algorithmic performance changes, e.g. turning amortized constant
// time string and array operations into linear time operations.
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "inout-deshadow"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"
using namespace swift;
STATISTIC(NumShadowsRemoved, "Number of inout shadow variables removed");
STATISTIC(NumShadowsKept, "Number of inout shadow variables kept");
//===----------------------------------------------------------------------===//
// inout Deshadowing
//===----------------------------------------------------------------------===//
/// promoteShadow - Given an AllocStackInst that is copied to/from an inout
/// argument, completely replace the alloc_stack with that inout argument.
static void promoteShadow(AllocStackInst *Alloc, SILArgument *InOutArg) {
// Since the allocation has already been promoted to an alloc_stack, we know
// it doesn't escape. Simply eliminate the allocation and any obviously
// trivial noop copies into and out of it.
while (!Alloc->use_empty()) {
auto Use = *Alloc->use_begin();
auto *User = Use->getUser();
// If this is the dealloc_stack, just zap the instruction.
if (isa<DeallocStackInst>(User)) {
User->eraseFromParent();
continue;
}
// Otherwise, it is a use of the argument. If this is a copy_addr that
// defines or destroys the value, then remove it.
if (auto *CAI = dyn_cast<CopyAddrInst>(User)) {
if (CAI->getSrc() == InOutArg || CAI->getDest() == InOutArg) {
User->eraseFromParent();
continue;
}
}
// Otherwise, this is something else that is using the memory. Remap this
// to use the InOutArg directly instead of using the allocation.
Use->set(InOutArg);
}
// Make the debug information stored in the AllocBox explicit.
SILBuilder B(Alloc);
B.createDebugValueAddr(Alloc->getLoc(), InOutArg, Alloc->getVarInfo());
Alloc->eraseFromParent();
}
//===----------------------------------------------------------------------===//
// Candidate Variable Identification
//===----------------------------------------------------------------------===//
class StackSlotState {
bool Failed : 1;
bool HaveEntrySlot : 1;
AllocStackInst *TheSlot = nullptr;
llvm::SmallPtrSet<SILBasicBlock*, 1> ExitBBs;
public:
StackSlotState(SILFunction *F)
: Failed(false), HaveEntrySlot(false)
{
// We need to see a store back to the inout on every exit path.
for (auto &bb : *F) {
auto term = bb.getTerminator();
if (term->isFunctionExiting()) {
DEBUG(llvm::dbgs() << " need load from stack slot on exit " << &bb
<< '\n');
ExitBBs.insert(&bb);
}
}
}
/// True if analysis has concluded that deshadowing cannot happen.
bool failed() {
return Failed;
}
/// Get the single stack slot we can deshadow, or null if no such slot was
/// found.
AllocStackInst *getDeshadowableSlot() {
if (Failed)
return nullptr;
// We must have seen both a store to and a load from the slot to deshadow
// on every exit BB.
if (!HaveEntrySlot) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: no store to stack slot\n");
return nullptr;
}
if (!ExitBBs.empty()) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: no load from stack slot "
"on some paths\n");
return nullptr;
}
return TheSlot;
}
/// Add a stack slot that was copy-initialized from the inout on entry
/// to the analysis. If we already have seen a load, or if the slot does not
/// match the exit slot, the analysis fails.
void addEntrySlot(AllocStackInst *slot) {
if (Failed) return;
if (HaveEntrySlot)
return setFailed("inout is loaded multiple times");
if (TheSlot && slot != TheSlot)
return setFailed("inout is loaded and stored into different slots");
DEBUG(llvm::dbgs() << " found store to stack slot on entry\n");
HaveEntrySlot = true;
TheSlot = slot;
}
/// Add a stack slot that was take-assigned to the inout from an exit BB
/// to the analysis. If we already have seen a store on this BB, or if the
/// slot does not match the entry slot, the analysis fails.
void addExitSlot(AllocStackInst *slot, SILBasicBlock *exitBB) {
if (Failed) return;
if (TheSlot && slot != TheSlot)
return setFailed("inout is loaded and stored into different slots");
if (!ExitBBs.erase(exitBB))
return setFailed("inout is stored multiple times from same exit BB");
DEBUG(llvm::dbgs() << " found load from stack slot on exit "
<< exitBB << '\n');
TheSlot = slot;
}
/// Fail the analysis.
void setFailed(StringRef reason) {
DEBUG(llvm::dbgs() << "*** Rejecting deshadow: " << reason << '\n');
Failed = true;
}
};
/// isCopyToOrFromStack - Check to see if the specified use of an inout
/// argument is a copy_addr to/from an alloc_stack.
///
/// This returns the alloc_stack if found, or null if not.
static void analyzeUseOfInOut(Operand *UI, StackSlotState &state) {
// Look for copy_addr instructions.
auto CAI = dyn_cast<CopyAddrInst>(UI->getUser());
// Non-copy_addr uses of the inout should only occur in canonical SIL, so
// fail the analysis if we see one.
if (!CAI)
return state.setFailed("inout is used by a non-copy_addr instruction");
// We only look at autogenerated copy_addr's. We don't want to muck with
// user variables, as in:
// func f(inout a : Int) { var b = a }
if (!CAI->getLoc().isAutoGenerated() && !CAI->getLoc().isSILFile())
return;
// Get a stack slot, looking through mark_uninitialized if necessary.
auto getStackSlot = [](SILValue V) {
// Look through mark_uninitialized.
if (auto *MUI = dyn_cast<MarkUninitializedInst>(V))
V = MUI->getOperand();
return dyn_cast<AllocStackInst>(V);
};
// Are we the source or destination?
switch (UI->getOperandNumber()) {
case 0: { // Source
// Is this copy in the entry block?
if (CAI->getParent()->getIterator() != CAI->getFunction()->begin())
// Any copy from the inout outside of the entry block fails the analysis.
// We don't need full flow-sensitive analysis for SILGen-ed code.
return state.setFailed("inout is loaded outside of the entry block");
// Fail if this isn't a copy-initialization.
if (CAI->isTakeOfSrc() || !CAI->isInitializationOfDest())
return state.setFailed("inout is loaded as a non-copy-initialization");
// Fail if this isn't a store to a stack slot.
AllocStackInst *destSlot = getStackSlot(CAI->getDest());
if (!destSlot)
return state.setFailed("inout is loaded to a non-stack slot");
// Add the entry slot to the analysis.
return state.addEntrySlot(destSlot);
}
case 1: { // Destination
// Is this copy in an exit block?
auto term = CAI->getParent()->getTerminator();
// Unreachable blocks don't matter to the analysis.
if (isa<UnreachableInst>(term))
return;
if (!term->isFunctionExiting())
// Any copy from the inout outside of an exit block fails the analysis.
// We don't need full flow-sensitive analysis for SILGen-ed code.
return state.setFailed("inout is stored outside of an exit block");
// Fail if this isn't an assignment.
if (CAI->isInitializationOfDest())
return state.setFailed("inout is stored as a non-assignment");
// Fail if this isn't a load from a stack slot.
AllocStackInst *srcSlot = getStackSlot(CAI->getSrc());
if (!srcSlot)
return state.setFailed("inout is stored from a non-stack slot");
// Add the exit slot to the analysis.
return state.addExitSlot(srcSlot, CAI->getParent());
}
default:
llvm_unreachable("copy_addr only has two operands");
}
}
/// processInOutValue - Walk the use-def list of the inout argument to find uses
/// of it. If we find any autogenerated copies to/from an alloc_stack, then
/// remove the alloc stack in favor of loading/storing to the inout pointer
/// directly.
///
/// This returns true if it promotes away the shadow variable.
///
static bool processInOutValue(SILArgument *InOutArg) {
assert(InOutArg->getType().isAddress() &&
"inout arguments should always be addresses");
{
StackSlotState state(InOutArg->getFunction());
for (auto UI : InOutArg->getUses()) {
analyzeUseOfInOut(UI, state);
if (state.failed())
goto failed;
}
AllocStackInst *stackSlot = state.getDeshadowableSlot();
if (!stackSlot)
goto failed;
DEBUG(llvm::dbgs() << " Promoting shadow variable " << *stackSlot);
promoteShadow(stackSlot, InOutArg);
return true;
}
failed:
// If we fail, dump out some internal state.
DEBUG({
llvm::dbgs() << "*** Failed to deshadow. Uses:\n";
for (auto UI : InOutArg->getUses())
llvm::dbgs() << " " << *UI->getUser();
});
return false;
}
namespace {
class InOutDeshadowing : public SILFunctionTransform {
/// The entry point to the transformation.
void run() override {
SILFunction &F = *getFunction();
// For each function, find any inout arguments and try to optimize each of
// them.
SILFunctionType *FTI = F.getLoweredFunctionType();
for (unsigned arg = 0, e = FTI->getParameters().size();
arg != e; ++arg) {
if (!FTI->getParameters()[arg].isIndirectInOut()) continue;
DEBUG(llvm::dbgs()<< " " << F.getName() << ": argument #"<< arg << "\n");
if (processInOutValue(F.getArgumentsWithoutIndirectResults()[arg]))
++NumShadowsRemoved;
else {
++NumShadowsKept;
}
}
}
StringRef getName() override { return "InOut Deshadowing"; }
};
} // end anonymous namespace
SILTransform *swift::createInOutDeshadowing() {
return new InOutDeshadowing();
}