Teach EarlyCSE to do trivial CSE of loads and read-only calls.

On 176.gcc, this catches 13090 loads and calls, and increases the
number of simple instructions CSE'd from 29658 to 36208.



git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@122727 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Chris Lattner 2011-01-03 03:18:43 +00:00
parent 152096275a
commit 8e7f0d70c7
2 changed files with 199 additions and 24 deletions

View File

@ -27,7 +27,12 @@
using namespace llvm; using namespace llvm;
STATISTIC(NumSimplify, "Number of insts simplified or DCE'd"); STATISTIC(NumSimplify, "Number of insts simplified or DCE'd");
STATISTIC(NumCSE, "Number of insts CSE'd"); STATISTIC(NumCSE, "Number of insts CSE'd");
STATISTIC(NumCSEMem, "Number of load and call insts CSE'd");
static unsigned getHash(const void *V) {
return DenseMapInfo<const void*>::getHashValue(V);
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// SimpleValue // SimpleValue
@ -78,10 +83,6 @@ template<> struct DenseMapInfo<SimpleValue> {
}; };
} }
unsigned getHash(const void *V) {
return DenseMapInfo<const void*>::getHashValue(V);
}
unsigned DenseMapInfo<SimpleValue>::getHashValue(SimpleValue Val) { unsigned DenseMapInfo<SimpleValue>::getHashValue(SimpleValue Val) {
Instruction *Inst = Val.Inst; Instruction *Inst = Val.Inst;
@ -124,9 +125,77 @@ bool DenseMapInfo<SimpleValue>::isEqual(SimpleValue LHS, SimpleValue RHS) {
return LHSI->isIdenticalTo(RHSI); return LHSI->isIdenticalTo(RHSI);
} }
//===----------------------------------------------------------------------===//
// MemoryValue
//===----------------------------------------------------------------------===//
namespace {
/// MemoryValue - Instances of this struct represent available load and call
/// values in the scoped hash table.
struct MemoryValue {
Instruction *Inst;
bool isSentinel() const {
return Inst == DenseMapInfo<Instruction*>::getEmptyKey() ||
Inst == DenseMapInfo<Instruction*>::getTombstoneKey();
}
static bool canHandle(Instruction *Inst) {
if (LoadInst *LI = dyn_cast<LoadInst>(Inst))
return !LI->isVolatile();
if (CallInst *CI = dyn_cast<CallInst>(Inst))
return CI->onlyReadsMemory();
return false;
}
static MemoryValue get(Instruction *I) {
MemoryValue X; X.Inst = I;
assert((X.isSentinel() || canHandle(I)) && "Inst can't be handled!");
return X;
}
};
}
namespace llvm {
// MemoryValue is POD.
template<> struct isPodLike<MemoryValue> {
static const bool value = true;
};
template<> struct DenseMapInfo<MemoryValue> {
static inline MemoryValue getEmptyKey() {
return MemoryValue::get(DenseMapInfo<Instruction*>::getEmptyKey());
}
static inline MemoryValue getTombstoneKey() {
return MemoryValue::get(DenseMapInfo<Instruction*>::getTombstoneKey());
}
static unsigned getHashValue(MemoryValue Val);
static bool isEqual(MemoryValue LHS, MemoryValue RHS);
};
}
unsigned DenseMapInfo<MemoryValue>::getHashValue(MemoryValue Val) {
Instruction *Inst = Val.Inst;
// Hash in all of the operands as pointers.
unsigned Res = 0;
for (unsigned i = 0, e = Inst->getNumOperands(); i != e; ++i)
Res ^= getHash(Inst->getOperand(i)) << i;
// Mix in the opcode.
return (Res << 1) ^ Inst->getOpcode();
}
bool DenseMapInfo<MemoryValue>::isEqual(MemoryValue LHS, MemoryValue RHS) {
Instruction *LHSI = LHS.Inst, *RHSI = RHS.Inst;
if (LHS.isSentinel() || RHS.isSentinel())
return LHSI == RHSI;
if (LHSI->getOpcode() != RHSI->getOpcode()) return false;
return LHSI->isIdenticalTo(RHSI);
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// EarlyCSE pass // EarlyCSE pass.
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
namespace { namespace {
@ -152,7 +221,21 @@ public:
/// we find, otherwise we insert them so that dominated values can succeed in /// we find, otherwise we insert them so that dominated values can succeed in
/// their lookup. /// their lookup.
ScopedHTType *AvailableValues; ScopedHTType *AvailableValues;
typedef ScopedHashTable<MemoryValue, std::pair<Value*, unsigned> > MemHTType;
/// AvailableMemValues - This scoped hash table contains the current values of
/// loads and other read-only memory values. This allows us to get efficient
/// access to dominating loads we we find a fully redundant load. In addition
/// to the most recent load, we keep track of a generation count of the read,
/// which is compared against the current generation count. The current
/// generation count is incremented after every possibly writing memory
/// operation, which ensures that we only CSE loads with other loads that have
/// no intervening store.
MemHTType *AvailableMemValues;
/// CurrentGeneration - This is the current generation of the memory value.
unsigned CurrentGeneration;
static char ID; static char ID;
explicit EarlyCSE() : FunctionPass(ID) { explicit EarlyCSE() : FunctionPass(ID) {
initializeEarlyCSEPass(*PassRegistry::getPassRegistry()); initializeEarlyCSEPass(*PassRegistry::getPassRegistry());
@ -187,11 +270,23 @@ bool EarlyCSE::processNode(DomTreeNode *Node) {
// Define a scope in the scoped hash table. When we are done processing this // Define a scope in the scoped hash table. When we are done processing this
// domtree node and recurse back up to our parent domtree node, this will pop // domtree node and recurse back up to our parent domtree node, this will pop
// off all the values we install. // off all the values we install.
ScopedHashTableScope<SimpleValue, Value*, DenseMapInfo<SimpleValue>, ScopedHTType::ScopeTy Scope(*AvailableValues);
AllocatorTy> Scope(*AvailableValues);
// Define a scope for the memory values so that anything we add will get
// popped when we recurse back up to our parent domtree node.
MemHTType::ScopeTy MemScope(*AvailableMemValues);
BasicBlock *BB = Node->getBlock(); BasicBlock *BB = Node->getBlock();
// If this block has a single predecessor, then the predecessor is the parent
// of the domtree node and all of the live out memory values are still current
// in this block. If this block has multiple predecessors, then they could
// have invalidated the live-out memory values of our parent value. For now,
// just be conservative and invalidate memory if this block has multiple
// predecessors.
if (BB->getSinglePredecessor() == 0)
++CurrentGeneration;
bool Changed = false; bool Changed = false;
// See if any instructions in the block can be eliminated. If so, do it. If // See if any instructions in the block can be eliminated. If so, do it. If
@ -219,27 +314,58 @@ bool EarlyCSE::processNode(DomTreeNode *Node) {
continue; continue;
} }
// If this instruction is something that we can't value number, ignore it. // If this is a simple instruction that we can value number, process it.
if (!SimpleValue::canHandle(Inst)) if (SimpleValue::canHandle(Inst)) {
continue; // See if the instruction has an available value. If so, use it.
if (Value *V = AvailableValues->lookup(SimpleValue::get(Inst))) {
// See if the instruction has an available value. If so, use it. DEBUG(dbgs() << "EarlyCSE CSE: " << *Inst << " to: " << *V << '\n');
if (Value *V = AvailableValues->lookup(SimpleValue::get(Inst))) { Inst->replaceAllUsesWith(V);
DEBUG(dbgs() << "EarlyCSE CSE: " << *Inst << " to: " << *V << '\n'); Inst->eraseFromParent();
Inst->replaceAllUsesWith(V); Changed = true;
Inst->eraseFromParent(); ++NumCSE;
Changed = true; continue;
++NumCSE; }
// Otherwise, just remember that this value is available.
AvailableValues->insert(SimpleValue::get(Inst), Inst);
continue; continue;
} }
// Otherwise, just remember that this value is available. // If this is a read-only memory value, process it.
AvailableValues->insert(SimpleValue::get(Inst), Inst); if (MemoryValue::canHandle(Inst)) {
// If we have an available version of this value, and if it is the right
// generation, replace this instruction.
std::pair<Value*, unsigned> InVal =
AvailableMemValues->lookup(MemoryValue::get(Inst));
if (InVal.first != 0 && InVal.second == CurrentGeneration) {
DEBUG(dbgs() << "EarlyCSE CSE MEM: " << *Inst << " to: "
<< *InVal.first << '\n');
if (!Inst->use_empty()) Inst->replaceAllUsesWith(InVal.first);
Inst->eraseFromParent();
Changed = true;
++NumCSEMem;
continue;
}
// Otherwise, remember that we have this instruction.
AvailableMemValues->insert(MemoryValue::get(Inst),
std::pair<Value*, unsigned>(Inst, CurrentGeneration));
continue;
}
// Okay, this isn't something we can CSE at all. Check to see if it is
// something that could modify memory. If so, our available memory values
// cannot be used so bump the generation count.
if (Inst->mayWriteToMemory())
++CurrentGeneration;
} }
unsigned LiveOutGeneration = CurrentGeneration;
for (DomTreeNode::iterator I = Node->begin(), E = Node->end(); I != E; ++I) for (DomTreeNode::iterator I = Node->begin(), E = Node->end(); I != E; ++I) {
Changed |= processNode(*I); Changed |= processNode(*I);
// Pop any generation changes off the stack from the recursive walk.
CurrentGeneration = LiveOutGeneration;
}
return Changed; return Changed;
} }
@ -250,5 +376,9 @@ bool EarlyCSE::runOnFunction(Function &F) {
ScopedHTType AVTable; ScopedHTType AVTable;
AvailableValues = &AVTable; AvailableValues = &AVTable;
MemHTType MemTable;
AvailableMemValues = &MemTable;
CurrentGeneration = 0;
return processNode(DT->getRootNode()); return processNode(DT->getRootNode());
} }

View File

@ -30,3 +30,48 @@ define void @test1(i8 %V, i32 *%P) {
; CHECK-NEXT: volatile store i32 %G ; CHECK-NEXT: volatile store i32 %G
ret void ret void
} }
;; Simple load value numbering.
; CHECK: @test2
define i32 @test2(i32 *%P) {
%V1 = load i32* %P
%V2 = load i32* %P
%Diff = sub i32 %V1, %V2
ret i32 %Diff
; CHECK: ret i32 0
}
;; Cross block load value numbering.
; CHECK: @test3
define i32 @test3(i32 *%P, i1 %Cond) {
%V1 = load i32* %P
br i1 %Cond, label %T, label %F
T:
store i32 4, i32* %P
ret i32 42
F:
%V2 = load i32* %P
%Diff = sub i32 %V1, %V2
ret i32 %Diff
; CHECK: F:
; CHECK: ret i32 0
}
;; Cross block load value numbering stops when stores happen.
; CHECK: @test4
define i32 @test4(i32 *%P, i1 %Cond) {
%V1 = load i32* %P
br i1 %Cond, label %T, label %F
T:
ret i32 42
F:
; Clobbers V1
store i32 42, i32* %P
%V2 = load i32* %P
%Diff = sub i32 %V1, %V2
ret i32 %Diff
; CHECK: F:
; CHECK: ret i32 %Diff
}