gem5/cpu/ozone/front_end_impl.hh
Kevin Lim 36581a5342 Faults generated at fetch are passed to the backend by creating a dummy nop instruction and giving it the fault. This unifies front end faults and normal instruction faults.
cpu/checker/cpu.cc:
    Fixups for fetch fault being sent with the instruction.
cpu/o3/fetch_impl.hh:
cpu/ozone/front_end_impl.hh:
    Send any faults generated at fetch along with a fake nop instruction to the back end.  This avoids having to use direct communication to check if the entire front end has drained; it is naturally handled through the nop's fault being handled when it reaches the head of commit.
cpu/ozone/front_end.hh:
    Add extra status TrapPending.
cpu/ozone/lw_back_end_impl.hh:
    Fetch fault handled through a dummy nop carrying the fetch fault.

    Avoid putting Nops on the exeList.

--HG--
extra : convert_revision : 8d9899748b34c204763a49c48a9b5113864f5789
2006-05-17 14:25:10 -04:00

926 lines
24 KiB
C++

#include "arch/faults.hh"
#include "arch/isa_traits.hh"
#include "base/statistics.hh"
#include "cpu/exec_context.hh"
#include "cpu/exetrace.hh"
#include "cpu/ozone/front_end.hh"
#include "mem/mem_interface.hh"
#include "sim/byte_swap.hh"
using namespace TheISA;
template <class Impl>
FrontEnd<Impl>::FrontEnd(Params *params)
: branchPred(params),
icacheInterface(params->icacheInterface),
instBufferSize(0),
maxInstBufferSize(params->maxInstBufferSize),
width(params->frontEndWidth),
freeRegs(params->numPhysicalRegs),
numPhysRegs(params->numPhysicalRegs),
serializeNext(false),
interruptPending(false)
{
switchedOut = false;
status = Idle;
// Setup branch predictor.
// Setup Memory Request
/*
memReq = new MemReq();
memReq->asid = 0;
memReq->data = new uint8_t[64];
*/
memReq = NULL;
// Size of cache block.
cacheBlkSize = icacheInterface ? icacheInterface->getBlockSize() : 64;
assert(isPowerOf2(cacheBlkSize));
// Create mask to get rid of offset bits.
cacheBlkMask = (cacheBlkSize - 1);
// Create space to store a cache line.
cacheData = new uint8_t[cacheBlkSize];
fetchCacheLineNextCycle = true;
cacheBlkValid = false;
#if !FULL_SYSTEM
// pTable = params->pTable;
#endif
fetchFault = NoFault;
}
template <class Impl>
std::string
FrontEnd<Impl>::name() const
{
return cpu->name() + ".frontend";
}
template <class Impl>
void
FrontEnd<Impl>::setCommBuffer(TimeBuffer<CommStruct> *_comm)
{
comm = _comm;
// @todo: Hardcoded for now. Allow this to be set by a latency.
fromCommit = comm->getWire(-1);
}
template <class Impl>
void
FrontEnd<Impl>::setXC(ExecContext *xc_ptr)
{
xc = xc_ptr;
// memReq->xc = xc;
}
template <class Impl>
void
FrontEnd<Impl>::regStats()
{
icacheStallCycles
.name(name() + ".icacheStallCycles")
.desc("Number of cycles fetch is stalled on an Icache miss")
.prereq(icacheStallCycles);
fetchedInsts
.name(name() + ".fetchedInsts")
.desc("Number of instructions fetch has processed")
.prereq(fetchedInsts);
fetchedBranches
.name(name() + ".fetchedBranches")
.desc("Number of fetched branches")
.prereq(fetchedBranches);
predictedBranches
.name(name() + ".predictedBranches")
.desc("Number of branches that fetch has predicted taken")
.prereq(predictedBranches);
fetchCycles
.name(name() + ".fetchCycles")
.desc("Number of cycles fetch has run and was not squashing or"
" blocked")
.prereq(fetchCycles);
fetchIdleCycles
.name(name() + ".fetchIdleCycles")
.desc("Number of cycles fetch was idle")
.prereq(fetchIdleCycles);
fetchSquashCycles
.name(name() + ".fetchSquashCycles")
.desc("Number of cycles fetch has spent squashing")
.prereq(fetchSquashCycles);
fetchBlockedCycles
.name(name() + ".fetchBlockedCycles")
.desc("Number of cycles fetch has spent blocked")
.prereq(fetchBlockedCycles);
fetchedCacheLines
.name(name() + ".fetchedCacheLines")
.desc("Number of cache lines fetched")
.prereq(fetchedCacheLines);
fetchIcacheSquashes
.name(name() + ".fetchIcacheSquashes")
.desc("Number of outstanding Icache misses that were squashed")
.prereq(fetchIcacheSquashes);
fetchNisnDist
.init(/* base value */ 0,
/* last value */ width,
/* bucket size */ 1)
.name(name() + ".rateDist")
.desc("Number of instructions fetched each cycle (Total)")
.flags(Stats::pdf);
idleRate
.name(name() + ".idleRate")
.desc("Percent of cycles fetch was idle")
.prereq(idleRate);
idleRate = fetchIdleCycles * 100 / cpu->numCycles;
branchRate
.name(name() + ".branchRate")
.desc("Number of branch fetches per cycle")
.flags(Stats::total);
branchRate = fetchedBranches / cpu->numCycles;
fetchRate
.name(name() + ".rate")
.desc("Number of inst fetches per cycle")
.flags(Stats::total);
fetchRate = fetchedInsts / cpu->numCycles;
IFQCount
.name(name() + ".IFQ:count")
.desc("cumulative IFQ occupancy")
;
IFQFcount
.name(name() + ".IFQ:fullCount")
.desc("cumulative IFQ full count")
.flags(Stats::total)
;
IFQOccupancy
.name(name() + ".IFQ:occupancy")
.desc("avg IFQ occupancy (inst's)")
;
IFQOccupancy = IFQCount / cpu->numCycles;
IFQLatency
.name(name() + ".IFQ:latency")
.desc("avg IFQ occupant latency (cycle's)")
.flags(Stats::total)
;
IFQFullRate
.name(name() + ".IFQ:fullRate")
.desc("fraction of time (cycles) IFQ was full")
.flags(Stats::total);
;
IFQFullRate = IFQFcount * Stats::constant(100) / cpu->numCycles;
dispatchCountStat
.name(name() + ".DIS:count")
.desc("cumulative count of dispatched insts")
.flags(Stats::total)
;
dispatchedSerializing
.name(name() + ".DIS:serializingInsts")
.desc("count of serializing insts dispatched")
.flags(Stats::total)
;
dispatchedTempSerializing
.name(name() + ".DIS:tempSerializingInsts")
.desc("count of temporary serializing insts dispatched")
.flags(Stats::total)
;
dispatchSerializeStallCycles
.name(name() + ".DIS:serializeStallCycles")
.desc("count of cycles dispatch stalled for serializing inst")
.flags(Stats::total)
;
dispatchRate
.name(name() + ".DIS:rate")
.desc("dispatched insts per cycle")
.flags(Stats::total)
;
dispatchRate = dispatchCountStat / cpu->numCycles;
regIntFull
.name(name() + ".REG:int:full")
.desc("number of cycles where there were no INT registers")
;
regFpFull
.name(name() + ".REG:fp:full")
.desc("number of cycles where there were no FP registers")
;
IFQLatency = IFQOccupancy / dispatchRate;
branchPred.regStats();
}
template <class Impl>
void
FrontEnd<Impl>::tick()
{
if (switchedOut)
return;
// @todo: Maybe I want to just have direct communication...
if (fromCommit->doneSeqNum) {
branchPred.update(fromCommit->doneSeqNum, 0);
}
IFQCount += instBufferSize;
IFQFcount += instBufferSize == maxInstBufferSize;
// Fetch cache line
if (status == IcacheMissComplete) {
cacheBlkValid = true;
status = Running;
if (barrierInst)
status = SerializeBlocked;
if (freeRegs <= 0)
status = RenameBlocked;
checkBE();
} else if (status == IcacheMissStall) {
DPRINTF(FE, "Still in Icache miss stall.\n");
icacheStallCycles++;
return;
}
if (status == RenameBlocked || status == SerializeBlocked ||
status == TrapPending || status == BEBlocked) {
// Will cause a one cycle bubble between changing state and
// restarting.
DPRINTF(FE, "In blocked status.\n");
fetchBlockedCycles++;
if (status == SerializeBlocked) {
dispatchSerializeStallCycles++;
}
updateStatus();
return;
} else if (status == QuiescePending) {
DPRINTF(FE, "Waiting for quiesce to execute or get squashed.\n");
return;
} else if (status != IcacheMissComplete) {
if (fetchCacheLineNextCycle) {
Fault fault = fetchCacheLine();
if (fault != NoFault) {
handleFault(fault);
fetchFault = fault;
return;
}
fetchCacheLineNextCycle = false;
}
// If miss, stall until it returns.
if (status == IcacheMissStall) {
// Tell CPU to not tick me for now.
return;
}
}
fetchCycles++;
int num_inst = 0;
// Otherwise loop and process instructions.
// One way to hack infinite width is to set width and maxInstBufferSize
// both really high. Inelegant, but probably will work.
while (num_inst < width &&
instBufferSize < maxInstBufferSize) {
// Get instruction from cache line.
DynInstPtr inst = getInstFromCacheline();
if (!inst) {
// PC is no longer in the cache line, end fetch.
// Might want to check this at the end of the cycle so that
// there's no cycle lost to checking for a new cache line.
DPRINTF(FE, "Need to get new cache line\n");
fetchCacheLineNextCycle = true;
break;
}
// if (generalizeFetch) {
processInst(inst);
if (status == SerializeBlocked) {
break;
}
// Possibly push into a time buffer that estimates the front end
// latency
instBuffer.push_back(inst);
++instBufferSize;
++num_inst;
// } else {
// fetch(num_inst);
// decode(num_inst);
// rename(num_inst);
// }
#if FULL_SYSTEM
if (inst->isQuiesce()) {
warn("%lli: Quiesce instruction encountered, halting fetch!", curTick);
status = QuiescePending;
break;
}
#endif
if (inst->predTaken()) {
// Start over with tick?
break;
} else if (freeRegs <= 0) {
DPRINTF(FE, "Ran out of free registers to rename to!\n");
status = RenameBlocked;
break;
} else if (serializeNext) {
break;
}
}
fetchNisnDist.sample(num_inst);
checkBE();
DPRINTF(FE, "Num insts processed: %i, Inst Buffer size: %i, Free "
"Regs %i\n", num_inst, instBufferSize, freeRegs);
}
template <class Impl>
Fault
FrontEnd<Impl>::fetchCacheLine()
{
// Read a cache line, based on the current PC.
#if FULL_SYSTEM
// Flag to say whether or not address is physical addr.
unsigned flags = cpu->inPalMode(PC) ? PHYSICAL : 0;
#else
unsigned flags = 0;
#endif // FULL_SYSTEM
Fault fault = NoFault;
if (interruptPending && flags == 0) {
return fault;
}
// Align the fetch PC so it's at the start of a cache block.
Addr fetch_PC = icacheBlockAlignPC(PC);
DPRINTF(FE, "Fetching cache line starting at %#x.\n", fetch_PC);
// Setup the memReq to do a read of the first isntruction's address.
// Set the appropriate read size and flags as well.
memReq = new MemReq();
memReq->asid = 0;
memReq->thread_num = 0;
memReq->data = new uint8_t[64];
memReq->xc = xc;
memReq->cmd = Read;
memReq->reset(fetch_PC, cacheBlkSize, flags);
// Translate the instruction request.
fault = cpu->translateInstReq(memReq);
// In the case of faults, the fetch stage may need to stall and wait
// on what caused the fetch (ITB or Icache miss).
// assert(fault == NoFault);
// Now do the timing access to see whether or not the instruction
// exists within the cache.
if (icacheInterface && fault == NoFault) {
#if FULL_SYSTEM
if (cpu->system->memctrl->badaddr(memReq->paddr) ||
memReq->flags & UNCACHEABLE) {
DPRINTF(FE, "Fetch: Bad address %#x (hopefully on a "
"misspeculating path!",
memReq->paddr);
return TheISA::genMachineCheckFault();
}
#endif
memReq->completionEvent = NULL;
memReq->time = curTick;
fault = cpu->mem->read(memReq, cacheData);
MemAccessResult res = icacheInterface->access(memReq);
// If the cache missed then schedule an event to wake
// up this stage once the cache miss completes.
if (icacheInterface->doEvents() && res != MA_HIT) {
memReq->completionEvent = new ICacheCompletionEvent(memReq, this);
status = IcacheMissStall;
cacheBlkValid = false;
DPRINTF(FE, "Cache miss.\n");
} else {
DPRINTF(FE, "Cache hit.\n");
cacheBlkValid = true;
// memcpy(cacheData, memReq->data, memReq->size);
}
}
// Note that this will set the cache block PC a bit earlier than it should
// be set.
cacheBlkPC = fetch_PC;
++fetchedCacheLines;
DPRINTF(FE, "Done fetching cache line.\n");
return fault;
}
template <class Impl>
void
FrontEnd<Impl>::processInst(DynInstPtr &inst)
{
if (processBarriers(inst)) {
return;
}
Addr inst_PC = inst->readPC();
// BPredInfo bp_info = branchPred.lookup(inst_PC);
if (!inst->isControl()) {
inst->setPredTarg(inst->readNextPC());
} else {
fetchedBranches++;
if (branchPred.predict(inst, inst_PC, inst->threadNumber)) {
predictedBranches++;
}
}
Addr next_PC = inst->readPredTarg();
DPRINTF(FE, "[sn:%lli] Predicted and processed inst PC %#x, next PC "
"%#x\n", inst->seqNum, inst_PC, next_PC);
// inst->setNextPC(next_PC);
// inst->setBPredInfo(bp_info);
// Not sure where I should set this
PC = next_PC;
renameInst(inst);
}
template <class Impl>
bool
FrontEnd<Impl>::processBarriers(DynInstPtr &inst)
{
if (serializeNext) {
inst->setSerializeBefore();
serializeNext = false;
} else if (!inst->isSerializing()) {
return false;
}
if (inst->isSerializeBefore() && !inst->isSerializeHandled()) {
DPRINTF(FE, "Serialize before instruction encountered.\n");
if (!inst->isTempSerializeBefore()) {
dispatchedSerializing++;
inst->setSerializeHandled();
} else {
dispatchedTempSerializing++;
}
// Change status over to SerializeBlocked so that other stages know
// what this is blocked on.
status = SerializeBlocked;
barrierInst = inst;
return true;
} else if (inst->isSerializeAfter() && !inst->isSerializeHandled()) {
DPRINTF(FE, "Serialize after instruction encountered.\n");
inst->setSerializeHandled();
dispatchedSerializing++;
serializeNext = true;
return false;
}
return false;
}
template <class Impl>
void
FrontEnd<Impl>::handleFault(Fault &fault)
{
DPRINTF(FE, "Fault at fetch, telling commit\n");
// backEnd->fetchFault(fault);
// We're blocked on the back end until it handles this fault.
status = TrapPending;
// Get a sequence number.
InstSeqNum inst_seq = getAndIncrementInstSeq();
// We will use a nop in order to carry the fault.
ExtMachInst ext_inst = TheISA::NoopMachInst;
// Create a new DynInst from the dummy nop.
DynInstPtr instruction = new DynInst(ext_inst, PC,
PC+sizeof(MachInst),
inst_seq, cpu);
instruction->setPredTarg(instruction->readNextPC());
// instruction->setThread(tid);
// instruction->setASID(tid);
instruction->setState(thread);
instruction->traceData = NULL;
instruction->fault = fault;
instruction->setCanIssue();
instBuffer.push_back(instruction);
++instBufferSize;
}
template <class Impl>
void
FrontEnd<Impl>::squash(const InstSeqNum &squash_num, const Addr &next_PC,
const bool is_branch, const bool branch_taken)
{
DPRINTF(FE, "Squashing from [sn:%lli], setting PC to %#x\n",
squash_num, next_PC);
if (fetchFault != NoFault)
fetchFault = NoFault;
while (!instBuffer.empty() &&
instBuffer.back()->seqNum > squash_num) {
DynInstPtr inst = instBuffer.back();
DPRINTF(FE, "Squashing instruction [sn:%lli] PC %#x\n",
inst->seqNum, inst->readPC());
inst->clearDependents();
instBuffer.pop_back();
--instBufferSize;
// Fix up branch predictor if necessary.
// branchPred.undo(inst->getBPredInfo());
freeRegs+= inst->numDestRegs();
}
// Copy over rename table from the back end.
renameTable.copyFrom(backEnd->renameTable);
PC = next_PC;
// Update BP with proper information.
if (is_branch) {
branchPred.squash(squash_num, next_PC, branch_taken, 0);
} else {
branchPred.squash(squash_num, 0);
}
// Clear the icache miss if it's outstanding.
if (status == IcacheMissStall && icacheInterface) {
DPRINTF(FE, "Squashing outstanding Icache miss.\n");
// icacheInterface->squash(0);
memReq = NULL;
}
if (status == SerializeBlocked) {
assert(barrierInst->seqNum > squash_num);
barrierInst = NULL;
}
// Unless this squash originated from the front end, we're probably
// in running mode now.
// Actually might want to make this latency dependent.
status = Running;
fetchCacheLineNextCycle = true;
}
template <class Impl>
typename Impl::DynInstPtr
FrontEnd<Impl>::getInst()
{
if (instBufferSize == 0) {
return NULL;
}
DynInstPtr inst = instBuffer.front();
instBuffer.pop_front();
--instBufferSize;
dispatchCountStat++;
return inst;
}
template <class Impl>
void
FrontEnd<Impl>::processCacheCompletion(MemReqPtr &req)
{
DPRINTF(FE, "Processing cache completion\n");
// Do something here.
if (status != IcacheMissStall ||
req != memReq ||
switchedOut) {
DPRINTF(FE, "Previous fetch was squashed.\n");
fetchIcacheSquashes++;
return;
}
status = IcacheMissComplete;
/* if (checkStall(tid)) {
fetchStatus[tid] = Blocked;
} else {
fetchStatus[tid] = IcacheMissComplete;
}
*/
// memcpy(cacheData, memReq->data, memReq->size);
// Reset the completion event to NULL.
// memReq->completionEvent = NULL;
memReq = NULL;
}
template <class Impl>
void
FrontEnd<Impl>::addFreeRegs(int num_freed)
{
if (status == RenameBlocked && freeRegs + num_freed > 0) {
status = Running;
}
DPRINTF(FE, "Adding %i freed registers\n", num_freed);
freeRegs+= num_freed;
// assert(freeRegs <= numPhysRegs);
if (freeRegs > numPhysRegs)
freeRegs = numPhysRegs;
}
template <class Impl>
bool
FrontEnd<Impl>::updateStatus()
{
// bool rename_block = freeRegs <= 0;
bool serialize_block = !backEnd->robEmpty() || instBufferSize;
bool be_block = cpu->decoupledFrontEnd ? false : backEnd->isBlocked();
bool ret_val = false;
/*
// Should already be handled through addFreeRegs function
if (status == RenameBlocked && !rename_block) {
status = Running;
ret_val = true;
}
*/
if (status == SerializeBlocked && !serialize_block) {
status = SerializeComplete;
ret_val = true;
}
if (status == BEBlocked && !be_block) {
if (barrierInst) {
status = SerializeBlocked;
} else {
status = Running;
}
ret_val = true;
}
return ret_val;
}
template <class Impl>
void
FrontEnd<Impl>::checkBE()
{
bool be_block = cpu->decoupledFrontEnd ? false : backEnd->isBlocked();
if (be_block) {
if (status == Running || status == Idle) {
status = BEBlocked;
}
}
}
template <class Impl>
typename Impl::DynInstPtr
FrontEnd<Impl>::getInstFromCacheline()
{
if (status == SerializeComplete) {
DynInstPtr inst = barrierInst;
status = Running;
barrierInst = NULL;
inst->clearSerializeBefore();
return inst;
}
InstSeqNum inst_seq;
MachInst inst;
// @todo: Fix this magic number used here to handle word offset (and
// getting rid of PAL bit)
unsigned offset = (PC & cacheBlkMask) & ~3;
// PC of inst is not in this cache block
if (PC >= (cacheBlkPC + cacheBlkSize) || PC < cacheBlkPC || !cacheBlkValid) {
// DPRINTF(OoOCPU, "OoOCPU: PC is not in this cache block\n");
// DPRINTF(OoOCPU, "OoOCPU: PC: %#x, cacheBlkPC: %#x, cacheBlkValid: %i",
// PC, cacheBlkPC, cacheBlkValid);
// panic("Instruction not in cache line or cache line invalid!");
return NULL;
}
//////////////////////////
// Fetch one instruction
//////////////////////////
// Get a sequence number.
inst_seq = getAndIncrementInstSeq();
// Make sure this is a valid index.
assert(offset <= cacheBlkSize - sizeof(MachInst));
// Get the instruction from the array of the cache line.
inst = htog(*reinterpret_cast<MachInst *>(&cacheData[offset]));
ExtMachInst decode_inst = TheISA::makeExtMI(inst, PC);
// Create a new DynInst from the instruction fetched.
DynInstPtr instruction = new DynInst(decode_inst, PC, PC+sizeof(MachInst),
inst_seq, cpu);
instruction->setState(thread);
DPRINTF(FE, "Instruction [sn:%lli] created, with PC %#x\n%s\n",
inst_seq, instruction->readPC(),
instruction->staticInst->disassemble(PC));
instruction->traceData =
Trace::getInstRecord(curTick, xc, cpu,
instruction->staticInst,
instruction->readPC(), 0);
// Increment stat of fetched instructions.
++fetchedInsts;
return instruction;
}
template <class Impl>
void
FrontEnd<Impl>::renameInst(DynInstPtr &inst)
{
DynInstPtr src_inst = NULL;
int num_src_regs = inst->numSrcRegs();
if (num_src_regs == 0) {
inst->setCanIssue();
} else {
for (int i = 0; i < num_src_regs; ++i) {
src_inst = renameTable[inst->srcRegIdx(i)];
inst->setSrcInst(src_inst, i);
DPRINTF(FE, "[sn:%lli]: Src reg %i is inst [sn:%lli]\n",
inst->seqNum, (int)inst->srcRegIdx(i), src_inst->seqNum);
if (src_inst->isResultReady()) {
DPRINTF(FE, "Reg ready.\n");
inst->markSrcRegReady(i);
} else {
DPRINTF(FE, "Adding to dependent list.\n");
src_inst->addDependent(inst);
}
}
}
for (int i = 0; i < inst->numDestRegs(); ++i) {
RegIndex idx = inst->destRegIdx(i);
DPRINTF(FE, "Dest reg %i is now inst [sn:%lli], was previously "
"[sn:%lli]\n",
(int)inst->destRegIdx(i), inst->seqNum,
renameTable[idx]->seqNum);
inst->setPrevDestInst(renameTable[idx], i);
renameTable[idx] = inst;
--freeRegs;
}
}
template <class Impl>
void
FrontEnd<Impl>::wakeFromQuiesce()
{
DPRINTF(FE, "Waking up from quiesce\n");
// Hopefully this is safe
status = Running;
}
template <class Impl>
void
FrontEnd<Impl>::switchOut()
{
switchedOut = true;
cpu->signalSwitched();
}
template <class Impl>
void
FrontEnd<Impl>::doSwitchOut()
{
memReq = NULL;
squash(0, 0);
instBuffer.clear();
instBufferSize = 0;
status = Idle;
}
template <class Impl>
void
FrontEnd<Impl>::takeOverFrom(ExecContext *old_xc)
{
assert(freeRegs == numPhysRegs);
fetchCacheLineNextCycle = true;
cacheBlkValid = false;
#if !FULL_SYSTEM
// pTable = params->pTable;
#endif
fetchFault = NoFault;
serializeNext = false;
barrierInst = NULL;
status = Running;
switchedOut = false;
interruptPending = false;
}
template <class Impl>
void
FrontEnd<Impl>::dumpInsts()
{
cprintf("instBuffer size: %i\n", instBuffer.size());
InstBuffIt buff_it = instBuffer.begin();
for (int num = 0; buff_it != instBuffer.end(); num++) {
cprintf("Instruction:%i\nPC:%#x\n[tid:%i]\n[sn:%lli]\nIssued:%i\n"
"Squashed:%i\n\n",
num, (*buff_it)->readPC(), (*buff_it)->threadNumber,
(*buff_it)->seqNum, (*buff_it)->isIssued(),
(*buff_it)->isSquashed());
buff_it++;
}
}
template <class Impl>
FrontEnd<Impl>::ICacheCompletionEvent::ICacheCompletionEvent(MemReqPtr &_req, FrontEnd *fe)
: Event(&mainEventQueue, Delayed_Writeback_Pri), req(_req), frontEnd(fe)
{
this->setFlags(Event::AutoDelete);
}
template <class Impl>
void
FrontEnd<Impl>::ICacheCompletionEvent::process()
{
frontEnd->processCacheCompletion(req);
}
template <class Impl>
const char *
FrontEnd<Impl>::ICacheCompletionEvent::description()
{
return "ICache completion event";
}