blob: d8b1a862eabbe5d761a9fe1a39813e7c2b4d17d1 [file] [log] [blame]
Chris Lattner22ee3eb2002-05-24 20:42:13 +00001//===- FunctionResolution.cpp - Resolve declarations to implementations ---===//
Misha Brukmanfd939082005-04-21 23:48:37 +00002//
John Criswellb576c942003-10-20 19:43:21 +00003// The LLVM Compiler Infrastructure
4//
5// This file was developed by the LLVM research group and is distributed under
6// the University of Illinois Open Source License. See LICENSE.TXT for details.
Misha Brukmanfd939082005-04-21 23:48:37 +00007//
John Criswellb576c942003-10-20 19:43:21 +00008//===----------------------------------------------------------------------===//
Chris Lattner22ee3eb2002-05-24 20:42:13 +00009//
10// Loop over the functions that are in the module and look for functions that
11// have the same name. More often than not, there will be things like:
12//
13// declare void %foo(...)
14// void %foo(int, int) { ... }
15//
16// because of the way things are declared in C. If this is the case, patch
17// things up.
18//
19//===----------------------------------------------------------------------===//
20
Chris Lattnerff1be262002-07-23 22:04:02 +000021#include "llvm/Transforms/IPO.h"
Chris Lattner22ee3eb2002-05-24 20:42:13 +000022#include "llvm/Module.h"
Chris Lattner22ee3eb2002-05-24 20:42:13 +000023#include "llvm/DerivedTypes.h"
24#include "llvm/Pass.h"
Misha Brukman47b14a42004-07-29 17:30:56 +000025#include "llvm/Instructions.h"
Chris Lattnera45ec542002-10-09 21:10:06 +000026#include "llvm/Constants.h"
Chris Lattner03fb8b22003-11-20 21:21:31 +000027#include "llvm/Support/CallSite.h"
Chris Lattnerefd47ba2003-10-22 03:35:34 +000028#include "llvm/Target/TargetData.h"
Chris Lattner250d91b2003-08-13 22:15:04 +000029#include "llvm/Assembly/Writer.h"
Reid Spencer551ccae2004-09-01 22:55:40 +000030#include "llvm/ADT/Statistic.h"
Chris Lattner22ee3eb2002-05-24 20:42:13 +000031#include <algorithm>
Chris Lattnerdac58ad2006-01-22 23:32:06 +000032#include <iostream>
Chris Lattner03fb8b22003-11-20 21:21:31 +000033using namespace llvm;
Brian Gaeked0fde302003-11-11 22:41:34 +000034
Chris Lattner22ee3eb2002-05-24 20:42:13 +000035namespace {
Chris Lattnera92f6962002-10-01 22:38:41 +000036 Statistic<>NumResolved("funcresolve", "Number of varargs functions resolved");
Chris Lattnera45ec542002-10-09 21:10:06 +000037 Statistic<> NumGlobals("funcresolve", "Number of global variables resolved");
Chris Lattner22ee3eb2002-05-24 20:42:13 +000038
Chris Lattnerb12914b2004-09-20 04:48:05 +000039 struct FunctionResolvingPass : public ModulePass {
Chris Lattnerefd47ba2003-10-22 03:35:34 +000040 virtual void getAnalysisUsage(AnalysisUsage &AU) const {
41 AU.addRequired<TargetData>();
42 }
43
Chris Lattnerb12914b2004-09-20 04:48:05 +000044 bool runOnModule(Module &M);
Chris Lattner22ee3eb2002-05-24 20:42:13 +000045 };
Chris Lattner1e435162002-07-26 21:12:44 +000046 RegisterOpt<FunctionResolvingPass> X("funcresolve", "Resolve Functions");
Chris Lattner22ee3eb2002-05-24 20:42:13 +000047}
48
Chris Lattnerb12914b2004-09-20 04:48:05 +000049ModulePass *llvm::createFunctionResolvingPass() {
Chris Lattner22ee3eb2002-05-24 20:42:13 +000050 return new FunctionResolvingPass();
51}
52
Chris Lattnerda902ba2003-01-30 18:22:32 +000053static bool ResolveFunctions(Module &M, std::vector<GlobalValue*> &Globals,
Chris Lattnera45ec542002-10-09 21:10:06 +000054 Function *Concrete) {
55 bool Changed = false;
56 for (unsigned i = 0; i != Globals.size(); ++i)
57 if (Globals[i] != Concrete) {
58 Function *Old = cast<Function>(Globals[i]);
Reid Spencer2f189072005-12-13 19:56:51 +000059 const FunctionType *OldFT = Old->getFunctionType();
60 const FunctionType *ConcreteFT = Concrete->getFunctionType();
Misha Brukmanfd939082005-04-21 23:48:37 +000061
Reid Spencer2f189072005-12-13 19:56:51 +000062 if (OldFT->getNumParams() > ConcreteFT->getNumParams() &&
63 !ConcreteFT->isVarArg())
Chris Lattner4e2fd752003-02-27 20:55:48 +000064 if (!Old->use_empty()) {
65 std::cerr << "WARNING: Linking function '" << Old->getName()
66 << "' is causing arguments to be dropped.\n";
67 std::cerr << "WARNING: Prototype: ";
68 WriteAsOperand(std::cerr, Old);
69 std::cerr << " resolved to ";
70 WriteAsOperand(std::cerr, Concrete);
71 std::cerr << "\n";
72 }
Misha Brukmanfd939082005-04-21 23:48:37 +000073
Chris Lattnera45ec542002-10-09 21:10:06 +000074 // Check to make sure that if there are specified types, that they
75 // match...
76 //
Reid Spencer2f189072005-12-13 19:56:51 +000077 unsigned NumArguments = std::min(OldFT->getNumParams(),
78 ConcreteFT->getNumParams());
Chris Lattner4e2fd752003-02-27 20:55:48 +000079
Chris Lattnerdde601d2003-03-03 19:57:46 +000080 if (!Old->use_empty() && !Concrete->use_empty())
81 for (unsigned i = 0; i < NumArguments; ++i)
Reid Spencer2f189072005-12-13 19:56:51 +000082 if (OldFT->getParamType(i) != ConcreteFT->getParamType(i))
83 if (OldFT->getParamType(i)->getTypeID() !=
84 ConcreteFT->getParamType(i)->getTypeID()) {
Chris Lattnera0f85e52003-08-23 20:03:05 +000085 std::cerr << "WARNING: Function [" << Old->getName()
Chris Lattner143df9a2003-11-20 18:19:35 +000086 << "]: Parameter types conflict for: '";
Reid Spencer2f189072005-12-13 19:56:51 +000087 WriteTypeSymbolic(std::cerr, OldFT, &M);
88 std::cerr << "' (in "
89 << Old->getParent()->getModuleIdentifier() << ") and '";
90 WriteTypeSymbolic(std::cerr, ConcreteFT, &M);
91 std::cerr << "'(in "
92 << Concrete->getParent()->getModuleIdentifier() << ")\n";
Chris Lattnerb29170f2003-08-20 23:50:38 +000093 return Changed;
Chris Lattnera0f85e52003-08-23 20:03:05 +000094 }
Misha Brukmanfd939082005-04-21 23:48:37 +000095
Chris Lattner1fd95af2003-04-28 01:23:29 +000096 // Attempt to convert all of the uses of the old function to the concrete
97 // form of the function. If there is a use of the fn that we don't
98 // understand here we punt to avoid making a bad transformation.
Chris Lattnera45ec542002-10-09 21:10:06 +000099 //
Chris Lattner1fd95af2003-04-28 01:23:29 +0000100 // At this point, we know that the return values are the same for our two
101 // functions and that the Old function has no varargs fns specified. In
102 // otherwords it's just <retty> (...)
Chris Lattnera45ec542002-10-09 21:10:06 +0000103 //
Chris Lattnerd514d822005-02-01 01:23:31 +0000104 if (!Old->use_empty()) {
Chris Lattner1078d112003-07-23 22:03:18 +0000105 Value *Replacement = Concrete;
106 if (Concrete->getType() != Old->getType())
Chris Lattnerd514d822005-02-01 01:23:31 +0000107 Replacement = ConstantExpr::getCast(Concrete, Old->getType());
108 NumResolved += Old->getNumUses();
Chris Lattner1078d112003-07-23 22:03:18 +0000109 Old->replaceAllUsesWith(Replacement);
110 }
Chris Lattner12ce59d2003-05-31 21:08:45 +0000111
112 // Since there are no uses of Old anymore, remove it from the module.
113 M.getFunctionList().erase(Old);
Chris Lattnera45ec542002-10-09 21:10:06 +0000114 }
115 return Changed;
116}
117
118
Chris Lattnerda902ba2003-01-30 18:22:32 +0000119static bool ResolveGlobalVariables(Module &M,
120 std::vector<GlobalValue*> &Globals,
Chris Lattnera45ec542002-10-09 21:10:06 +0000121 GlobalVariable *Concrete) {
122 bool Changed = false;
Chris Lattnerea2294a2003-04-19 00:15:27 +0000123
Chris Lattnera45ec542002-10-09 21:10:06 +0000124 for (unsigned i = 0; i != Globals.size(); ++i)
125 if (Globals[i] != Concrete) {
Reid Spencer518310c2004-07-18 00:44:37 +0000126 Constant *Cast = ConstantExpr::getCast(Concrete, Globals[i]->getType());
Chris Lattner5858e1e2003-10-21 23:17:56 +0000127 Globals[i]->replaceAllUsesWith(Cast);
Chris Lattnerea2294a2003-04-19 00:15:27 +0000128
Chris Lattnera45ec542002-10-09 21:10:06 +0000129 // Since there are no uses of Old anymore, remove it from the module.
Chris Lattner5858e1e2003-10-21 23:17:56 +0000130 M.getGlobalList().erase(cast<GlobalVariable>(Globals[i]));
Chris Lattnera45ec542002-10-09 21:10:06 +0000131
132 ++NumGlobals;
133 Changed = true;
134 }
135 return Changed;
136}
137
Chris Lattner03fb8b22003-11-20 21:21:31 +0000138// Check to see if all of the callers of F ignore the return value.
139static bool CallersAllIgnoreReturnValue(Function &F) {
140 if (F.getReturnType() == Type::VoidTy) return true;
141 for (Value::use_iterator I = F.use_begin(), E = F.use_end(); I != E; ++I) {
Reid Spencer518310c2004-07-18 00:44:37 +0000142 if (GlobalValue *GV = dyn_cast<GlobalValue>(*I)) {
143 for (Value::use_iterator I = GV->use_begin(), E = GV->use_end();
Chris Lattner03fb8b22003-11-20 21:21:31 +0000144 I != E; ++I) {
145 CallSite CS = CallSite::get(*I);
146 if (!CS.getInstruction() || !CS.getInstruction()->use_empty())
147 return false;
148 }
149 } else {
150 CallSite CS = CallSite::get(*I);
151 if (!CS.getInstruction() || !CS.getInstruction()->use_empty())
152 return false;
153 }
154 }
155 return true;
156}
157
Chris Lattnerefd47ba2003-10-22 03:35:34 +0000158static bool ProcessGlobalsWithSameName(Module &M, TargetData &TD,
Chris Lattnerda902ba2003-01-30 18:22:32 +0000159 std::vector<GlobalValue*> &Globals) {
Chris Lattnera45ec542002-10-09 21:10:06 +0000160 assert(!Globals.empty() && "Globals list shouldn't be empty here!");
161
162 bool isFunction = isa<Function>(Globals[0]); // Is this group all functions?
Chris Lattnera45ec542002-10-09 21:10:06 +0000163 GlobalValue *Concrete = 0; // The most concrete implementation to resolve to
164
Chris Lattnera45ec542002-10-09 21:10:06 +0000165 for (unsigned i = 0; i != Globals.size(); ) {
166 if (isa<Function>(Globals[i]) != isFunction) {
167 std::cerr << "WARNING: Found function and global variable with the "
168 << "same name: '" << Globals[i]->getName() << "'.\n";
169 return false; // Don't know how to handle this, bail out!
170 }
171
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000172 if (isFunction) {
Chris Lattnera45ec542002-10-09 21:10:06 +0000173 // For functions, we look to merge functions definitions of "int (...)"
174 // to 'int (int)' or 'int ()' or whatever else is not completely generic.
175 //
176 Function *F = cast<Function>(Globals[i]);
Chris Lattner6f239632002-11-08 00:38:20 +0000177 if (!F->isExternal()) {
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000178 if (Concrete && !Concrete->isExternal())
Chris Lattnera45ec542002-10-09 21:10:06 +0000179 return false; // Found two different functions types. Can't choose!
Misha Brukmanfd939082005-04-21 23:48:37 +0000180
Chris Lattnera45ec542002-10-09 21:10:06 +0000181 Concrete = Globals[i];
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000182 } else if (Concrete) {
Chris Lattner03fb8b22003-11-20 21:21:31 +0000183 if (Concrete->isExternal()) // If we have multiple external symbols...
Misha Brukmanfd939082005-04-21 23:48:37 +0000184 if (F->getFunctionType()->getNumParams() >
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000185 cast<Function>(Concrete)->getFunctionType()->getNumParams())
186 Concrete = F; // We are more concrete than "Concrete"!
187
188 } else {
189 Concrete = F;
Chris Lattnera45ec542002-10-09 21:10:06 +0000190 }
Chris Lattnera45ec542002-10-09 21:10:06 +0000191 } else {
Chris Lattnera45ec542002-10-09 21:10:06 +0000192 GlobalVariable *GV = cast<GlobalVariable>(Globals[i]);
Chris Lattner5858e1e2003-10-21 23:17:56 +0000193 if (!GV->isExternal()) {
194 if (Concrete) {
195 std::cerr << "WARNING: Two global variables with external linkage"
196 << " exist with the same name: '" << GV->getName()
197 << "'!\n";
198 return false;
Chris Lattnera45ec542002-10-09 21:10:06 +0000199 }
Chris Lattner5858e1e2003-10-21 23:17:56 +0000200 Concrete = GV;
Chris Lattnera45ec542002-10-09 21:10:06 +0000201 }
Chris Lattnera45ec542002-10-09 21:10:06 +0000202 }
Chris Lattnerea2294a2003-04-19 00:15:27 +0000203 ++i;
Chris Lattnera45ec542002-10-09 21:10:06 +0000204 }
205
206 if (Globals.size() > 1) { // Found a multiply defined global...
Chris Lattnerc16e6312003-05-31 21:57:06 +0000207 // If there are no external declarations, and there is at most one
208 // externally visible instance of the global, then there is nothing to do.
209 //
210 bool HasExternal = false;
211 unsigned NumInstancesWithExternalLinkage = 0;
212
213 for (unsigned i = 0, e = Globals.size(); i != e; ++i) {
214 if (Globals[i]->isExternal())
215 HasExternal = true;
216 else if (!Globals[i]->hasInternalLinkage())
217 NumInstancesWithExternalLinkage++;
218 }
Misha Brukmanfd939082005-04-21 23:48:37 +0000219
Chris Lattnerc16e6312003-05-31 21:57:06 +0000220 if (!HasExternal && NumInstancesWithExternalLinkage <= 1)
221 return false; // Nothing to do? Must have multiple internal definitions.
222
Chris Lattnerce94319552003-10-22 23:03:38 +0000223 // There are a couple of special cases we don't want to print the warning
224 // for, check them now.
225 bool DontPrintWarning = false;
226 if (Concrete && Globals.size() == 2) {
227 GlobalValue *Other = Globals[Globals[0] == Concrete];
228 // If the non-concrete global is a function which takes (...) arguments,
Chris Lattner03fb8b22003-11-20 21:21:31 +0000229 // and the return values match (or was never used), do not warn.
Chris Lattnerce94319552003-10-22 23:03:38 +0000230 if (Function *ConcreteF = dyn_cast<Function>(Concrete))
231 if (Function *OtherF = dyn_cast<Function>(Other))
Chris Lattner03fb8b22003-11-20 21:21:31 +0000232 if ((ConcreteF->getReturnType() == OtherF->getReturnType() ||
233 CallersAllIgnoreReturnValue(*OtherF)) &&
Chris Lattnerce94319552003-10-22 23:03:38 +0000234 OtherF->getFunctionType()->isVarArg() &&
Chris Lattnerd5d89962004-02-09 04:14:01 +0000235 OtherF->getFunctionType()->getNumParams() == 0)
Chris Lattnerce94319552003-10-22 23:03:38 +0000236 DontPrintWarning = true;
Misha Brukmanfd939082005-04-21 23:48:37 +0000237
Chris Lattnerce94319552003-10-22 23:03:38 +0000238 // Otherwise, if the non-concrete global is a global array variable with a
239 // size of 0, and the concrete global is an array with a real size, don't
240 // warn. This occurs due to declaring 'extern int A[];'.
241 if (GlobalVariable *ConcreteGV = dyn_cast<GlobalVariable>(Concrete))
Chris Lattner4e4c4442004-08-20 00:30:39 +0000242 if (GlobalVariable *OtherGV = dyn_cast<GlobalVariable>(Other)) {
243 const Type *CTy = ConcreteGV->getType();
244 const Type *OTy = OtherGV->getType();
245
246 if (CTy->isSized())
247 if (!OTy->isSized() || !TD.getTypeSize(OTy) ||
248 TD.getTypeSize(OTy) == TD.getTypeSize(CTy))
249 DontPrintWarning = true;
250 }
Chris Lattnerce94319552003-10-22 23:03:38 +0000251 }
Chris Lattnerc16e6312003-05-31 21:57:06 +0000252
Chris Lattner23367a72004-09-30 00:12:29 +0000253 if (0 && !DontPrintWarning) {
Chris Lattnerce94319552003-10-22 23:03:38 +0000254 std::cerr << "WARNING: Found global types that are not compatible:\n";
255 for (unsigned i = 0; i < Globals.size(); ++i) {
Chris Lattner143df9a2003-11-20 18:19:35 +0000256 std::cerr << "\t";
257 WriteTypeSymbolic(std::cerr, Globals[i]->getType(), &M);
258 std::cerr << " %" << Globals[i]->getName() << "\n";
Chris Lattnerce94319552003-10-22 23:03:38 +0000259 }
Chris Lattnera45ec542002-10-09 21:10:06 +0000260 }
261
Chris Lattner5858e1e2003-10-21 23:17:56 +0000262 if (!Concrete)
263 Concrete = Globals[0];
Chris Lattnerefd47ba2003-10-22 03:35:34 +0000264 else if (GlobalVariable *GV = dyn_cast<GlobalVariable>(Concrete)) {
265 // Handle special case hack to change globals if it will make their types
266 // happier in the long run. The situation we do this is intentionally
267 // extremely limited.
268 if (GV->use_empty() && GV->hasInitializer() &&
269 GV->getInitializer()->isNullValue()) {
270 // Check to see if there is another (external) global with the same size
271 // and a non-empty use-list. If so, we will make IT be the real
272 // implementation.
273 unsigned TS = TD.getTypeSize(Concrete->getType()->getElementType());
274 for (unsigned i = 0, e = Globals.size(); i != e; ++i)
275 if (Globals[i] != Concrete && !Globals[i]->use_empty() &&
276 isa<GlobalVariable>(Globals[i]) &&
277 TD.getTypeSize(Globals[i]->getType()->getElementType()) == TS) {
278 // At this point we want to replace Concrete with Globals[i]. Make
279 // concrete external, and Globals[i] have an initializer.
280 GlobalVariable *NGV = cast<GlobalVariable>(Globals[i]);
281 const Type *ElTy = NGV->getType()->getElementType();
282 NGV->setInitializer(Constant::getNullValue(ElTy));
283 cast<GlobalVariable>(Concrete)->setInitializer(0);
284 Concrete = NGV;
285 break;
286 }
287 }
288 }
Chris Lattner5858e1e2003-10-21 23:17:56 +0000289
Chris Lattnera45ec542002-10-09 21:10:06 +0000290 if (isFunction)
Chris Lattnerea2294a2003-04-19 00:15:27 +0000291 return ResolveFunctions(M, Globals, cast<Function>(Concrete));
Chris Lattnera45ec542002-10-09 21:10:06 +0000292 else
Chris Lattnerea2294a2003-04-19 00:15:27 +0000293 return ResolveGlobalVariables(M, Globals,
294 cast<GlobalVariable>(Concrete));
Chris Lattnera45ec542002-10-09 21:10:06 +0000295 }
Chris Lattnerea2294a2003-04-19 00:15:27 +0000296 return false;
Chris Lattnera45ec542002-10-09 21:10:06 +0000297}
298
Chris Lattnerb12914b2004-09-20 04:48:05 +0000299bool FunctionResolvingPass::runOnModule(Module &M) {
Chris Lattnerda902ba2003-01-30 18:22:32 +0000300 std::map<std::string, std::vector<GlobalValue*> > Globals;
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000301
Chris Lattner4cb766a2003-10-22 04:42:20 +0000302 // Loop over the globals, adding them to the Globals map. We use a two pass
303 // algorithm here to avoid problems with iterators getting invalidated if we
304 // did a one pass scheme.
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000305 //
Chris Lattner6be5e562003-10-22 04:43:18 +0000306 bool Changed = false;
Chris Lattner4cb766a2003-10-22 04:42:20 +0000307 for (Module::iterator I = M.begin(), E = M.end(); I != E; ) {
308 Function *F = I++;
Chris Lattner6be5e562003-10-22 04:43:18 +0000309 if (F->use_empty() && F->isExternal()) {
Chris Lattner4cb766a2003-10-22 04:42:20 +0000310 M.getFunctionList().erase(F);
Chris Lattner6be5e562003-10-22 04:43:18 +0000311 Changed = true;
Chris Lattnere5ad50b2004-06-18 05:50:48 +0000312 } else if (!F->hasInternalLinkage() && !F->getName().empty() &&
313 !F->getIntrinsicID())
Chris Lattner4cb766a2003-10-22 04:42:20 +0000314 Globals[F->getName()].push_back(F);
315 }
316
Chris Lattnere4d5c442005-03-15 04:54:21 +0000317 for (Module::global_iterator I = M.global_begin(), E = M.global_end(); I != E; ) {
Chris Lattner4cb766a2003-10-22 04:42:20 +0000318 GlobalVariable *GV = I++;
Chris Lattner6be5e562003-10-22 04:43:18 +0000319 if (GV->use_empty() && GV->isExternal()) {
Chris Lattner4cb766a2003-10-22 04:42:20 +0000320 M.getGlobalList().erase(GV);
Chris Lattner6be5e562003-10-22 04:43:18 +0000321 Changed = true;
322 } else if (!GV->hasInternalLinkage() && !GV->getName().empty())
Chris Lattner4cb766a2003-10-22 04:42:20 +0000323 Globals[GV->getName()].push_back(GV);
324 }
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000325
Chris Lattnerefd47ba2003-10-22 03:35:34 +0000326 TargetData &TD = getAnalysis<TargetData>();
327
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000328 // Now we have a list of all functions with a particular name. If there is
329 // more than one entry in a list, merge the functions together.
330 //
Chris Lattnerda902ba2003-01-30 18:22:32 +0000331 for (std::map<std::string, std::vector<GlobalValue*> >::iterator
332 I = Globals.begin(), E = Globals.end(); I != E; ++I)
Chris Lattnerefd47ba2003-10-22 03:35:34 +0000333 Changed |= ProcessGlobalsWithSameName(M, TD, I->second);
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000334
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000335 // Now loop over all of the globals, checking to see if any are trivially
336 // dead. If so, remove them now.
337
338 for (Module::iterator I = M.begin(), E = M.end(); I != E; )
339 if (I->isExternal() && I->use_empty()) {
340 Function *F = I;
341 ++I;
342 M.getFunctionList().erase(F);
343 ++NumResolved;
344 Changed = true;
345 } else {
346 ++I;
347 }
348
Chris Lattnere4d5c442005-03-15 04:54:21 +0000349 for (Module::global_iterator I = M.global_begin(), E = M.global_end(); I != E; )
Chris Lattnera2b8d7b2002-11-10 03:36:55 +0000350 if (I->isExternal() && I->use_empty()) {
351 GlobalVariable *GV = I;
352 ++I;
353 M.getGlobalList().erase(GV);
354 ++NumGlobals;
355 Changed = true;
356 } else {
357 ++I;
358 }
359
Chris Lattner22ee3eb2002-05-24 20:42:13 +0000360 return Changed;
361}