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.