Files
swift-mirror/lib/SILOptimizer/Transforms/StackPromotion.cpp
Andrew Trick f009cf3de8 EscapeAnalysis cleanup and add utilities [nearly NFC]
This is the first in a series of patches that reworks
EscapeAnalysis. For this patch, I extracted every change that does not
introduce new features, rewrite logic, or appear to change
functionality.

These cleanups were done in preparation for:

- adding a graph representation for reference counted objects

- rewriting parts to the query logic

- ...which then allows the analysis to safely assume that all
  exclusive arguments are unique

- ...which then allows more aggressive optimization of local variables
  that don't escape

There are two possible functional changes:

1. getUnderlyingAddressRoot in InstructionUtils now sees through OSSA
instructions: begin_borrow and copy_value

2. The getPointerBase helper in EscapeAnalysis now sees through all of
these reference and pointer casts:

+  case ValueKind::UncheckedRefCastInst:
+  case ValueKind::ConvertFunctionInst:
+  case ValueKind::UpcastInst:
+  case ValueKind::InitExistentialRefInst:
+  case ValueKind::OpenExistentialRefInst:
+  case ValueKind::RawPointerToRefInst:
+  case ValueKind::RefToRawPointerInst:
+  case ValueKind::RefToBridgeObjectInst:
+  case ValueKind::BridgeObjectToRefInst:
+  case ValueKind::UncheckedAddrCastInst:
+  case ValueKind::UnconditionalCheckedCastInst:
+  case ValueKind::RefTo##Name##Inst:
+  case ValueKind::Name##ToRefInst:

This coalesces a whole bunch of nodes together that were just there
because of casts. The existing code was already doing this for one
level of casts, but there was a bug that prevented it from happening
transitively. So, in theory, anything that breaks with this fix could
also break without this fix, but may not have been exposed. The fact
that this analysis coalesces address-to-reference casts at all is what
caused me to spent vast amounts of time debugging any time I tried to
force some structure on the graph via assertions. If it is done at
all, it should be done everywhere consistently to expose issues as
early as possible.

Here is a description of the changes in diff order. If something in
the diff is not listed here, then the code probably just moved around
in the file:

Rename isNotAliasedIndirectParameter to
isExclusiveIndirectParameter. The argument may be aliased in the
caller's scope and it's contents may have already escaped.

Add comments to SILType APIs (isTrivial, isReferenceCounted) that give
answers about the AST type which don't really make sense for address
SILTypes.

Add comments about CGNode's 'Value' field. I spent lots of time
attempting to tighten this down with asserts, but it's only possible
for non-content nodes. For content nodes, the node's value is highly
unpredictable and basically nonsense but needed for debugging.

Add comments about not assuming that the content nodes pointsTo edge
represents physical indirection. This matters when reasoning about
aliasing and it's a tempting assumption to make.

Add a CGNode::mergeProperties placeholder for adding node properties.

Factor out a CGNode::canAddDeferred helper for use later.

Rename `setPointsTo` to `setPointsToEdge` because it actually creates
an edge rather than just setting `pointsTo`.

Add CGNode::getValue() and related helpers to help maintain invariants.

Factor out a `markEscaping` helper.

Clean up the `escapesInsideFunction` helper.

Add node visitor helpers: visitSuccessors, visitDefers. This made is
much easier to prototype utilities.

Add comments to clarify the `pointsTo` invariant. In particular, an
entire defer web may have a null pointsTo for a while.

Add an `activeWorklist` to avoid nasty bugs when composing multiple
helpers that each use the worklist.

Remove the `EA` argument from `getNode`. I ended up needing access to
the `EA` context from the ConnectionGraph many times during
prototyping and passing `this` was all the `getNode` calls was very
silly.

Add graph visitor helpers: backwardTraverse, forwardTraverseDefer,
forwardTraversePointsToEdges, and mayReach for ease in developing new
logic and utilities.

Add isExclusiveArgument helper and distinguish exclusive arguments
from local objects. Confusing these properties could lead to scary
bugs. For example, unlike a local object, an exclusive argument's
contents may still escape even when the content's connection graph
node is non-escaping!

Add isUniquelyIdentified helper when we want to treat exclusive
arguments and local objects uniformly.

getUnderlyingAddressRoot now looks through OSSA instructions.

Rename `getAccessedMemory` to `getDirectlyAccessedMemory` with
comments. This is another dangerous API because it assumes the memory
access to a given address won't touch memory represented by different
graph nodes, but graph edges don't necessarily represent physical
indirection. Further clarify this issue in comments in
AliasAnalysis.cpp.

Factor out a 'findRecursiveRefType' helper from the old
'mayContainReference' for checking whether types may or must contain
references. Support both kinds of queries so the analysis can be
certain when a pointer's content is a physical heap object.

Factor out 'getPointerBase' and 'getPointerRoot' helpers that follow
address projections within what EscapeAnalysis will consider a single
node.

Create a CGNodeWorklist abstraction to safely formalize the worklist
mechanism that's used all over the place. In one place, there were
even two separate independent lists used independently (nodes added to
one list could appear to be in the other list).

The CGNodeMap abstraction did not significantly change, it was just moved.

Added 'dumpCG' for dumping .dot files making it possible to remote debug.

