mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This directly adds support to BasicBlockCloner for updating OSSA. It also adds a much more general-purpose GuaranteedPhiBorrowFixup utility which can be used for more complicated SSA updates, in which multiple phis need to be created. More generally, it handles adding nested borrow scopes for guaranteed phis even when that phi is used by other guaranteed phis.
391 lines
14 KiB
C++
391 lines
14 KiB
C++
//===--- BasicBlockOptUtils.h - SIL basic block utilities -------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2019 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// Utilities used by the SILOptimizer for analyzing and operating on whole
|
|
/// basic blocks, including as removal, cloning, and SSA update.
|
|
///
|
|
/// CFGOptUtils.h provides lower-level CFG branch and edge utilities.
|
|
///
|
|
/// SIL/BasicBlockUtils.h provides essential SILBasicBlock utilities.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H
|
|
#define SWIFT_SILOPTIMIZER_UTILS_BASICBLOCKOPTUTILS_H
|
|
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/BasicBlockBits.h"
|
|
#include "swift/SIL/SILCloner.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
|
|
namespace swift {
|
|
|
|
class BasicBlockCloner;
|
|
class SILLoop;
|
|
class SILLoopInfo;
|
|
|
|
/// Compute the set of reachable blocks.
|
|
class ReachableBlocks {
|
|
BasicBlockSet visited;
|
|
|
|
public:
|
|
ReachableBlocks(SILFunction *function) : visited(function) {}
|
|
|
|
/// Invoke \p visitor for each reachable block in \p f in worklist order (at
|
|
/// least one predecessor has been visited--defs are always visited before
|
|
/// uses except for phi-type block args). The \p visitor takes a block
|
|
/// argument, which is already marked visited, and must return true to
|
|
/// continue visiting blocks.
|
|
///
|
|
/// Returns true if all reachable blocks were visited.
|
|
bool visit(function_ref<bool(SILBasicBlock *)> visitor);
|
|
|
|
/// Return true if \p bb has been visited.
|
|
bool isVisited(SILBasicBlock *bb) const { return visited.contains(bb); }
|
|
};
|
|
|
|
/// Remove all instructions in the body of \p bb in safe manner by using
|
|
/// undef.
|
|
void clearBlockBody(SILBasicBlock *bb);
|
|
|
|
/// Handle the mechanical aspects of removing an unreachable block.
|
|
void removeDeadBlock(SILBasicBlock *bb);
|
|
|
|
/// Remove all unreachable blocks in a function.
|
|
bool removeUnreachableBlocks(SILFunction &f);
|
|
|
|
/// Return true if there are any users of v outside the specified block.
|
|
inline bool isUsedOutsideOfBlock(SILValue v) {
|
|
auto *bb = v->getParentBlock();
|
|
for (auto *use : v->getUses())
|
|
if (use->getUser()->getParent() != bb)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Rotate a loop's header as long as it is exiting and not equal to the
|
|
/// passed basic block.
|
|
/// If \p RotateSingleBlockLoops is true a single basic block loop will be
|
|
/// rotated once. ShouldVerify specifies whether to perform verification after
|
|
/// the transformation.
|
|
/// Returns true if the loop could be rotated.
|
|
bool rotateLoop(SILLoop *loop, DominanceInfo *domInfo, SILLoopInfo *loopInfo,
|
|
bool rotateSingleBlockLoops, SILBasicBlock *upToBB,
|
|
bool shouldVerify);
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// BasicBlock Cloning
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Return true if the \p termInst can be cloned. If termInst produces a
|
|
/// guaranteed result, then it must be possible to create a nested borrow scope
|
|
/// for that result when the cloner generates a guaranteed phi.
|
|
///
|
|
/// Note: if all the terminators uses will also be cloned, then a guaranteed phi
|
|
/// won't be necessary. This is one of the reasons that cloning a region is much
|
|
/// better than cloning a single block at a time.
|
|
bool canCloneTerminator(TermInst *termInst);
|
|
|
|
/// Sink address projections to their out-of-block uses. This is
|
|
/// required after cloning a block and before calling
|
|
/// updateSSAAfterCloning to avoid address-type phis.
|
|
///
|
|
/// This clones address projections at their use points, but does not
|
|
/// mutate the block containing the projections.
|
|
///
|
|
/// BasicBlockCloner handles this internally.
|
|
class SinkAddressProjections {
|
|
// Projections ordered from last to first in the chain.
|
|
SmallVector<SingleValueInstruction *, 4> projections;
|
|
SmallSetVector<SILValue, 4> inBlockDefs;
|
|
|
|
// Transient per-projection data for use during cloning.
|
|
SmallVector<Operand *, 4> usesToReplace;
|
|
llvm::SmallDenseMap<SILBasicBlock *, Operand *, 4> firstBlockUse;
|
|
|
|
public:
|
|
/// Check for an address projection chain ending at \p inst. Return true if
|
|
/// the given instruction is successfully analyzed.
|
|
///
|
|
/// If \p inst does not produce an address, then return
|
|
/// true. getInBlockDefs() will contain \p inst if any of its
|
|
/// (non-address) values are used outside its block.
|
|
///
|
|
/// If \p inst does produce an address, return true only of the
|
|
/// chain of address projections within this block is clonable at
|
|
/// their use sites. getInBlockDefs will return all non-address
|
|
/// operands in the chain that are also defined in this block. These
|
|
/// may require phis after cloning the projections.
|
|
bool analyzeAddressProjections(SILInstruction *inst);
|
|
|
|
/// After analyzing projections, returns the list of (non-address) values
|
|
/// defined in the same block as the projections which will have uses outside
|
|
/// the block after cloning.
|
|
ArrayRef<SILValue> getInBlockDefs() const {
|
|
return inBlockDefs.getArrayRef();
|
|
}
|
|
/// Clone the chain of projections at their use sites.
|
|
///
|
|
/// Return true if anything was done.
|
|
///
|
|
/// getInBlockProjectionOperandValues() can be called before or after cloning.
|
|
bool cloneProjections();
|
|
};
|
|
|
|
/// Clone a single basic block and any required successor edges within the same
|
|
/// function.
|
|
///
|
|
/// Before cloning, call either canCloneBlock or call canCloneInstruction for
|
|
/// every instruction in the original block.
|
|
///
|
|
/// To clone just the block, call cloneBlock. To also update the original
|
|
/// block's branch to jump to the newly cloned block, call cloneBranchTarget
|
|
/// instead.
|
|
///
|
|
/// After cloning, call updateSSAAfterCloning. This is decoupled from cloning
|
|
/// becaused some clients perform CFG edges updates after cloning but before
|
|
/// splitting CFG edges.
|
|
class BasicBlockCloner : public SILCloner<BasicBlockCloner> {
|
|
using SuperTy = SILCloner<BasicBlockCloner>;
|
|
friend class SILCloner<BasicBlockCloner>;
|
|
|
|
protected:
|
|
/// The original block to be cloned.
|
|
SILBasicBlock *origBB;
|
|
|
|
/// Will cloning require an SSA update?
|
|
bool needsSSAUpdate = false;
|
|
|
|
/// Transient object for analyzing a single address projction chain. It's
|
|
/// state is reset each time analyzeAddressProjections is called.
|
|
SinkAddressProjections sinkProj;
|
|
|
|
public:
|
|
/// An ordered list of old to new available value pairs.
|
|
///
|
|
/// updateSSAAfterCloning() expects this public field to hold values that may
|
|
/// be remapped in the cloned block and live out.
|
|
SmallVector<std::pair<SILValue, SILValue>, 16> availVals;
|
|
|
|
// Clone blocks starting at `origBB`, within the same function.
|
|
BasicBlockCloner(SILBasicBlock *origBB)
|
|
: SILCloner(*origBB->getParent()), origBB(origBB) {}
|
|
|
|
bool canCloneBlock() {
|
|
for (auto &inst : *origBB) {
|
|
if (!canCloneInstruction(&inst))
|
|
return false;
|
|
}
|
|
canCloneTerminator(origBB->getTerminator());
|
|
return true;
|
|
}
|
|
|
|
/// Returns true if \p inst can be cloned.
|
|
///
|
|
/// If canCloneBlock is not called, then this must be called for every
|
|
/// instruction in origBB, both to ensure clonability and to handle internal
|
|
/// book-keeping (needsSSAUpdate).
|
|
bool canCloneInstruction(SILInstruction *inst) {
|
|
assert(inst->getParent() == origBB);
|
|
|
|
if (!inst->isTriviallyDuplicatable())
|
|
return false;
|
|
|
|
if (!sinkProj.analyzeAddressProjections(inst))
|
|
return false;
|
|
|
|
// Check if any of the non-address defs in the cloned block (including the
|
|
// current instruction) will still have uses outside the block after sinking
|
|
// address projections.
|
|
needsSSAUpdate |= !sinkProj.getInBlockDefs().empty();
|
|
return true;
|
|
}
|
|
|
|
void cloneBlock(SILBasicBlock *insertAfterBB = nullptr) {
|
|
sinkAddressProjections();
|
|
|
|
SmallVector<SILBasicBlock *, 4> successorBBs;
|
|
successorBBs.reserve(origBB->getSuccessors().size());
|
|
llvm::copy(origBB->getSuccessors(), std::back_inserter(successorBBs));
|
|
cloneReachableBlocks(origBB, successorBBs, insertAfterBB);
|
|
}
|
|
|
|
/// Clone the given branch instruction's destination block, splitting
|
|
/// its successors, and rewrite the branch instruction.
|
|
///
|
|
/// Return false if the branch's destination block cannot be cloned. When
|
|
/// false is returned, no changes have been made.
|
|
void cloneBranchTarget(BranchInst *bi) {
|
|
assert(origBB == bi->getDestBB());
|
|
|
|
cloneBlock(/*insertAfter*/ bi->getParent());
|
|
|
|
SILBuilderWithScope(bi).createBranch(bi->getLoc(), getNewBB(),
|
|
bi->getArgs());
|
|
bi->eraseFromParent();
|
|
}
|
|
|
|
/// Create phis and maintain OSSA invariants.
|
|
///
|
|
/// Note: This must be called after calling cloneBlock or cloneBranchTarget,
|
|
/// before using any OSSA utilities.
|
|
///
|
|
/// The client may perform arbitrary branch fixups and dead block removal
|
|
/// after cloning and before calling this.
|
|
///
|
|
/// WARNING: If client converts terminator results to phis (e.g. replaces a
|
|
/// switch_enum with a branch), then it must call this before performing that
|
|
/// transformation, or fix the OSSA representation of that value itself.
|
|
void updateOSSAAfterCloning();
|
|
|
|
/// Get the newly cloned block corresponding to `origBB`.
|
|
SILBasicBlock *getNewBB() {
|
|
return remapBasicBlock(origBB);
|
|
}
|
|
|
|
bool wasCloned() { return isBlockCloned(origBB); }
|
|
|
|
protected:
|
|
/// Helper function to perform SSA updates used by updateOSSAAfterCloning.
|
|
void updateSSAAfterCloning(SmallVectorImpl<SILPhiArgument *> &newPhis);
|
|
|
|
/// Given a terminator result, either from the original or the cloned block,
|
|
/// update OSSA for any phis created for the result during edge splitting.
|
|
void updateOSSATerminatorResult(SILPhiArgument *termResult);
|
|
|
|
// MARK: CRTP overrides.
|
|
|
|
/// Override getMappedValue to allow values defined outside the block to be
|
|
/// cloned to be reused in the newly cloned block.
|
|
SILValue getMappedValue(SILValue value) {
|
|
if (auto si = value->getDefiningInstruction()) {
|
|
if (!isBlockCloned(si->getParent()))
|
|
return value;
|
|
} else if (auto bbArg = dyn_cast<SILArgument>(value)) {
|
|
if (!isBlockCloned(bbArg->getParent()))
|
|
return value;
|
|
} else {
|
|
assert(isa<SILUndef>(value) && "Unexpected Value kind");
|
|
return value;
|
|
}
|
|
// `value` is not defined outside the cloned block, so consult the cloner's
|
|
// map of cloned values.
|
|
return SuperTy::getMappedValue(value);
|
|
}
|
|
|
|
void mapValue(SILValue origValue, SILValue mappedValue) {
|
|
SuperTy::mapValue(origValue, mappedValue);
|
|
availVals.emplace_back(origValue, mappedValue);
|
|
}
|
|
|
|
void sinkAddressProjections();
|
|
};
|
|
|
|
// Helper class that provides a callback that can be used in
|
|
// inliners/cloners for collecting new call sites.
|
|
class CloneCollector {
|
|
public:
|
|
typedef std::pair<SILInstruction *, SILInstruction *> value_type;
|
|
typedef std::function<void(SILInstruction *, SILInstruction *)> CallbackType;
|
|
typedef std::function<bool (SILInstruction *)> FilterType;
|
|
|
|
private:
|
|
FilterType filter;
|
|
|
|
// Pairs of collected instructions; (new, old)
|
|
llvm::SmallVector<value_type, 4> instructionpairs;
|
|
|
|
void collect(SILInstruction *oldI, SILInstruction *newI) {
|
|
if (filter(newI))
|
|
instructionpairs.push_back(std::make_pair(newI, oldI));
|
|
}
|
|
|
|
public:
|
|
CloneCollector(FilterType filter) : filter(filter) {}
|
|
|
|
CallbackType getCallback() {
|
|
return std::bind(&CloneCollector::collect, this, std::placeholders::_1,
|
|
std::placeholders::_2);
|
|
}
|
|
|
|
llvm::SmallVectorImpl<value_type> &getInstructionPairs() {
|
|
return instructionpairs;
|
|
}
|
|
};
|
|
|
|
/// Utility class for cloning init values into the static initializer of a
|
|
/// SILGlobalVariable.
|
|
class StaticInitCloner : public SILCloner<StaticInitCloner> {
|
|
friend class SILInstructionVisitor<StaticInitCloner>;
|
|
friend class SILCloner<StaticInitCloner>;
|
|
|
|
/// The number of not yet cloned operands for each instruction.
|
|
llvm::DenseMap<SILInstruction *, int> numOpsToClone;
|
|
|
|
/// List of instructions for which all operands are already cloned (or which
|
|
/// don't have any operands).
|
|
llvm::SmallVector<SILInstruction *, 8> readyToClone;
|
|
|
|
SILInstruction *insertionPoint = nullptr;
|
|
|
|
public:
|
|
StaticInitCloner(SILGlobalVariable *gVar)
|
|
: SILCloner<StaticInitCloner>(gVar) {}
|
|
|
|
StaticInitCloner(SILInstruction *insertionPoint)
|
|
: SILCloner<StaticInitCloner>(*insertionPoint->getFunction()),
|
|
insertionPoint(insertionPoint) {
|
|
Builder.setInsertionPoint(insertionPoint);
|
|
}
|
|
|
|
/// Add \p InitVal and all its operands (transitively) for cloning.
|
|
///
|
|
/// Note: all init values must are added, before calling clone().
|
|
/// Returns false if cloning is not possible, e.g. if we would end up cloning
|
|
/// a reference to a private function into a function which is serialized.
|
|
bool add(SILInstruction *initVal);
|
|
|
|
/// Clone \p InitVal and all its operands into the initializer of the
|
|
/// SILGlobalVariable.
|
|
///
|
|
/// \return Returns the cloned instruction in the SILGlobalVariable.
|
|
SingleValueInstruction *clone(SingleValueInstruction *initVal);
|
|
|
|
/// Convenience function to clone a single \p InitVal.
|
|
static void appendToInitializer(SILGlobalVariable *gVar,
|
|
SingleValueInstruction *initVal) {
|
|
StaticInitCloner cloner(gVar);
|
|
bool success = cloner.add(initVal);
|
|
(void)success;
|
|
assert(success && "adding initVal cannot fail for a global variable");
|
|
cloner.clone(initVal);
|
|
}
|
|
|
|
protected:
|
|
SILLocation remapLocation(SILLocation loc) {
|
|
if (insertionPoint)
|
|
return insertionPoint->getLoc();
|
|
return ArtificialUnreachableLocation();
|
|
}
|
|
|
|
const SILDebugScope *remapScope(const SILDebugScope *DS) {
|
|
if (insertionPoint)
|
|
return insertionPoint->getDebugScope();
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
} // namespace swift
|
|
|
|
#endif
|