Files
swift-mirror/lib/SILOptimizer/Mandatory/InOutDeshadowing.cpp
Michael Gottesman 650600aa2f [upstream-update] dexonsmith in an upstream commit realized he missed an
iterator/pointer comparison issue that yields undefined behavior. This updates
Swift for the landing of this change in swift-llvm/stable.

I am going to cherry-pick the given change into swift-llvm/stable since there is no
reason not to do this now and it will prevent more of these conversions from
creeping into the code base.

We really want to avoid as much undefined behavior as we possibly can.
2016-02-22 13:03:15 -08:00

322 lines
11 KiB
C++

//===--- 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 (isa<ReturnInst>(term) || isa<ThrowInst>(term)) {
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 (!isa<ReturnInst>(term) && !isa<ThrowInst>(term))
// 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();
}