blob: 1d06c26af8485aaca8dbafc2126bb10580f3bf7f [file] [log] [blame]
Ted Kremenekaa04c512007-09-06 00:17:54 +00001//==- LiveVariables.cpp - Live Variable Analysis for Source CFGs -*- C++ --*-==//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file was developed by Ted Kremenek and is distributed under
6// the University of Illinois Open Source License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements Live Variables analysis for source-level CFGs.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Analysis/LiveVariables.h"
Ted Kremenek05334682007-09-06 21:26:58 +000015#include "clang/Basic/SourceManager.h"
Ted Kremenekaa04c512007-09-06 00:17:54 +000016#include "clang/AST/Expr.h"
17#include "clang/AST/CFG.h"
18#include "clang/AST/StmtVisitor.h"
19#include "clang/Lex/IdentifierTable.h"
20#include "llvm/ADT/SmallPtrSet.h"
21
Ted Kremenek05334682007-09-06 21:26:58 +000022#include <string.h>
23#include <stdio.h>
Ted Kremenekaa04c512007-09-06 00:17:54 +000024
25using namespace clang;
26
27//===----------------------------------------------------------------------===//
28// RegisterDecls - Utility class to create VarInfo objects for all
29// Decls referenced in a function.
30//
31
32namespace {
33
34class RegisterDecls : public StmtVisitor<RegisterDecls,void> {
35 LiveVariables& L;
36 const CFG& cfg;
37public:
38 RegisterDecls(LiveVariables& l, const CFG& c)
39 : L(l), cfg(c) {}
40
41 void VisitStmt(Stmt* S);
42 void VisitDeclRefExpr(DeclRefExpr* DR);
Ted Kremenekf63bda52007-09-06 23:25:10 +000043 void VisitDeclStmt(DeclStmt* DS);
Ted Kremenekaa04c512007-09-06 00:17:54 +000044 void Register(Decl* D);
Ted Kremenekf63bda52007-09-06 23:25:10 +000045 void RegisterDeclChain(Decl* D);
Ted Kremenekaa04c512007-09-06 00:17:54 +000046 void RegisterUsedDecls();
47};
48
49void RegisterDecls::VisitStmt(Stmt* S) {
50 for (Stmt::child_iterator I = S->child_begin(),E = S->child_end(); I != E;++I)
51 Visit(*I);
52}
53
54void RegisterDecls::VisitDeclRefExpr(DeclRefExpr* DR) {
Ted Kremenekf63bda52007-09-06 23:25:10 +000055 RegisterDeclChain(DR->getDecl());
56}
57
58void RegisterDecls::VisitDeclStmt(DeclStmt* DS) {
59 RegisterDeclChain(DS->getDecl());
60}
61
62void RegisterDecls::RegisterDeclChain(Decl* D) {
63 for (; D != NULL ; D = D->getNextDeclarator())
Ted Kremenekaa04c512007-09-06 00:17:54 +000064 Register(D);
65}
66
67void RegisterDecls::Register(Decl* D) {
68 LiveVariables::VPair& VP = L.getVarInfoMap()[const_cast<const Decl*>(D)];
69
70 VP.V.AliveBlocks.reserve(cfg.getNumBlockIDs());
71 VP.Idx = L.getNumDecls()++;
72}
73
74void RegisterDecls::RegisterUsedDecls() {
75 for (CFG::const_iterator BI = cfg.begin(), BE = cfg.end(); BI != BE; ++BI)
76 for (CFGBlock::const_iterator SI=BI->begin(),SE = BI->end();SI != SE;++SI)
77 Visit(const_cast<Stmt*>(*SI));
78}
79
80
81} // end anonymous namespace
82
83//===----------------------------------------------------------------------===//
84// WorkList - Data structure representing the liveness algorithm worklist.
85//
86
87namespace {
88
89class WorkListTy {
90 typedef llvm::SmallPtrSet<const CFGBlock*,20> BlockSet;
91 BlockSet wlist;
92public:
93 void enqueue(const CFGBlock* B) { wlist.insert(B); }
94
95 const CFGBlock* dequeue() {
96 assert (!wlist.empty());
97 const CFGBlock* B = *wlist.begin();
98 wlist.erase(B);
99 return B;
100 }
101
102 bool isEmpty() const { return wlist.empty(); }
103};
104
105} // end anonymous namespace
106
107//===----------------------------------------------------------------------===//
108// TFuncs
109//
110
111namespace {
112
113class LivenessTFuncs : public StmtVisitor<LivenessTFuncs,void> {
114 LiveVariables& L;
115 llvm::BitVector Live;
Ted Kremenek05334682007-09-06 21:26:58 +0000116 llvm::BitVector KilledAtLeastOnce;
117 Stmt* CurrentStmt;
118 const CFGBlock* CurrentBlock;
119 bool blockPreviouslyProcessed;
Ted Kremenekd1d88262007-09-10 15:56:38 +0000120 LiveVariablesObserver* Observer;
Ted Kremenekaa04c512007-09-06 00:17:54 +0000121public:
Ted Kremenekd1d88262007-09-10 15:56:38 +0000122 LivenessTFuncs(LiveVariables& l, LiveVariablesObserver* A = NULL)
Ted Kremenek05334682007-09-06 21:26:58 +0000123 : L(l), CurrentStmt(NULL), CurrentBlock(NULL),
Ted Kremenekd1d88262007-09-10 15:56:38 +0000124 blockPreviouslyProcessed(false), Observer(A)
Ted Kremenek05334682007-09-06 21:26:58 +0000125 {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000126 Live.resize(l.getNumDecls());
Ted Kremenek05334682007-09-06 21:26:58 +0000127 KilledAtLeastOnce.resize(l.getNumDecls());
Ted Kremenekaa04c512007-09-06 00:17:54 +0000128 }
129
130 void VisitStmt(Stmt* S);
131 void VisitDeclRefExpr(DeclRefExpr* DR);
132 void VisitBinaryOperator(BinaryOperator* B);
133 void VisitAssign(BinaryOperator* B);
134 void VisitStmtExpr(StmtExpr* S);
Ted Kremenek05334682007-09-06 21:26:58 +0000135 void VisitDeclStmt(DeclStmt* DS);
136 void VisitUnaryOperator(UnaryOperator* U);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000137
138 unsigned getIdx(const Decl* D) {
139 LiveVariables::VarInfoMap& V = L.getVarInfoMap();
140 LiveVariables::VarInfoMap::iterator I = V.find(D);
141 assert (I != V.end());
142 return I->second.Idx;
143 }
144
145 bool ProcessBlock(const CFGBlock* B);
Ted Kremenek05334682007-09-06 21:26:58 +0000146 llvm::BitVector* getBlockEntryLiveness(const CFGBlock* B);
147 LiveVariables::VarInfo& KillVar(Decl* D);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000148};
149
150void LivenessTFuncs::VisitStmt(Stmt* S) {
Ted Kremenekd1d88262007-09-10 15:56:38 +0000151 if (Observer)
152 Observer->ObserveStmt(S,L,Live);
Ted Kremenek05334682007-09-06 21:26:58 +0000153
Ted Kremenekaa04c512007-09-06 00:17:54 +0000154 // Evaluate the transfer functions for all subexpressions. Note that
155 // each invocation of "Visit" will have a side-effect: "Liveness" and "Kills"
Ted Kremenek05334682007-09-06 21:26:58 +0000156 // will be updated.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000157 for (Stmt::child_iterator I = S->child_begin(),E = S->child_end(); I != E;++I)
158 Visit(*I);
159}
160
161void LivenessTFuncs::VisitDeclRefExpr(DeclRefExpr* DR) {
Ted Kremenekd1d88262007-09-10 15:56:38 +0000162 if (Observer)
163 Observer->ObserveStmt(DR,L,Live);
Ted Kremenek05334682007-09-06 21:26:58 +0000164
Ted Kremenekaa04c512007-09-06 00:17:54 +0000165 // Register a use of the variable.
166 Live.set(getIdx(DR->getDecl()));
167}
168
169void LivenessTFuncs::VisitStmtExpr(StmtExpr* S) {
170 // Do nothing. The substatements of S are segmented into separate
171 // statements in the CFG.
172}
173
174void LivenessTFuncs::VisitBinaryOperator(BinaryOperator* B) {
175 switch (B->getOpcode()) {
176 case BinaryOperator::LAnd:
177 case BinaryOperator::LOr:
178 case BinaryOperator::Comma:
179 // Do nothing. These operations are broken up into multiple
180 // statements in the CFG. All these expressions do is return
Ted Kremenek05334682007-09-06 21:26:58 +0000181 // the value of their subexpressions, but these subexpressions will
Ted Kremenekaa04c512007-09-06 00:17:54 +0000182 // be evalualated elsewhere in the CFG.
183 break;
184
185 // FIXME: handle '++' and '--'
Ted Kremenek05334682007-09-06 21:26:58 +0000186 default:
Ted Kremenekaa04c512007-09-06 00:17:54 +0000187 if (B->isAssignmentOp()) VisitAssign(B);
Ted Kremenek05334682007-09-06 21:26:58 +0000188 else VisitStmt(B);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000189 }
190}
191
Ted Kremenek05334682007-09-06 21:26:58 +0000192void LivenessTFuncs::VisitUnaryOperator(UnaryOperator* U) {
193 switch (U->getOpcode()) {
194 case UnaryOperator::PostInc:
195 case UnaryOperator::PostDec:
196 case UnaryOperator::PreInc:
197 case UnaryOperator::PreDec:
198 case UnaryOperator::AddrOf:
199 // Walk through the subexpressions, blasting through ParenExprs until
200 // we either find a DeclRefExpr or some non-DeclRefExpr expression.
201 for (Stmt* S = U->getSubExpr() ; ; ) {
202 if (ParenExpr* P = dyn_cast<ParenExpr>(S)) {
203 S = P->getSubExpr();
204 continue;
205 }
206 else if (DeclRefExpr* DR = dyn_cast<DeclRefExpr>(S)) {
207 // Treat the --/++/& operator as a kill.
208 LiveVariables::VarInfo& V = KillVar(DR->getDecl());
209
210 if (!blockPreviouslyProcessed)
211 V.AddKill(CurrentStmt,DR);
212
213 VisitDeclRefExpr(DR);
214 }
215 else
216 Visit(S);
217
218 break;
219 }
220 break;
221
222 default:
223 VisitStmt(U->getSubExpr());
224 break;
225 }
226}
227
228LiveVariables::VarInfo& LivenessTFuncs::KillVar(Decl* D) {
229 LiveVariables::VarInfoMap::iterator I = L.getVarInfoMap().find(D);
230
231 assert (I != L.getVarInfoMap().end() &&
232 "Declaration not managed by variable map in LiveVariables");
233
234 // Mark the variable dead, and remove the current block from
235 // the set of blocks where the variable may be alive the entire time.
236 Live.reset(I->second.Idx);
237 I->second.V.AliveBlocks.reset(CurrentBlock->getBlockID());
238
239 return I->second.V;
240}
Ted Kremenekaa04c512007-09-06 00:17:54 +0000241
242void LivenessTFuncs::VisitAssign(BinaryOperator* B) {
Ted Kremenekd1d88262007-09-10 15:56:38 +0000243 if (Observer)
244 Observer->ObserveStmt(B,L,Live);
Ted Kremenek05334682007-09-06 21:26:58 +0000245
246 // Check if we are assigning to a variable.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000247 Stmt* LHS = B->getLHS();
Ted Kremenek05334682007-09-06 21:26:58 +0000248
Ted Kremenekaa04c512007-09-06 00:17:54 +0000249 if (DeclRefExpr* DR = dyn_cast<DeclRefExpr>(LHS)) {
Ted Kremenek05334682007-09-06 21:26:58 +0000250 LiveVariables::VarInfo& V = KillVar(DR->getDecl());
251
252 // We only need to register kills once, so we check if this block
253 // has been previously processed.
254 if (!blockPreviouslyProcessed)
Ted Kremenek42276bc2007-09-06 23:39:53 +0000255 V.AddKill(CurrentStmt,DR);
256
257 if (B->getOpcode() != BinaryOperator::Assign)
258 Visit(LHS);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000259 }
Ted Kremenek05334682007-09-06 21:26:58 +0000260 else
261 Visit(LHS);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000262
263 Visit(B->getRHS());
264}
265
Ted Kremenek05334682007-09-06 21:26:58 +0000266void LivenessTFuncs::VisitDeclStmt(DeclStmt* DS) {
Ted Kremenekd1d88262007-09-10 15:56:38 +0000267 if (Observer)
268 Observer->ObserveStmt(DS,L,Live);
Ted Kremenek05334682007-09-06 21:26:58 +0000269
270 // Declarations effectively "kill" a variable since they cannot possibly
271 // be live before they are declared. Declarations, however, are not kills
272 // in the sense that the value is obliterated, so we do not register
273 // DeclStmts as a "kill site" for a variable.
274 for (Decl* D = DS->getDecl(); D != NULL ; D = D->getNextDeclarator())
275 KillVar(D);
276}
Ted Kremenekaa04c512007-09-06 00:17:54 +0000277
Ted Kremenek05334682007-09-06 21:26:58 +0000278llvm::BitVector* LivenessTFuncs::getBlockEntryLiveness(const CFGBlock* B) {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000279 LiveVariables::BlockLivenessMap& BMap = L.getLiveAtBlockEntryMap();
280
281 LiveVariables::BlockLivenessMap::iterator I = BMap.find(B);
282 return (I == BMap.end()) ? NULL : &(I->second);
283}
284
285bool LivenessTFuncs::ProcessBlock(const CFGBlock* B) {
Ted Kremenek05334682007-09-06 21:26:58 +0000286
287 CurrentBlock = B;
Ted Kremenekaa04c512007-09-06 00:17:54 +0000288 Live.reset();
Ted Kremenek05334682007-09-06 21:26:58 +0000289 KilledAtLeastOnce.reset();
Ted Kremenekaa04c512007-09-06 00:17:54 +0000290
Ted Kremenek05334682007-09-06 21:26:58 +0000291 // Check if this block has been previously processed.
292 LiveVariables::BlockLivenessMap& BMap = L.getLiveAtBlockEntryMap();
293 LiveVariables::BlockLivenessMap::iterator BI = BMap.find(B);
294
295 blockPreviouslyProcessed = BI != BMap.end();
296
297 // Merge liveness information from all predecessors.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000298 for (CFGBlock::const_succ_iterator I=B->succ_begin(),E=B->succ_end();I!=E;++I)
Ted Kremenek05334682007-09-06 21:26:58 +0000299 if (llvm::BitVector* V = getBlockEntryLiveness(*I))
Ted Kremenekaa04c512007-09-06 00:17:54 +0000300 Live |= *V;
Ted Kremenek05334682007-09-06 21:26:58 +0000301
Ted Kremenekd1d88262007-09-10 15:56:38 +0000302 if (Observer)
303 Observer->ObserveBlockExit(B,L,Live);
Ted Kremenek05334682007-09-06 21:26:58 +0000304
305 // Tentatively mark all variables alive at the end of the current block
306 // as being alive during the whole block. We then cull these out as
307 // we process the statements of this block.
308 for (LiveVariables::VarInfoMap::iterator
309 I=L.getVarInfoMap().begin(), E=L.getVarInfoMap().end(); I != E; ++I)
310 if (Live[I->second.Idx])
311 I->second.V.AliveBlocks.set(B->getBlockID());
Ted Kremenekaa04c512007-09-06 00:17:54 +0000312
Ted Kremenek05334682007-09-06 21:26:58 +0000313 // March up the statements and process the transfer functions.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000314 for (CFGBlock::const_reverse_iterator I=B->rbegin(), E=B->rend(); I!=E; ++I) {
Ted Kremenek05334682007-09-06 21:26:58 +0000315 CurrentStmt = *I;
316 Visit(CurrentStmt);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000317 }
318
Ted Kremenek05334682007-09-06 21:26:58 +0000319 // Compare the computed "Live" values with what we already have
320 // for the entry to this block.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000321 bool hasChanged = false;
Ted Kremenek05334682007-09-06 21:26:58 +0000322
Ted Kremenekaa04c512007-09-06 00:17:54 +0000323
Ted Kremenek05334682007-09-06 21:26:58 +0000324 if (!blockPreviouslyProcessed) {
325 // We have not previously calculated liveness information for this block.
326 // Lazily instantiate a bitvector, and copy the bits from Live.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000327 hasChanged = true;
328 llvm::BitVector& V = BMap[B];
329 V.resize(L.getNumDecls());
Ted Kremenek05334682007-09-06 21:26:58 +0000330 V = Live;
Ted Kremenekaa04c512007-09-06 00:17:54 +0000331 }
Ted Kremenek05334682007-09-06 21:26:58 +0000332 else if (BI->second != Live) {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000333 hasChanged = true;
Ted Kremenek05334682007-09-06 21:26:58 +0000334 BI->second = Live;
Ted Kremenekaa04c512007-09-06 00:17:54 +0000335 }
336
337 return hasChanged;
338}
339
340} // end anonymous namespace
341
342//===----------------------------------------------------------------------===//
343// runOnCFG - Method to run the actual liveness computation.
344//
345
Ted Kremenekd1d88262007-09-10 15:56:38 +0000346void LiveVariables::runOnCFG(const CFG& cfg, LiveVariablesObserver* Observer) {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000347 // Scan a CFG for DeclRefStmts. For each one, create a VarInfo object.
348 {
349 RegisterDecls R(*this,cfg);
350 R.RegisterUsedDecls();
351 }
352
353 // Create the worklist and enqueue the exit block.
354 WorkListTy WorkList;
355 WorkList.enqueue(&cfg.getExit());
356
357 // Create the state for transfer functions.
Ted Kremenekd1d88262007-09-10 15:56:38 +0000358 LivenessTFuncs TF(*this,Observer);
Ted Kremenekaa04c512007-09-06 00:17:54 +0000359
360 // Process the worklist until it is empty.
361
362 while (!WorkList.isEmpty()) {
363 const CFGBlock* B = WorkList.dequeue();
364 if (TF.ProcessBlock(B))
365 for (CFGBlock::const_pred_iterator I = B->pred_begin(), E = B->pred_end();
366 I != E; ++I)
367 WorkList.enqueue(*I);
368 }
369
Ted Kremenek05334682007-09-06 21:26:58 +0000370 // Go through each block and reserve a bitvector. This is needed if
371 // a block was never visited by the worklist algorithm.
Ted Kremenekaa04c512007-09-06 00:17:54 +0000372 for (CFG::const_iterator I = cfg.begin(), E = cfg.end(); I != E; ++I)
373 LiveAtBlockEntryMap[&(*I)].resize(NumDecls);
374}
375
Ted Kremenek05334682007-09-06 21:26:58 +0000376
Ted Kremenekd1d88262007-09-10 15:56:38 +0000377void LiveVariables::runOnBlock(const CFGBlock* B,
378 LiveVariablesObserver* Observer)
Ted Kremenek05334682007-09-06 21:26:58 +0000379{
Ted Kremenekd1d88262007-09-10 15:56:38 +0000380 LivenessTFuncs TF(*this,Observer);
Ted Kremenek05334682007-09-06 21:26:58 +0000381 TF.ProcessBlock(B);
382}
383
384//===----------------------------------------------------------------------===//
385// liveness queries
386//
387
Ted Kremeneke805c4a2007-09-06 23:00:42 +0000388bool LiveVariables::isLive(const CFGBlock* B, const Decl* D) const {
Ted Kremenek05334682007-09-06 21:26:58 +0000389 BlockLivenessMap::const_iterator I = LiveAtBlockEntryMap.find(B);
390 assert (I != LiveAtBlockEntryMap.end());
391
392 VarInfoMap::const_iterator VI = VarInfos.find(D);
393 assert (VI != VarInfos.end());
394
395 return I->second[VI->second.Idx];
396}
397
Ted Kremeneke805c4a2007-09-06 23:00:42 +0000398bool LiveVariables::isLive(llvm::BitVector& Live, const Decl* D) const {
399 VarInfoMap::const_iterator VI = VarInfos.find(D);
400 assert (VI != VarInfos.end());
401 return Live[VI->second.Idx];
402}
403
Ted Kremenek05334682007-09-06 21:26:58 +0000404bool LiveVariables::KillsVar(const Stmt* S, const Decl* D) const {
405 VarInfoMap::const_iterator VI = VarInfos.find(D);
406 assert (VI != VarInfos.end());
407
408 for (VarInfo::KillsSet::const_iterator
409 I = VI->second.V.Kills.begin(), E = VI->second.V.Kills.end(); I!=E;++I)
410 if (I->first == S)
411 return true;
412
413 return false;
414}
415
416LiveVariables::VarInfo& LiveVariables::getVarInfo(const Decl* D) {
417 VarInfoMap::iterator VI = VarInfos.find(D);
418 assert (VI != VarInfos.end());
419 return VI->second.V;
420}
421
422const LiveVariables::VarInfo& LiveVariables::getVarInfo(const Decl* D) const {
423 return const_cast<LiveVariables*>(this)->getVarInfo(D);
424}
425
Ted Kremenekaa04c512007-09-06 00:17:54 +0000426//===----------------------------------------------------------------------===//
Ted Kremenekd1d88262007-09-10 15:56:38 +0000427// Defaults for LiveVariablesObserver
Ted Kremeneke805c4a2007-09-06 23:00:42 +0000428
Ted Kremenekd1d88262007-09-10 15:56:38 +0000429void LiveVariablesObserver::ObserveStmt(Stmt* S, LiveVariables& L,
430 llvm::BitVector& V) {}
Ted Kremeneke805c4a2007-09-06 23:00:42 +0000431
Ted Kremenekd1d88262007-09-10 15:56:38 +0000432void LiveVariablesObserver::ObserveBlockExit(const CFGBlock* B,
433 LiveVariables& L,
434 llvm::BitVector& V) {}
Ted Kremeneke805c4a2007-09-06 23:00:42 +0000435
436//===----------------------------------------------------------------------===//
Ted Kremenekaa04c512007-09-06 00:17:54 +0000437// printing liveness state for debugging
438//
439
Ted Kremenek05334682007-09-06 21:26:58 +0000440void LiveVariables::dumpLiveness(const llvm::BitVector& V,
441 SourceManager& SM) const {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000442
443 for (VarInfoMap::iterator I = VarInfos.begin(), E=VarInfos.end(); I!=E; ++I) {
444 if (V[I->second.Idx]) {
Ted Kremenek05334682007-09-06 21:26:58 +0000445
446 SourceLocation PhysLoc = SM.getPhysicalLoc(I->first->getLocation());
447
448 fprintf(stderr, " %s <%s:%u:%u>\n",
449 I->first->getIdentifier()->getName(),
450 SM.getSourceName(PhysLoc),
451 SM.getLineNumber(PhysLoc),
452 SM.getColumnNumber(PhysLoc));
Ted Kremenekaa04c512007-09-06 00:17:54 +0000453 }
454 }
455}
456
Ted Kremenek05334682007-09-06 21:26:58 +0000457void LiveVariables::dumpBlockLiveness(SourceManager& M) const {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000458 for (BlockLivenessMap::iterator I = LiveAtBlockEntryMap.begin(),
459 E = LiveAtBlockEntryMap.end();
460 I != E; ++I) {
Ted Kremenekaa04c512007-09-06 00:17:54 +0000461
Ted Kremenek05334682007-09-06 21:26:58 +0000462 fprintf(stderr,
463 "\n[ B%d (live variables at block entry) ]\n",
464 I->first->getBlockID());
465
466 dumpLiveness(I->second,M);
467 }
Ted Kremenekaa04c512007-09-06 00:17:54 +0000468}