2014-01-25 02:02:55 +00:00
|
|
|
//===- ConstantHoisting.cpp - Prepare code for expensive constants --------===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// This pass identifies expensive constants to hoist and coalesces them to
|
|
|
|
// better prepare it for SelectionDAG-based code generation. This works around
|
|
|
|
// the limitations of the basic-block-at-a-time approach.
|
|
|
|
//
|
|
|
|
// First it scans all instructions for integer constants and calculates its
|
|
|
|
// cost. If the constant can be folded into the instruction (the cost is
|
|
|
|
// TCC_Free) or the cost is just a simple operation (TCC_BASIC), then we don't
|
|
|
|
// consider it expensive and leave it alone. This is the default behavior and
|
|
|
|
// the default implementation of getIntImmCost will always return TCC_Free.
|
|
|
|
//
|
|
|
|
// If the cost is more than TCC_BASIC, then the integer constant can't be folded
|
|
|
|
// into the instruction and it might be beneficial to hoist the constant.
|
|
|
|
// Similar constants are coalesced to reduce register pressure and
|
|
|
|
// materialization code.
|
|
|
|
//
|
|
|
|
// When a constant is hoisted, it is also hidden behind a bitcast to force it to
|
|
|
|
// be live-out of the basic block. Otherwise the constant would be just
|
|
|
|
// duplicated and each basic block would have its own copy in the SelectionDAG.
|
|
|
|
// The SelectionDAG recognizes such constants as opaque and doesn't perform
|
|
|
|
// certain transformations on them, which would create a new expensive constant.
|
|
|
|
//
|
|
|
|
// This optimization is only applied to integer constants in instructions and
|
2014-03-21 06:04:45 +00:00
|
|
|
// simple (this means not nested) constant cast expressions. For example:
|
2014-01-25 02:02:55 +00:00
|
|
|
// %0 = load i64* inttoptr (i64 big_constant to i64*)
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "llvm/Transforms/Scalar.h"
|
|
|
|
#include "llvm/ADT/SmallSet.h"
|
2014-03-21 06:04:30 +00:00
|
|
|
#include "llvm/ADT/SmallVector.h"
|
2014-01-25 02:02:55 +00:00
|
|
|
#include "llvm/ADT/Statistic.h"
|
|
|
|
#include "llvm/Analysis/TargetTransformInfo.h"
|
|
|
|
#include "llvm/IR/Constants.h"
|
|
|
|
#include "llvm/IR/Dominators.h"
|
|
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
|
|
#include "llvm/Pass.h"
|
|
|
|
#include "llvm/Support/Debug.h"
|
2014-04-30 06:44:50 +00:00
|
|
|
#include <tuple>
|
2014-01-25 02:02:55 +00:00
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
|
2014-04-22 02:55:47 +00:00
|
|
|
#define DEBUG_TYPE "consthoist"
|
|
|
|
|
2014-01-25 02:02:55 +00:00
|
|
|
STATISTIC(NumConstantsHoisted, "Number of constants hoisted");
|
|
|
|
STATISTIC(NumConstantsRebased, "Number of constants rebased");
|
|
|
|
|
2014-03-20 20:17:13 +00:00
|
|
|
namespace {
|
2014-03-21 06:04:36 +00:00
|
|
|
struct ConstantUser;
|
|
|
|
struct RebasedConstantInfo;
|
|
|
|
|
|
|
|
typedef SmallVector<ConstantUser, 8> ConstantUseListType;
|
|
|
|
typedef SmallVector<RebasedConstantInfo, 4> RebasedConstantListType;
|
|
|
|
|
|
|
|
/// \brief Keeps track of the user of a constant and the operand index where the
|
|
|
|
/// constant is used.
|
|
|
|
struct ConstantUser {
|
|
|
|
Instruction *Inst;
|
|
|
|
unsigned OpndIdx;
|
|
|
|
|
|
|
|
ConstantUser(Instruction *Inst, unsigned Idx) : Inst(Inst), OpndIdx(Idx) { }
|
|
|
|
};
|
|
|
|
|
2014-03-21 06:04:45 +00:00
|
|
|
/// \brief Keeps track of a constant candidate and its uses.
|
2014-01-25 02:02:55 +00:00
|
|
|
struct ConstantCandidate {
|
|
|
|
ConstantUseListType Uses;
|
2014-03-21 06:04:30 +00:00
|
|
|
ConstantInt *ConstInt;
|
|
|
|
unsigned CumulativeCost;
|
|
|
|
|
|
|
|
ConstantCandidate(ConstantInt *ConstInt)
|
|
|
|
: ConstInt(ConstInt), CumulativeCost(0) { }
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
/// \brief Add the user to the use list and update the cost.
|
|
|
|
void addUser(Instruction *Inst, unsigned Idx, unsigned Cost) {
|
|
|
|
CumulativeCost += Cost;
|
|
|
|
Uses.push_back(ConstantUser(Inst, Idx));
|
|
|
|
}
|
2014-01-25 02:02:55 +00:00
|
|
|
};
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief This represents a constant that has been rebased with respect to a
|
|
|
|
/// base constant. The difference to the base constant is recorded in Offset.
|
|
|
|
struct RebasedConstantInfo {
|
|
|
|
ConstantUseListType Uses;
|
|
|
|
Constant *Offset;
|
|
|
|
|
|
|
|
RebasedConstantInfo(ConstantUseListType &&Uses, Constant *Offset)
|
2014-10-04 16:55:56 +00:00
|
|
|
: Uses(std::move(Uses)), Offset(Offset) { }
|
2014-03-21 06:04:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// \brief A base constant and all its rebased constants.
|
2014-01-25 02:02:55 +00:00
|
|
|
struct ConstantInfo {
|
|
|
|
ConstantInt *BaseConstant;
|
|
|
|
RebasedConstantListType RebasedConstants;
|
|
|
|
};
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief The constant hoisting pass.
|
2014-01-25 02:02:55 +00:00
|
|
|
class ConstantHoisting : public FunctionPass {
|
2014-03-21 06:04:30 +00:00
|
|
|
typedef DenseMap<ConstantInt *, unsigned> ConstCandMapType;
|
|
|
|
typedef std::vector<ConstantCandidate> ConstCandVecType;
|
|
|
|
|
2014-01-25 02:02:55 +00:00
|
|
|
const TargetTransformInfo *TTI;
|
|
|
|
DominatorTree *DT;
|
2014-03-21 06:04:36 +00:00
|
|
|
BasicBlock *Entry;
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:30 +00:00
|
|
|
/// Keeps track of constant candidates found in the function.
|
|
|
|
ConstCandVecType ConstCandVec;
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// Keep track of cast instructions we already cloned.
|
|
|
|
SmallDenseMap<Instruction *, Instruction *> ClonedCastMap;
|
|
|
|
|
2014-01-25 02:02:55 +00:00
|
|
|
/// These are the final constants we decided to hoist.
|
2014-03-21 06:04:36 +00:00
|
|
|
SmallVector<ConstantInfo, 8> ConstantVec;
|
2014-01-25 02:02:55 +00:00
|
|
|
public:
|
|
|
|
static char ID; // Pass identification, replacement for typeid
|
2014-04-25 05:29:35 +00:00
|
|
|
ConstantHoisting() : FunctionPass(ID), TTI(nullptr), DT(nullptr),
|
|
|
|
Entry(nullptr) {
|
2014-01-25 02:02:55 +00:00
|
|
|
initializeConstantHoistingPass(*PassRegistry::getPassRegistry());
|
|
|
|
}
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
bool runOnFunction(Function &Fn) override;
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-05 09:10:37 +00:00
|
|
|
const char *getPassName() const override { return "Constant Hoisting"; }
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-05 09:10:37 +00:00
|
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
2014-01-25 02:02:55 +00:00
|
|
|
AU.setPreservesCFG();
|
|
|
|
AU.addRequired<DominatorTreeWrapperPass>();
|
[PM] Change the core design of the TTI analysis to use a polymorphic
type erased interface and a single analysis pass rather than an
extremely complex analysis group.
The end result is that the TTI analysis can contain a type erased
implementation that supports the polymorphic TTI interface. We can build
one from a target-specific implementation or from a dummy one in the IR.
I've also factored all of the code into "mix-in"-able base classes,
including CRTP base classes to facilitate calling back up to the most
specialized form when delegating horizontally across the surface. These
aren't as clean as I would like and I'm planning to work on cleaning
some of this up, but I wanted to start by putting into the right form.
There are a number of reasons for this change, and this particular
design. The first and foremost reason is that an analysis group is
complete overkill, and the chaining delegation strategy was so opaque,
confusing, and high overhead that TTI was suffering greatly for it.
Several of the TTI functions had failed to be implemented in all places
because of the chaining-based delegation making there be no checking of
this. A few other functions were implemented with incorrect delegation.
The message to me was very clear working on this -- the delegation and
analysis group structure was too confusing to be useful here.
The other reason of course is that this is *much* more natural fit for
the new pass manager. This will lay the ground work for a type-erased
per-function info object that can look up the correct subtarget and even
cache it.
Yet another benefit is that this will significantly simplify the
interaction of the pass managers and the TargetMachine. See the future
work below.
The downside of this change is that it is very, very verbose. I'm going
to work to improve that, but it is somewhat an implementation necessity
in C++ to do type erasure. =/ I discussed this design really extensively
with Eric and Hal prior to going down this path, and afterward showed
them the result. No one was really thrilled with it, but there doesn't
seem to be a substantially better alternative. Using a base class and
virtual method dispatch would make the code much shorter, but as
discussed in the update to the programmer's manual and elsewhere,
a polymorphic interface feels like the more principled approach even if
this is perhaps the least compelling example of it. ;]
Ultimately, there is still a lot more to be done here, but this was the
huge chunk that I couldn't really split things out of because this was
the interface change to TTI. I've tried to minimize all the other parts
of this. The follow up work should include at least:
1) Improving the TargetMachine interface by having it directly return
a TTI object. Because we have a non-pass object with value semantics
and an internal type erasure mechanism, we can narrow the interface
of the TargetMachine to *just* do what we need: build and return
a TTI object that we can then insert into the pass pipeline.
2) Make the TTI object be fully specialized for a particular function.
This will include splitting off a minimal form of it which is
sufficient for the inliner and the old pass manager.
3) Add a new pass manager analysis which produces TTI objects from the
target machine for each function. This may actually be done as part
of #2 in order to use the new analysis to implement #2.
4) Work on narrowing the API between TTI and the targets so that it is
easier to understand and less verbose to type erase.
5) Work on narrowing the API between TTI and its clients so that it is
easier to understand and less verbose to forward.
6) Try to improve the CRTP-based delegation. I feel like this code is
just a bit messy and exacerbating the complexity of implementing
the TTI in each target.
Many thanks to Eric and Hal for their help here. I ended up blocked on
this somewhat more abruptly than I expected, and so I appreciate getting
it sorted out very quickly.
Differential Revision: http://reviews.llvm.org/D7293
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@227669 91177308-0d34-0410-b5e6-96231b3b80d8
2015-01-31 03:43:40 +00:00
|
|
|
AU.addRequired<TargetTransformInfoWrapperPass>();
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief Initialize the pass.
|
|
|
|
void setup(Function &Fn) {
|
|
|
|
DT = &getAnalysis<DominatorTreeWrapperPass>().getDomTree();
|
2015-02-01 12:01:35 +00:00
|
|
|
TTI = &getAnalysis<TargetTransformInfoWrapperPass>().getTTI(Fn);
|
2014-03-21 06:04:36 +00:00
|
|
|
Entry = &Fn.getEntryBlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Cleanup.
|
|
|
|
void cleanup() {
|
|
|
|
ConstantVec.clear();
|
|
|
|
ClonedCastMap.clear();
|
|
|
|
ConstCandVec.clear();
|
|
|
|
|
|
|
|
TTI = nullptr;
|
|
|
|
DT = nullptr;
|
|
|
|
Entry = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Instruction *findMatInsertPt(Instruction *Inst, unsigned Idx = ~0U) const;
|
|
|
|
Instruction *findConstantInsertionPoint(const ConstantInfo &ConstInfo) const;
|
2014-03-25 21:21:10 +00:00
|
|
|
void collectConstantCandidates(ConstCandMapType &ConstCandMap,
|
|
|
|
Instruction *Inst, unsigned Idx,
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstantInt *ConstInt);
|
2014-03-25 21:21:10 +00:00
|
|
|
void collectConstantCandidates(ConstCandMapType &ConstCandMap,
|
|
|
|
Instruction *Inst);
|
2014-03-21 06:04:36 +00:00
|
|
|
void collectConstantCandidates(Function &Fn);
|
2014-03-21 06:04:33 +00:00
|
|
|
void findAndMakeBaseConstant(ConstCandVecType::iterator S,
|
2014-03-21 06:04:30 +00:00
|
|
|
ConstCandVecType::iterator E);
|
2014-03-21 06:04:33 +00:00
|
|
|
void findBaseConstants();
|
2014-03-21 06:04:36 +00:00
|
|
|
void emitBaseConstants(Instruction *Base, Constant *Offset,
|
|
|
|
const ConstantUser &ConstUser);
|
|
|
|
bool emitBaseConstants();
|
|
|
|
void deleteDeadCastInst() const;
|
|
|
|
bool optimizeConstants(Function &Fn);
|
2014-01-25 02:02:55 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
char ConstantHoisting::ID = 0;
|
|
|
|
INITIALIZE_PASS_BEGIN(ConstantHoisting, "consthoist", "Constant Hoisting",
|
|
|
|
false, false)
|
|
|
|
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
|
[PM] Change the core design of the TTI analysis to use a polymorphic
type erased interface and a single analysis pass rather than an
extremely complex analysis group.
The end result is that the TTI analysis can contain a type erased
implementation that supports the polymorphic TTI interface. We can build
one from a target-specific implementation or from a dummy one in the IR.
I've also factored all of the code into "mix-in"-able base classes,
including CRTP base classes to facilitate calling back up to the most
specialized form when delegating horizontally across the surface. These
aren't as clean as I would like and I'm planning to work on cleaning
some of this up, but I wanted to start by putting into the right form.
There are a number of reasons for this change, and this particular
design. The first and foremost reason is that an analysis group is
complete overkill, and the chaining delegation strategy was so opaque,
confusing, and high overhead that TTI was suffering greatly for it.
Several of the TTI functions had failed to be implemented in all places
because of the chaining-based delegation making there be no checking of
this. A few other functions were implemented with incorrect delegation.
The message to me was very clear working on this -- the delegation and
analysis group structure was too confusing to be useful here.
The other reason of course is that this is *much* more natural fit for
the new pass manager. This will lay the ground work for a type-erased
per-function info object that can look up the correct subtarget and even
cache it.
Yet another benefit is that this will significantly simplify the
interaction of the pass managers and the TargetMachine. See the future
work below.
The downside of this change is that it is very, very verbose. I'm going
to work to improve that, but it is somewhat an implementation necessity
in C++ to do type erasure. =/ I discussed this design really extensively
with Eric and Hal prior to going down this path, and afterward showed
them the result. No one was really thrilled with it, but there doesn't
seem to be a substantially better alternative. Using a base class and
virtual method dispatch would make the code much shorter, but as
discussed in the update to the programmer's manual and elsewhere,
a polymorphic interface feels like the more principled approach even if
this is perhaps the least compelling example of it. ;]
Ultimately, there is still a lot more to be done here, but this was the
huge chunk that I couldn't really split things out of because this was
the interface change to TTI. I've tried to minimize all the other parts
of this. The follow up work should include at least:
1) Improving the TargetMachine interface by having it directly return
a TTI object. Because we have a non-pass object with value semantics
and an internal type erasure mechanism, we can narrow the interface
of the TargetMachine to *just* do what we need: build and return
a TTI object that we can then insert into the pass pipeline.
2) Make the TTI object be fully specialized for a particular function.
This will include splitting off a minimal form of it which is
sufficient for the inliner and the old pass manager.
3) Add a new pass manager analysis which produces TTI objects from the
target machine for each function. This may actually be done as part
of #2 in order to use the new analysis to implement #2.
4) Work on narrowing the API between TTI and the targets so that it is
easier to understand and less verbose to type erase.
5) Work on narrowing the API between TTI and its clients so that it is
easier to understand and less verbose to forward.
6) Try to improve the CRTP-based delegation. I feel like this code is
just a bit messy and exacerbating the complexity of implementing
the TTI in each target.
Many thanks to Eric and Hal for their help here. I ended up blocked on
this somewhat more abruptly than I expected, and so I appreciate getting
it sorted out very quickly.
Differential Revision: http://reviews.llvm.org/D7293
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@227669 91177308-0d34-0410-b5e6-96231b3b80d8
2015-01-31 03:43:40 +00:00
|
|
|
INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass)
|
2014-01-25 02:02:55 +00:00
|
|
|
INITIALIZE_PASS_END(ConstantHoisting, "consthoist", "Constant Hoisting",
|
|
|
|
false, false)
|
|
|
|
|
|
|
|
FunctionPass *llvm::createConstantHoistingPass() {
|
|
|
|
return new ConstantHoisting();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Perform the constant hoisting optimization for the given function.
|
2014-03-21 06:04:36 +00:00
|
|
|
bool ConstantHoisting::runOnFunction(Function &Fn) {
|
2015-02-14 15:11:48 +00:00
|
|
|
if (skipOptnoneFunction(Fn))
|
|
|
|
return false;
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
DEBUG(dbgs() << "********** Begin Constant Hoisting **********\n");
|
|
|
|
DEBUG(dbgs() << "********** Function: " << Fn.getName() << '\n');
|
|
|
|
|
|
|
|
setup(Fn);
|
|
|
|
|
|
|
|
bool MadeChange = optimizeConstants(Fn);
|
|
|
|
|
|
|
|
if (MadeChange) {
|
|
|
|
DEBUG(dbgs() << "********** Function after Constant Hoisting: "
|
|
|
|
<< Fn.getName() << '\n');
|
|
|
|
DEBUG(dbgs() << Fn);
|
|
|
|
}
|
|
|
|
DEBUG(dbgs() << "********** End Constant Hoisting **********\n");
|
|
|
|
|
|
|
|
cleanup();
|
|
|
|
|
|
|
|
return MadeChange;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// \brief Find the constant materialization insertion point.
|
|
|
|
Instruction *ConstantHoisting::findMatInsertPt(Instruction *Inst,
|
|
|
|
unsigned Idx) const {
|
2014-04-22 18:06:58 +00:00
|
|
|
// If the operand is a cast instruction, then we have to materialize the
|
|
|
|
// constant before the cast instruction.
|
|
|
|
if (Idx != ~0U) {
|
|
|
|
Value *Opnd = Inst->getOperand(Idx);
|
|
|
|
if (auto CastInst = dyn_cast<Instruction>(Opnd))
|
|
|
|
if (CastInst->isCast())
|
|
|
|
return CastInst;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The simple and common case. This also includes constant expressions.
|
2014-03-21 06:04:36 +00:00
|
|
|
if (!isa<PHINode>(Inst) && !isa<LandingPadInst>(Inst))
|
|
|
|
return Inst;
|
|
|
|
|
|
|
|
// We can't insert directly before a phi node or landing pad. Insert before
|
|
|
|
// the terminator of the incoming or dominating block.
|
|
|
|
assert(Entry != Inst->getParent() && "PHI or landing pad in entry block!");
|
|
|
|
if (Idx != ~0U && isa<PHINode>(Inst))
|
|
|
|
return cast<PHINode>(Inst)->getIncomingBlock(Idx)->getTerminator();
|
|
|
|
|
|
|
|
BasicBlock *IDom = DT->getNode(Inst->getParent())->getIDom()->getBlock();
|
|
|
|
return IDom->getTerminator();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Find an insertion point that dominates all uses.
|
|
|
|
Instruction *ConstantHoisting::
|
|
|
|
findConstantInsertionPoint(const ConstantInfo &ConstInfo) const {
|
|
|
|
assert(!ConstInfo.RebasedConstants.empty() && "Invalid constant info entry.");
|
2014-04-03 01:38:47 +00:00
|
|
|
// Collect all basic blocks.
|
2014-03-21 06:04:36 +00:00
|
|
|
SmallPtrSet<BasicBlock *, 8> BBs;
|
|
|
|
for (auto const &RCI : ConstInfo.RebasedConstants)
|
2014-04-03 01:38:47 +00:00
|
|
|
for (auto const &U : RCI.Uses)
|
2014-04-22 18:06:58 +00:00
|
|
|
BBs.insert(findMatInsertPt(U.Inst, U.OpndIdx)->getParent());
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
if (BBs.count(Entry))
|
|
|
|
return &Entry->front();
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
while (BBs.size() >= 2) {
|
|
|
|
BasicBlock *BB, *BB1, *BB2;
|
|
|
|
BB1 = *BBs.begin();
|
|
|
|
BB2 = *std::next(BBs.begin());
|
|
|
|
BB = DT->findNearestCommonDominator(BB1, BB2);
|
|
|
|
if (BB == Entry)
|
|
|
|
return &Entry->front();
|
|
|
|
BBs.erase(BB1);
|
|
|
|
BBs.erase(BB2);
|
|
|
|
BBs.insert(BB);
|
|
|
|
}
|
|
|
|
assert((BBs.size() == 1) && "Expected only one element.");
|
|
|
|
Instruction &FirstInst = (*BBs.begin())->front();
|
|
|
|
return findMatInsertPt(&FirstInst);
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
/// \brief Record constant integer ConstInt for instruction Inst at operand
|
|
|
|
/// index Idx.
|
|
|
|
///
|
2014-03-21 06:04:45 +00:00
|
|
|
/// The operand at index Idx is not necessarily the constant integer itself. It
|
2014-03-21 06:04:36 +00:00
|
|
|
/// could also be a cast instruction or a constant expression that uses the
|
|
|
|
// constant integer.
|
2014-03-25 21:21:10 +00:00
|
|
|
void ConstantHoisting::collectConstantCandidates(ConstCandMapType &ConstCandMap,
|
|
|
|
Instruction *Inst,
|
2014-03-21 06:04:36 +00:00
|
|
|
unsigned Idx,
|
|
|
|
ConstantInt *ConstInt) {
|
2014-01-25 02:02:55 +00:00
|
|
|
unsigned Cost;
|
2014-03-21 06:04:36 +00:00
|
|
|
// Ask the target about the cost of materializing the constant for the given
|
2014-03-21 06:04:45 +00:00
|
|
|
// instruction and operand index.
|
2014-03-21 06:04:36 +00:00
|
|
|
if (auto IntrInst = dyn_cast<IntrinsicInst>(Inst))
|
2014-03-21 06:04:45 +00:00
|
|
|
Cost = TTI->getIntImmCost(IntrInst->getIntrinsicID(), Idx,
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstInt->getValue(), ConstInt->getType());
|
2014-01-25 02:02:55 +00:00
|
|
|
else
|
2014-03-21 06:04:45 +00:00
|
|
|
Cost = TTI->getIntImmCost(Inst->getOpcode(), Idx, ConstInt->getValue(),
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstInt->getType());
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:30 +00:00
|
|
|
// Ignore cheap integer constants.
|
2014-01-25 02:02:55 +00:00
|
|
|
if (Cost > TargetTransformInfo::TCC_Basic) {
|
2014-03-21 06:04:30 +00:00
|
|
|
ConstCandMapType::iterator Itr;
|
|
|
|
bool Inserted;
|
2014-03-21 06:04:36 +00:00
|
|
|
std::tie(Itr, Inserted) = ConstCandMap.insert(std::make_pair(ConstInt, 0));
|
2014-03-21 06:04:30 +00:00
|
|
|
if (Inserted) {
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstCandVec.push_back(ConstantCandidate(ConstInt));
|
2014-03-21 06:04:30 +00:00
|
|
|
Itr->second = ConstCandVec.size() - 1;
|
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstCandVec[Itr->second].addUser(Inst, Idx, Cost);
|
|
|
|
DEBUG(if (isa<ConstantInt>(Inst->getOperand(Idx)))
|
|
|
|
dbgs() << "Collect constant " << *ConstInt << " from " << *Inst
|
|
|
|
<< " with cost " << Cost << '\n';
|
|
|
|
else
|
|
|
|
dbgs() << "Collect constant " << *ConstInt << " indirectly from "
|
|
|
|
<< *Inst << " via " << *Inst->getOperand(Idx) << " with cost "
|
|
|
|
<< Cost << '\n';
|
|
|
|
);
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief Scan the instruction for expensive integer constants and record them
|
|
|
|
/// in the constant candidate vector.
|
2014-03-25 21:21:10 +00:00
|
|
|
void ConstantHoisting::collectConstantCandidates(ConstCandMapType &ConstCandMap,
|
|
|
|
Instruction *Inst) {
|
2014-03-21 06:04:36 +00:00
|
|
|
// Skip all cast instructions. They are visited indirectly later on.
|
|
|
|
if (Inst->isCast())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Can't handle inline asm. Skip it.
|
|
|
|
if (auto Call = dyn_cast<CallInst>(Inst))
|
|
|
|
if (isa<InlineAsm>(Call->getCalledValue()))
|
|
|
|
return;
|
2014-01-25 02:02:55 +00:00
|
|
|
|
|
|
|
// Scan all operands.
|
2014-03-21 06:04:36 +00:00
|
|
|
for (unsigned Idx = 0, E = Inst->getNumOperands(); Idx != E; ++Idx) {
|
|
|
|
Value *Opnd = Inst->getOperand(Idx);
|
|
|
|
|
2014-03-25 21:21:10 +00:00
|
|
|
// Visit constant integers.
|
2014-03-21 06:04:36 +00:00
|
|
|
if (auto ConstInt = dyn_cast<ConstantInt>(Opnd)) {
|
2014-03-25 21:21:10 +00:00
|
|
|
collectConstantCandidates(ConstCandMap, Inst, Idx, ConstInt);
|
2014-01-25 02:02:55 +00:00
|
|
|
continue;
|
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
// Visit cast instructions that have constant integers.
|
|
|
|
if (auto CastInst = dyn_cast<Instruction>(Opnd)) {
|
|
|
|
// Only visit cast instructions, which have been skipped. All other
|
|
|
|
// instructions should have already been visited.
|
|
|
|
if (!CastInst->isCast())
|
2014-01-25 02:02:55 +00:00
|
|
|
continue;
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
if (auto *ConstInt = dyn_cast<ConstantInt>(CastInst->getOperand(0))) {
|
|
|
|
// Pretend the constant is directly used by the instruction and ignore
|
|
|
|
// the cast instruction.
|
2014-03-25 21:21:10 +00:00
|
|
|
collectConstantCandidates(ConstCandMap, Inst, Idx, ConstInt);
|
2014-01-25 02:02:55 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
// Visit constant expressions that have constant integers.
|
|
|
|
if (auto ConstExpr = dyn_cast<ConstantExpr>(Opnd)) {
|
|
|
|
// Only visit constant cast expressions.
|
|
|
|
if (!ConstExpr->isCast())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (auto ConstInt = dyn_cast<ConstantInt>(ConstExpr->getOperand(0))) {
|
|
|
|
// Pretend the constant is directly used by the instruction and ignore
|
|
|
|
// the constant expression.
|
2014-03-25 21:21:10 +00:00
|
|
|
collectConstantCandidates(ConstCandMap, Inst, Idx, ConstInt);
|
2014-03-21 06:04:36 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end of for all operands
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Collect all integer constants in the function that cannot be folded
|
|
|
|
/// into an instruction itself.
|
2014-03-21 06:04:36 +00:00
|
|
|
void ConstantHoisting::collectConstantCandidates(Function &Fn) {
|
2014-03-25 21:21:10 +00:00
|
|
|
ConstCandMapType ConstCandMap;
|
2014-03-21 06:04:36 +00:00
|
|
|
for (Function::iterator BB : Fn)
|
|
|
|
for (BasicBlock::iterator Inst : *BB)
|
2014-03-25 21:21:10 +00:00
|
|
|
collectConstantCandidates(ConstCandMap, Inst);
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Find the base constant within the given range and rebase all other
|
|
|
|
/// constants with respect to the base constant.
|
2014-03-21 06:04:33 +00:00
|
|
|
void ConstantHoisting::findAndMakeBaseConstant(ConstCandVecType::iterator S,
|
2014-03-21 06:04:30 +00:00
|
|
|
ConstCandVecType::iterator E) {
|
2014-03-21 06:04:36 +00:00
|
|
|
auto MaxCostItr = S;
|
2014-01-25 02:02:55 +00:00
|
|
|
unsigned NumUses = 0;
|
|
|
|
// Use the constant that has the maximum cost as base constant.
|
2014-03-21 06:04:36 +00:00
|
|
|
for (auto ConstCand = S; ConstCand != E; ++ConstCand) {
|
|
|
|
NumUses += ConstCand->Uses.size();
|
|
|
|
if (ConstCand->CumulativeCost > MaxCostItr->CumulativeCost)
|
|
|
|
MaxCostItr = ConstCand;
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't hoist constants that have only one use.
|
|
|
|
if (NumUses <= 1)
|
|
|
|
return;
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
ConstantInfo ConstInfo;
|
|
|
|
ConstInfo.BaseConstant = MaxCostItr->ConstInt;
|
|
|
|
Type *Ty = ConstInfo.BaseConstant->getType();
|
|
|
|
|
2014-01-25 02:02:55 +00:00
|
|
|
// Rebase the constants with respect to the base constant.
|
2014-03-21 06:04:36 +00:00
|
|
|
for (auto ConstCand = S; ConstCand != E; ++ConstCand) {
|
|
|
|
APInt Diff = ConstCand->ConstInt->getValue() -
|
|
|
|
ConstInfo.BaseConstant->getValue();
|
|
|
|
Constant *Offset = Diff == 0 ? nullptr : ConstantInt::get(Ty, Diff);
|
|
|
|
ConstInfo.RebasedConstants.push_back(
|
|
|
|
RebasedConstantInfo(std::move(ConstCand->Uses), Offset));
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
2014-10-04 16:55:56 +00:00
|
|
|
ConstantVec.push_back(std::move(ConstInfo));
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief Finds and combines constant candidates that can be easily
|
|
|
|
/// rematerialized with an add from a common base constant.
|
2014-03-21 06:04:33 +00:00
|
|
|
void ConstantHoisting::findBaseConstants() {
|
2014-03-21 06:04:36 +00:00
|
|
|
// Sort the constants by value and type. This invalidates the mapping!
|
2014-03-21 06:04:30 +00:00
|
|
|
std::sort(ConstCandVec.begin(), ConstCandVec.end(),
|
|
|
|
[](const ConstantCandidate &LHS, const ConstantCandidate &RHS) {
|
|
|
|
if (LHS.ConstInt->getType() != RHS.ConstInt->getType())
|
|
|
|
return LHS.ConstInt->getType()->getBitWidth() <
|
|
|
|
RHS.ConstInt->getType()->getBitWidth();
|
|
|
|
return LHS.ConstInt->getValue().ult(RHS.ConstInt->getValue());
|
2014-03-20 20:17:13 +00:00
|
|
|
});
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
// Simple linear scan through the sorted constant candidate vector for viable
|
|
|
|
// merge candidates.
|
|
|
|
auto MinValItr = ConstCandVec.begin();
|
|
|
|
for (auto CC = std::next(ConstCandVec.begin()), E = ConstCandVec.end();
|
|
|
|
CC != E; ++CC) {
|
|
|
|
if (MinValItr->ConstInt->getType() == CC->ConstInt->getType()) {
|
2014-01-25 02:02:55 +00:00
|
|
|
// Check if the constant is in range of an add with immediate.
|
2014-03-21 06:04:36 +00:00
|
|
|
APInt Diff = CC->ConstInt->getValue() - MinValItr->ConstInt->getValue();
|
2014-01-25 02:02:55 +00:00
|
|
|
if ((Diff.getBitWidth() <= 64) &&
|
|
|
|
TTI->isLegalAddImmediate(Diff.getSExtValue()))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// We either have now a different constant type or the constant is not in
|
|
|
|
// range of an add with immediate anymore.
|
2014-03-21 06:04:36 +00:00
|
|
|
findAndMakeBaseConstant(MinValItr, CC);
|
2014-01-25 02:02:55 +00:00
|
|
|
// Start a new base constant search.
|
2014-03-21 06:04:36 +00:00
|
|
|
MinValItr = CC;
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
// Finalize the last base constant search.
|
2014-03-21 06:04:33 +00:00
|
|
|
findAndMakeBaseConstant(MinValItr, ConstCandVec.end());
|
2014-01-27 13:11:43 +00:00
|
|
|
}
|
|
|
|
|
2014-03-22 01:49:27 +00:00
|
|
|
/// \brief Updates the operand at Idx in instruction Inst with the result of
|
|
|
|
/// instruction Mat. If the instruction is a PHI node then special
|
|
|
|
/// handling for duplicate values form the same incomming basic block is
|
|
|
|
/// required.
|
|
|
|
/// \return The update will always succeed, but the return value indicated if
|
|
|
|
/// Mat was used for the update or not.
|
|
|
|
static bool updateOperand(Instruction *Inst, unsigned Idx, Instruction *Mat) {
|
|
|
|
if (auto PHI = dyn_cast<PHINode>(Inst)) {
|
|
|
|
// Check if any previous operand of the PHI node has the same incoming basic
|
|
|
|
// block. This is a very odd case that happens when the incoming basic block
|
|
|
|
// has a switch statement. In this case use the same value as the previous
|
|
|
|
// operand(s), otherwise we will fail verification due to different values.
|
|
|
|
// The values are actually the same, but the variable names are different
|
|
|
|
// and the verifier doesn't like that.
|
|
|
|
BasicBlock *IncomingBB = PHI->getIncomingBlock(Idx);
|
|
|
|
for (unsigned i = 0; i < Idx; ++i) {
|
|
|
|
if (PHI->getIncomingBlock(i) == IncomingBB) {
|
|
|
|
Value *IncomingVal = PHI->getIncomingValue(i);
|
|
|
|
Inst->setOperand(Idx, IncomingVal);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Inst->setOperand(Idx, Mat);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-03-20 20:17:13 +00:00
|
|
|
/// \brief Emit materialization code for all rebased constants and update their
|
|
|
|
/// users.
|
2014-03-21 06:04:36 +00:00
|
|
|
void ConstantHoisting::emitBaseConstants(Instruction *Base, Constant *Offset,
|
|
|
|
const ConstantUser &ConstUser) {
|
|
|
|
Instruction *Mat = Base;
|
|
|
|
if (Offset) {
|
|
|
|
Instruction *InsertionPt = findMatInsertPt(ConstUser.Inst,
|
|
|
|
ConstUser.OpndIdx);
|
|
|
|
Mat = BinaryOperator::Create(Instruction::Add, Base, Offset,
|
|
|
|
"const_mat", InsertionPt);
|
|
|
|
|
|
|
|
DEBUG(dbgs() << "Materialize constant (" << *Base->getOperand(0)
|
|
|
|
<< " + " << *Offset << ") in BB "
|
|
|
|
<< Mat->getParent()->getName() << '\n' << *Mat << '\n');
|
|
|
|
Mat->setDebugLoc(ConstUser.Inst->getDebugLoc());
|
2014-03-20 19:55:52 +00:00
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
Value *Opnd = ConstUser.Inst->getOperand(ConstUser.OpndIdx);
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
// Visit constant integer.
|
|
|
|
if (isa<ConstantInt>(Opnd)) {
|
|
|
|
DEBUG(dbgs() << "Update: " << *ConstUser.Inst << '\n');
|
2014-03-22 01:49:27 +00:00
|
|
|
if (!updateOperand(ConstUser.Inst, ConstUser.OpndIdx, Mat) && Offset)
|
|
|
|
Mat->eraseFromParent();
|
2014-03-21 06:04:36 +00:00
|
|
|
DEBUG(dbgs() << "To : " << *ConstUser.Inst << '\n');
|
|
|
|
return;
|
|
|
|
}
|
2014-03-20 20:17:13 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
// Visit cast instruction.
|
|
|
|
if (auto CastInst = dyn_cast<Instruction>(Opnd)) {
|
|
|
|
assert(CastInst->isCast() && "Expected an cast instruction!");
|
|
|
|
// Check if we already have visited this cast instruction before to avoid
|
|
|
|
// unnecessary cloning.
|
|
|
|
Instruction *&ClonedCastInst = ClonedCastMap[CastInst];
|
|
|
|
if (!ClonedCastInst) {
|
|
|
|
ClonedCastInst = CastInst->clone();
|
|
|
|
ClonedCastInst->setOperand(0, Mat);
|
|
|
|
ClonedCastInst->insertAfter(CastInst);
|
|
|
|
// Use the same debug location as the original cast instruction.
|
|
|
|
ClonedCastInst->setDebugLoc(CastInst->getDebugLoc());
|
2014-04-22 18:06:51 +00:00
|
|
|
DEBUG(dbgs() << "Clone instruction: " << *CastInst << '\n'
|
|
|
|
<< "To : " << *ClonedCastInst << '\n');
|
2014-03-20 20:17:13 +00:00
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
DEBUG(dbgs() << "Update: " << *ConstUser.Inst << '\n');
|
2014-03-22 01:49:27 +00:00
|
|
|
updateOperand(ConstUser.Inst, ConstUser.OpndIdx, ClonedCastInst);
|
2014-03-21 06:04:36 +00:00
|
|
|
DEBUG(dbgs() << "To : " << *ConstUser.Inst << '\n');
|
|
|
|
return;
|
2014-03-20 20:17:13 +00:00
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
|
|
|
|
// Visit constant expression.
|
|
|
|
if (auto ConstExpr = dyn_cast<ConstantExpr>(Opnd)) {
|
|
|
|
Instruction *ConstExprInst = ConstExpr->getAsInstruction();
|
|
|
|
ConstExprInst->setOperand(0, Mat);
|
|
|
|
ConstExprInst->insertBefore(findMatInsertPt(ConstUser.Inst,
|
|
|
|
ConstUser.OpndIdx));
|
|
|
|
|
|
|
|
// Use the same debug location as the instruction we are about to update.
|
|
|
|
ConstExprInst->setDebugLoc(ConstUser.Inst->getDebugLoc());
|
|
|
|
|
|
|
|
DEBUG(dbgs() << "Create instruction: " << *ConstExprInst << '\n'
|
|
|
|
<< "From : " << *ConstExpr << '\n');
|
|
|
|
DEBUG(dbgs() << "Update: " << *ConstUser.Inst << '\n');
|
2014-03-22 01:49:27 +00:00
|
|
|
if (!updateOperand(ConstUser.Inst, ConstUser.OpndIdx, ConstExprInst)) {
|
|
|
|
ConstExprInst->eraseFromParent();
|
|
|
|
if (Offset)
|
|
|
|
Mat->eraseFromParent();
|
|
|
|
}
|
2014-03-21 06:04:36 +00:00
|
|
|
DEBUG(dbgs() << "To : " << *ConstUser.Inst << '\n');
|
|
|
|
return;
|
2014-02-08 00:20:45 +00:00
|
|
|
}
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Hoist and hide the base constant behind a bitcast and emit
|
|
|
|
/// materialization code for derived constants.
|
2014-03-21 06:04:36 +00:00
|
|
|
bool ConstantHoisting::emitBaseConstants() {
|
2014-01-25 02:02:55 +00:00
|
|
|
bool MadeChange = false;
|
2014-03-21 06:04:36 +00:00
|
|
|
for (auto const &ConstInfo : ConstantVec) {
|
2014-01-25 02:02:55 +00:00
|
|
|
// Hoist and hide the base constant behind a bitcast.
|
2014-03-21 06:04:36 +00:00
|
|
|
Instruction *IP = findConstantInsertionPoint(ConstInfo);
|
|
|
|
IntegerType *Ty = ConstInfo.BaseConstant->getType();
|
|
|
|
Instruction *Base =
|
|
|
|
new BitCastInst(ConstInfo.BaseConstant, Ty, "const", IP);
|
|
|
|
DEBUG(dbgs() << "Hoist constant (" << *ConstInfo.BaseConstant << ") to BB "
|
|
|
|
<< IP->getParent()->getName() << '\n' << *Base << '\n');
|
2014-01-25 02:02:55 +00:00
|
|
|
NumConstantsHoisted++;
|
|
|
|
|
|
|
|
// Emit materialization code for all rebased constants.
|
2014-03-21 06:04:36 +00:00
|
|
|
for (auto const &RCI : ConstInfo.RebasedConstants) {
|
2014-01-25 02:02:55 +00:00
|
|
|
NumConstantsRebased++;
|
2014-03-21 06:04:36 +00:00
|
|
|
for (auto const &U : RCI.Uses)
|
|
|
|
emitBaseConstants(Base, RCI.Offset, U);
|
2014-01-25 02:02:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Use the same debug location as the last user of the constant.
|
|
|
|
assert(!Base->use_empty() && "The use list is empty!?");
|
2014-03-09 03:16:01 +00:00
|
|
|
assert(isa<Instruction>(Base->user_back()) &&
|
2014-01-25 02:02:55 +00:00
|
|
|
"All uses should be instructions.");
|
2014-03-09 03:16:01 +00:00
|
|
|
Base->setDebugLoc(cast<Instruction>(Base->user_back())->getDebugLoc());
|
2014-01-25 02:02:55 +00:00
|
|
|
|
|
|
|
// Correct for base constant, which we counted above too.
|
|
|
|
NumConstantsRebased--;
|
|
|
|
MadeChange = true;
|
|
|
|
}
|
|
|
|
return MadeChange;
|
|
|
|
}
|
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief Check all cast instructions we made a copy of and remove them if they
|
|
|
|
/// have no more users.
|
|
|
|
void ConstantHoisting::deleteDeadCastInst() const {
|
|
|
|
for (auto const &I : ClonedCastMap)
|
|
|
|
if (I.first->use_empty())
|
2014-03-22 01:49:30 +00:00
|
|
|
I.first->eraseFromParent();
|
2014-03-21 06:04:36 +00:00
|
|
|
}
|
2014-03-20 20:17:13 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
/// \brief Optimize expensive integer constants in the given function.
|
|
|
|
bool ConstantHoisting::optimizeConstants(Function &Fn) {
|
2014-01-25 02:02:55 +00:00
|
|
|
// Collect all constant candidates.
|
2014-03-21 06:04:36 +00:00
|
|
|
collectConstantCandidates(Fn);
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:30 +00:00
|
|
|
// There are no constant candidates to worry about.
|
|
|
|
if (ConstCandVec.empty())
|
|
|
|
return false;
|
2014-01-25 02:02:55 +00:00
|
|
|
|
|
|
|
// Combine constants that can be easily materialized with an add from a common
|
|
|
|
// base constant.
|
2014-03-21 06:04:33 +00:00
|
|
|
findBaseConstants();
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
// There are no constants to emit.
|
|
|
|
if (ConstantVec.empty())
|
|
|
|
return false;
|
|
|
|
|
2014-03-21 06:04:45 +00:00
|
|
|
// Finally hoist the base constant and emit materialization code for dependent
|
2014-01-25 02:02:55 +00:00
|
|
|
// constants.
|
2014-03-21 06:04:36 +00:00
|
|
|
bool MadeChange = emitBaseConstants();
|
2014-01-25 02:02:55 +00:00
|
|
|
|
2014-03-21 06:04:36 +00:00
|
|
|
// Cleanup dead instructions.
|
|
|
|
deleteDeadCastInst();
|
2014-01-25 02:02:55 +00:00
|
|
|
|
|
|
|
return MadeChange;
|
|
|
|
}
|