mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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.
161 lines
5.4 KiB
C++
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();
|
|
}
|