Files
gem5/src/cpu/o3/commit.cc
Melissa Jost e85cf4f717 cpu: Move commit stats from simple to base cpu
Created stat group CommitCPUStats in BaseCPU and moved stats from the
simple cpu model.

The stats moved from SImpleCPU are numCondCtrlInsts, numFpInsts,
numIntInsts, numLoadInsts, numStoreInsts, numVecInsts.

Moved committedControl of MinorCPU to BaseCPU::CommittedCPUStats. In
MinorCPU, this stat was a 2D vector, where the first dimension is the
thread ID. In base it is now  a 1D vector that is tied to a thread ID
via the commitStats vector.

The committedControl stat vector in CommitCPUStats is updated in the
same way in all CPU models. The function updateComCtrlStats will
update committedControl and the CPU models will call this function
instead of updating committedControl directly. This function takes
a StaticInstPtr as input, which Simple, Minor, and O3 CPU models are
able to provide.

Removed stat "branches" from O3 commit stage. This stat duplicates
BaseCPU::CommittedCPUStats::committedControl::IsControl.

O3 commit stats floating, integer, loads, memRefs, vectorInstructions
are replaced by numFpInsts, numIntInsts, numLoadInsts, numMemRefs,
numVecInsts from BaseCPU::CommitCPUStats respectively. Implemented
numStoreInsts from BaseCPU::commitCPUStats for O3 commit stage.

Change-Id: I362cec51513a404de56a02b450d7663327be20f5
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/67391
Tested-by: kokoro <noreply+kokoro@google.com>
Maintainer: Bobby Bruce <bbruce@ucdavis.edu>
Reviewed-by: Bobby Bruce <bbruce@ucdavis.edu>
2023-03-07 00:17:25 +00:00

1505 lines
48 KiB
C++

