Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 1 | //===- FunctionRepBuilder.cpp - Build the local datastructure graph -------===// |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 2 | // |
| 3 | // Build the local datastructure graph for a single method. |
| 4 | // |
| 5 | //===----------------------------------------------------------------------===// |
| 6 | |
| 7 | #include "FunctionRepBuilder.h" |
| 8 | #include "llvm/Function.h" |
Chris Lattner | 42a4127 | 2002-04-09 18:37:46 +0000 | [diff] [blame] | 9 | #include "llvm/BasicBlock.h" |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 10 | #include "llvm/iMemory.h" |
| 11 | #include "llvm/iPHINode.h" |
| 12 | #include "llvm/iOther.h" |
| 13 | #include "llvm/iTerminators.h" |
| 14 | #include "llvm/DerivedTypes.h" |
Chris Lattner | 31bcdb8 | 2002-04-28 19:55:58 +0000 | [diff] [blame^] | 15 | #include "llvm/Constants.h" |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 16 | #include "Support/STLExtras.h" |
| 17 | #include <algorithm> |
| 18 | |
| 19 | // synthesizeNode - Create a new shadow node that is to be linked into this |
| 20 | // chain.. |
| 21 | // FIXME: This should not take a FunctionRepBuilder as an argument! |
| 22 | // |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 23 | ShadowDSNode *DSNode::synthesizeNode(const Type *Ty, |
| 24 | FunctionRepBuilder *Rep) { |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 25 | // If we are a derived shadow node, defer to our parent to synthesize the node |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 26 | if (ShadowDSNode *Th = dyn_cast<ShadowDSNode>(this)) |
| 27 | if (Th->getShadowParent()) |
| 28 | return Th->getShadowParent()->synthesizeNode(Ty, Rep); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 29 | |
| 30 | // See if we have already synthesized a node of this type... |
| 31 | for (unsigned i = 0, e = SynthNodes.size(); i != e; ++i) |
| 32 | if (SynthNodes[i].first == Ty) return SynthNodes[i].second; |
| 33 | |
| 34 | // No we haven't. Do so now and add it to our list of saved nodes... |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 35 | ShadowDSNode *SN = Rep->makeSynthesizedShadow(Ty, this); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 36 | SynthNodes.push_back(make_pair(Ty, SN)); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 37 | return SN; |
| 38 | } |
| 39 | |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 40 | ShadowDSNode *FunctionRepBuilder::makeSynthesizedShadow(const Type *Ty, |
| 41 | DSNode *Parent) { |
| 42 | ShadowDSNode *Result = new ShadowDSNode(Ty, F->getFunction()->getParent(), |
| 43 | Parent); |
| 44 | ShadowNodes.push_back(Result); |
| 45 | return Result; |
| 46 | } |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 47 | |
| 48 | |
| 49 | |
| 50 | // visitOperand - If the specified instruction operand is a global value, add |
| 51 | // a node for it... |
| 52 | // |
| 53 | void InitVisitor::visitOperand(Value *V) { |
| 54 | if (!Rep->ValueMap.count(V)) // Only process it once... |
| 55 | if (GlobalValue *GV = dyn_cast<GlobalValue>(V)) { |
| 56 | GlobalDSNode *N = new GlobalDSNode(GV); |
Chris Lattner | 1120c8b | 2002-03-28 17:56:03 +0000 | [diff] [blame] | 57 | Rep->GlobalNodes.push_back(N); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 58 | Rep->ValueMap[V].add(N); |
| 59 | Rep->addAllUsesToWorkList(GV); |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 60 | |
| 61 | // FIXME: If the global variable has fields, we should add critical |
| 62 | // shadow nodes to represent them! |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 63 | } |
| 64 | } |
| 65 | |
| 66 | |
| 67 | // visitCallInst - Create a call node for the callinst, and create as shadow |
| 68 | // node if the call returns a pointer value. Check to see if the call node |
| 69 | // uses any global variables... |
| 70 | // |
| 71 | void InitVisitor::visitCallInst(CallInst *CI) { |
| 72 | CallDSNode *C = new CallDSNode(CI); |
Chris Lattner | 1120c8b | 2002-03-28 17:56:03 +0000 | [diff] [blame] | 73 | Rep->CallNodes.push_back(C); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 74 | Rep->CallMap[CI] = C; |
| 75 | |
Chris Lattner | 3feaf02 | 2002-04-01 00:14:41 +0000 | [diff] [blame] | 76 | if (PointerType *PT = dyn_cast<PointerType>(CI->getType())) { |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 77 | // Create a critical shadow node to represent the memory object that the |
| 78 | // return value points to... |
Chris Lattner | 3feaf02 | 2002-04-01 00:14:41 +0000 | [diff] [blame] | 79 | ShadowDSNode *Shad = new ShadowDSNode(PT->getElementType(), |
Chris Lattner | 7650b94 | 2002-04-16 20:39:59 +0000 | [diff] [blame] | 80 | Func->getParent()); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 81 | Rep->ShadowNodes.push_back(Shad); |
| 82 | |
| 83 | // The return value of the function is a pointer to the shadow value |
| 84 | // just created... |
| 85 | // |
| 86 | C->getLink(0).add(Shad); |
| 87 | |
| 88 | // The call instruction returns a pointer to the shadow block... |
| 89 | Rep->ValueMap[CI].add(Shad, CI); |
| 90 | |
| 91 | // If the call returns a value with pointer type, add all of the users |
| 92 | // of the call instruction to the work list... |
| 93 | Rep->addAllUsesToWorkList(CI); |
| 94 | } |
| 95 | |
| 96 | // Loop over all of the operands of the call instruction (except the first |
| 97 | // one), to look for global variable references... |
| 98 | // |
Chris Lattner | 7650b94 | 2002-04-16 20:39:59 +0000 | [diff] [blame] | 99 | for_each(CI->op_begin(), CI->op_end(), |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 100 | bind_obj(this, &InitVisitor::visitOperand)); |
| 101 | } |
| 102 | |
| 103 | |
| 104 | // visitAllocationInst - Create an allocation node for the allocation. Since |
| 105 | // allocation instructions do not take pointer arguments, they cannot refer to |
| 106 | // global vars... |
| 107 | // |
| 108 | void InitVisitor::visitAllocationInst(AllocationInst *AI) { |
Chris Lattner | 1120c8b | 2002-03-28 17:56:03 +0000 | [diff] [blame] | 109 | AllocDSNode *N = new AllocDSNode(AI); |
| 110 | Rep->AllocNodes.push_back(N); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 111 | |
| 112 | Rep->ValueMap[AI].add(N, AI); |
| 113 | |
| 114 | // Add all of the users of the malloc instruction to the work list... |
| 115 | Rep->addAllUsesToWorkList(AI); |
| 116 | } |
| 117 | |
| 118 | |
| 119 | // Visit all other instruction types. Here we just scan, looking for uses of |
| 120 | // global variables... |
| 121 | // |
| 122 | void InitVisitor::visitInstruction(Instruction *I) { |
| 123 | for_each(I->op_begin(), I->op_end(), |
| 124 | bind_obj(this, &InitVisitor::visitOperand)); |
| 125 | } |
| 126 | |
| 127 | |
| 128 | // addAllUsesToWorkList - Add all of the instructions users of the specified |
| 129 | // value to the work list for further processing... |
| 130 | // |
| 131 | void FunctionRepBuilder::addAllUsesToWorkList(Value *V) { |
| 132 | //cerr << "Adding all uses of " << V << "\n"; |
| 133 | for (Value::use_iterator I = V->use_begin(), E = V->use_end(); I != E; ++I) { |
| 134 | Instruction *Inst = cast<Instruction>(*I); |
| 135 | // When processing global values, it's possible that the instructions on |
| 136 | // the use list are not all in this method. Only add the instructions |
| 137 | // that _are_ in this method. |
| 138 | // |
| 139 | if (Inst->getParent()->getParent() == F->getFunction()) |
| 140 | // Only let an instruction occur on the work list once... |
| 141 | if (std::find(WorkList.begin(), WorkList.end(), Inst) == WorkList.end()) |
| 142 | WorkList.push_back(Inst); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | |
| 147 | |
| 148 | |
| 149 | void FunctionRepBuilder::initializeWorkList(Function *Func) { |
| 150 | // Add all of the arguments to the method to the graph and add all users to |
| 151 | // the worklists... |
| 152 | // |
| 153 | for (Function::ArgumentListType::iterator I = Func->getArgumentList().begin(), |
Chris Lattner | 73e2142 | 2002-04-09 19:48:49 +0000 | [diff] [blame] | 154 | E = Func->getArgumentList().end(); I != E; ++I) { |
| 155 | Value *Arg = (Value*)(*I); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 156 | // Only process arguments that are of pointer type... |
Chris Lattner | 73e2142 | 2002-04-09 19:48:49 +0000 | [diff] [blame] | 157 | if (PointerType *PT = dyn_cast<PointerType>(Arg->getType())) { |
Chris Lattner | 212be2e | 2002-04-16 03:44:03 +0000 | [diff] [blame] | 158 | // Add a shadow value for it to represent what it is pointing to and add |
| 159 | // this to the value map... |
Chris Lattner | 3feaf02 | 2002-04-01 00:14:41 +0000 | [diff] [blame] | 160 | ShadowDSNode *Shad = new ShadowDSNode(PT->getElementType(), |
Chris Lattner | 7650b94 | 2002-04-16 20:39:59 +0000 | [diff] [blame] | 161 | Func->getParent()); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 162 | ShadowNodes.push_back(Shad); |
Chris Lattner | 73e2142 | 2002-04-09 19:48:49 +0000 | [diff] [blame] | 163 | ValueMap[Arg].add(PointerVal(Shad), Arg); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 164 | |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 165 | // Make sure that all users of the argument are processed... |
Chris Lattner | 73e2142 | 2002-04-09 19:48:49 +0000 | [diff] [blame] | 166 | addAllUsesToWorkList(Arg); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 167 | } |
Chris Lattner | 73e2142 | 2002-04-09 19:48:49 +0000 | [diff] [blame] | 168 | } |
| 169 | |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 170 | // Iterate over the instructions in the method. Create nodes for malloc and |
| 171 | // call instructions. Add all uses of these to the worklist of instructions |
| 172 | // to process. |
| 173 | // |
| 174 | InitVisitor IV(this, Func); |
| 175 | IV.visit(Func); |
| 176 | } |
| 177 | |
| 178 | |
| 179 | |
| 180 | |
| 181 | PointerVal FunctionRepBuilder::getIndexedPointerDest(const PointerVal &InP, |
| 182 | const MemAccessInst *MAI) { |
| 183 | unsigned Index = InP.Index; |
| 184 | const Type *SrcTy = MAI->getPointerOperand()->getType(); |
| 185 | |
| 186 | for (MemAccessInst::const_op_iterator I = MAI->idx_begin(), |
| 187 | E = MAI->idx_end(); I != E; ++I) |
| 188 | if ((*I)->getType() == Type::UByteTy) { // Look for struct indices... |
| 189 | StructType *STy = cast<StructType>(SrcTy); |
| 190 | unsigned StructIdx = cast<ConstantUInt>(*I)->getValue(); |
| 191 | for (unsigned i = 0; i != StructIdx; ++i) |
| 192 | Index += countPointerFields(STy->getContainedType(i)); |
| 193 | |
| 194 | // Advance SrcTy to be the new element type... |
| 195 | SrcTy = STy->getContainedType(StructIdx); |
| 196 | } else { |
| 197 | // Otherwise, stepping into array or initial pointer, just increment type |
| 198 | SrcTy = cast<SequentialType>(SrcTy)->getElementType(); |
| 199 | } |
| 200 | |
| 201 | return PointerVal(InP.Node, Index); |
| 202 | } |
| 203 | |
| 204 | static PointerValSet &getField(const PointerVal &DestPtr) { |
| 205 | assert(DestPtr.Node != 0); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 206 | return DestPtr.Node->getLink(DestPtr.Index); |
| 207 | } |
| 208 | |
| 209 | |
| 210 | // Reprocessing a GEP instruction is the result of the pointer operand |
| 211 | // changing. This means that the set of possible values for the GEP |
| 212 | // needs to be expanded. |
| 213 | // |
| 214 | void FunctionRepBuilder::visitGetElementPtrInst(GetElementPtrInst *GEP) { |
| 215 | PointerValSet &GEPPVS = ValueMap[GEP]; // PointerValSet to expand |
| 216 | |
| 217 | // Get the input pointer val set... |
| 218 | const PointerValSet &SrcPVS = ValueMap[GEP->getOperand(0)]; |
| 219 | |
| 220 | bool Changed = false; // Process each input value... propogating it. |
| 221 | for (unsigned i = 0, e = SrcPVS.size(); i != e; ++i) { |
| 222 | // Calculate where the resulting pointer would point based on an |
| 223 | // input of 'Val' as the pointer type... and add it to our outgoing |
| 224 | // value set. Keep track of whether or not we actually changed |
| 225 | // anything. |
| 226 | // |
| 227 | Changed |= GEPPVS.add(getIndexedPointerDest(SrcPVS[i], GEP)); |
| 228 | } |
| 229 | |
| 230 | // If our current value set changed, notify all of the users of our |
| 231 | // value. |
| 232 | // |
| 233 | if (Changed) addAllUsesToWorkList(GEP); |
| 234 | } |
| 235 | |
| 236 | void FunctionRepBuilder::visitReturnInst(ReturnInst *RI) { |
| 237 | RetNode.add(ValueMap[RI->getOperand(0)]); |
| 238 | } |
| 239 | |
| 240 | void FunctionRepBuilder::visitLoadInst(LoadInst *LI) { |
| 241 | // Only loads that return pointers are interesting... |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 242 | const PointerType *DestTy = dyn_cast<PointerType>(LI->getType()); |
| 243 | if (DestTy == 0) return; |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 244 | |
| 245 | const PointerValSet &SrcPVS = ValueMap[LI->getOperand(0)]; |
| 246 | PointerValSet &LIPVS = ValueMap[LI]; |
| 247 | |
| 248 | bool Changed = false; |
| 249 | for (unsigned si = 0, se = SrcPVS.size(); si != se; ++si) { |
| 250 | PointerVal Ptr = getIndexedPointerDest(SrcPVS[si], LI); |
| 251 | PointerValSet &Field = getField(Ptr); |
| 252 | |
| 253 | if (Field.size()) { // Field loaded wasn't null? |
| 254 | Changed |= LIPVS.add(Field); |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 255 | } else { |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 256 | // If we are loading a null field out of a shadow node, we need to |
| 257 | // synthesize a new shadow node and link it in... |
| 258 | // |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 259 | ShadowDSNode *SynthNode = |
Chris Lattner | fe14568 | 2002-04-17 03:24:59 +0000 | [diff] [blame] | 260 | Ptr.Node->synthesizeNode(DestTy->getElementType(), this); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 261 | Field.add(SynthNode); |
| 262 | |
| 263 | Changed |= LIPVS.add(Field); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | if (Changed) addAllUsesToWorkList(LI); |
| 268 | } |
| 269 | |
| 270 | void FunctionRepBuilder::visitStoreInst(StoreInst *SI) { |
| 271 | // The only stores that are interesting are stores the store pointers |
| 272 | // into data structures... |
| 273 | // |
| 274 | if (!isa<PointerType>(SI->getOperand(0)->getType())) return; |
Chris Lattner | e0d1d1a | 2002-04-01 00:45:09 +0000 | [diff] [blame] | 275 | if (!ValueMap.count(SI->getOperand(0))) return; // Src scalar has no values! |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 276 | |
| 277 | const PointerValSet &SrcPVS = ValueMap[SI->getOperand(0)]; |
| 278 | const PointerValSet &PtrPVS = ValueMap[SI->getOperand(1)]; |
| 279 | |
| 280 | for (unsigned si = 0, se = SrcPVS.size(); si != se; ++si) { |
| 281 | const PointerVal &SrcPtr = SrcPVS[si]; |
| 282 | for (unsigned pi = 0, pe = PtrPVS.size(); pi != pe; ++pi) { |
| 283 | PointerVal Dest = getIndexedPointerDest(PtrPVS[pi], SI); |
| 284 | |
| 285 | #if 0 |
| 286 | cerr << "Setting Dest:\n"; |
| 287 | Dest.print(cerr); |
| 288 | cerr << "to point to Src:\n"; |
| 289 | SrcPtr.print(cerr); |
| 290 | #endif |
| 291 | |
| 292 | // Add SrcPtr into the Dest field... |
| 293 | if (getField(Dest).add(SrcPtr)) { |
| 294 | // If we modified the dest field, then invalidate everyone that points |
| 295 | // to Dest. |
| 296 | const std::vector<Value*> &Ptrs = Dest.Node->getPointers(); |
| 297 | for (unsigned i = 0, e = Ptrs.size(); i != e; ++i) |
| 298 | addAllUsesToWorkList(Ptrs[i]); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | void FunctionRepBuilder::visitCallInst(CallInst *CI) { |
| 305 | CallDSNode *DSN = CallMap[CI]; |
Chris Lattner | 7650b94 | 2002-04-16 20:39:59 +0000 | [diff] [blame] | 306 | unsigned PtrNum = 0; |
| 307 | for (unsigned i = 0, e = CI->getNumOperands(); i != e; ++i) |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 308 | if (isa<PointerType>(CI->getOperand(i)->getType())) |
| 309 | DSN->addArgValue(PtrNum++, ValueMap[CI->getOperand(i)]); |
| 310 | } |
| 311 | |
| 312 | void FunctionRepBuilder::visitPHINode(PHINode *PN) { |
| 313 | assert(isa<PointerType>(PN->getType()) && "Should only update ptr phis"); |
| 314 | |
| 315 | PointerValSet &PN_PVS = ValueMap[PN]; |
| 316 | bool Changed = false; |
| 317 | for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) |
| 318 | Changed |= PN_PVS.add(ValueMap[PN->getIncomingValue(i)], |
| 319 | PN->getIncomingValue(i)); |
| 320 | |
| 321 | if (Changed) addAllUsesToWorkList(PN); |
| 322 | } |
| 323 | |
| 324 | |
| 325 | |
| 326 | |
| 327 | // FunctionDSGraph constructor - Perform the global analysis to determine |
| 328 | // what the data structure usage behavior or a method looks like. |
| 329 | // |
| 330 | FunctionDSGraph::FunctionDSGraph(Function *F) : Func(F) { |
| 331 | FunctionRepBuilder Builder(this); |
Chris Lattner | 1120c8b | 2002-03-28 17:56:03 +0000 | [diff] [blame] | 332 | AllocNodes = Builder.getAllocNodes(); |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 333 | ShadowNodes = Builder.getShadowNodes(); |
Chris Lattner | 1120c8b | 2002-03-28 17:56:03 +0000 | [diff] [blame] | 334 | GlobalNodes = Builder.getGlobalNodes(); |
| 335 | CallNodes = Builder.getCallNodes(); |
| 336 | RetNode = Builder.getRetNode(); |
| 337 | ValueMap = Builder.getValueMap(); |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 338 | |
Chris Lattner | 7650b94 | 2002-04-16 20:39:59 +0000 | [diff] [blame] | 339 | // Remove all entries in the value map that consist of global values pointing |
| 340 | // at things. They can only point to their node, so there is no use keeping |
| 341 | // them. |
| 342 | // |
| 343 | for (map<Value*, PointerValSet>::iterator I = ValueMap.begin(), |
| 344 | E = ValueMap.end(); I != E;) |
| 345 | if (isa<GlobalValue>(I->first)) { |
| 346 | #if MAP_DOESNT_HAVE_BROKEN_ERASE_MEMBER |
| 347 | I = ValueMap.erase(I); |
| 348 | #else |
| 349 | ValueMap.erase(I); // This is really lame. |
| 350 | I = ValueMap.begin(); // GCC's stdc++ lib doesn't return an it! |
| 351 | #endif |
| 352 | } else |
| 353 | ++I; |
| 354 | |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 355 | bool Changed = true; |
| 356 | while (Changed) { |
| 357 | // Eliminate shadow nodes that are not distinguishable from some other |
| 358 | // node in the graph... |
| 359 | // |
Chris Lattner | 7d093d4 | 2002-03-28 19:16:48 +0000 | [diff] [blame] | 360 | Changed = UnlinkUndistinguishableNodes(); |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 361 | |
| 362 | // Eliminate shadow nodes that are now extraneous due to linking... |
Chris Lattner | 7d093d4 | 2002-03-28 19:16:48 +0000 | [diff] [blame] | 363 | Changed |= RemoveUnreachableNodes(); |
Chris Lattner | f4066b3 | 2002-03-27 19:45:12 +0000 | [diff] [blame] | 364 | } |
Chris Lattner | bb2a28f | 2002-03-26 22:39:06 +0000 | [diff] [blame] | 365 | } |