Added '-escapes-enable-graphwriter' option to dump .dot files, since
they are so much more useful than the textual dump of the connection
graph, which lacks node identity!
2019-11-06 11:07:52 -08:00

173 lines
5.9 KiB
C++

//===--- StackPromotion.cpp - Promotes allocations to the stack -----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SILOptimizer/Analysis/EscapeAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/StackNesting.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
#include "llvm/ADT/Statistic.h"
#define DEBUG_TYPE "stack-promotion"
STATISTIC(NumStackPromoted, "Number of objects promoted to the stack");
using namespace swift;
namespace {
/// Promotes heap allocated objects to the stack.
///
/// It handles alloc_ref instructions of native swift classes: if promoted,
/// the [stack] attribute is set in the alloc_ref and a dealloc_ref [stack] is
/// inserted at the end of the object's lifetime.
class StackPromotion : public SILFunctionTransform {
public:
StackPromotion() {}
private:
/// The entry point to the transformation.
void run() override;
/// Promotes allocations in \p BB.
bool promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA,
DeadEndBlocks &DEBlocks);
/// Tries to promote the allocation \p ARI.
bool tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
DeadEndBlocks &DEBlocks);
};
void StackPromotion::run() {
SILFunction *F = getFunction();
// FIXME: We should be able to support ownership.
if (F->hasOwnership())
return;
LLVM_DEBUG(llvm::dbgs() << "** StackPromotion in " << F->getName() << " **\n");
auto *EA = PM->getAnalysis<EscapeAnalysis>();
DeadEndBlocks DEBlocks(F);
bool Changed = false;
// Search the whole function for stack promotable allocations.
for (SILBasicBlock &BB : *F) {
Changed |= promoteInBlock(&BB, EA, DEBlocks);
}
if (!Changed)
return;
// Make sure that all stack allocating instructions are nested correctly.
StackNesting SN;
if (SN.correctStackNesting(F) == StackNesting::Changes::CFG) {
invalidateAnalysis(SILAnalysis::InvalidationKind::BranchesAndInstructions);
} else {
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
}
bool StackPromotion::promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA,
DeadEndBlocks &DEBlocks) {
bool Changed = false;
for (auto Iter = BB->begin(); Iter != BB->end();) {
// The allocation instruction may be moved, so increment Iter prior to
// doing the optimization.
SILInstruction *I = &*Iter++;
if (auto *ARI = dyn_cast<AllocRefInst>(I)) {
// Don't stack promote any allocation inside a code region which ends up
// in a no-return block. Such allocations may missing their final release.
// We would insert the deallocation too early, which may result in a
// use-after-free problem.
if (DEBlocks.isDeadEnd(BB))
return false;
Changed |= tryPromoteAlloc(ARI, EA, DEBlocks);
}
}
return Changed;
}
bool StackPromotion::tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
DeadEndBlocks &DEBlocks) {
if (ARI->isObjC() || ARI->canAllocOnStack())
return false;
auto *ConGraph = EA->getConnectionGraph(ARI->getFunction());
auto *Node = ConGraph->getNodeOrNull(ARI);
if (!Node)
return false;
// The most important check: does the object escape the current function?
if (Node->escapes())
return false;
LLVM_DEBUG(llvm::dbgs() << "Promote " << *ARI);
// Collect all use-points of the allocation. These are refcount instructions
// and apply instructions.
llvm::SmallVector<SILNode *, 8> BaseUsePoints;
llvm::SmallVector<SILInstruction *, 8> UsePoints;
ConGraph->getUsePoints(Node, BaseUsePoints);
for (SILNode *UsePoint : BaseUsePoints) {
if (SILInstruction *I = dyn_cast<SILInstruction>(UsePoint)) {
UsePoints.push_back(I);
} else {
// Also block arguments can be use points.
SILBasicBlock *UseBB = cast<SILPhiArgument>(UsePoint)->getParent();
// For simplicity we just add the first instruction of the block as use
// point.
UsePoints.push_back(&UseBB->front());
}
}
ValueLifetimeAnalysis VLA(ARI, UsePoints);
// Check if there is a use point before the allocation (this can happen e.g.
// if the allocated object is stored into another object, which is already
// alive at the allocation point).
// In such a case the value lifetime extends up to the function entry.
if (VLA.isAliveAtBeginOfBlock(ARI->getFunction()->getEntryBlock())) {
LLVM_DEBUG(llvm::dbgs() << " use before allocation -> don't promote");
return false;
}
// Compute the places where the lifetime of the object ends.
ValueLifetimeAnalysis::Frontier Frontier;
if (!VLA.computeFrontier(Frontier, ValueLifetimeAnalysis::UsersMustPostDomDef,
&DEBlocks)) {
LLVM_DEBUG(llvm::dbgs() << " uses don't post-dom allocation -> don't promote");
return false;
}
NumStackPromoted++;
// We set the [stack] attribute in the alloc_ref.
ARI->setStackAllocatable();
/// And create dealloc_ref [stack] at the end of the object's lifetime.
for (SILInstruction *FrontierInst : Frontier) {
SILBuilder B(FrontierInst);
B.createDeallocRef(ARI->getLoc(), ARI, true);
}
return true;
}
} // end anonymous namespace
SILTransform *swift::createStackPromotion() {
return new StackPromotion();
}