//===--- 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(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(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 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(term) || isa(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(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(V)) V = MUI->getOperand(); return dyn_cast(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(term)) return; if (!isa(term) && !isa(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(); }