diff options
Diffstat (limited to 'src/mem/ruby/tester/XactRequestGenerator.cc')
-rw-r--r-- | src/mem/ruby/tester/XactRequestGenerator.cc | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/src/mem/ruby/tester/XactRequestGenerator.cc b/src/mem/ruby/tester/XactRequestGenerator.cc new file mode 100644 index 000000000..c7870bb25 --- /dev/null +++ b/src/mem/ruby/tester/XactRequestGenerator.cc @@ -0,0 +1,637 @@ +/* + * Copyright (c) 1999 by Mark Hill and David Wood for the Wisconsin + * Multifacet Project. ALL RIGHTS RESERVED. + * + * ##HEADER## + * + * This software is furnished under a license and may be used and + * copied only in accordance with the terms of such license and the + * inclusion of the above copyright notice. This software or any + * other copies thereof or any derivative works may not be provided or + * otherwise made available to any other persons. Title to and + * ownership of the software is retained by Mark Hill and David Wood. + * Any use of this software must include the above copyright notice. + * + * THIS SOFTWARE IS PROVIDED "AS IS". THE LICENSOR MAKES NO + * WARRANTIES ABOUT ITS CORRECTNESS OR PERFORMANCE. + * */ + +/* + * $Id: XactRequestGenerator.C 1.7 05/09/22 16:49:19-05:00 xu@s0-29.cs.wisc.edu $ + * + */ + +#include "XactRequestGenerator.hh" +#include "LockStatus.hh" +#include "Sequencer.hh" +#include "System.hh" +#include "RubyConfig.hh" +#include "SubBlock.hh" +#include "SyntheticDriver.hh" +#include "Chip.hh" +#include "Instruction.hh" + +XactRequestGenerator::XactRequestGenerator(NodeID node, SyntheticDriver& driver) : + m_driver(driver), RequestGenerator(node, driver) +{ + DEBUG_EXPR(TESTER_COMP, MedPrio, "#### -- Creating XactRequestGenerator\n"); + cout << "#### -- Creating XactRequestGenerator " << node << endl; + + assert(XACT_SIZE > 0); + testArray = new uint8[g_MEMORY_SIZE_BYTES];; + // Create instructions + m_instructions = new Instruction[XACT_LENGTH]; + newTransaction(true); + + m_status = XactRequestGeneratorStatus_Ready; + m_last_transition = 0; + m_node = node; + //pickAddress(); + m_counter = 0; + m_register = 5; + m_pc = 0; + + m_abortPending = false; + g_eventQueue_ptr->scheduleEvent(this, 1+(random() % 200)); +} + +void XactRequestGenerator::newTransaction(bool init){ + // important: reset abort flag + m_abortPending = false; + + int depth = 0; + bool prev_ldst = false; + m_size = (random() % (XACT_LENGTH-2)) + 2; + cout << "XactRequestGenerator::newTransaction m_size=" << m_size << endl; + ASSERT(m_size >= 2); + if (!init) + ASSERT(!transactionManager()->inTransaction(0)); + m_transaction = (random() % 2); + + if(m_transaction){ + cout << " " << m_node << " new transaction " << endl; + } else { + cout << " " << m_node << " new NON-transaction " << endl; + } + + cout << "***INSTR STREAM "; + for(int i=0; i<m_size; ++i){ + if (i == 0 && m_transaction){ // new xact must start with begin + m_instructions[i].init(Opcode_BEGIN, Address(1)); + depth++; + cout << "begin "; + prev_ldst = false; + } else if (i == m_size - 1){ + if(m_transaction) { // new xact must end with commit + m_instructions[i].init(Opcode_COMMIT, Address(1)); + depth--; + cout << "commit "; + } else { + m_instructions[i].init(Opcode_DONE, Address(1)); + cout << "done "; + } + } else { + int selectAction; + if (!m_transaction) { // non-xact: must choose op + selectAction = 1; + } else { // xact + if (depth == m_size - i) { // must choose commit + selectAction = 0; + } else if (prev_ldst) { // only choose xact if intervenient ld/st + if (m_size - i < depth + 3) { // can choose op or + // commit (can't choose + // begin) + selectAction = (random() % 2); + } else if (depth == 0) { // can choose begin or op (can't + // choose commit) + selectAction = (random() % 2); + if (selectAction == 0) selectAction = 2; + } else { // can choose begin, op, or commit + selectAction = (random() % 3); + } + } else { + selectAction = 1; + } + } + + physical_address_t address; + int selectOpcode; + switch (selectAction) { + case 0: // commit + m_instructions[i].init(Opcode_COMMIT, Address(1)); + depth--; + cout << "commit "; + prev_ldst = false; + break; + case 1: + address = (random() % XACT_SIZE); + //cout << "address: " << address << endl; + //cout << "XACT_SIZE: " << XACT_SIZE << endl; + + //assert(address < XACT_SIZE); + //physical_address_t address = 0; + selectOpcode = random() % 3; + Opcode op; + switch(selectOpcode){ + case 0: + op = Opcode_LD; + cout << "ld "; + break; + case 1: + op = Opcode_ST; + cout << "st "; + break; + case 2: + op = Opcode_INC; + cout << "inc "; + break; + default: + assert(false); + }; + assert(op < Opcode_NUM_OPCODES); + m_instructions[i].init(op, Address(address)); + prev_ldst = true; + break; + case 2: + m_instructions[i].init(Opcode_BEGIN, Address(1)); + depth++; + cout << "begin "; + prev_ldst = false; + break; + default: + assert(false); + }; + } + } + cout << endl; + if(m_transaction){ + ASSERT(m_instructions[0].getOpcode() == Opcode_BEGIN); + } +} + +XactRequestGenerator::~XactRequestGenerator() +{ + if(testArray){ + delete [] testArray; + testArray = NULL; + } + delete m_instructions; +} + +void XactRequestGenerator::wakeup() +{ + DEBUG_EXPR(TESTER_COMP, MedPrio, m_node); + DEBUG_EXPR(TESTER_COMP, MedPrio, m_status); + + assert(m_status == XactRequestGeneratorStatus_Ready || m_status == XactRequestGeneratorStatus_Aborted); + m_status = XactRequestGeneratorStatus_Blocked; + + m_last_transition = g_eventQueue_ptr->getTime(); + execute(); +} + +void XactRequestGenerator::execute(){ + cout << "XactRequestGenerator::execute m_node=" << m_node << " m_pc=" << m_pc << " m_size=" << m_size << endl; + assert(m_pc >= 0); + assert(m_pc < m_size); + assert(m_pc < XACT_LENGTH); + + Instruction current = m_instructions[m_pc]; + switch (current.getOpcode()){ + case Opcode_BEGIN: + cout << " -- begin."; + initiateBeginTransaction(); + break; + case Opcode_LD: + cout << " -- load: " << current.getAddress(); + initiateLoad(current.getAddress()); + break; + case Opcode_INC: + cout << " -- inc."; + initiateInc(current.getAddress()); + break; + case Opcode_ST: + cout << " -- store: " << current.getAddress(); + initiateStore(current.getAddress()); + break; + case Opcode_COMMIT: + cout << " -- commit."; + initiateCommit(); + break; + case Opcode_DONE: + cout << " -- done."; + initiateDone(); + break; + default: + WARN_EXPR(current.getOpcode()); + ERROR_MSG("Invalid opcode"); + }; + cout << endl; +} + +void XactRequestGenerator::performCallback(NodeID proc, SubBlock& data) +{ + cout << "XactRequestGenerator::performCallback m_node=" << m_node << endl; + assert(m_status == XactRequestGeneratorStatus_Waiting || + m_status == XactRequestGeneratorStatus_Aborted); + assert(proc == m_node); + + Address address = data.getAddress(); + //assert(address == m_address); + + DEBUG_EXPR(TESTER_COMP, LowPrio, proc); + DEBUG_EXPR(TESTER_COMP, LowPrio, m_status); + DEBUG_EXPR(TESTER_COMP, LowPrio, address); + DEBUG_EXPR(TESTER_COMP, LowPrio, data); + + m_last_transition = g_eventQueue_ptr->getTime(); + + cout << " " << m_node << " in performCallback, pc:" << m_pc + << ", addr:" << address << endl; + + + int depth; + if(m_status == XactRequestGeneratorStatus_Aborted){ + depth = transactionManager()->postAbortIndex(0); + m_pc = pc_stack[depth]; + cout << "XactRequestGenerator::performCallback m_node=" << m_node << " setting m_pc=" << m_pc << endl; + printPcStack(depth); + m_register = 5; + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, ABORT_RETRY_TIME); + } else { + m_status = XactRequestGeneratorStatus_Blocked; + bool found, outermost; + uint8 value; + switch (m_instructions[m_pc].getOpcode()){ + case Opcode_BEGIN: + m_driver.recordTestLatency(g_eventQueue_ptr->getTime() - m_last_transition); + m_register = 5; + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, waitTime()); + depth = transactionManager()->getDepth(0); + if (!transactionManager()->isSubsuming(0)) { + pc_stack[depth - 1] = m_pc; + cout << "XactRequestGenerator::performCallback m_node=" << m_node << " SETTING PC_STACK" << endl; + printPcStack(depth); + } + m_pc++; + break; + case Opcode_LD: + m_driver.recordTestLatency(g_eventQueue_ptr->getTime() - m_last_transition); + m_register = data.getByte(0); + //cout << " " << m_node << " " << g_eventQueue_ptr->getTime() << " Callback--LD: " << (int) m_register << endl; + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, waitTime()); + m_pc++; + break; + //case Opcode_INC: // We shouldn't get a callback for this! + //m_driver.recordSwapLatency(g_eventQueue_ptr->getTime() - m_last_transition); + + // break; + case Opcode_ST: + m_driver.recordReleaseLatency(g_eventQueue_ptr->getTime() - m_last_transition); + //data.setByte(address.getOffset(), m_register); + data.setByte(0, m_register); + //cout << " " << m_node << " " << g_eventQueue_ptr->getTime() << " Callback--ST: " << (int) m_register << endl; + + //dataArray[address.getAddress()] = m_register; + found = sequencer()->setRubyMemoryValue(address, (char *) (&m_register), 1); + assert(found); + found = sequencer()->getRubyMemoryValue(address, (char *) (&value), 1); + assert(found); + assert(value == m_register); + + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, thinkTime()); + m_pc++; + break; + case Opcode_COMMIT: + outermost = transactionManager()->getDepth(0) == 1; + if (outermost) { // about to commit outermost + m_counter++; + } + + cout << " " << m_node << " callback--commit, counter is " << m_counter << " length is: " << g_tester_length << endl; + // Check for correctness + + checkCorrectness(); + transactionManager()->commitTransaction(); + + m_driver.recordReleaseLatency(g_eventQueue_ptr->getTime() - m_last_transition); + + if (outermost) { + if (m_counter < g_tester_length) { + m_last_transition = g_eventQueue_ptr->getTime(); + //pickAddress(); // Necessary? + + // Create new random transaction + cout << "CREATING NEW RANDOM XACT SET" << endl; + newTransaction(false); + m_pc = 0; + + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, thinkTime()); + } else { + cout << " " << m_node << " Done." << endl; + m_driver.reportDone(); + m_status = XactRequestGeneratorStatus_Done; + } + } else { + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, thinkTime()); + m_pc++; + } + break; + default: + ERROR_MSG("Invalid Opcode"); + }; + } +} + +int XactRequestGenerator::thinkTime() const +{ + return g_think_time; +} + +int XactRequestGenerator::waitTime() const +{ + return g_wait_time; +} + +int XactRequestGenerator::holdTime() const +{ + return g_hold_time; +} + +void XactRequestGenerator::pickAddress() +{ + //m_address = m_driver.pickAddress(m_node); +} + +void XactRequestGenerator::initiateBeginTransaction() +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Begin Transaction"); + cout << "### -- initiating Begin " << m_node << endl; + m_status = XactRequestGeneratorStatus_Waiting; + sequencer()->makeRequest(CacheMsg(Address(physical_address_t(0)), Address(physical_address_t(0)), CacheRequestType_BEGIN_XACT, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0), transactionManager()->inExposedAction(0), 0)); + transactionManager()->beginTransaction(); +} + +void XactRequestGenerator::initiateStore(Address addr) +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Store"); + DEBUG_MSG(TESTER_COMP, MedPrio, addr); + cout << "### -- initiating Store " << m_node << endl; + m_status = XactRequestGeneratorStatus_Waiting; + if(m_transaction){ + sequencer()->makeRequest(CacheMsg(addr, addr, CacheRequestType_ST_XACT, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0), transactionManager()->inExposedAction(0), 0)); + } else { + sequencer()->makeRequest(CacheMsg(addr, addr, CacheRequestType_ST, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0),transactionManager()->inExposedAction(0), 0)); + } +} + +void XactRequestGenerator::initiateCommit() +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Commit "); + cout << "### -- initiating Commit " << m_node << endl; + + m_status = XactRequestGeneratorStatus_Waiting; + sequencer()->makeRequest(CacheMsg(Address(physical_address_t(0)), Address(physical_address_t(0)), CacheRequestType_COMMIT_XACT, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0), transactionManager()->inExposedAction(0), 0)); +} + +void XactRequestGenerator::initiateLoad(Address addr) +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Load "); + DEBUG_MSG(TESTER_COMP, MedPrio, addr); + + m_status = XactRequestGeneratorStatus_Waiting; + if(m_transaction){ + cout << "### -- initiating Load XACT" << m_node << endl; + sequencer()->makeRequest(CacheMsg(addr, addr, CacheRequestType_LD_XACT, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0), transactionManager()->inExposedAction(0), 0 )); + } else { + cout << "### -- initiating Load " << m_node << endl; + sequencer()->makeRequest(CacheMsg(addr, addr, CacheRequestType_LD, Address(m_pc), AccessModeType_UserMode, 1, PrefetchBit_No, 0, false, Address(0), transactionManager()->getTransactionLevel(0), 0, 0 /* only 1 SMT thread */, transactionManager()->getTimestamp(0), transactionManager()->inExposedAction(0), 0)); + } +} + +void XactRequestGenerator::initiateInc(Address addr) +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Inc "); + DEBUG_MSG(TESTER_COMP, MedPrio, addr); + cout << "### -- initiating Inc " << m_node << endl; + m_register++; + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, holdTime()); + m_pc++; +} + +void XactRequestGenerator::initiateDone() +{ + DEBUG_MSG(TESTER_COMP, MedPrio, "### -- initiating Done "); + cout << "### -- initiating Done " << m_node << endl; + newTransaction(false); + m_pc = 0; + //m_register = 5; + + m_status = XactRequestGeneratorStatus_Ready; + g_eventQueue_ptr->scheduleEvent(this, thinkTime()); + +} + +void XactRequestGenerator::checkCorrectness(){ + // Execute the transaction on the test array + int testPC = 0; + bool done = false; + for(int i=0; i<XACT_LENGTH && !done; ++i){ + Opcode op = m_instructions[i].getOpcode(); + Address addr = m_instructions[i].getAddress(); + + uint8 reg_val; + switch(op){ + case Opcode_BEGIN: + reg_val = 0; + break; // do nothing + case Opcode_LD: + //cout << "\tcheckCorrectness " << m_node << " LD: " << addr << " address = " << hex << addr.getAddress() << endl; + ASSERT(addr.getAddress() < g_MEMORY_SIZE_BYTES); + reg_val = testArray[addr.getAddress()]; + //cout << m_node << " LD: " << addr << ", " << (int) reg_val << endl; + break; + case Opcode_INC: + reg_val++; + //cout << m_node << " INC: " << (int) reg_val << endl; + break; + case Opcode_ST: + //cout << "\tcheckCorrectness " << m_node << " ST: " << addr << " address = " << hex << addr.getAddress() << endl; + ASSERT(addr.getAddress() < g_MEMORY_SIZE_BYTES); + testArray[addr.getAddress()] = reg_val; + //cout << m_node << " ST: " << addr << ", " << (int) reg_val << endl; + break; + case Opcode_DONE: + case Opcode_COMMIT: + done = true; + break; + default: + ERROR_MSG("Invalid Opcode."); + }; + } + + bool success = true; + uint8 ruby_value; + done = false; + bool found = false; + for(int i=0; i<XACT_LENGTH && !done; ++i){ + Opcode op = m_instructions[i].getOpcode(); + Address addr = m_instructions[i].getAddress(); + + uint8 reg_val; + switch(op){ + case Opcode_BEGIN: + case Opcode_INC: + break; // do nothing + case Opcode_LD: + case Opcode_ST: + found = sequencer()->getRubyMemoryValue(m_instructions[i].getAddress(), (char *) &ruby_value, 1); + assert(found); + + if (ruby_value != testArray[i]){ + success = false; + WARN_MSG("DATA MISMATCH!"); + WARN_EXPR((int) ruby_value); + WARN_EXPR((int) testArray[i]); + WARN_EXPR(i); + assert(success); + } + + break; + case Opcode_COMMIT: + done = true; + break; + default: + ERROR_MSG("Invalid Opcode."); + }; + } + cout << m_node << " CORRECT!" << endl; +} + +Sequencer* XactRequestGenerator::sequencer() const +{ + return g_system_ptr->getChip(m_node/RubyConfig::numberOfProcsPerChip())->getSequencer(m_node%RubyConfig::numberOfProcsPerChip()); +} + +TransactionManager* XactRequestGenerator::transactionManager() const +{ + return g_system_ptr->getChip(m_node/RubyConfig::numberOfProcsPerChip())->getTransactionManager(m_node%RubyConfig::numberOfProcsPerChip()); +} + +void XactRequestGenerator::print(ostream& out) const +{ +} + +void XactRequestGenerator::abortTransaction(){ + cout << " " << m_node << " *** ABORT! ***" << endl; + m_status = XactRequestGeneratorStatus_Aborted; +} + +void XactRequestGenerator::printPcStack(int depth) { + cout << "XactRequestGenerator::printPcStack m_node=" << m_node << " ["; + for (int i = 0; i < depth; i++) { + cout << pc_stack[i] << ", "; + } + cout << "]" << endl; +} + +void XactRequestGenerator::notifySendNack( const Address & physicalAddr, uint64 remote_timestamp, const MachineID & remote_id) { + Address addr = physicalAddr; + addr.makeLineAddress(); + TransactionManager * xact_mgr = transactionManager(); + bool isOlder = xact_mgr->isOlder(remote_timestamp, remote_id, addr ); + if(isOlder){ + bool inReadSet = xact_mgr->isInReadSetFilter(physicalAddr, 0); + bool inWriteSet = xact_mgr->isInWriteSetFilter(physicalAddr, 0); + // addr should be in perfect or Bloom filter + ASSERT( inReadSet || inWriteSet ); + cout << "notifySendNack addr = " << addr << " setting POSSIBLE CYCLE " << " my_ts = " << xact_mgr->getTimestamp(0) << " remote_ts = " << remote_timestamp << " proc = " << m_node << endl; + xact_mgr->setPossibleCycle(addr, L1CacheMachIDToProcessorNum(remote_id), 0 /*remote thread*/ , remote_timestamp, 0 /*our thread*/); + } + // otherwise don't set the proc possible cycle flag +} + +void XactRequestGenerator::notifyReceiveNack( const Address & physicalAddr, uint64 remote_timestamp, const MachineID & remote_id ) { + Address addr = physicalAddr; + addr.makeLineAddress(); + // check whether the possible cycle is set, and whether remote_timestamp is older. + // we only have 1 SMT thread + TransactionManager * xact_mgr = transactionManager(); + int local_timestamp = xact_mgr->getTimestamp(0); + bool possible_cycle = xact_mgr->possibleCycle(0); + // calculate isOlder() only if possible_cycle is set. This is because isOlder assumes proc is in a transaction + bool isOlder = false; + if(possible_cycle){ + isOlder = xact_mgr->isOlder(remote_timestamp, remote_id, addr ); + } + + if(isOlder && possible_cycle){ + // set our pendingAbort flag + cout << "notifyReceiveNack Setting Abort Pending Flag addr= " << addr << " ID = " << m_node << " possible_cycle = " << possible_cycle << " time = " << g_eventQueue_ptr->getTime() << endl; + m_abortPending = true; + assert(possible_cycle); + assert(isOlder); + } + + // profile this nack + xact_mgr->profileNack(addr, IDToInt(L1CacheMachIDToProcessorNum(remote_id)), remote_timestamp, 0, 0); +} + +void XactRequestGenerator::notifyReceiveNackFinal(const Address & physicalAddr) { + Address addr = physicalAddr; + addr.makeLineAddress(); + TransactionManager * xact_mgr = transactionManager(); + int local_timestamp = xact_mgr->getTimestamp(0); + bool possible_cycle = xact_mgr->possibleCycle(0); + + // we should still have an active request + if(m_abortPending){ + cout << "notifyReceiveNackFinal ABORTING addr= " << addr << " ID = " << m_node << " possible_cycle = " << possible_cycle << " time = " << g_eventQueue_ptr->getTime() << endl; + assert(possible_cycle); + + // we abort + // Step 1: remove the aborting request from sequencer, and mark it as "Trap" in our request table if needed + // Note: by marking request as "Trap" we can simulate HW abort delay + switch (m_instructions[m_pc].getOpcode()){ + case Opcode_LD: + sequencer()->readCallbackAbort(addr, 0); + break; + case Opcode_ST: + sequencer()->writeCallbackAbort(addr, 0); + break; + default: + cout << "Invalid Opcode = " << m_instructions[m_pc].getOpcode() << endl; + ASSERT(0); + }; + // Step 2: call the abort handler explicitly. If using software handler + trap aborts we wait until + // Simics transfers control to us again + // Note: it is impossible for this request to be a prefetch, so don't need that check + xact_mgr->abortTransaction(0, 0, -1 /*dummy remote ID*/, addr); + //reset the abort flag + m_abortPending = false; + } + else{ + // retry the request + // figure out whether to retry transactional load or store + switch (m_instructions[m_pc].getOpcode()){ + case Opcode_LD: + cout << "RETRYING LOAD " << addr << " of proc = " << m_node << " transactionLevel = " << xact_mgr->getTransactionLevel(0) << " time = " << g_eventQueue_ptr->getTime() << endl; + sequencer()->issueInterProcLoadRetryRequest(addr, 0); + break; + case Opcode_ST: + cout << "RETRYING STORE " << addr << " of proc = " << m_node << " transactionLevel = " << xact_mgr->getTransactionLevel(0) << " time = " << g_eventQueue_ptr->getTime() << endl; + sequencer()->issueInterProcStoreRetryRequest(addr, 0); + break; + default: + cout << "Invalid Opcode = " << m_instructions[m_pc].getOpcode() << endl; + ASSERT(0); + }; + } +} |