/*
* Copyright 2014 Google, Inc.
* Copyright (c) 2010-2014, 2017, 2020 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Copyright (c) 2004-2005 The Regents of The University of Michigan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cpu/o3/commit.hh"
#include <algorithm>
#include <set>
#include <string>
#include "base/compiler.hh"
#include "base/loader/symtab.hh"
#include "base/logging.hh"
#include "cpu/base.hh"
#include "cpu/checker/cpu.hh"
#include "cpu/exetrace.hh"
#include "cpu/o3/cpu.hh"
#include "cpu/o3/dyn_inst.hh"
#include "cpu/o3/limits.hh"
#include "cpu/o3/thread_state.hh"
#include "cpu/timebuf.hh"
#include "debug/Activity.hh"
#include "debug/Commit.hh"
#include "debug/CommitRate.hh"
#include "debug/Drain.hh"
#include "debug/ExecFaulting.hh"
#include "debug/HtmCpu.hh"
#include "debug/O3PipeView.hh"
#include "params/BaseO3CPU.hh"
#include "sim/faults.hh"
#include "sim/full_system.hh"
namespace gem5
{
namespace o3
{
void
Commit::processTrapEvent(ThreadID tid)
{
// This will get reset by commit if it was switched out at the
// time of this event processing.
trapSquash[tid] = true;
}
Commit::Commit(CPU *_cpu, const BaseO3CPUParams &params)
: commitPolicy(params.smtCommitPolicy),
cpu(_cpu),
iewToCommitDelay(params.iewToCommitDelay),
commitToIEWDelay(params.commitToIEWDelay),
renameToROBDelay(params.renameToROBDelay),
fetchToCommitDelay(params.commitToFetchDelay),
renameWidth(params.renameWidth),
commitWidth(params.commitWidth),
numThreads(params.numThreads),
drainPending(false),
drainImminent(false),
trapLatency(params.trapLatency),
canHandleInterrupts(true),
avoidQuiesceLiveLock(false),
stats(_cpu, this)
{
if (commitWidth > MaxWidth)
fatal("commitWidth (%d) is larger than compiled limit (%d),\n"
"\tincrease MaxWidth in src/cpu/o3/limits.hh\n",
commitWidth, static_cast<int>(MaxWidth));
_status = Active;
_nextStatus = Inactive;
if (commitPolicy == CommitPolicy::RoundRobin) {
//Set-Up Priority List
for (ThreadID tid = 0; tid < numThreads; tid++) {
priority_list.push_back(tid);
}
}
for (ThreadID tid = 0; tid < MaxThreads; tid++) {
commitStatus[tid] = Idle;
changedROBNumEntries[tid] = false;
trapSquash[tid] = false;
tcSquash[tid] = false;
squashAfterInst[tid] = nullptr;
pc[tid].reset(params.isa[0]->newPCState());
youngestSeqNum[tid] = 0;
lastCommitedSeqNum[tid] = 0;
trapInFlight[tid] = false;
committedStores[tid] = false;
checkEmptyROB[tid] = false;
renameMap[tid] = nullptr;
htmStarts[tid] = 0;
htmStops[tid] = 0;
}
interrupt = NoFault;
}
std::string Commit::name() const { return cpu->name() + ".commit"; }
void
Commit::regProbePoints()
{
ppCommit = new ProbePointArg<DynInstPtr>(
cpu->getProbeManager(), "Commit");
ppCommitStall = new ProbePointArg<DynInstPtr>(
cpu->getProbeManager(), "CommitStall");
ppSquash = new ProbePointArg<DynInstPtr>(
cpu->getProbeManager(), "Squash");
}
Commit::CommitStats::CommitStats(CPU *cpu, Commit *commit)
: statistics::Group(cpu, "commit"),
ADD_STAT(commitSquashedInsts, statistics::units::Count::get(),
"The number of squashed insts skipped by commit"),
ADD_STAT(commitNonSpecStalls, statistics::units::Count::get(),
"The number of times commit has been forced to stall to "
"communicate backwards"),
ADD_STAT(branchMispredicts, statistics::units::Count::get(),
"The number of times a branch was mispredicted"),
ADD_STAT(numCommittedDist, statistics::units::Count::get(),
"Number of insts commited each cycle"),
ADD_STAT(instsCommitted, statistics::units::Count::get(),
"Number of instructions committed"),
ADD_STAT(opsCommitted, statistics::units::Count::get(),
"Number of ops (including micro ops) committed"),
ADD_STAT(amos, statistics::units::Count::get(),
"Number of atomic instructions committed"),
ADD_STAT(membars, statistics::units::Count::get(),
"Number of memory barriers committed"),
ADD_STAT(functionCalls, statistics::units::Count::get(),
"Number of function calls committed."),
ADD_STAT(committedInstType, statistics::units::Count::get(),
"Class of committed instruction"),
ADD_STAT(commitEligibleSamples, statistics::units::Cycle::get(),
"number cycles where commit BW limit reached")
{
using namespace statistics;
commitSquashedInsts.prereq(commitSquashedInsts);
commitNonSpecStalls.prereq(commitNonSpecStalls);
branchMispredicts.prereq(branchMispredicts);
numCommittedDist
.init(0,commit->commitWidth,1)
.flags(statistics::pdf);
instsCommitted
.init(cpu->numThreads)
.flags(total);
opsCommitted
.init(cpu->numThreads)
.flags(total);
amos
.init(cpu->numThreads)
.flags(total);
membars
.init(cpu->numThreads)
.flags(total);
functionCalls
.init(commit->numThreads)
.flags(total);
committedInstType
.init(commit->numThreads,enums::Num_OpClass)
.flags(total | pdf | dist);
committedInstType.ysubnames(enums::OpClassStrings);
}
void
Commit::setThreads(std::vector<ThreadState *> &threads)
{
thread = threads;
}
void
Commit::setTimeBuffer(TimeBuffer<TimeStruct> *tb_ptr)
{
timeBuffer = tb_ptr;
// Setup wire to send information back to IEW.
toIEW = timeBuffer->getWire(0);
// Setup wire to read data from IEW (for the ROB).
robInfoFromIEW = timeBuffer->getWire(-iewToCommitDelay);
}
void
Commit::setFetchQueue(TimeBuffer<FetchStruct> *fq_ptr)
{
fetchQueue = fq_ptr;
// Setup wire to get instructions from rename (for the ROB).
fromFetch = fetchQueue->getWire(-fetchToCommitDelay);
}
void
Commit::setRenameQueue(TimeBuffer<RenameStruct> *rq_ptr)
{
renameQueue = rq_ptr;
// Setup wire to get instructions from rename (for the ROB).
fromRename = renameQueue->getWire(-renameToROBDelay);
}
void
Commit::setIEWQueue(TimeBuffer<IEWStruct> *iq_ptr)
{
iewQueue = iq_ptr;
// Setup wire to get instructions from IEW.
fromIEW = iewQueue->getWire(-iewToCommitDelay);
}
void
Commit::setIEWStage(IEW *iew_stage)
{
iewStage = iew_stage;
}
void
Commit::setActiveThreads(std::list<ThreadID> *at_ptr)
{
activeThreads = at_ptr;
}
void
Commit::setRenameMap(UnifiedRenameMap rm_ptr[])
{
for (ThreadID tid = 0; tid < numThreads; tid++)
renameMap[tid] = &rm_ptr[tid];
}
void Commit::setROB(ROB *rob_ptr) { rob = rob_ptr; }
void
Commit::startupStage()
{
rob->setActiveThreads(activeThreads);
rob->resetEntries();
// Broadcast the number of free entries.
for (ThreadID tid = 0; tid < numThreads; tid++) {
toIEW->commitInfo[tid].usedROB = true;
toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid);
toIEW->commitInfo[tid].emptyROB = true;
}
// Commit must broadcast the number of free entries it has at the
// start of the simulation, so it starts as active.
cpu->activateStage(CPU::CommitIdx);
cpu->activityThisCycle();
}
void
Commit::clearStates(ThreadID tid)
{
commitStatus[tid] = Idle;
changedROBNumEntries[tid] = false;
checkEmptyROB[tid] = false;
trapInFlight[tid] = false;
committedStores[tid] = false;
trapSquash[tid] = false;
tcSquash[tid] = false;
pc[tid].reset(cpu->tcBase(tid)->getIsaPtr()->newPCState());
lastCommitedSeqNum[tid] = 0;
squashAfterInst[tid] = NULL;
}
void Commit::drain() { drainPending = true; }
void
Commit::drainResume()
{
drainPending = false;
drainImminent = false;
}
void
Commit::drainSanityCheck() const
{
assert(isDrained());
rob->drainSanityCheck();
// hardware transactional memory
// cannot drain partially through a transaction
for (ThreadID tid = 0; tid < numThreads; tid++) {
if (executingHtmTransaction(tid)) {
panic("cannot drain partially through a HTM transaction");
}
}
}
bool
Commit::isDrained() const
{
/* Make sure no one is executing microcode. There are two reasons
* for this:
* - Hardware virtualized CPUs can't switch into the middle of a
* microcode sequence.
* - The current fetch implementation will most likely get very
* confused if it tries to start fetching an instruction that
* is executing in the middle of a ucode sequence that changes
* address mappings. This can happen on for example x86.
*/
for (ThreadID tid = 0; tid < numThreads; tid++) {
if (pc[tid]->microPC() != 0)
return false;
}
/* Make sure that all instructions have finished committing before
* declaring the system as drained. We want the pipeline to be
* completely empty when we declare the CPU to be drained. This
* makes debugging easier since CPU handover and restoring from a
* checkpoint with a different CPU should have the same timing.
*/
return rob->isEmpty() &&
interrupt == NoFault;
}
void
Commit::takeOverFrom()
{
_status = Active;
_nextStatus = Inactive;
for (ThreadID tid = 0; tid < numThreads; tid++) {
commitStatus[tid] = Idle;
changedROBNumEntries[tid] = false;
trapSquash[tid] = false;
tcSquash[tid] = false;
squashAfterInst[tid] = NULL;
}
rob->takeOverFrom();
}
void
Commit::deactivateThread(ThreadID tid)
{
std::list<ThreadID>::iterator thread_it = std::find(priority_list.begin(),
priority_list.end(), tid);
if (thread_it != priority_list.end()) {
priority_list.erase(thread_it);
}
}
bool
Commit::executingHtmTransaction(ThreadID tid) const
{
if (tid == InvalidThreadID)
return false;
else
return (htmStarts[tid] > htmStops[tid]);
}
void
Commit::resetHtmStartsStops(ThreadID tid)
{
if (tid != InvalidThreadID)
{
htmStarts[tid] = 0;
htmStops[tid] = 0;
}
}
void
Commit::updateStatus()
{
// reset ROB changed variable
std::list<ThreadID>::iterator threads = activeThreads->begin();
std::list<ThreadID>::iterator end = activeThreads->end();
while (threads != end) {
ThreadID tid = *threads++;
changedROBNumEntries[tid] = false;
// Also check if any of the threads has a trap pending
if (commitStatus[tid] == TrapPending ||
commitStatus[tid] == FetchTrapPending) {
_nextStatus = Active;
}
}
if (_nextStatus == Inactive && _status == Active) {
DPRINTF(Activity, "Deactivating stage.\n");
cpu->deactivateStage(CPU::CommitIdx);
} else if (_nextStatus == Active && _status == Inactive) {
DPRINTF(Activity, "Activating stage.\n");
cpu->activateStage(CPU::CommitIdx);
}
_status = _nextStatus;
}
bool
Commit::changedROBEntries()
{
std::list<ThreadID>::iterator threads = activeThreads->begin();
std::list<ThreadID>::iterator end = activeThreads->end();
while (threads != end) {
ThreadID tid = *threads++;
if (changedROBNumEntries[tid]) {
return true;
}
}
return false;
}
size_t
Commit::numROBFreeEntries(ThreadID tid)
{
return rob->numFreeEntries(tid);
}
void
Commit::generateTrapEvent(ThreadID tid, Fault inst_fault)
{
DPRINTF(Commit, "Generating trap event for [tid:%i]\n", tid);
EventFunctionWrapper *trap = new EventFunctionWrapper(
[this, tid]{ processTrapEvent(tid); },
"Trap", true, Event::CPU_Tick_Pri);
Cycles latency = std::dynamic_pointer_cast<SyscallRetryFault>(inst_fault) ?
cpu->syscallRetryLatency : trapLatency;
// hardware transactional memory
if (inst_fault != nullptr &&
std::dynamic_pointer_cast<GenericHtmFailureFault>(inst_fault)) {
// TODO
// latency = default abort/restore latency
// could also do some kind of exponential back off if desired
}
cpu->schedule(trap, cpu->clockEdge(latency));
trapInFlight[tid] = true;
thread[tid]->trapPending = true;
}
void
Commit::generateTCEvent(ThreadID tid)
{
assert(!trapInFlight[tid]);
DPRINTF(Commit, "Generating TC squash event for [tid:%i]\n", tid);
tcSquash[tid] = true;
}
void
Commit::squashAll(ThreadID tid)
{
// If we want to include the squashing instruction in the squash,
// then use one older sequence number.
// Hopefully this doesn't mess things up. Basically I want to squash
// all instructions of this thread.
InstSeqNum squashed_inst = rob->isEmpty(tid) ?
lastCommitedSeqNum[tid] : rob->readHeadInst(tid)->seqNum - 1;
// All younger instructions will be squashed. Set the sequence
// number as the youngest instruction in the ROB (0 in this case.
// Hopefully nothing breaks.)
youngestSeqNum[tid] = lastCommitedSeqNum[tid];
rob->squash(squashed_inst, tid);
changedROBNumEntries[tid] = true;
// Send back the sequence number of the squashed instruction.
toIEW->commitInfo[tid].doneSeqNum = squashed_inst;
// Send back the squash signal to tell stages that they should
// squash.
toIEW->commitInfo[tid].squash = true;
// Send back the rob squashing signal so other stages know that
// the ROB is in the process of squashing.
toIEW->commitInfo[tid].robSquashing = true;
toIEW->commitInfo[tid].mispredictInst = NULL;
toIEW->commitInfo[tid].squashInst = NULL;
set(toIEW->commitInfo[tid].pc, pc[tid]);
}
void
Commit::squashFromTrap(ThreadID tid)
{
squashAll(tid);
DPRINTF(Commit, "Squashing from trap, restarting at PC %s\n", *pc[tid]);
thread[tid]->trapPending = false;
thread[tid]->noSquashFromTC = false;
trapInFlight[tid] = false;
trapSquash[tid] = false;
commitStatus[tid] = ROBSquashing;
cpu->activityThisCycle();
}
void
Commit::squashFromTC(ThreadID tid)
{
squashAll(tid);
DPRINTF(Commit, "Squashing from TC, restarting at PC %s\n", *pc[tid]);
thread[tid]->noSquashFromTC = false;
assert(!thread[tid]->trapPending);
commitStatus[tid] = ROBSquashing;
cpu->activityThisCycle();
tcSquash[tid] = false;
}
void
Commit::squashFromSquashAfter(ThreadID tid)
{
DPRINTF(Commit, "Squashing after squash after request, "
"restarting at PC %s\n", *pc[tid]);
squashAll(tid);
// Make sure to inform the fetch stage of which instruction caused
// the squash. It'll try to re-fetch an instruction executing in
// microcode unless this is set.
toIEW->commitInfo[tid].squashInst = squashAfterInst[tid];
squashAfterInst[tid] = NULL;
commitStatus[tid] = ROBSquashing;
cpu->activityThisCycle();
}
void
Commit::squashAfter(ThreadID tid, const DynInstPtr &head_inst)
{
DPRINTF(Commit, "Executing squash after for [tid:%i] inst [sn:%llu]\n",
tid, head_inst->seqNum);
assert(!squashAfterInst[tid] || squashAfterInst[tid] == head_inst);
commitStatus[tid] = SquashAfterPending;
squashAfterInst[tid] = head_inst;
}
void
Commit::tick()
{
wroteToTimeBuffer = false;
_nextStatus = Inactive;
if (activeThreads->empty())
return;
std::list<ThreadID>::iterator threads = activeThreads->begin();
std::list<ThreadID>::iterator end = activeThreads->end();
// Check if any of the threads are done squashing. Change the
// status if they are done.
while (threads != end) {
ThreadID tid = *threads++;
// Clear the bit saying if the thread has committed stores
// this cycle.
committedStores[tid] = false;
if (commitStatus[tid] == ROBSquashing) {
if (rob->isDoneSquashing(tid)) {
commitStatus[tid] = Running;
} else {
DPRINTF(Commit,"[tid:%i] Still Squashing, cannot commit any"
" insts this cycle.\n", tid);
rob->doSquash(tid);
toIEW->commitInfo[tid].robSquashing = true;
wroteToTimeBuffer = true;
}
}
}
commit();
markCompletedInsts();
threads = activeThreads->begin();
while (threads != end) {
ThreadID tid = *threads++;
if (!rob->isEmpty(tid) && rob->readHeadInst(tid)->readyToCommit()) {
// The ROB has more instructions it can commit. Its next status
// will be active.
_nextStatus = Active;
[[maybe_unused]] const DynInstPtr &inst = rob->readHeadInst(tid);
DPRINTF(Commit,"[tid:%i] Instruction [sn:%llu] PC %s is head of"
" ROB and ready to commit\n",
tid, inst->seqNum, inst->pcState());
} else if (!rob->isEmpty(tid)) {
const DynInstPtr &inst = rob->readHeadInst(tid);
ppCommitStall->notify(inst);
DPRINTF(Commit,"[tid:%i] Can't commit, Instruction [sn:%llu] PC "
"%s is head of ROB and not ready\n",
tid, inst->seqNum, inst->pcState());
}
DPRINTF(Commit, "[tid:%i] ROB has %d insts & %d free entries.\n",
tid, rob->countInsts(tid), rob->numFreeEntries(tid));
}
if (wroteToTimeBuffer) {
DPRINTF(Activity, "Activity This Cycle.\n");
cpu->activityThisCycle();
}
updateStatus();
}
void
Commit::handleInterrupt()
{
// Verify that we still have an interrupt to handle
if (!cpu->checkInterrupts(0)) {
DPRINTF(Commit, "Pending interrupt is cleared by requestor before "
"it got handled. Restart fetching from the orig path.\n");
toIEW->commitInfo[0].clearInterrupt = true;
interrupt = NoFault;
avoidQuiesceLiveLock = true;
return;
}
// Wait until all in flight instructions are finished before enterring
// the interrupt.
if (canHandleInterrupts && cpu->instList.empty()) {
// Squash or record that I need to squash this cycle if
// an interrupt needed to be handled.
DPRINTF(Commit, "Interrupt detected.\n");
// Clear the interrupt now that it's going to be handled
toIEW->commitInfo[0].clearInterrupt = true;
assert(!thread[0]->noSquashFromTC);
thread[0]->noSquashFromTC = true;
if (cpu->checker) {
cpu->checker->handlePendingInt();
}
// CPU will handle interrupt. Note that we ignore the local copy of
// interrupt. This is because the local copy may no longer be the
// interrupt that the interrupt controller thinks is being handled.
cpu->processInterrupts(cpu->getInterrupts());
thread[0]->noSquashFromTC = false;
commitStatus[0] = TrapPending;
interrupt = NoFault;
// Generate trap squash event.
generateTrapEvent(0, interrupt);
avoidQuiesceLiveLock = false;
} else {
DPRINTF(Commit, "Interrupt pending: instruction is %sin "
"flight, ROB is %sempty\n",
canHandleInterrupts ? "not " : "",
cpu->instList.empty() ? "" : "not " );
}
}
void
Commit::propagateInterrupt()
{
// Don't propagate intterupts if we are currently handling a trap or
// in draining and the last observable instruction has been committed.
if (commitStatus[0] == TrapPending || interrupt || trapSquash[0] ||
tcSquash[0] || drainImminent)
return;
// Process interrupts if interrupts are enabled, not in PAL
// mode, and no other traps or external squashes are currently
// pending.
// @todo: Allow other threads to handle interrupts.
// Get any interrupt that happened
interrupt = cpu->getInterrupts();
// Tell fetch that there is an interrupt pending. This
// will make fetch wait until it sees a non PAL-mode PC,
// at which point it stops fetching instructions.
if (interrupt != NoFault)
toIEW->commitInfo[0].interruptPending = true;
}
void
Commit::commit()
{
if (FullSystem) {
// Check if we have a interrupt and get read to handle it
if (cpu->checkInterrupts(0))
propagateInterrupt();
}
////////////////////////////////////
// Check for any possible squashes, handle them first
////////////////////////////////////
std::list<ThreadID>::iterator threads = activeThreads->begin();
std::list<ThreadID>::iterator end = activeThreads->end();
int num_squashing_threads = 0;
while (threads != end) {
ThreadID tid = *threads++;
// Not sure which one takes priority. I think if we have
// both, that's a bad sign.
if (trapSquash[tid]) {
assert(!tcSquash[tid]);
squashFromTrap(tid);
// If the thread is trying to exit (i.e., an exit syscall was
// executed), this trapSquash was originated by the exit
// syscall earlier. In this case, schedule an exit event in
// the next cycle to fully terminate this thread
if (cpu->isThreadExiting(tid))
cpu->scheduleThreadExitEvent(tid);
} else if (tcSquash[tid]) {
assert(commitStatus[tid] != TrapPending);
squashFromTC(tid);
} else if (commitStatus[tid] == SquashAfterPending) {
// A squash from the previous cycle of the commit stage (i.e.,
// commitInsts() called squashAfter) is pending. Squash the
// thread now.
squashFromSquashAfter(tid);
}
// Squashed sequence number must be older than youngest valid
// instruction in the ROB. This prevents squashes from younger
// instructions overriding squashes from older instructions.
if (fromIEW->squash[tid] &&
commitStatus[tid] != TrapPending &&
fromIEW->squashedSeqNum[tid] <= youngestSeqNum[tid]) {
if (fromIEW->mispredictInst[tid]) {
DPRINTF(Commit,
"[tid:%i] Squashing due to branch mispred "
"PC:%#x [sn:%llu]\n",
tid,
fromIEW->mispredictInst[tid]->pcState().instAddr(),
fromIEW->squashedSeqNum[tid]);
} else {
DPRINTF(Commit,
"[tid:%i] Squashing due to order violation [sn:%llu]\n",
tid, fromIEW->squashedSeqNum[tid]);
}
DPRINTF(Commit, "[tid:%i] Redirecting to PC %#x\n",
tid, *fromIEW->pc[tid]);
commitStatus[tid] = ROBSquashing;
// If we want to include the squashing instruction in the squash,
// then use one older sequence number.
InstSeqNum squashed_inst = fromIEW->squashedSeqNum[tid];
if (fromIEW->includeSquashInst[tid]) {
squashed_inst--;
}
// All younger instructions will be squashed. Set the sequence
// number as the youngest instruction in the ROB.
youngestSeqNum[tid] = squashed_inst;
rob->squash(squashed_inst, tid);
changedROBNumEntries[tid] = true;
toIEW->commitInfo[tid].doneSeqNum = squashed_inst;
toIEW->commitInfo[tid].squash = true;
// Send back the rob squashing signal so other stages know that
// the ROB is in the process of squashing.
toIEW->commitInfo[tid].robSquashing = true;
toIEW->commitInfo[tid].mispredictInst =
fromIEW->mispredictInst[tid];
toIEW->commitInfo[tid].branchTaken =
fromIEW->branchTaken[tid];
toIEW->commitInfo[tid].squashInst =
rob->findInst(tid, squashed_inst);
if (toIEW->commitInfo[tid].mispredictInst) {
if (toIEW->commitInfo[tid].mispredictInst->isUncondCtrl()) {
toIEW->commitInfo[tid].branchTaken = true;
}
++stats.branchMispredicts;
}
set(toIEW->commitInfo[tid].pc, fromIEW->pc[tid]);
}
if (commitStatus[tid] == ROBSquashing) {
num_squashing_threads++;
}
}
// If commit is currently squashing, then it will have activity for the
// next cycle. Set its next status as active.
if (num_squashing_threads) {
_nextStatus = Active;
}
if (num_squashing_threads != numThreads) {
// If we're not currently squashing, then get instructions.
getInsts();
// Try to commit any instructions.
commitInsts();
}
//Check for any activity
threads = activeThreads->begin();
while (threads != end) {
ThreadID tid = *threads++;
if (changedROBNumEntries[tid]) {
toIEW->commitInfo[tid].usedROB = true;
toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid);
wroteToTimeBuffer = true;
changedROBNumEntries[tid] = false;
if (rob->isEmpty(tid))
checkEmptyROB[tid] = true;
}
// ROB is only considered "empty" for previous stages if: a)
// ROB is empty, b) there are no outstanding stores, c) IEW
// stage has received any information regarding stores that
// committed.
// c) is checked by making sure to not consider the ROB empty
// on the same cycle as when stores have been committed.
// @todo: Make this handle multi-cycle communication between
// commit and IEW.
if (checkEmptyROB[tid] && rob->isEmpty(tid) &&
!iewStage->hasStoresToWB(tid) && !committedStores[tid]) {
checkEmptyROB[tid] = false;
toIEW->commitInfo[tid].usedROB = true;
toIEW->commitInfo[tid].emptyROB = true;
toIEW->commitInfo[tid].freeROBEntries = rob->numFreeEntries(tid);
wroteToTimeBuffer = true;
}
}
}
void
Commit::commitInsts()
{
////////////////////////////////////
// Handle commit
// Note that commit will be handled prior to putting new
// instructions in the ROB so that the ROB only tries to commit
// instructions it has in this current cycle, and not instructions
// it is writing in during this cycle. Can't commit and squash
// things at the same time...
////////////////////////////////////
DPRINTF(Commit, "Trying to commit instructions in the ROB.\n");
unsigned num_committed = 0;
DynInstPtr head_inst;
// Commit as many instructions as possible until the commit bandwidth
// limit is reached, or it becomes impossible to commit any more.
while (num_committed < commitWidth) {
// hardware transactionally memory
// If executing within a transaction,
// need to handle interrupts specially
ThreadID commit_thread = getCommittingThread();
// Check for any interrupt that we've already squashed for
// and start processing it.
if (interrupt != NoFault) {
// If inside a transaction, postpone interrupts
if (executingHtmTransaction(commit_thread)) {
cpu->clearInterrupts(0);
toIEW->commitInfo[0].clearInterrupt = true;
interrupt = NoFault;
avoidQuiesceLiveLock = true;
} else {
handleInterrupt();
}
}
// ThreadID commit_thread = getCommittingThread();
if (commit_thread == -1 || !rob->isHeadReady(commit_thread))
break;
head_inst = rob->readHeadInst(commit_thread);
ThreadID tid = head_inst->threadNumber;
assert(tid == commit_thread);
DPRINTF(Commit,
"Trying to commit head instruction, [tid:%i] [sn:%llu]\n",
tid, head_inst->seqNum);
// If the head instruction is squashed, it is ready to retire
// (be removed from the ROB) at any time.
if (head_inst->isSquashed()) {
DPRINTF(Commit, "Retiring squashed instruction from "
"ROB.\n");
rob->retireHead(commit_thread);
++stats.commitSquashedInsts;
// Notify potential listeners that this instruction is squashed
ppSquash->notify(head_inst);
// Record that the number of ROB entries has changed.
changedROBNumEntries[tid] = true;
} else {
set(pc[tid], head_inst->pcState());
// Try to commit the head instruction.
bool commit_success = commitHead(head_inst, num_committed);
if (commit_success) {
++num_committed;
stats.committedInstType[tid][head_inst->opClass()]++;
ppCommit->notify(head_inst);
// hardware transactional memory
// update nesting depth
if (head_inst->isHtmStart())
htmStarts[tid]++;
// sanity check
if (head_inst->inHtmTransactionalState()) {
assert(executingHtmTransaction(tid));
} else {
assert(!executingHtmTransaction(tid));
}
// update nesting depth
if (head_inst->isHtmStop())
htmStops[tid]++;
changedROBNumEntries[tid] = true;
// Set the doneSeqNum to the youngest committed instruction.
toIEW->commitInfo[tid].doneSeqNum = head_inst->seqNum;
if (tid == 0)
canHandleInterrupts = !head_inst->isDelayedCommit();
// at this point store conditionals should either have
// been completed or predicated false
assert(!head_inst->isStoreConditional() ||
head_inst->isCompleted() ||
!head_inst->readPredicate());
// Updates misc. registers.
head_inst->updateMiscRegs();
// Check instruction execution if it successfully commits and
// is not carrying a fault.
if (cpu->checker) {
cpu->checker->verify(head_inst);
}
cpu->traceFunctions(pc[tid]->instAddr());
head_inst->staticInst->advancePC(*pc[tid]);
// Keep track of the last sequence number commited
lastCommitedSeqNum[tid] = head_inst->seqNum;
// If this is an instruction that doesn't play nicely with
// others squash everything and restart fetch
if (head_inst->isSquashAfter())
squashAfter(tid, head_inst);
if (drainPending) {
if (pc[tid]->microPC() == 0 && interrupt == NoFault &&
!thread[tid]->trapPending) {
// Last architectually committed instruction.
// Squash the pipeline, stall fetch, and use
// drainImminent to disable interrupts
DPRINTF(Drain, "Draining: %i:%s\n", tid, *pc[tid]);
squashAfter(tid, head_inst);
cpu->commitDrained(tid);
drainImminent = true;
}
}
bool onInstBoundary = !head_inst->isMicroop() ||
head_inst->isLastMicroop() ||
!head_inst->isDelayedCommit();
if (onInstBoundary) {
int count = 0;
Addr oldpc;
// Make sure we're not currently updating state while
// handling PC events.
assert(!thread[tid]->noSquashFromTC &&
!thread[tid]->trapPending);
do {
oldpc = pc[tid]->instAddr();
thread[tid]->pcEventQueue.service(
oldpc, thread[tid]->getTC());
count++;
} while (oldpc != pc[tid]->instAddr());
if (count > 1) {
DPRINTF(Commit,
"PC skip function event, stopping commit\n");
break;
}
}
// Check if an instruction just enabled interrupts and we've
// previously had an interrupt pending that was not handled
// because interrupts were subsequently disabled before the
// pipeline reached a place to handle the interrupt. In that
// case squash now to make sure the interrupt is handled.
//
// If we don't do this, we might end up in a live lock
// situation.
if (!interrupt && avoidQuiesceLiveLock &&
onInstBoundary && cpu->checkInterrupts(0))
squashAfter(tid, head_inst);
} else {
DPRINTF(Commit, "Unable to commit head instruction PC:%s "
"[tid:%i] [sn:%llu].\n",
head_inst->pcState(), tid ,head_inst->seqNum);
break;
}
}
}
DPRINTF(CommitRate, "%i\n", num_committed);
stats.numCommittedDist.sample(num_committed);
if (num_committed == commitWidth) {
stats.commitEligibleSamples++;
}
}
bool
Commit::commitHead(const DynInstPtr &head_inst, unsigned inst_num)
{
assert(head_inst);
ThreadID tid = head_inst->threadNumber;
// If the instruction is not executed yet, then it will need extra
// handling. Signal backwards that it should be executed.
if (!head_inst->isExecuted()) {
// Make sure we are only trying to commit un-executed instructions we
// think are possible.
assert(head_inst->isNonSpeculative() || head_inst->isStoreConditional()
|| head_inst->isReadBarrier() || head_inst->isWriteBarrier()
|| head_inst->isAtomic()
|| (head_inst->isLoad() && head_inst->strictlyOrdered()));
DPRINTF(Commit,
"Encountered a barrier or non-speculative "
"instruction [tid:%i] [sn:%llu] "
"at the head of the ROB, PC %s.\n",
tid, head_inst->seqNum, head_inst->pcState());
if (inst_num > 0 || iewStage->hasStoresToWB(tid)) {
DPRINTF(Commit,
"[tid:%i] [sn:%llu] "
"Waiting for all stores to writeback.\n",
tid, head_inst->seqNum);
return false;
}
toIEW->commitInfo[tid].nonSpecSeqNum = head_inst->seqNum;
// Change the instruction so it won't try to commit again until
// it is executed.
head_inst->clearCanCommit();
if (head_inst->isLoad() && head_inst->strictlyOrdered()) {
DPRINTF(Commit, "[tid:%i] [sn:%llu] "
"Strictly ordered load, PC %s.\n",
tid, head_inst->seqNum, head_inst->pcState());
toIEW->commitInfo[tid].strictlyOrdered = true;
toIEW->commitInfo[tid].strictlyOrderedLoad = head_inst;
} else {
++stats.commitNonSpecStalls;
}
return false;
}
// Check if the instruction caused a fault. If so, trap.
Fault inst_fault = head_inst->getFault();
// hardware transactional memory
// if a fault occurred within a HTM transaction
// ensure that the transaction aborts
if (inst_fault != NoFault && head_inst->inHtmTransactionalState()) {
// There exists a generic HTM fault common to all ISAs
if (!std::dynamic_pointer_cast<GenericHtmFailureFault>(inst_fault)) {
DPRINTF(HtmCpu, "%s - fault (%s) encountered within transaction"
" - converting to GenericHtmFailureFault\n",
head_inst->staticInst->getName(), inst_fault->name());
inst_fault = std::make_shared<GenericHtmFailureFault>(
head_inst->getHtmTransactionUid(),
HtmFailureFaultCause::EXCEPTION);
}
// If this point is reached and the fault inherits from the HTM fault,
// then there is no need to raise a new fault
}
// Stores mark themselves as completed.
if (!head_inst->isStore() && inst_fault == NoFault) {
head_inst->setCompleted();
}
if (inst_fault != NoFault) {
DPRINTF(Commit, "Inst [tid:%i] [sn:%llu] PC %s has a fault\n",
tid, head_inst->seqNum, head_inst->pcState());
if (iewStage->hasStoresToWB(tid) || inst_num > 0) {
DPRINTF(Commit,
"[tid:%i] [sn:%llu] "
"Stores outstanding, fault must wait.\n",
tid, head_inst->seqNum);
return false;
}
head_inst->setCompleted();
// If instruction has faulted, let the checker execute it and
// check if it sees the same fault and control flow.
if (cpu->checker) {
// Need to check the instruction before its fault is processed
cpu->checker->verify(head_inst);
}
assert(!thread[tid]->noSquashFromTC);
// Mark that we're in state update mode so that the trap's
// execution doesn't generate extra squashes.
thread[tid]->noSquashFromTC = true;
// Execute the trap. Although it's slightly unrealistic in
// terms of timing (as it doesn't wait for the full timing of
// the trap event to complete before updating state), it's
// needed to update the state as soon as possible. This
// prevents external agents from changing any specific state
// that the trap need.
cpu->trap(inst_fault, tid,
head_inst->notAnInst() ? nullStaticInstPtr :
head_inst->staticInst);
// Exit state update mode to avoid accidental updating.
thread[tid]->noSquashFromTC = false;
commitStatus[tid] = TrapPending;
DPRINTF(Commit,
"[tid:%i] [sn:%llu] Committing instruction with fault\n",
tid, head_inst->seqNum);
if (head_inst->traceData) {
// We ignore ReExecution "faults" here as they are not real
// (architectural) faults but signal flush/replays.
if (debug::ExecFaulting
&& dynamic_cast<ReExec*>(inst_fault.get()) == nullptr) {
head_inst->traceData->setFaulting(true);
head_inst->traceData->setFetchSeq(head_inst->seqNum);
head_inst->traceData->setCPSeq(thread[tid]->numOp);
head_inst->traceData->dump();
}
delete head_inst->traceData;
head_inst->traceData = NULL;
}
// Generate trap squash event.
generateTrapEvent(tid, inst_fault);
return false;
}
updateComInstStats(head_inst);
DPRINTF(Commit,
"[tid:%i] [sn:%llu] Committing instruction with PC %s\n",
tid, head_inst->seqNum, head_inst->pcState());
if (head_inst->traceData) {
head_inst->traceData->setFetchSeq(head_inst->seqNum);
head_inst->traceData->setCPSeq(thread[tid]->numOp);
head_inst->traceData->dump();
delete head_inst->traceData;
head_inst->traceData = NULL;
}
if (head_inst->isReturn()) {
DPRINTF(Commit,
"[tid:%i] [sn:%llu] Return Instruction Committed PC %s \n",
tid, head_inst->seqNum, head_inst->pcState());
}
// Update the commit rename map
for (int i = 0; i < head_inst->numDestRegs(); i++) {
renameMap[tid]->setEntry(head_inst->flattenedDestIdx(i),
head_inst->renamedDestIdx(i));
}
// hardware transactional memory
// the HTM UID is purely for correctness and debugging purposes
if (head_inst->isHtmStart())
iewStage->setLastRetiredHtmUid(tid, head_inst->getHtmTransactionUid());
// Finally clear the head ROB entry.
rob->retireHead(tid);
#if TRACING_ON
if (debug::O3PipeView) {
head_inst->commitTick = curTick() - head_inst->fetchTick;
}
#endif
// If this was a store, record it for this cycle.
if (head_inst->isStore() || head_inst->isAtomic())
committedStores[tid] = true;
// Return true to indicate that we have committed an instruction.
return true;
}
void
Commit::getInsts()
{
DPRINTF(Commit, "Getting instructions from Rename stage.\n");
// Read any renamed instructions and place them into the ROB.
int insts_to_process = std::min((int)renameWidth, fromRename->size);
for (int inst_num = 0; inst_num < insts_to_process; ++inst_num) {
const DynInstPtr &inst = fromRename->insts[inst_num];
ThreadID tid = inst->threadNumber;
if (!inst->isSquashed() &&
commitStatus[tid] != ROBSquashing &&
commitStatus[tid] != TrapPending) {
changedROBNumEntries[tid] = true;
DPRINTF(Commit, "[tid:%i] [sn:%llu] Inserting PC %s into ROB.\n",
tid, inst->seqNum, inst->pcState());
rob->insertInst(inst);
assert(rob->getThreadEntries(tid) <= rob->getMaxEntries(tid));
youngestSeqNum[tid] = inst->seqNum;
} else {
DPRINTF(Commit, "[tid:%i] [sn:%llu] "
"Instruction PC %s was squashed, skipping.\n",
tid, inst->seqNum, inst->pcState());
}
}
}
void
Commit::markCompletedInsts()
{
// Grab completed insts out of the IEW instruction queue, and mark
// instructions completed within the ROB.
for (int inst_num = 0; inst_num < fromIEW->size; ++inst_num) {
assert(fromIEW->insts[inst_num]);
if (!fromIEW->insts[inst_num]->isSquashed()) {
DPRINTF(Commit, "[tid:%i] Marking PC %s, [sn:%llu] ready "
"within ROB.\n",
fromIEW->insts[inst_num]->threadNumber,
fromIEW->insts[inst_num]->pcState(),
fromIEW->insts[inst_num]->seqNum);
// Mark the instruction as ready to commit.
fromIEW->insts[inst_num]->setCanCommit();
}
}
}
void
Commit::updateComInstStats(const DynInstPtr &inst)
{
ThreadID tid = inst->threadNumber;
if (!inst->isMicroop() || inst->isLastMicroop())
stats.instsCommitted[tid]++;
stats.opsCommitted[tid]++;
// To match the old model, don't count nops and instruction
// prefetches towards the total commit count.
if (!inst->isNop() && !inst->isInstPrefetch()) {
cpu->instDone(tid, inst);
}
//
// Control Instructions
//
cpu->commitStats[tid]->updateComCtrlStats(inst->staticInst);
//
// Memory references
//
if (inst->isMemRef()) {
cpu->commitStats[tid]->numMemRefs++;
if (inst->isLoad()) {
cpu->commitStats[tid]->numLoadInsts++;
}
if (inst->isStore()) {
cpu->commitStats[tid]->numStoreInsts++;
}
}
if (inst->isFullMemBarrier()) {
stats.membars[tid]++;
}
// Integer Instruction
if (inst->isInteger())
cpu->commitStats[tid]->numIntInsts++;
// Floating Point Instruction
if (inst->isFloating())
cpu->commitStats[tid]->numFpInsts++;
// Vector Instruction
if (inst->isVector())
cpu->commitStats[tid]->numVecInsts++;
// Function Calls
if (inst->isCall())
stats.functionCalls[tid]++;
}
////////////////////////////////////////
// //
// SMT COMMIT POLICY MAINTAINED HERE //
// //
////////////////////////////////////////
ThreadID
Commit::getCommittingThread()
{
if (numThreads > 1) {
switch (commitPolicy) {
case CommitPolicy::RoundRobin:
return roundRobin();
case CommitPolicy::OldestReady:
return oldestReady();
default:
return InvalidThreadID;
}
} else {
assert(!activeThreads->empty());
ThreadID tid = activeThreads->front();
if (commitStatus[tid] == Running ||
commitStatus[tid] == Idle ||
commitStatus[tid] == FetchTrapPending) {
return tid;
} else {
return InvalidThreadID;
}
}
}
ThreadID
Commit::roundRobin()
{
std::list<ThreadID>::iterator pri_iter = priority_list.begin();
std::list<ThreadID>::iterator end = priority_list.end();
while (pri_iter != end) {
ThreadID tid = *pri_iter;
if (commitStatus[tid] == Running ||
commitStatus[tid] == Idle ||
commitStatus[tid] == FetchTrapPending) {
if (rob->isHeadReady(tid)) {
priority_list.erase(pri_iter);
priority_list.push_back(tid);
return tid;
}
}
pri_iter++;
}
return InvalidThreadID;
}
ThreadID
Commit::oldestReady()
{
unsigned oldest = 0;
unsigned oldest_seq_num = 0;
bool first = true;
std::list<ThreadID>::iterator threads = activeThreads->begin();
std::list<ThreadID>::iterator end = activeThreads->end();
while (threads != end) {
ThreadID tid = *threads++;
if (!rob->isEmpty(tid) &&
(commitStatus[tid] == Running ||
commitStatus[tid] == Idle ||
commitStatus[tid] == FetchTrapPending)) {
if (rob->isHeadReady(tid)) {
const DynInstPtr &head_inst = rob->readHeadInst(tid);
if (first) {
oldest = tid;
oldest_seq_num = head_inst->seqNum;
first = false;
} else if (head_inst->seqNum < oldest_seq_num) {
oldest = tid;
oldest_seq_num = head_inst->seqNum;
}
}
}
}
if (!first) {
return oldest;
} else {
return InvalidThreadID;
}
}
} // namespace o3
} // namespace gem5