1 //===- MachineScheduler.cpp - Machine Instruction Scheduler ---------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // MachineScheduler schedules machine instructions after phi elimination. It 11 // preserves LiveIntervals so it can be invoked before register allocation. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #define DEBUG_TYPE "misched" 16 17 #include "llvm/CodeGen/LiveIntervalAnalysis.h" 18 #include "llvm/CodeGen/MachineScheduler.h" 19 #include "llvm/CodeGen/Passes.h" 20 #include "llvm/CodeGen/ScheduleDAGInstrs.h" 21 #include "llvm/Analysis/AliasAnalysis.h" 22 #include "llvm/Target/TargetInstrInfo.h" 23 #include "llvm/Support/CommandLine.h" 24 #include "llvm/Support/Debug.h" 25 #include "llvm/Support/ErrorHandling.h" 26 #include "llvm/Support/raw_ostream.h" 27 #include "llvm/ADT/OwningPtr.h" 28 #include "llvm/ADT/PriorityQueue.h" 29 30 #include <queue> 31 32 using namespace llvm; 33 34 static cl::opt<bool> ForceTopDown("misched-topdown", cl::Hidden, 35 cl::desc("Force top-down list scheduling")); 36 static cl::opt<bool> ForceBottomUp("misched-bottomup", cl::Hidden, 37 cl::desc("Force bottom-up list scheduling")); 38 39 #ifndef NDEBUG 40 static cl::opt<bool> ViewMISchedDAGs("view-misched-dags", cl::Hidden, 41 cl::desc("Pop up a window to show MISched dags after they are processed")); 42 43 static cl::opt<unsigned> MISchedCutoff("misched-cutoff", cl::Hidden, 44 cl::desc("Stop scheduling after N instructions"), cl::init(~0U)); 45 #else 46 static bool ViewMISchedDAGs = false; 47 #endif // NDEBUG 48 49 //===----------------------------------------------------------------------===// 50 // Machine Instruction Scheduling Pass and Registry 51 //===----------------------------------------------------------------------===// 52 53 namespace { 54 /// MachineScheduler runs after coalescing and before register allocation. 55 class MachineScheduler : public MachineSchedContext, 56 public MachineFunctionPass { 57 public: 58 MachineScheduler(); 59 60 virtual void getAnalysisUsage(AnalysisUsage &AU) const; 61 62 virtual void releaseMemory() {} 63 64 virtual bool runOnMachineFunction(MachineFunction&); 65 66 virtual void print(raw_ostream &O, const Module* = 0) const; 67 68 static char ID; // Class identification, replacement for typeinfo 69 }; 70 } // namespace 71 72 char MachineScheduler::ID = 0; 73 74 char &llvm::MachineSchedulerID = MachineScheduler::ID; 75 76 INITIALIZE_PASS_BEGIN(MachineScheduler, "misched", 77 "Machine Instruction Scheduler", false, false) 78 INITIALIZE_AG_DEPENDENCY(AliasAnalysis) 79 INITIALIZE_PASS_DEPENDENCY(SlotIndexes) 80 INITIALIZE_PASS_DEPENDENCY(LiveIntervals) 81 INITIALIZE_PASS_END(MachineScheduler, "misched", 82 "Machine Instruction Scheduler", false, false) 83 84 MachineScheduler::MachineScheduler() 85 : MachineFunctionPass(ID) { 86 initializeMachineSchedulerPass(*PassRegistry::getPassRegistry()); 87 } 88 89 void MachineScheduler::getAnalysisUsage(AnalysisUsage &AU) const { 90 AU.setPreservesCFG(); 91 AU.addRequiredID(MachineDominatorsID); 92 AU.addRequired<MachineLoopInfo>(); 93 AU.addRequired<AliasAnalysis>(); 94 AU.addRequired<TargetPassConfig>(); 95 AU.addRequired<SlotIndexes>(); 96 AU.addPreserved<SlotIndexes>(); 97 AU.addRequired<LiveIntervals>(); 98 AU.addPreserved<LiveIntervals>(); 99 MachineFunctionPass::getAnalysisUsage(AU); 100 } 101 102 MachinePassRegistry MachineSchedRegistry::Registry; 103 104 /// A dummy default scheduler factory indicates whether the scheduler 105 /// is overridden on the command line. 106 static ScheduleDAGInstrs *useDefaultMachineSched(MachineSchedContext *C) { 107 return 0; 108 } 109 110 /// MachineSchedOpt allows command line selection of the scheduler. 111 static cl::opt<MachineSchedRegistry::ScheduleDAGCtor, false, 112 RegisterPassParser<MachineSchedRegistry> > 113 MachineSchedOpt("misched", 114 cl::init(&useDefaultMachineSched), cl::Hidden, 115 cl::desc("Machine instruction scheduler to use")); 116 117 static MachineSchedRegistry 118 DefaultSchedRegistry("default", "Use the target's default scheduler choice.", 119 useDefaultMachineSched); 120 121 /// Forward declare the standard machine scheduler. This will be used as the 122 /// default scheduler if the target does not set a default. 123 static ScheduleDAGInstrs *createConvergingSched(MachineSchedContext *C); 124 125 /// Top-level MachineScheduler pass driver. 126 /// 127 /// Visit blocks in function order. Divide each block into scheduling regions 128 /// and visit them bottom-up. Visiting regions bottom-up is not required, but is 129 /// consistent with the DAG builder, which traverses the interior of the 130 /// scheduling regions bottom-up. 131 /// 132 /// This design avoids exposing scheduling boundaries to the DAG builder, 133 /// simplifying the DAG builder's support for "special" target instructions. 134 /// At the same time the design allows target schedulers to operate across 135 /// scheduling boundaries, for example to bundle the boudary instructions 136 /// without reordering them. This creates complexity, because the target 137 /// scheduler must update the RegionBegin and RegionEnd positions cached by 138 /// ScheduleDAGInstrs whenever adding or removing instructions. A much simpler 139 /// design would be to split blocks at scheduling boundaries, but LLVM has a 140 /// general bias against block splitting purely for implementation simplicity. 141 bool MachineScheduler::runOnMachineFunction(MachineFunction &mf) { 142 // Initialize the context of the pass. 143 MF = &mf; 144 MLI = &getAnalysis<MachineLoopInfo>(); 145 MDT = &getAnalysis<MachineDominatorTree>(); 146 PassConfig = &getAnalysis<TargetPassConfig>(); 147 AA = &getAnalysis<AliasAnalysis>(); 148 149 LIS = &getAnalysis<LiveIntervals>(); 150 const TargetInstrInfo *TII = MF->getTarget().getInstrInfo(); 151 152 // Select the scheduler, or set the default. 153 MachineSchedRegistry::ScheduleDAGCtor Ctor = MachineSchedOpt; 154 if (Ctor == useDefaultMachineSched) { 155 // Get the default scheduler set by the target. 156 Ctor = MachineSchedRegistry::getDefault(); 157 if (!Ctor) { 158 Ctor = createConvergingSched; 159 MachineSchedRegistry::setDefault(Ctor); 160 } 161 } 162 // Instantiate the selected scheduler. 163 OwningPtr<ScheduleDAGInstrs> Scheduler(Ctor(this)); 164 165 // Visit all machine basic blocks. 166 for (MachineFunction::iterator MBB = MF->begin(), MBBEnd = MF->end(); 167 MBB != MBBEnd; ++MBB) { 168 169 Scheduler->startBlock(MBB); 170 171 // Break the block into scheduling regions [I, RegionEnd), and schedule each 172 // region as soon as it is discovered. RegionEnd points the the scheduling 173 // boundary at the bottom of the region. The DAG does not include RegionEnd, 174 // but the region does (i.e. the next RegionEnd is above the previous 175 // RegionBegin). If the current block has no terminator then RegionEnd == 176 // MBB->end() for the bottom region. 177 // 178 // The Scheduler may insert instructions during either schedule() or 179 // exitRegion(), even for empty regions. So the local iterators 'I' and 180 // 'RegionEnd' are invalid across these calls. 181 unsigned RemainingCount = MBB->size(); 182 for(MachineBasicBlock::iterator RegionEnd = MBB->end(); 183 RegionEnd != MBB->begin(); RegionEnd = Scheduler->begin()) { 184 // Avoid decrementing RegionEnd for blocks with no terminator. 185 if (RegionEnd != MBB->end() 186 || TII->isSchedulingBoundary(llvm::prior(RegionEnd), MBB, *MF)) { 187 --RegionEnd; 188 // Count the boundary instruction. 189 --RemainingCount; 190 } 191 192 // The next region starts above the previous region. Look backward in the 193 // instruction stream until we find the nearest boundary. 194 MachineBasicBlock::iterator I = RegionEnd; 195 for(;I != MBB->begin(); --I, --RemainingCount) { 196 if (TII->isSchedulingBoundary(llvm::prior(I), MBB, *MF)) 197 break; 198 } 199 // Notify the scheduler of the region, even if we may skip scheduling 200 // it. Perhaps it still needs to be bundled. 201 Scheduler->enterRegion(MBB, I, RegionEnd, RemainingCount); 202 203 // Skip empty scheduling regions (0 or 1 schedulable instructions). 204 if (I == RegionEnd || I == llvm::prior(RegionEnd)) { 205 // Close the current region. Bundle the terminator if needed. 206 // This invalidates 'RegionEnd' and 'I'. 207 Scheduler->exitRegion(); 208 continue; 209 } 210 DEBUG(dbgs() << "MachineScheduling " << MF->getFunction()->getName() 211 << ":BB#" << MBB->getNumber() << "\n From: " << *I << " To: "; 212 if (RegionEnd != MBB->end()) dbgs() << *RegionEnd; 213 else dbgs() << "End"; 214 dbgs() << " Remaining: " << RemainingCount << "\n"); 215 216 // Schedule a region: possibly reorder instructions. 217 // This invalidates 'RegionEnd' and 'I'. 218 Scheduler->schedule(); 219 220 // Close the current region. 221 Scheduler->exitRegion(); 222 223 // Scheduling has invalidated the current iterator 'I'. Ask the 224 // scheduler for the top of it's scheduled region. 225 RegionEnd = Scheduler->begin(); 226 } 227 assert(RemainingCount == 0 && "Instruction count mismatch!"); 228 Scheduler->finishBlock(); 229 } 230 return true; 231 } 232 233 void MachineScheduler::print(raw_ostream &O, const Module* m) const { 234 // unimplemented 235 } 236 237 //===----------------------------------------------------------------------===// 238 // MachineSchedStrategy - Interface to a machine scheduling algorithm. 239 //===----------------------------------------------------------------------===// 240 241 namespace { 242 class ScheduleDAGMI; 243 244 /// MachineSchedStrategy - Interface used by ScheduleDAGMI to drive the selected 245 /// scheduling algorithm. 246 /// 247 /// If this works well and targets wish to reuse ScheduleDAGMI, we may expose it 248 /// in ScheduleDAGInstrs.h 249 class MachineSchedStrategy { 250 public: 251 virtual ~MachineSchedStrategy() {} 252 253 /// Initialize the strategy after building the DAG for a new region. 254 virtual void initialize(ScheduleDAGMI *DAG) = 0; 255 256 /// Pick the next node to schedule, or return NULL. Set IsTopNode to true to 257 /// schedule the node at the top of the unscheduled region. Otherwise it will 258 /// be scheduled at the bottom. 259 virtual SUnit *pickNode(bool &IsTopNode) = 0; 260 261 /// When all predecessor dependencies have been resolved, free this node for 262 /// top-down scheduling. 263 virtual void releaseTopNode(SUnit *SU) = 0; 264 /// When all successor dependencies have been resolved, free this node for 265 /// bottom-up scheduling. 266 virtual void releaseBottomNode(SUnit *SU) = 0; 267 }; 268 } // namespace 269 270 //===----------------------------------------------------------------------===// 271 // ScheduleDAGMI - Base class for MachineInstr scheduling with LiveIntervals 272 // preservation. 273 //===----------------------------------------------------------------------===// 274 275 namespace { 276 /// ScheduleDAGMI is an implementation of ScheduleDAGInstrs that schedules 277 /// machine instructions while updating LiveIntervals. 278 class ScheduleDAGMI : public ScheduleDAGInstrs { 279 AliasAnalysis *AA; 280 MachineSchedStrategy *SchedImpl; 281 282 /// The top of the unscheduled zone. 283 MachineBasicBlock::iterator CurrentTop; 284 285 /// The bottom of the unscheduled zone. 286 MachineBasicBlock::iterator CurrentBottom; 287 288 /// The number of instructions scheduled so far. Used to cut off the 289 /// scheduler at the point determined by misched-cutoff. 290 unsigned NumInstrsScheduled; 291 public: 292 ScheduleDAGMI(MachineSchedContext *C, MachineSchedStrategy *S): 293 ScheduleDAGInstrs(*C->MF, *C->MLI, *C->MDT, /*IsPostRA=*/false, C->LIS), 294 AA(C->AA), SchedImpl(S), CurrentTop(), CurrentBottom(), 295 NumInstrsScheduled(0) {} 296 297 ~ScheduleDAGMI() { 298 delete SchedImpl; 299 } 300 301 MachineBasicBlock::iterator top() const { return CurrentTop; } 302 MachineBasicBlock::iterator bottom() const { return CurrentBottom; } 303 304 /// Implement ScheduleDAGInstrs interface. 305 void schedule(); 306 307 protected: 308 void moveInstruction(MachineInstr *MI, MachineBasicBlock::iterator InsertPos); 309 310 void releaseSucc(SUnit *SU, SDep *SuccEdge); 311 void releaseSuccessors(SUnit *SU); 312 void releasePred(SUnit *SU, SDep *PredEdge); 313 void releasePredecessors(SUnit *SU); 314 }; 315 } // namespace 316 317 /// ReleaseSucc - Decrement the NumPredsLeft count of a successor. When 318 /// NumPredsLeft reaches zero, release the successor node. 319 void ScheduleDAGMI::releaseSucc(SUnit *SU, SDep *SuccEdge) { 320 SUnit *SuccSU = SuccEdge->getSUnit(); 321 322 #ifndef NDEBUG 323 if (SuccSU->NumPredsLeft == 0) { 324 dbgs() << "*** Scheduling failed! ***\n"; 325 SuccSU->dump(this); 326 dbgs() << " has been released too many times!\n"; 327 llvm_unreachable(0); 328 } 329 #endif 330 --SuccSU->NumPredsLeft; 331 if (SuccSU->NumPredsLeft == 0 && SuccSU != &ExitSU) 332 SchedImpl->releaseTopNode(SuccSU); 333 } 334 335 /// releaseSuccessors - Call releaseSucc on each of SU's successors. 336 void ScheduleDAGMI::releaseSuccessors(SUnit *SU) { 337 for (SUnit::succ_iterator I = SU->Succs.begin(), E = SU->Succs.end(); 338 I != E; ++I) { 339 releaseSucc(SU, &*I); 340 } 341 } 342 343 /// ReleasePred - Decrement the NumSuccsLeft count of a predecessor. When 344 /// NumSuccsLeft reaches zero, release the predecessor node. 345 void ScheduleDAGMI::releasePred(SUnit *SU, SDep *PredEdge) { 346 SUnit *PredSU = PredEdge->getSUnit(); 347 348 #ifndef NDEBUG 349 if (PredSU->NumSuccsLeft == 0) { 350 dbgs() << "*** Scheduling failed! ***\n"; 351 PredSU->dump(this); 352 dbgs() << " has been released too many times!\n"; 353 llvm_unreachable(0); 354 } 355 #endif 356 --PredSU->NumSuccsLeft; 357 if (PredSU->NumSuccsLeft == 0 && PredSU != &EntrySU) 358 SchedImpl->releaseBottomNode(PredSU); 359 } 360 361 /// releasePredecessors - Call releasePred on each of SU's predecessors. 362 void ScheduleDAGMI::releasePredecessors(SUnit *SU) { 363 for (SUnit::pred_iterator I = SU->Preds.begin(), E = SU->Preds.end(); 364 I != E; ++I) { 365 releasePred(SU, &*I); 366 } 367 } 368 369 void ScheduleDAGMI::moveInstruction(MachineInstr *MI, 370 MachineBasicBlock::iterator InsertPos) { 371 BB->splice(InsertPos, BB, MI); 372 LIS->handleMove(MI); 373 if (RegionBegin == InsertPos) 374 RegionBegin = MI; 375 } 376 377 /// schedule - Called back from MachineScheduler::runOnMachineFunction 378 /// after setting up the current scheduling region. 379 void ScheduleDAGMI::schedule() { 380 buildSchedGraph(AA); 381 382 DEBUG(dbgs() << "********** MI Scheduling **********\n"); 383 DEBUG(for (unsigned su = 0, e = SUnits.size(); su != e; ++su) 384 SUnits[su].dumpAll(this)); 385 386 if (ViewMISchedDAGs) viewGraph(); 387 388 SchedImpl->initialize(this); 389 390 // Release edges from the special Entry node or to the special Exit node. 391 releaseSuccessors(&EntrySU); 392 releasePredecessors(&ExitSU); 393 394 // Release all DAG roots for scheduling. 395 for (std::vector<SUnit>::iterator I = SUnits.begin(), E = SUnits.end(); 396 I != E; ++I) { 397 // A SUnit is ready to top schedule if it has no predecessors. 398 if (I->Preds.empty()) 399 SchedImpl->releaseTopNode(&(*I)); 400 // A SUnit is ready to bottom schedule if it has no successors. 401 if (I->Succs.empty()) 402 SchedImpl->releaseBottomNode(&(*I)); 403 } 404 405 CurrentTop = RegionBegin; 406 CurrentBottom = RegionEnd; 407 bool IsTopNode = false; 408 while (SUnit *SU = SchedImpl->pickNode(IsTopNode)) { 409 410 #ifndef NDEBUG 411 // Enable break 412 if (NumInstrsScheduled == MISchedCutoff && MISchedCutoff != ~0U) { 413 CurrentTop = CurrentBottom; 414 break; 415 } 416 ++NumInstrsScheduled; 417 #endif 418 419 DEBUG(dbgs() << "*** " << (IsTopNode ? "Top" : "Bottom") 420 << " Scheduling Instruction:\n"; SU->dump(this)); 421 422 // Move the instruction to its new location in the instruction stream. 423 MachineInstr *MI = SU->getInstr(); 424 425 if (IsTopNode) { 426 assert(SU->isTopReady() && "node still has unscheduled dependencies"); 427 if (&*CurrentTop == MI) 428 ++CurrentTop; 429 else 430 moveInstruction(MI, CurrentTop); 431 // Release dependent instructions for scheduling. 432 releaseSuccessors(SU); 433 } 434 else { 435 assert(SU->isBottomReady() && "node still has unscheduled dependencies"); 436 if (&*llvm::prior(CurrentBottom) == MI) 437 --CurrentBottom; 438 else { 439 moveInstruction(MI, CurrentBottom); 440 CurrentBottom = MI; 441 } 442 // Release dependent instructions for scheduling. 443 releasePredecessors(SU); 444 } 445 SU->isScheduled = true; 446 } 447 assert(CurrentTop == CurrentBottom && "Nonempty unscheduled zone."); 448 } 449 450 //===----------------------------------------------------------------------===// 451 // ConvergingScheduler - Implementation of the standard MachineSchedStrategy. 452 //===----------------------------------------------------------------------===// 453 454 namespace { 455 /// ConvergingScheduler shrinks the unscheduled zone using heuristics to balance 456 /// the schedule. 457 class ConvergingScheduler : public MachineSchedStrategy { 458 ScheduleDAGMI *DAG; 459 460 unsigned NumTopReady; 461 unsigned NumBottomReady; 462 463 public: 464 virtual void initialize(ScheduleDAGMI *dag) { 465 DAG = dag; 466 467 assert((!ForceTopDown || !ForceBottomUp) && 468 "-misched-topdown incompatible with -misched-bottomup"); 469 } 470 471 virtual SUnit *pickNode(bool &IsTopNode) { 472 if (DAG->top() == DAG->bottom()) 473 return NULL; 474 475 // As an initial placeholder heuristic, schedule in the direction that has 476 // the fewest choices. 477 SUnit *SU; 478 if (ForceTopDown || (!ForceBottomUp && NumTopReady <= NumBottomReady)) { 479 SU = DAG->getSUnit(DAG->top()); 480 IsTopNode = true; 481 } 482 else { 483 SU = DAG->getSUnit(llvm::prior(DAG->bottom())); 484 IsTopNode = false; 485 } 486 if (SU->isTopReady()) { 487 assert(NumTopReady > 0 && "bad ready count"); 488 --NumTopReady; 489 } 490 if (SU->isBottomReady()) { 491 assert(NumBottomReady > 0 && "bad ready count"); 492 --NumBottomReady; 493 } 494 return SU; 495 } 496 497 virtual void releaseTopNode(SUnit *SU) { 498 ++NumTopReady; 499 } 500 virtual void releaseBottomNode(SUnit *SU) { 501 ++NumBottomReady; 502 } 503 }; 504 } // namespace 505 506 /// Create the standard converging machine scheduler. This will be used as the 507 /// default scheduler if the target does not set a default. 508 static ScheduleDAGInstrs *createConvergingSched(MachineSchedContext *C) { 509 assert((!ForceTopDown || !ForceBottomUp) && 510 "-misched-topdown incompatible with -misched-bottomup"); 511 return new ScheduleDAGMI(C, new ConvergingScheduler()); 512 } 513 static MachineSchedRegistry 514 ConvergingSchedRegistry("converge", "Standard converging scheduler.", 515 createConvergingSched); 516 517 //===----------------------------------------------------------------------===// 518 // Machine Instruction Shuffler for Correctness Testing 519 //===----------------------------------------------------------------------===// 520 521 #ifndef NDEBUG 522 namespace { 523 /// Apply a less-than relation on the node order, which corresponds to the 524 /// instruction order prior to scheduling. IsReverse implements greater-than. 525 template<bool IsReverse> 526 struct SUnitOrder { 527 bool operator()(SUnit *A, SUnit *B) const { 528 if (IsReverse) 529 return A->NodeNum > B->NodeNum; 530 else 531 return A->NodeNum < B->NodeNum; 532 } 533 }; 534 535 /// Reorder instructions as much as possible. 536 class InstructionShuffler : public MachineSchedStrategy { 537 bool IsAlternating; 538 bool IsTopDown; 539 540 // Using a less-than relation (SUnitOrder<false>) for the TopQ priority 541 // gives nodes with a higher number higher priority causing the latest 542 // instructions to be scheduled first. 543 PriorityQueue<SUnit*, std::vector<SUnit*>, SUnitOrder<false> > 544 TopQ; 545 // When scheduling bottom-up, use greater-than as the queue priority. 546 PriorityQueue<SUnit*, std::vector<SUnit*>, SUnitOrder<true> > 547 BottomQ; 548 public: 549 InstructionShuffler(bool alternate, bool topdown) 550 : IsAlternating(alternate), IsTopDown(topdown) {} 551 552 virtual void initialize(ScheduleDAGMI *) { 553 TopQ.clear(); 554 BottomQ.clear(); 555 } 556 557 /// Implement MachineSchedStrategy interface. 558 /// ----------------------------------------- 559 560 virtual SUnit *pickNode(bool &IsTopNode) { 561 SUnit *SU; 562 if (IsTopDown) { 563 do { 564 if (TopQ.empty()) return NULL; 565 SU = TopQ.top(); 566 TopQ.pop(); 567 } while (SU->isScheduled); 568 IsTopNode = true; 569 } 570 else { 571 do { 572 if (BottomQ.empty()) return NULL; 573 SU = BottomQ.top(); 574 BottomQ.pop(); 575 } while (SU->isScheduled); 576 IsTopNode = false; 577 } 578 if (IsAlternating) 579 IsTopDown = !IsTopDown; 580 return SU; 581 } 582 583 virtual void releaseTopNode(SUnit *SU) { 584 TopQ.push(SU); 585 } 586 virtual void releaseBottomNode(SUnit *SU) { 587 BottomQ.push(SU); 588 } 589 }; 590 } // namespace 591 592 static ScheduleDAGInstrs *createInstructionShuffler(MachineSchedContext *C) { 593 bool Alternate = !ForceTopDown && !ForceBottomUp; 594 bool TopDown = !ForceBottomUp; 595 assert((TopDown || !ForceTopDown) && 596 "-misched-topdown incompatible with -misched-bottomup"); 597 return new ScheduleDAGMI(C, new InstructionShuffler(Alternate, TopDown)); 598 } 599 static MachineSchedRegistry ShufflerRegistry( 600 "shuffle", "Shuffle machine instructions alternating directions", 601 createInstructionShuffler); 602 #endif // !NDEBUG 603