Files
swift-mirror/lib/SILOptimizer/Transforms/StackPromotion.cpp
Andrew Trick 7fb4e21bc0 EscapeAnalysis: Make EscapeState and UsePoints a property of the content node only.
For alias analysis query to be generally correct, we need to
effectively merge the escape state and use points for everything in a
defer web.

It was unclear from the current design whether the "escaping" property
applied to the pointer value or its content. The implementation is
inconsistent in how it was treated. It appears that some bugs have
been worked around by propagating forward through defer edges, some
have been worked around by querying the content instead of the
pointer, and others have been worked around be creating fake use
points at block arguments.

If we always simply query the content for escape state and use points,
then we never need to propagate along defer edges. The current code
that propagates escape state along defer edges in one direction is
simply incorrect from the perspective of alias analysis.

One very attractive solution is to merge nodes eagerly without
creating any defer edges, but that would be a much more radical change
even than what I've done here. It would also pose some new issues: how
to resolve the current "node types" when merging and how to deal with
missing content nodes.

This solution of applying escape state to content nodes solves all
these problems without too radical of a change at the expense of
eagerly creating content nodes. (The potential graph memory usage is
not really an issue because it's possible to drastically shrink the
size of the graph anyway in a future commit--I've been able to fit a
node within one cache line). This solution nicely preserves graph
structure which makes it easy to debug and relate to the IR.

Eagerly creating content nodes also solves the missing content node
problem. For example, when querying canEscapeTo, we need to know
whether to look at the escape state for just the pointer value itself,
or also for its content. It may be possible the its content node is
actually part of the same object at the IR level. If the content node
is missing, then we don't know if the object's interior address is not
recognizable/representable or whether we simply never saw an access to
the interior address. We can't simply look at whether the current IR
value happens to be a reference, because that doesn't tell us whether
the graph node may have been merged with a non-reference node or even
with it's own content node. To be correct in general, this query would
need to be extremely conservative. However, if content nodes are
always created for references, then we only need to query the escape
state of a pointer's content node. The content node's flag tells us if
it's an interior node, in which case it will always point to another
content node which also needs to be queried.
2019-12-19 00:12:37 -08:00

161 lines
5.4 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 *contentNode = ConGraph->getValueContent(ARI);
if (!contentNode)
return false;
// The most important check: does the object escape the current function?
if (contentNode->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<SILInstruction *, 8> UsePoints;
ConGraph->getUsePoints(contentNode, UsePoints);
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();
}