Commit 1bdb7352 by Jim Stichnoth

Subzero: Reduce copying of Liveness bitvectors.

There were a lot of unnecessary copying and resizing and sloppiness in the use of BitVector for liveness analysis. This tries to reduce the amount of copying and associated memory allocation. Also, works around a ConstantRelocatable hashing problem that was causing a huge slowdown in MINIMAL mode for the vector_shuffle scons test. BUG= none R=jpp@chromium.org Review URL: https://codereview.chromium.org/1746613002 .
parent fc707ffe
...@@ -308,7 +308,7 @@ public: ...@@ -308,7 +308,7 @@ public:
explicit BitVector(unsigned s, bool t = false, Allocator A = Allocator()) explicit BitVector(unsigned s, bool t = false, Allocator A = Allocator())
: Size(s), Alloc(std::move(A)) { : Size(s), Alloc(std::move(A)) {
Capacity = NumBitWords(s); Capacity = NumBitWords(s);
Bits = Alloc.allocate(Capacity * sizeof(BitWord)); Bits = Alloc.allocate(Capacity);
init_words(Bits, Capacity, t); init_words(Bits, Capacity, t);
if (t) if (t)
clear_unused_bits(); clear_unused_bits();
...@@ -323,7 +323,7 @@ public: ...@@ -323,7 +323,7 @@ public:
} }
Capacity = NumBitWords(RHS.size()); Capacity = NumBitWords(RHS.size());
Bits = Alloc.allocate(Capacity * sizeof(BitWord)); Bits = Alloc.allocate(Capacity);
std::memcpy(Bits, RHS.Bits, Capacity * sizeof(BitWord)); std::memcpy(Bits, RHS.Bits, Capacity * sizeof(BitWord));
} }
...@@ -335,7 +335,7 @@ public: ...@@ -335,7 +335,7 @@ public:
~BitVector() { ~BitVector() {
if (Bits != nullptr) { if (Bits != nullptr) {
Alloc.deallocate(Bits, Capacity * sizeof(BitWord)); Alloc.deallocate(Bits, Capacity);
} }
} }
...@@ -655,15 +655,21 @@ public: ...@@ -655,15 +655,21 @@ public:
return *this; return *this;
} }
// Currently, BitVector is only used by liveness analysis. With the
// following assert, we make sure BitVectors grow in a single step from 0 to
// their final capacity, rather than growing slowly and "leaking" memory in
// the process.
assert(Capacity == 0);
// Grow the bitvector to have enough elements. // Grow the bitvector to have enough elements.
const auto OldCapacity = Capacity; const auto OldCapacity = Capacity;
Capacity = RHSWords; Capacity = RHSWords;
assert(Capacity > 0 && "negative capacity?"); assert(Capacity > 0 && "negative capacity?");
BitWord *NewBits = Alloc.allocate(Capacity * sizeof(BitWord)); BitWord *NewBits = Alloc.allocate(Capacity);
std::memcpy(NewBits, RHS.Bits, Capacity * sizeof(BitWord)); std::memcpy(NewBits, RHS.Bits, Capacity * sizeof(BitWord));
// Destroy the old bits. // Destroy the old bits.
Alloc.deallocate(Bits, OldCapacity * sizeof(BitWord)); Alloc.deallocate(Bits, OldCapacity);
Bits = NewBits; Bits = NewBits;
return *this; return *this;
...@@ -673,7 +679,7 @@ public: ...@@ -673,7 +679,7 @@ public:
if (this == &RHS) if (this == &RHS)
return *this; return *this;
Alloc.deallocate(Bits, Capacity * sizeof(BitWord)); Alloc.deallocate(Bits, Capacity);
Bits = RHS.Bits; Bits = RHS.Bits;
Size = RHS.Size; Size = RHS.Size;
Capacity = RHS.Capacity; Capacity = RHS.Capacity;
...@@ -755,9 +761,9 @@ private: ...@@ -755,9 +761,9 @@ private:
const auto OldCapacity = Capacity; const auto OldCapacity = Capacity;
Capacity = std::max(NumBitWords(NewSize), Capacity * 2); Capacity = std::max(NumBitWords(NewSize), Capacity * 2);
assert(Capacity > 0 && "realloc-ing zero space"); assert(Capacity > 0 && "realloc-ing zero space");
auto *NewBits = Alloc.allocate(Capacity * sizeof(BitWord)); auto *NewBits = Alloc.allocate(Capacity);
std::memcpy(Bits, NewBits, OldCapacity * sizeof(BitWord)); std::memcpy(Bits, NewBits, OldCapacity * sizeof(BitWord));
Alloc.deallocate(Bits, OldCapacity * sizeof(BitWord)); Alloc.deallocate(Bits, OldCapacity);
Bits = NewBits; Bits = NewBits;
clear_unused_bits(); clear_unused_bits();
......
...@@ -1076,6 +1076,14 @@ void Cfg::emitIAS() { ...@@ -1076,6 +1076,14 @@ void Cfg::emitIAS() {
emitJumpTables(); emitJumpTables();
} }
size_t Cfg::getTotalMemoryMB() {
constexpr size_t OneMB = 1024 * 1024;
using ArbitraryType = int;
// CfgLocalAllocator draws from the same memory pool regardless of allocated
// object type, so pick an arbitrary type for the template parameter.
return CfgLocalAllocator<ArbitraryType>().current()->getTotalMemory() / OneMB;
}
// Dumps the IR with an optional introductory message. // Dumps the IR with an optional introductory message.
void Cfg::dump(const IceString &Message) { void Cfg::dump(const IceString &Message) {
if (!BuildDefs::dump()) if (!BuildDefs::dump())
...@@ -1087,10 +1095,7 @@ void Cfg::dump(const IceString &Message) { ...@@ -1087,10 +1095,7 @@ void Cfg::dump(const IceString &Message) {
if (!Message.empty()) if (!Message.empty())
Str << "================ " << Message << " ================\n"; Str << "================ " << Message << " ================\n";
if (isVerbose(IceV_Mem)) { if (isVerbose(IceV_Mem)) {
constexpr size_t OneMB = 1024 * 1024; Str << "Memory size = " << getTotalMemoryMB() << " MB\n";
Str << "Memory size = "
<< (CfgLocalAllocator<int>().current()->getTotalMemory() / OneMB)
<< " MB\n";
} }
setCurrentNode(getEntryNode()); setCurrentNode(getEntryNode());
// Print function name+args // Print function name+args
......
...@@ -219,6 +219,10 @@ public: ...@@ -219,6 +219,10 @@ public:
const CfgNode *getCurrentNode() const { return CurrentNode; } const CfgNode *getCurrentNode() const { return CurrentNode; }
/// @} /// @}
/// Get the total amount of memory held by the per-Cfg allocator. This is
/// mostly meant for use inside a debugger.
static size_t getTotalMemoryMB();
void emit(); void emit();
void emitIAS(); void emitIAS();
static void emitTextHeader(const IceString &MangledName, GlobalContext *Ctx, static void emitTextHeader(const IceString &MangledName, GlobalContext *Ctx,
......
...@@ -626,8 +626,11 @@ void CfgNode::livenessLightweight() { ...@@ -626,8 +626,11 @@ void CfgNode::livenessLightweight() {
// liveness changed from before, false if it stayed the same. (If it changes, // liveness changed from before, false if it stayed the same. (If it changes,
// the node's predecessors need to be processed again.) // the node's predecessors need to be processed again.)
bool CfgNode::liveness(Liveness *Liveness) { bool CfgNode::liveness(Liveness *Liveness) {
SizeT NumVars = Liveness->getNumVarsInNode(this); const SizeT NumVars = Liveness->getNumVarsInNode(this);
LivenessBV Live(NumVars); const SizeT NumGlobalVars = Liveness->getNumGlobalVars();
LivenessBV &Live = Liveness->getScratchBV();
Live.clear();
LiveBeginEndMap *LiveBegin = nullptr; LiveBeginEndMap *LiveBegin = nullptr;
LiveBeginEndMap *LiveEnd = nullptr; LiveBeginEndMap *LiveEnd = nullptr;
// Mark the beginning and ending of each variable's live range with the // Mark the beginning and ending of each variable's live range with the
...@@ -642,19 +645,25 @@ bool CfgNode::liveness(Liveness *Liveness) { ...@@ -642,19 +645,25 @@ bool CfgNode::liveness(Liveness *Liveness) {
LiveBegin->reserve(getInstCountEstimate()); LiveBegin->reserve(getInstCountEstimate());
LiveEnd->reserve(getInstCountEstimate()); LiveEnd->reserve(getInstCountEstimate());
} }
// Initialize Live to be the union of all successors' LiveIn. // Initialize Live to be the union of all successors' LiveIn.
for (CfgNode *Succ : OutEdges) { for (CfgNode *Succ : OutEdges) {
Live |= Liveness->getLiveIn(Succ); const LivenessBV &LiveIn = Liveness->getLiveIn(Succ);
assert(LiveIn.empty() || LiveIn.size() == NumGlobalVars);
Live |= LiveIn;
// Mark corresponding argument of phis in successor as live. // Mark corresponding argument of phis in successor as live.
for (Inst &I : Succ->Phis) { for (Inst &I : Succ->Phis) {
if (I.isDeleted()) if (I.isDeleted())
continue; continue;
auto *Phi = llvm::dyn_cast<InstPhi>(&I); auto *Phi = llvm::cast<InstPhi>(&I);
Phi->livenessPhiOperand(Live, this, Liveness); Phi->livenessPhiOperand(Live, this, Liveness);
} }
} }
assert(Live.empty() || Live.size() == NumGlobalVars);
Liveness->getLiveOut(this) = Live; Liveness->getLiveOut(this) = Live;
// Expand Live so it can hold locals in addition to globals.
Live.resize(NumVars);
// Process regular instructions in reverse order. // Process regular instructions in reverse order.
for (Inst &I : reverse_range(Insts)) { for (Inst &I : reverse_range(Insts)) {
if (I.isDeleted()) if (I.isDeleted())
...@@ -676,20 +685,17 @@ bool CfgNode::liveness(Liveness *Liveness) { ...@@ -676,20 +685,17 @@ bool CfgNode::liveness(Liveness *Liveness) {
// When using the sparse representation, after traversing the instructions in // When using the sparse representation, after traversing the instructions in
// the block, the Live bitvector should only contain set bits for global // the block, the Live bitvector should only contain set bits for global
// variables upon block entry. We validate this by shrinking the Live vector // variables upon block entry. We validate this by testing the upper bits of
// and then testing it against the pre-shrunk version. (The shrinking is // the Live bitvector.
// required, but the validation is not.) if (Live.find_next(NumGlobalVars) != -1) {
LivenessBV LiveOrig = Live;
Live.resize(Liveness->getNumGlobalVars());
if (Live != LiveOrig) {
if (BuildDefs::dump()) { if (BuildDefs::dump()) {
// This is a fatal liveness consistency error. Print some diagnostics and // This is a fatal liveness consistency error. Print some diagnostics and
// abort. // abort.
Ostream &Str = Func->getContext()->getStrDump(); Ostream &Str = Func->getContext()->getStrDump();
Func->resetCurrentNode(); Func->resetCurrentNode();
Str << "LiveOrig-Live ="; Str << "Invalid Live =";
for (SizeT i = Live.size(); i < LiveOrig.size(); ++i) { for (SizeT i = NumGlobalVars; i < Live.size(); ++i) {
if (LiveOrig.test(i)) { if (Live.test(i)) {
Str << " "; Str << " ";
Liveness->getVariable(i, this)->dump(Func); Liveness->getVariable(i, this)->dump(Func);
} }
...@@ -698,9 +704,12 @@ bool CfgNode::liveness(Liveness *Liveness) { ...@@ -698,9 +704,12 @@ bool CfgNode::liveness(Liveness *Liveness) {
} }
llvm::report_fatal_error("Fatal inconsistency in liveness analysis"); llvm::report_fatal_error("Fatal inconsistency in liveness analysis");
} }
// Now truncate Live to prevent LiveIn from growing.
Live.resize(NumGlobalVars);
bool Changed = false; bool Changed = false;
LivenessBV &LiveIn = Liveness->getLiveIn(this); LivenessBV &LiveIn = Liveness->getLiveIn(this);
assert(LiveIn.empty() || LiveIn.size() == NumGlobalVars);
// Add in current LiveIn // Add in current LiveIn
Live |= LiveIn; Live |= LiveIn;
// Check result, set LiveIn=Live // Check result, set LiveIn=Live
...@@ -778,9 +787,9 @@ void CfgNode::livenessAddIntervals(Liveness *Liveness, InstNumberT FirstInstNum, ...@@ -778,9 +787,9 @@ void CfgNode::livenessAddIntervals(Liveness *Liveness, InstNumberT FirstInstNum,
InstNumberT LastInstNum) { InstNumberT LastInstNum) {
TimerMarker T1(TimerStack::TT_liveRange, Func); TimerMarker T1(TimerStack::TT_liveRange, Func);
SizeT NumVars = Liveness->getNumVarsInNode(this); const SizeT NumVars = Liveness->getNumVarsInNode(this);
LivenessBV &LiveIn = Liveness->getLiveIn(this); const LivenessBV &LiveIn = Liveness->getLiveIn(this);
LivenessBV &LiveOut = Liveness->getLiveOut(this); const LivenessBV &LiveOut = Liveness->getLiveOut(this);
LiveBeginEndMap &MapBegin = *Liveness->getLiveBegin(this); LiveBeginEndMap &MapBegin = *Liveness->getLiveBegin(this);
LiveBeginEndMap &MapEnd = *Liveness->getLiveEnd(this); LiveBeginEndMap &MapEnd = *Liveness->getLiveEnd(this);
std::sort(MapBegin.begin(), MapBegin.end()); std::sort(MapBegin.begin(), MapBegin.end());
...@@ -791,7 +800,8 @@ void CfgNode::livenessAddIntervals(Liveness *Liveness, InstNumberT FirstInstNum, ...@@ -791,7 +800,8 @@ void CfgNode::livenessAddIntervals(Liveness *Liveness, InstNumberT FirstInstNum,
return; return;
} }
LivenessBV LiveInAndOut = LiveIn; LivenessBV &LiveInAndOut = Liveness->getScratchBV();
LiveInAndOut = LiveIn;
LiveInAndOut &= LiveOut; LiveInAndOut &= LiveOut;
// Iterate in parallel across the sorted MapBegin[] and MapEnd[]. // Iterate in parallel across the sorted MapBegin[] and MapEnd[].
...@@ -1350,10 +1360,9 @@ void CfgNode::dump(Cfg *Func) const { ...@@ -1350,10 +1360,9 @@ void CfgNode::dump(Cfg *Func) const {
Str << "\n"; Str << "\n";
} }
// Dump the live-in variables. // Dump the live-in variables.
LivenessBV LiveIn; if (Func->isVerbose(IceV_Liveness)) {
if (Liveness) if (Liveness != nullptr && !Liveness->getLiveIn(this).empty()) {
LiveIn = Liveness->getLiveIn(this); const LivenessBV &LiveIn = Liveness->getLiveIn(this);
if (Func->isVerbose(IceV_Liveness) && !LiveIn.empty()) {
Str << " // LiveIn:"; Str << " // LiveIn:";
for (SizeT i = 0; i < LiveIn.size(); ++i) { for (SizeT i = 0; i < LiveIn.size(); ++i) {
if (LiveIn[i]) { if (LiveIn[i]) {
...@@ -1368,6 +1377,7 @@ void CfgNode::dump(Cfg *Func) const { ...@@ -1368,6 +1377,7 @@ void CfgNode::dump(Cfg *Func) const {
} }
Str << "\n"; Str << "\n";
} }
}
// Dump each instruction. // Dump each instruction.
if (Func->isVerbose(IceV_Instructions)) { if (Func->isVerbose(IceV_Instructions)) {
for (const Inst &I : Phis) for (const Inst &I : Phis)
...@@ -1376,10 +1386,9 @@ void CfgNode::dump(Cfg *Func) const { ...@@ -1376,10 +1386,9 @@ void CfgNode::dump(Cfg *Func) const {
I.dumpDecorated(Func); I.dumpDecorated(Func);
} }
// Dump the live-out variables. // Dump the live-out variables.
LivenessBV LiveOut; if (Func->isVerbose(IceV_Liveness)) {
if (Liveness) if (Liveness != nullptr && !Liveness->getLiveOut(this).empty()) {
LiveOut = Liveness->getLiveOut(this); const LivenessBV &LiveOut = Liveness->getLiveOut(this);
if (Func->isVerbose(IceV_Liveness) && !LiveOut.empty()) {
Str << " // LiveOut:"; Str << " // LiveOut:";
for (SizeT i = 0; i < LiveOut.size(); ++i) { for (SizeT i = 0; i < LiveOut.size(); ++i) {
if (LiveOut[i]) { if (LiveOut[i]) {
...@@ -1394,6 +1403,7 @@ void CfgNode::dump(Cfg *Func) const { ...@@ -1394,6 +1403,7 @@ void CfgNode::dump(Cfg *Func) const {
} }
Str << "\n"; Str << "\n";
} }
}
// Dump list of successor nodes. // Dump list of successor nodes.
if (Func->isVerbose(IceV_Succs)) { if (Func->isVerbose(IceV_Succs)) {
Str << " // succs = "; Str << " // succs = ";
......
...@@ -98,8 +98,11 @@ InstImpl<TraitsType>::InstX86Label::InstX86Label(Cfg *Func, ...@@ -98,8 +98,11 @@ InstImpl<TraitsType>::InstX86Label::InstX86Label(Cfg *Func,
template <typename TraitsType> template <typename TraitsType>
IceString InstImpl<TraitsType>::InstX86Label::getName(const Cfg *Func) const { IceString InstImpl<TraitsType>::InstX86Label::getName(const Cfg *Func) const {
// TODO(stichnot): Returning an empty string in a non-DUMP build can cause a
// huge degradation in ConstantRelocatable hashing. Investigate and fix, but
// for now return something reasonably unique.
if (!BuildDefs::dump()) if (!BuildDefs::dump())
return IceString(); return Func->getFunctionName() + std::to_string(Number);
return ".L" + Func->getFunctionName() + "$local$__" + std::to_string(Number); return ".L" + Func->getFunctionName() + "$local$__" + std::to_string(Number);
} }
......
...@@ -106,6 +106,7 @@ void Liveness::initInternal(NodeList::const_iterator FirstNode, ...@@ -106,6 +106,7 @@ void Liveness::initInternal(NodeList::const_iterator FirstNode,
RangeMask[VarIndex] = false; RangeMask[VarIndex] = false;
} }
SizeT MaxLocals = 0;
// Process each node. // Process each node.
for (auto I = FirstNode, E = Func->getNodes().end(); I != E; ++I) { for (auto I = FirstNode, E = Func->getNodes().end(); I != E; ++I) {
LivenessNode &Node = Nodes[(*I)->getIndex()]; LivenessNode &Node = Nodes[(*I)->getIndex()];
...@@ -113,7 +114,9 @@ void Liveness::initInternal(NodeList::const_iterator FirstNode, ...@@ -113,7 +114,9 @@ void Liveness::initInternal(NodeList::const_iterator FirstNode,
Node.LiveIn.resize(NumGlobals); Node.LiveIn.resize(NumGlobals);
Node.LiveOut.resize(NumGlobals); Node.LiveOut.resize(NumGlobals);
// LiveBegin and LiveEnd are reinitialized before each pass over the block. // LiveBegin and LiveEnd are reinitialized before each pass over the block.
MaxLocals = std::max(MaxLocals, Node.NumLocals);
} }
ScratchBV.reserve(NumGlobals + MaxLocals);
} }
void Liveness::init() { void Liveness::init() {
......
...@@ -85,6 +85,7 @@ public: ...@@ -85,6 +85,7 @@ public:
resize(Index); resize(Index);
return Nodes[Index].LiveOut; return Nodes[Index].LiveOut;
} }
LivenessBV &getScratchBV() { return ScratchBV; }
LiveBeginEndMap *getLiveBegin(const CfgNode *Node) { LiveBeginEndMap *getLiveBegin(const CfgNode *Node) {
SizeT Index = Node->getIndex(); SizeT Index = Node->getIndex();
resize(Index); resize(Index);
...@@ -119,6 +120,9 @@ private: ...@@ -119,6 +120,9 @@ private:
/// RangeMask[Variable::Number] indicates whether we want to track that /// RangeMask[Variable::Number] indicates whether we want to track that
/// Variable's live range. /// Variable's live range.
LivenessBV RangeMask; LivenessBV RangeMask;
/// ScratchBV is a bitvector that can be reused across CfgNode passes, to
/// avoid having to allocate/deallocate memory so frequently.
LivenessBV ScratchBV;
}; };
} // end of namespace Ice } // end of namespace Ice
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment