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.