24.22.6.3. gem5 ExecContext
ExecContext
gets used in gem5 instruction definitions, e.g.:
build/ARM/arch/arm/generated/exec-ns.cc.inc
contains:
Fault Mul::execute( ExecContext *xc, Trace::InstRecord *traceData) const
It contains methods to allow interacting with CPU state from inside instruction execution, notably reading and writing from/to registers.
For example, the ARM mul
instruction uses ExecContext
to read the input operands, multiply them, and write to the output:
Fault Mul::execute( ExecContext *xc, Trace::InstRecord *traceData) const { Fault fault = NoFault; uint64_t resTemp = 0; resTemp = resTemp; uint32_t OptCondCodesNZ = 0; uint32_t OptCondCodesC = 0; uint32_t OptCondCodesV = 0; uint32_t Reg0 = 0; uint32_t Reg1 = 0; uint32_t Reg2 = 0; OptCondCodesNZ = xc->readCCRegOperand(this, 0); OptCondCodesC = xc->readCCRegOperand(this, 1); OptCondCodesV = xc->readCCRegOperand(this, 2); Reg1 = ((reg1 == PCReg) ? readPC(xc) : xc->readIntRegOperand(this, 3)); Reg2 = ((reg2 == PCReg) ? readPC(xc) : xc->readIntRegOperand(this, 4)); if (testPredicate(OptCondCodesNZ, OptCondCodesC, OptCondCodesV, condCode)/*auto*/) { Reg0 = resTemp = Reg1 * Reg2;; if (fault == NoFault) { { uint32_t final_val = Reg0; ((reg0 == PCReg) ? setNextPC(xc, Reg0) : xc->setIntRegOperand(this, 0, Reg0)); if (traceData) { traceData->setData(final_val); } }; } } else { xc->setPredicate(false); } return fault; }
ExecContext
is however basically just a wrapper that forwards to other classes that actually contain the data in a microarchitectural neutral manner. For example, in SimpleExecContext
:
/** Reads an integer register. */ RegVal readIntRegOperand(const StaticInst *si, int idx) override { numIntRegReads++; const RegId& reg = si->srcRegIdx(idx); assert(reg.isIntReg()); return thread->readIntReg(reg.index()); }
So we see that this just does some register position bookkeeping needed for instruction execution, but the actual data comes from SimpleThread::readIntReg
, which is a specialization of gem5 ThreadContext
.
ExecContext
is a fully virtual class. The hierarchy is:
-
ExecContext
-
SimpleExecContext
-
Minor::MinorExecContext
-
BaseDynInst
-
BaseO3DynInst
-
-
If we follow SimpleExecContext
creation for example, we see:
class BaseSimpleCPU : public BaseCPU { std::vector<SimpleExecContext*> threadInfo;
and:
BaseSimpleCPU::BaseSimpleCPU(BaseSimpleCPUParams *p) : BaseCPU(p), curThread(0), branchPred(p->branchPred), traceData(NULL), inst(), _status(Idle) { SimpleThread *thread; for (unsigned i = 0; i < numThreads; i++) { if (FullSystem) { thread = new SimpleThread(this, i, p->system, p->itb, p->dtb, p->isa[i]); } else { thread = new SimpleThread(this, i, p->system, p->workload[i], p->itb, p->dtb, p->isa[i]); } threadInfo.push_back(new SimpleExecContext(this, thread)); ThreadContext *tc = thread->getTC(); threadContexts.push_back(tc); }
therefore there is one ExecContext
for each ThreadContext
, and each ExecContext
knows about its own ThreadContext
.
This makes sense, since each ThreadContext
represents one CPU register set, and therefore needs a separate ExecContext
which allows instruction implementations to access those registers.