mirror of
https://github.com/c64scene-ar/llvm-6502.git
synced 2025-08-09 11:25:55 +00:00
This patch implements two things (sorry).
First, it allows SRA of globals that have embedded arrays, implementing GlobalOpt/globalsra-partial.llx. This comes up infrequently, but does allow, for example, deleting several stores to dead parts of globals in dhrystone. Second, this implements GlobalOpt/malloc-promote-*.llx, which is the following nifty transformation: Basically if a global pointer is initialized with malloc, and we can tell that the program won't notice, we transform this: struct foo *FooPtr; ... FooPtr = malloc(sizeof(struct foo)); ... FooPtr->A FooPtr->B Into: struct foo FooPtrBody; ... FooPtrBody.A FooPtrBody.B This comes up occasionally, for example, the 'disp' global in 183.equake (where the xform speeds the CBE version of the program up from 56.16s to 52.40s (7%) on apoc), and the 'desired_accept', 'fixLRBT', 'macroArray', & 'key_queue' globals in 300.twolf (speeding it up from 22.29s to 21.55s (3.4%)). The nice thing about this xform is that it exposes the resulting global to global variable optimization and makes alias analysis easier in addition to eliminating a few loads. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@16916 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
@@ -21,6 +21,8 @@
|
|||||||
#include "llvm/Module.h"
|
#include "llvm/Module.h"
|
||||||
#include "llvm/Pass.h"
|
#include "llvm/Pass.h"
|
||||||
#include "llvm/Support/Debug.h"
|
#include "llvm/Support/Debug.h"
|
||||||
|
#include "llvm/Target/TargetData.h"
|
||||||
|
#include "llvm/Transforms/Utils/Local.h"
|
||||||
#include "llvm/ADT/Statistic.h"
|
#include "llvm/ADT/Statistic.h"
|
||||||
#include "llvm/ADT/StringExtras.h"
|
#include "llvm/ADT/StringExtras.h"
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -36,7 +38,14 @@ namespace {
|
|||||||
Statistic<> NumGlobUses ("globalopt", "Number of global uses devirtualized");
|
Statistic<> NumGlobUses ("globalopt", "Number of global uses devirtualized");
|
||||||
|
|
||||||
struct GlobalOpt : public ModulePass {
|
struct GlobalOpt : public ModulePass {
|
||||||
|
virtual void getAnalysisUsage(AnalysisUsage &AU) const {
|
||||||
|
AU.addRequired<TargetData>();
|
||||||
|
}
|
||||||
|
|
||||||
bool runOnModule(Module &M);
|
bool runOnModule(Module &M);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ProcessInternalGlobal(GlobalVariable *GV, Module::giterator &GVI);
|
||||||
};
|
};
|
||||||
|
|
||||||
RegisterOpt<GlobalOpt> X("globalopt", "Global Variable Optimizer");
|
RegisterOpt<GlobalOpt> X("globalopt", "Global Variable Optimizer");
|
||||||
@@ -161,10 +170,22 @@ static bool AnalyzeGlobal(Value *V, GlobalStatus &GS,
|
|||||||
}
|
}
|
||||||
} else if (I->getOpcode() == Instruction::GetElementPtr) {
|
} else if (I->getOpcode() == Instruction::GetElementPtr) {
|
||||||
if (AnalyzeGlobal(I, GS, PHIUsers)) return true;
|
if (AnalyzeGlobal(I, GS, PHIUsers)) return true;
|
||||||
// Theoretically we could SRA globals with GEP insts if all indexes are
|
|
||||||
// constants. In practice, these GEPs would already be constant exprs
|
// If the first two indices are constants, this can be SRA'd.
|
||||||
// if that was the case though.
|
if (isa<GlobalVariable>(I->getOperand(0))) {
|
||||||
GS.isNotSuitableForSRA = true;
|
if (I->getNumOperands() < 3 || !isa<Constant>(I->getOperand(1)) ||
|
||||||
|
!cast<Constant>(I->getOperand(1))->isNullValue() ||
|
||||||
|
!isa<ConstantInt>(I->getOperand(2)))
|
||||||
|
GS.isNotSuitableForSRA = true;
|
||||||
|
} else if (ConstantExpr *CE = dyn_cast<ConstantExpr>(I->getOperand(0))){
|
||||||
|
if (CE->getOpcode() != Instruction::GetElementPtr ||
|
||||||
|
CE->getNumOperands() < 3 || I->getNumOperands() < 2 ||
|
||||||
|
!isa<Constant>(I->getOperand(0)) ||
|
||||||
|
!cast<Constant>(I->getOperand(0))->isNullValue())
|
||||||
|
GS.isNotSuitableForSRA = true;
|
||||||
|
} else {
|
||||||
|
GS.isNotSuitableForSRA = true;
|
||||||
|
}
|
||||||
} else if (I->getOpcode() == Instruction::Select) {
|
} else if (I->getOpcode() == Instruction::Select) {
|
||||||
if (AnalyzeGlobal(I, GS, PHIUsers)) return true;
|
if (AnalyzeGlobal(I, GS, PHIUsers)) return true;
|
||||||
GS.isNotSuitableForSRA = true;
|
GS.isNotSuitableForSRA = true;
|
||||||
@@ -323,7 +344,7 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV) {
|
|||||||
else
|
else
|
||||||
assert(0 && "Unknown aggregate sequential type!");
|
assert(0 && "Unknown aggregate sequential type!");
|
||||||
|
|
||||||
if (NumElements > 16) return 0; // It's not worth it.
|
if (NumElements > 16 && GV->use_size() > 16) return 0; // It's not worth it.
|
||||||
NewGlobals.reserve(NumElements);
|
NewGlobals.reserve(NumElements);
|
||||||
for (unsigned i = 0, e = NumElements; i != e; ++i) {
|
for (unsigned i = 0, e = NumElements; i != e; ++i) {
|
||||||
Constant *In = getAggregateConstantElement(Init,
|
Constant *In = getAggregateConstantElement(Init,
|
||||||
@@ -341,38 +362,66 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV) {
|
|||||||
if (NewGlobals.empty())
|
if (NewGlobals.empty())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
DEBUG(std::cerr << "PERFORMING GLOBAL SRA ON: " << *GV);
|
||||||
|
|
||||||
Constant *NullInt = Constant::getNullValue(Type::IntTy);
|
Constant *NullInt = Constant::getNullValue(Type::IntTy);
|
||||||
|
|
||||||
// Loop over all of the uses of the global, replacing the constantexpr geps,
|
// Loop over all of the uses of the global, replacing the constantexpr geps,
|
||||||
// with smaller constantexpr geps or direct references.
|
// with smaller constantexpr geps or direct references.
|
||||||
while (!GV->use_empty()) {
|
while (!GV->use_empty()) {
|
||||||
ConstantExpr *CE = cast<ConstantExpr>(GV->use_back());
|
User *GEP = GV->use_back();
|
||||||
assert(CE->getOpcode() == Instruction::GetElementPtr &&
|
assert(((isa<ConstantExpr>(GEP) &&
|
||||||
"NonGEP CE's are not SRAable!");
|
cast<ConstantExpr>(GEP)->getOpcode()==Instruction::GetElementPtr)||
|
||||||
|
isa<GetElementPtrInst>(GEP)) && "NonGEP CE's are not SRAable!");
|
||||||
|
|
||||||
// Ignore the 1th operand, which has to be zero or else the program is quite
|
// Ignore the 1th operand, which has to be zero or else the program is quite
|
||||||
// broken (undefined). Get the 2nd operand, which is the structure or array
|
// broken (undefined). Get the 2nd operand, which is the structure or array
|
||||||
// index.
|
// index.
|
||||||
unsigned Val = cast<ConstantInt>(CE->getOperand(2))->getRawValue();
|
unsigned Val = cast<ConstantInt>(GEP->getOperand(2))->getRawValue();
|
||||||
if (Val >= NewGlobals.size()) Val = 0; // Out of bound array access.
|
if (Val >= NewGlobals.size()) Val = 0; // Out of bound array access.
|
||||||
|
|
||||||
Constant *NewPtr = NewGlobals[Val];
|
Value *NewPtr = NewGlobals[Val];
|
||||||
|
|
||||||
// Form a shorter GEP if needed.
|
// Form a shorter GEP if needed.
|
||||||
if (CE->getNumOperands() > 3) {
|
if (GEP->getNumOperands() > 3)
|
||||||
std::vector<Constant*> Idxs;
|
if (ConstantExpr *CE = dyn_cast<ConstantExpr>(GEP)) {
|
||||||
Idxs.push_back(NullInt);
|
std::vector<Constant*> Idxs;
|
||||||
for (unsigned i = 3, e = CE->getNumOperands(); i != e; ++i)
|
Idxs.push_back(NullInt);
|
||||||
Idxs.push_back(CE->getOperand(i));
|
for (unsigned i = 3, e = CE->getNumOperands(); i != e; ++i)
|
||||||
NewPtr = ConstantExpr::getGetElementPtr(NewPtr, Idxs);
|
Idxs.push_back(CE->getOperand(i));
|
||||||
}
|
NewPtr = ConstantExpr::getGetElementPtr(cast<Constant>(NewPtr), Idxs);
|
||||||
CE->replaceAllUsesWith(NewPtr);
|
} else {
|
||||||
CE->destroyConstant();
|
GetElementPtrInst *GEPI = cast<GetElementPtrInst>(GEP);
|
||||||
|
std::vector<Value*> Idxs;
|
||||||
|
Idxs.push_back(NullInt);
|
||||||
|
for (unsigned i = 3, e = GEPI->getNumOperands(); i != e; ++i)
|
||||||
|
Idxs.push_back(GEPI->getOperand(i));
|
||||||
|
NewPtr = new GetElementPtrInst(NewPtr, Idxs,
|
||||||
|
GEPI->getName()+"."+utostr(Val), GEPI);
|
||||||
|
}
|
||||||
|
GEP->replaceAllUsesWith(NewPtr);
|
||||||
|
|
||||||
|
if (GetElementPtrInst *GEPI = dyn_cast<GetElementPtrInst>(GEP))
|
||||||
|
GEPI->getParent()->getInstList().erase(GEPI);
|
||||||
|
else
|
||||||
|
cast<ConstantExpr>(GEP)->destroyConstant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the old global, now that it is dead.
|
// Delete the old global, now that it is dead.
|
||||||
Globals.erase(GV);
|
Globals.erase(GV);
|
||||||
++NumSRA;
|
++NumSRA;
|
||||||
return NewGlobals[0];
|
|
||||||
|
// Loop over the new globals array deleting any globals that are obviously
|
||||||
|
// dead. This can arise due to scalarization of a structure or an array that
|
||||||
|
// has elements that are dead.
|
||||||
|
unsigned FirstGlobal = 0;
|
||||||
|
for (unsigned i = 0, e = NewGlobals.size(); i != e; ++i)
|
||||||
|
if (NewGlobals[i]->use_empty()) {
|
||||||
|
Globals.erase(NewGlobals[i]);
|
||||||
|
if (FirstGlobal == i) ++FirstGlobal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstGlobal != NewGlobals.size() ? NewGlobals[FirstGlobal] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AllUsesOfValueWillTrapIfNull - Return true if all users of the specified
|
/// AllUsesOfValueWillTrapIfNull - Return true if all users of the specified
|
||||||
@@ -535,10 +584,98 @@ static bool OptimizeAwayTrappingUsesOfLoads(GlobalVariable *GV, Constant *LV) {
|
|||||||
return Changed;
|
return Changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ConstantPropUsersOf - Walk the use list of V, constant folding all of the
|
||||||
|
/// instructions that are foldable.
|
||||||
|
static void ConstantPropUsersOf(Value *V) {
|
||||||
|
for (Value::use_iterator UI = V->use_begin(), E = V->use_end(); UI != E; )
|
||||||
|
if (Instruction *I = dyn_cast<Instruction>(*UI++))
|
||||||
|
if (Constant *NewC = ConstantFoldInstruction(I)) {
|
||||||
|
I->replaceAllUsesWith(NewC);
|
||||||
|
|
||||||
|
// Back up UI to avoid invalidating it!
|
||||||
|
bool AtBegin = false;
|
||||||
|
if (UI == V->use_begin())
|
||||||
|
AtBegin = true;
|
||||||
|
else
|
||||||
|
--UI;
|
||||||
|
I->getParent()->getInstList().erase(I);
|
||||||
|
if (AtBegin)
|
||||||
|
UI = V->use_begin();
|
||||||
|
else
|
||||||
|
++UI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OptimizeGlobalAddressOfMalloc - This function takes the specified global
|
||||||
|
/// variable, and transforms the program as if it always contained the result of
|
||||||
|
/// the specified malloc. Because it is always the result of the specified
|
||||||
|
/// malloc, there is no reason to actually DO the malloc. Instead, turn the
|
||||||
|
/// malloc into a global, and any laods of GV as uses of the new global.
|
||||||
|
static GlobalVariable *OptimizeGlobalAddressOfMalloc(GlobalVariable *GV,
|
||||||
|
MallocInst *MI) {
|
||||||
|
DEBUG(std::cerr << "PROMOTING MALLOC GLOBAL: " << *GV << " MALLOC = " <<*MI);
|
||||||
|
ConstantInt *NElements = cast<ConstantInt>(MI->getArraySize());
|
||||||
|
|
||||||
|
if (NElements->getRawValue() != 1) {
|
||||||
|
// If we have an array allocation, transform it to a single element
|
||||||
|
// allocation to make the code below simpler.
|
||||||
|
Type *NewTy = ArrayType::get(MI->getAllocatedType(),
|
||||||
|
NElements->getRawValue());
|
||||||
|
MallocInst *NewMI =
|
||||||
|
new MallocInst(NewTy, Constant::getNullValue(Type::UIntTy),
|
||||||
|
MI->getName(), MI);
|
||||||
|
std::vector<Value*> Indices;
|
||||||
|
Indices.push_back(Constant::getNullValue(Type::IntTy));
|
||||||
|
Indices.push_back(Indices[0]);
|
||||||
|
Value *NewGEP = new GetElementPtrInst(NewMI, Indices,
|
||||||
|
NewMI->getName()+".el0", MI);
|
||||||
|
MI->replaceAllUsesWith(NewGEP);
|
||||||
|
MI->getParent()->getInstList().erase(MI);
|
||||||
|
MI = NewMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new global variable.
|
||||||
|
Constant *Init = Constant::getNullValue(MI->getAllocatedType());
|
||||||
|
GlobalVariable *NewGV = new GlobalVariable(MI->getAllocatedType(), false,
|
||||||
|
GlobalValue::InternalLinkage, Init,
|
||||||
|
GV->getName()+".body");
|
||||||
|
GV->getParent()->getGlobalList().insert(GV, NewGV);
|
||||||
|
|
||||||
|
// Anything that used the malloc now uses the global directly.
|
||||||
|
MI->replaceAllUsesWith(NewGV);
|
||||||
|
MI->getParent()->getInstList().erase(MI);
|
||||||
|
|
||||||
|
Constant *RepValue = NewGV;
|
||||||
|
if (NewGV->getType() != GV->getType()->getElementType())
|
||||||
|
RepValue = ConstantExpr::getCast(RepValue, GV->getType()->getElementType());
|
||||||
|
|
||||||
|
// Loop over all uses of GV, processing them in turn.
|
||||||
|
while (!GV->use_empty())
|
||||||
|
if (LoadInst *LI = dyn_cast<LoadInst>(GV->use_back())) {
|
||||||
|
LI->replaceAllUsesWith(RepValue);
|
||||||
|
LI->getParent()->getInstList().erase(LI);
|
||||||
|
} else {
|
||||||
|
StoreInst *SI = cast<StoreInst>(GV->use_back());
|
||||||
|
SI->getParent()->getInstList().erase(SI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the GV is dead, nuke it.
|
||||||
|
GV->getParent()->getGlobalList().erase(GV);
|
||||||
|
|
||||||
|
// To further other optimizations, loop over all users of NewGV and try to
|
||||||
|
// constant prop them. This will promote GEP instructions with constant
|
||||||
|
// indices into GEP constant-exprs, which will allow global-opt to hack on it.
|
||||||
|
ConstantPropUsersOf(NewGV);
|
||||||
|
if (RepValue != NewGV)
|
||||||
|
ConstantPropUsersOf(RepValue);
|
||||||
|
|
||||||
|
return NewGV;
|
||||||
|
}
|
||||||
|
|
||||||
// OptimizeOnceStoredGlobal - Try to optimize globals based on the knowledge
|
// OptimizeOnceStoredGlobal - Try to optimize globals based on the knowledge
|
||||||
// that only one value (besides its initializer) is ever stored to the global.
|
// that only one value (besides its initializer) is ever stored to the global.
|
||||||
static bool OptimizeOnceStoredGlobal(GlobalVariable *GV, Value *StoredOnceVal) {
|
static bool OptimizeOnceStoredGlobal(GlobalVariable *GV, Value *StoredOnceVal,
|
||||||
|
Module::giterator &GVI, TargetData &TD) {
|
||||||
if (CastInst *CI = dyn_cast<CastInst>(StoredOnceVal))
|
if (CastInst *CI = dyn_cast<CastInst>(StoredOnceVal))
|
||||||
StoredOnceVal = CI->getOperand(0);
|
StoredOnceVal = CI->getOperand(0);
|
||||||
else if (GetElementPtrInst *GEPI =dyn_cast<GetElementPtrInst>(StoredOnceVal)){
|
else if (GetElementPtrInst *GEPI =dyn_cast<GetElementPtrInst>(StoredOnceVal)){
|
||||||
@@ -567,15 +704,35 @@ static bool OptimizeOnceStoredGlobal(GlobalVariable *GV, Value *StoredOnceVal) {
|
|||||||
// Optimize away any trapping uses of the loaded value.
|
// Optimize away any trapping uses of the loaded value.
|
||||||
if (OptimizeAwayTrappingUsesOfLoads(GV, SOVC))
|
if (OptimizeAwayTrappingUsesOfLoads(GV, SOVC))
|
||||||
return true;
|
return true;
|
||||||
|
} else if (MallocInst *MI = dyn_cast<MallocInst>(StoredOnceVal)) {
|
||||||
|
// If we have a global that is only initialized with a fixed size malloc,
|
||||||
|
// and if all users of the malloc trap, and if the malloc'd address is not
|
||||||
|
// put anywhere else, transform the program to use global memory instead
|
||||||
|
// of malloc'd memory. This eliminates dynamic allocation (good) and
|
||||||
|
// exposes the resultant global to further GlobalOpt (even better). Note
|
||||||
|
// that we restrict this transformation to only working on small
|
||||||
|
// allocations (2048 bytes currently), as we don't want to introduce a 16M
|
||||||
|
// global or something.
|
||||||
|
if (ConstantInt *NElements = dyn_cast<ConstantInt>(MI->getArraySize()))
|
||||||
|
if (MI->getAllocatedType()->isSized() &&
|
||||||
|
NElements->getRawValue()*
|
||||||
|
TD.getTypeSize(MI->getAllocatedType()) < 2048 &&
|
||||||
|
AllUsesOfLoadedValueWillTrapIfNull(GV)) {
|
||||||
|
// FIXME: do more correctness checking to make sure the result of the
|
||||||
|
// malloc isn't squirrelled away somewhere.
|
||||||
|
GVI = OptimizeGlobalAddressOfMalloc(GV, MI);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//if (isa<MallocInst>(StoredOnceValue))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ProcessInternalGlobal - Analyze the specified global variable and optimize
|
/// ProcessInternalGlobal - Analyze the specified global variable and optimize
|
||||||
/// it if possible. If we make a change, return true.
|
/// it if possible. If we make a change, return true.
|
||||||
static bool ProcessInternalGlobal(GlobalVariable *GV, Module::giterator &GVI) {
|
bool GlobalOpt::ProcessInternalGlobal(GlobalVariable *GV,
|
||||||
|
Module::giterator &GVI) {
|
||||||
std::set<PHINode*> PHIUsers;
|
std::set<PHINode*> PHIUsers;
|
||||||
GlobalStatus GS;
|
GlobalStatus GS;
|
||||||
PHIUsers.clear();
|
PHIUsers.clear();
|
||||||
@@ -625,7 +782,6 @@ static bool ProcessInternalGlobal(GlobalVariable *GV, Module::giterator &GVI) {
|
|||||||
return true;
|
return true;
|
||||||
} else if (!GS.isNotSuitableForSRA &&
|
} else if (!GS.isNotSuitableForSRA &&
|
||||||
!GV->getInitializer()->getType()->isFirstClassType()) {
|
!GV->getInitializer()->getType()->isFirstClassType()) {
|
||||||
DEBUG(std::cerr << "PERFORMING GLOBAL SRA ON: " << *GV);
|
|
||||||
if (GlobalVariable *FirstNewGV = SRAGlobal(GV)) {
|
if (GlobalVariable *FirstNewGV = SRAGlobal(GV)) {
|
||||||
GVI = FirstNewGV; // Don't skip the newly produced globals!
|
GVI = FirstNewGV; // Don't skip the newly produced globals!
|
||||||
return true;
|
return true;
|
||||||
@@ -633,7 +789,8 @@ static bool ProcessInternalGlobal(GlobalVariable *GV, Module::giterator &GVI) {
|
|||||||
} else if (GS.StoredType == GlobalStatus::isStoredOnce) {
|
} else if (GS.StoredType == GlobalStatus::isStoredOnce) {
|
||||||
// Try to optimize globals based on the knowledge that only one value
|
// Try to optimize globals based on the knowledge that only one value
|
||||||
// (besides its initializer) is ever stored to the global.
|
// (besides its initializer) is ever stored to the global.
|
||||||
if (OptimizeOnceStoredGlobal(GV, GS.StoredOnceValue))
|
if (OptimizeOnceStoredGlobal(GV, GS.StoredOnceValue, GVI,
|
||||||
|
getAnalysis<TargetData>()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user