//===-- PoolAllocate.cpp - Pool Allocation Pass ---------------------------===// // // This transform changes programs so that disjoint data structures are // allocated out of different pools of memory, increasing locality. // //===----------------------------------------------------------------------===// #include "llvm/Transforms/PoolAllocate.h" #include "llvm/Transforms/Utils/Cloning.h" #include "llvm/Analysis/DataStructure.h" #include "llvm/Analysis/DSGraph.h" #include "llvm/Module.h" #include "llvm/DerivedTypes.h" #include "llvm/Constants.h" #include "llvm/Instructions.h" #include "llvm/Target/TargetData.h" #include "llvm/Support/InstVisitor.h" #include "Support/Statistic.h" #include "Support/VectorExtras.h" using namespace PA; namespace { const Type *VoidPtrTy = PointerType::get(Type::SByteTy); // The type to allocate for a pool descriptor: { sbyte*, uint } const Type *PoolDescType = StructType::get(make_vector(VoidPtrTy, Type::UIntTy, 0)); const PointerType *PoolDescPtr = PointerType::get(PoolDescType); RegisterOpt X("poolalloc", "Pool allocate disjoint data structures"); } void PoolAllocate::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); AU.addRequired(); } bool PoolAllocate::run(Module &M) { if (M.begin() == M.end()) return false; CurModule = &M; AddPoolPrototypes(); BU = &getAnalysis(); std::map FuncMap; // Loop over only the function initially in the program, don't traverse newly // added ones. If the function uses memory, make its clone. Module::iterator LastOrigFunction = --M.end(); for (Module::iterator I = M.begin(); ; ++I) { if (!I->isExternal()) if (Function *R = MakeFunctionClone(*I)) FuncMap[I] = R; if (I == LastOrigFunction) break; } ++LastOrigFunction; // Now that all call targets are available, rewrite the function bodies of the // clones. for (Module::iterator I = M.begin(); I != LastOrigFunction; ++I) if (!I->isExternal()) { std::map::iterator FI = FuncMap.find(I); ProcessFunctionBody(*I, FI != FuncMap.end() ? *FI->second : *I); } FunctionInfo.clear(); return true; } // AddPoolPrototypes - Add prototypes for the pool functions to the specified // module and update the Pool* instance variables to point to them. // void PoolAllocate::AddPoolPrototypes() { CurModule->addTypeName("PoolDescriptor", PoolDescType); // Get poolinit function... FunctionType *PoolInitTy = FunctionType::get(Type::VoidTy, make_vector(PoolDescPtr, Type::UIntTy, 0), false); PoolInit = CurModule->getOrInsertFunction("poolinit", PoolInitTy); // Get pooldestroy function... std::vector PDArgs(1, PoolDescPtr); FunctionType *PoolDestroyTy = FunctionType::get(Type::VoidTy, PDArgs, false); PoolDestroy = CurModule->getOrInsertFunction("pooldestroy", PoolDestroyTy); // Get the poolalloc function... FunctionType *PoolAllocTy = FunctionType::get(VoidPtrTy, PDArgs, false); PoolAlloc = CurModule->getOrInsertFunction("poolalloc", PoolAllocTy); // Get the poolfree function... PDArgs.push_back(VoidPtrTy); // Pointer to free FunctionType *PoolFreeTy = FunctionType::get(Type::VoidTy, PDArgs, false); PoolFree = CurModule->getOrInsertFunction("poolfree", PoolFreeTy); #if 0 Args[0] = Type::UIntTy; // Number of slots to allocate FunctionType *PoolAllocArrayTy = FunctionType::get(VoidPtrTy, Args, true); PoolAllocArray = CurModule->getOrInsertFunction("poolallocarray", PoolAllocArrayTy); #endif } // MakeFunctionClone - If the specified function needs to be modified for pool // allocation support, make a clone of it, adding additional arguments as // neccesary, and return it. If not, just return null. // Function *PoolAllocate::MakeFunctionClone(Function &F) { DSGraph &G = BU->getDSGraph(F); std::vector &Nodes = G.getNodes(); if (Nodes.empty()) return 0; // No memory activity, nothing is required FuncInfo &FI = FunctionInfo[&F]; // Create a new entry for F FI.Clone = 0; // Find DataStructure nodes which are allocated in pools non-local to the // current function. This set will contain all of the DSNodes which require // pools to be passed in from outside of the function. hash_set &MarkedNodes = FI.MarkedNodes; // Mark globals and incomplete nodes as live... (this handles arguments) if (F.getName() != "main") for (unsigned i = 0, e = Nodes.size(); i != e; ++i) if (Nodes[i]->NodeType & (DSNode::GlobalNode | DSNode::Incomplete) && Nodes[i]->NodeType & (DSNode::HeapNode)) Nodes[i]->markReachableNodes(MarkedNodes); // Marked the returned node as alive... if (DSNode *RetNode = G.getRetNode().getNode()) if (RetNode->NodeType & DSNode::HeapNode) RetNode->markReachableNodes(MarkedNodes); if (MarkedNodes.empty()) // We don't need to clone the function if there return 0; // are no incoming arguments to be added. // Figure out what the arguments are to be for the new version of the function const FunctionType *OldFuncTy = F.getFunctionType(); std::vector ArgTys; ArgTys.reserve(OldFuncTy->getParamTypes().size() + MarkedNodes.size()); FI.ArgNodes.reserve(MarkedNodes.size()); for (hash_set::iterator I = MarkedNodes.begin(), E = MarkedNodes.end(); I != E; ++I) if ((*I)->NodeType & DSNode::Incomplete) { ArgTys.push_back(PoolDescPtr); // Add the appropriate # of pool descs FI.ArgNodes.push_back(*I); } if (FI.ArgNodes.empty()) return 0; // No nodes to be pool allocated! ArgTys.insert(ArgTys.end(), OldFuncTy->getParamTypes().begin(), OldFuncTy->getParamTypes().end()); // Create the new function prototype FunctionType *FuncTy = FunctionType::get(OldFuncTy->getReturnType(), ArgTys, OldFuncTy->isVarArg()); // Create the new function... Function *New = new Function(FuncTy, GlobalValue::InternalLinkage, F.getName(), F.getParent()); // Set the rest of the new arguments names to be PDa and add entries to the // pool descriptors map std::map &PoolDescriptors = FI.PoolDescriptors; Function::aiterator NI = New->abegin(); for (unsigned i = 0, e = FI.ArgNodes.size(); i != e; ++i, ++NI) { NI->setName("PDa"); // Add pd entry PoolDescriptors.insert(std::make_pair(FI.ArgNodes[i], NI)); } // Map the existing arguments of the old function to the corresponding // arguments of the new function. std::map ValueMap; for (Function::aiterator I = F.abegin(), E = F.aend(); I != E; ++I, ++NI) { ValueMap[I] = NI; NI->setName(I->getName()); } // Populate the value map with all of the globals in the program. // FIXME: This should be unneccesary! Module &M = *F.getParent(); for (Module::iterator I = M.begin(), E=M.end(); I!=E; ++I) ValueMap[I] = I; for (Module::giterator I = M.gbegin(), E=M.gend(); I!=E; ++I) ValueMap[I] = I; // Perform the cloning. std::vector Returns; CloneFunctionInto(New, &F, ValueMap, Returns); // Invert the ValueMap into the NewToOldValueMap std::map &NewToOldValueMap = FI.NewToOldValueMap; for (std::map::iterator I = ValueMap.begin(), E = ValueMap.end(); I != E; ++I) NewToOldValueMap.insert(std::make_pair(I->second, I->first)); return FI.Clone = New; } // processFunction - Pool allocate any data structures which are contained in // the specified function... // void PoolAllocate::ProcessFunctionBody(Function &F, Function &NewF) { DSGraph &G = BU->getDSGraph(F); std::vector &Nodes = G.getNodes(); if (Nodes.empty()) return; // Quick exit if nothing to do... FuncInfo &FI = FunctionInfo[&F]; // Get FuncInfo for F hash_set &MarkedNodes = FI.MarkedNodes; DEBUG(std::cerr << "[" << F.getName() << "] Pool Allocate: "); // Loop over all of the nodes which are non-escaping, adding pool-allocatable // ones to the NodesToPA vector. std::vector NodesToPA; for (unsigned i = 0, e = Nodes.size(); i != e; ++i) if (Nodes[i]->NodeType & DSNode::HeapNode && // Pick nodes with heap elems !(Nodes[i]->NodeType & DSNode::Array) && // Doesn't handle arrays yet. !MarkedNodes.count(Nodes[i])) // Can't be marked NodesToPA.push_back(Nodes[i]); DEBUG(std::cerr << NodesToPA.size() << " nodes to pool allocate\n"); if (!NodesToPA.empty()) { // Create pool construction/destruction code std::map &PoolDescriptors = FI.PoolDescriptors; CreatePools(NewF, NodesToPA, PoolDescriptors); } // Transform the body of the function now... TransformFunctionBody(NewF, G, FI); } // CreatePools - This creates the pool initialization and destruction code for // the DSNodes specified by the NodesToPA list. This adds an entry to the // PoolDescriptors map for each DSNode. // void PoolAllocate::CreatePools(Function &F, const std::vector &NodesToPA, std::map &PoolDescriptors) { // Find all of the return nodes in the CFG... std::vector ReturnNodes; for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I) if (isa(I->getTerminator())) ReturnNodes.push_back(I); TargetData &TD = getAnalysis(); // Loop over all of the pools, inserting code into the entry block of the // function for the initialization and code in the exit blocks for // destruction. // Instruction *InsertPoint = F.front().begin(); for (unsigned i = 0, e = NodesToPA.size(); i != e; ++i) { DSNode *Node = NodesToPA[i]; // Create a new alloca instruction for the pool... Value *AI = new AllocaInst(PoolDescType, 0, "PD", InsertPoint); Value *ElSize = ConstantUInt::get(Type::UIntTy, TD.getTypeSize(Node->getType())); // Insert the call to initialize the pool... new CallInst(PoolInit, make_vector(AI, ElSize, 0), "", InsertPoint); // Update the PoolDescriptors map PoolDescriptors.insert(std::make_pair(Node, AI)); // Insert a call to pool destroy before each return inst in the function for (unsigned r = 0, e = ReturnNodes.size(); r != e; ++r) new CallInst(PoolDestroy, make_vector(AI, 0), "", ReturnNodes[r]->getTerminator()); } } namespace { /// FuncTransform - This class implements transformation required of pool /// allocated functions. struct FuncTransform : public InstVisitor { PoolAllocate &PAInfo; DSGraph &G; FuncInfo &FI; FuncTransform(PoolAllocate &P, DSGraph &g, FuncInfo &fi) : PAInfo(P), G(g), FI(fi) {} void visitMallocInst(MallocInst &MI); void visitFreeInst(FreeInst &FI); void visitCallInst(CallInst &CI); private: DSNode *getDSNodeFor(Value *V) { if (!FI.NewToOldValueMap.empty()) { // If the NewToOldValueMap is in effect, use it. std::map::iterator I = FI.NewToOldValueMap.find(V); if (I != FI.NewToOldValueMap.end()) V = (Value*)I->second; } return G.getScalarMap()[V].getNode(); } Value *getPoolHandle(Value *V) { DSNode *Node = getDSNodeFor(V); // Get the pool handle for this DSNode... std::map::iterator I = FI.PoolDescriptors.find(Node); return I != FI.PoolDescriptors.end() ? I->second : 0; } }; } void PoolAllocate::TransformFunctionBody(Function &F, DSGraph &G, FuncInfo &FI){ FuncTransform(*this, G, FI).visit(F); } void FuncTransform::visitMallocInst(MallocInst &MI) { // Get the pool handle for the node that this contributes to... Value *PH = getPoolHandle(&MI); if (PH == 0) return; // Insert a call to poolalloc Value *V = new CallInst(PAInfo.PoolAlloc, make_vector(PH, 0), MI.getName(), &MI); MI.setName(""); // Nuke MIs name // Cast to the appropriate type... Value *Casted = new CastInst(V, MI.getType(), V->getName(), &MI); // Update def-use info MI.replaceAllUsesWith(Casted); // Remove old malloc instruction MI.getParent()->getInstList().erase(&MI); hash_map &SM = G.getScalarMap(); hash_map::iterator MII = SM.find(&MI); // If we are modifying the original function, update the DSGraph... if (MII != SM.end()) { // V and Casted now point to whatever the original malloc did... SM.insert(std::make_pair(V, MII->second)); SM.insert(std::make_pair(Casted, MII->second)); SM.erase(MII); // The malloc is now destroyed } else { // Otherwise, update the NewToOldValueMap std::map::iterator MII = FI.NewToOldValueMap.find(&MI); assert(MII != FI.NewToOldValueMap.end() && "MI not found in clone?"); FI.NewToOldValueMap.insert(std::make_pair(V, MII->second)); FI.NewToOldValueMap.insert(std::make_pair(Casted, MII->second)); FI.NewToOldValueMap.erase(MII); } } void FuncTransform::visitFreeInst(FreeInst &FI) { Value *Arg = FI.getOperand(0); Value *PH = getPoolHandle(Arg); // Get the pool handle for this DSNode... if (PH == 0) return; // Insert a cast and a call to poolfree... Value *Casted = new CastInst(Arg, PointerType::get(Type::SByteTy), Arg->getName()+".casted", &FI); new CallInst(PAInfo.PoolFree, make_vector(PH, Casted, 0), "", &FI); // Delete the now obsolete free instruction... FI.getParent()->getInstList().erase(&FI); } static void CalcNodeMapping(DSNode *Caller, DSNode *Callee, std::map &NodeMapping) { if (Callee == 0) return; assert(Caller && "Callee has node but caller doesn't??"); std::map::iterator I = NodeMapping.find(Callee); if (I != NodeMapping.end()) { // Node already in map... assert(I->second == Caller && "Node maps to different nodes on paths?"); } else { NodeMapping.insert(I, std::make_pair(Callee, Caller)); // Recursively add pointed to nodes... for (unsigned i = 0, e = Callee->getNumLinks(); i != e; ++i) CalcNodeMapping(Caller->getLink(i << DS::PointerShift).getNode(), Callee->getLink(i << DS::PointerShift).getNode(), NodeMapping); } } void FuncTransform::visitCallInst(CallInst &CI) { Function *CF = CI.getCalledFunction(); assert(CF && "FIXME: Pool allocation doesn't handle indirect calls!"); FuncInfo *CFI = PAInfo.getFuncInfo(*CF); if (CFI == 0 || CFI->Clone == 0) return; // Nothing to transform... DEBUG(std::cerr << " Handling call: " << CI); DSGraph &CG = PAInfo.getBUDataStructures().getDSGraph(*CF); // Callee graph // We need to figure out which local pool descriptors correspond to the pool // descriptor arguments passed into the function call. Calculate a mapping // from callee DSNodes to caller DSNodes. We construct a partial isomophism // between the graphs to figure out which pool descriptors need to be passed // in. The roots of this mapping is found from arguments and return values. // std::map NodeMapping; Function::aiterator AI = CF->abegin(), AE = CF->aend(); unsigned OpNum = 1; for (; AI != AE; ++AI, ++OpNum) CalcNodeMapping(getDSNodeFor(CI.getOperand(OpNum)), CG.getScalarMap()[AI].getNode(), NodeMapping); assert(OpNum == CI.getNumOperands() && "Varargs calls not handled yet!"); // Map the return value as well... CalcNodeMapping(getDSNodeFor(&CI), CG.getRetNode().getNode(), NodeMapping); // Okay, now that we have established our mapping, we can figure out which // pool descriptors to pass in... std::vector Args; // Add an argument for each pool which must be passed in... for (unsigned i = 0, e = CFI->ArgNodes.size(); i != e; ++i) { if (NodeMapping.count(CFI->ArgNodes[i])) { assert(NodeMapping.count(CFI->ArgNodes[i]) && "Node not in mapping!"); DSNode *LocalNode = NodeMapping.find(CFI->ArgNodes[i])->second; assert(FI.PoolDescriptors.count(LocalNode) && "Node not pool allocated?"); Args.push_back(FI.PoolDescriptors.find(LocalNode)->second); } else { Args.push_back(Constant::getNullValue(PoolDescPtr)); } } // Add the rest of the arguments... Args.insert(Args.end(), CI.op_begin()+1, CI.op_end()); std::string Name = CI.getName(); CI.setName(""); Value *NewCall = new CallInst(CFI->Clone, Args, Name, &CI); CI.replaceAllUsesWith(NewCall); DEBUG(std::cerr << " Result Call: " << *NewCall); CI.getParent()->getInstList().erase(&CI); }