cpu: Refactor indirect predictor (#429)
This commit is contained in:
@@ -100,6 +100,13 @@ class SimpleIndirectPredictor(IndirectPredictor):
|
||||
indirectPathLength = Param.Unsigned(
|
||||
3, "Previous indirect targets to use for path history"
|
||||
)
|
||||
speculativePathLength = Param.Unsigned(
|
||||
256,
|
||||
"Additional buffer space to store speculative path history. "
|
||||
"If there are more speculative branches in flight the history cannot "
|
||||
"be recovered. Set this to an appropriate value respective the CPU"
|
||||
"pipeline depth or a high value e.g. 256 to make it 'unlimited'.",
|
||||
)
|
||||
indirectGHRBits = Param.Unsigned(13, "Indirect GHR number of bits")
|
||||
instShiftAmt = Param.Unsigned(2, "Number of bits to shift instructions by")
|
||||
|
||||
@@ -119,7 +126,8 @@ class BranchPredictor(SimObject):
|
||||
|
||||
indirectBranchPred = Param.IndirectPredictor(
|
||||
SimpleIndirectPredictor(),
|
||||
"Indirect branch predictor, set to NULL to disable indirect predictions",
|
||||
"Indirect branch predictor, set to NULL to disable "
|
||||
"indirect predictions",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -158,10 +158,6 @@ BPredUnit::predict(const StaticInstPtr &inst, const InstSeqNum &seqNum,
|
||||
tid, seqNum, pred_taken, pc);
|
||||
}
|
||||
|
||||
const bool orig_pred_taken = pred_taken;
|
||||
if (iPred) {
|
||||
iPred->genIndirectInfo(tid, indirect_history);
|
||||
}
|
||||
|
||||
DPRINTF(Branch,
|
||||
"[tid:%i] [sn:%llu] Creating prediction history for PC %s\n",
|
||||
@@ -250,11 +246,16 @@ BPredUnit::predict(const StaticInstPtr &inst, const InstSeqNum &seqNum,
|
||||
predict_record.wasIndirect = true;
|
||||
++stats.indirectLookups;
|
||||
//Consult indirect predictor on indirect control
|
||||
if (iPred->lookup(pc.instAddr(), *target, tid)) {
|
||||
const PCStateBase *itarget = iPred->lookup(tid,
|
||||
seqNum, pc.instAddr(),
|
||||
predict_record.indirectHistory);
|
||||
if (itarget) {
|
||||
// Indirect predictor hit
|
||||
++stats.indirectHits;
|
||||
set(target, *itarget);
|
||||
|
||||
DPRINTF(Branch,
|
||||
"[tid:%i] [sn:%llu] Instruction %s predicted "
|
||||
"[tid:%i, sn:%llu] Instruction %s predicted "
|
||||
"indirect target is %s\n",
|
||||
tid, seqNum, pc, *target);
|
||||
} else {
|
||||
@@ -262,9 +263,9 @@ BPredUnit::predict(const StaticInstPtr &inst, const InstSeqNum &seqNum,
|
||||
pred_taken = false;
|
||||
predict_record.predTaken = pred_taken;
|
||||
DPRINTF(Branch,
|
||||
"[tid:%i] [sn:%llu] Instruction %s no indirect "
|
||||
"target\n",
|
||||
tid, seqNum, pc);
|
||||
"[tid:%i, sn:%llu] PC:%#x no indirect target\n",
|
||||
tid, seqNum, pc.instAddr());
|
||||
|
||||
if (!inst->isCall() && !inst->isReturn()) {
|
||||
|
||||
} else if (inst->isCall() && !inst->isUncondCtrl()) {
|
||||
@@ -273,8 +274,6 @@ BPredUnit::predict(const StaticInstPtr &inst, const InstSeqNum &seqNum,
|
||||
}
|
||||
inst->advancePC(*target);
|
||||
}
|
||||
iPred->recordIndirect(pc.instAddr(), target->instAddr(),
|
||||
seqNum, tid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -289,11 +288,10 @@ BPredUnit::predict(const StaticInstPtr &inst, const InstSeqNum &seqNum,
|
||||
|
||||
if (iPred) {
|
||||
// Update the indirect predictor with the direction prediction
|
||||
// Note that this happens after indirect lookup, so it does not use
|
||||
// the new information
|
||||
// Note also that we use orig_pred_taken instead of pred_taken in
|
||||
// as this is the actual outcome of the direction prediction
|
||||
iPred->updateDirectionInfo(tid, orig_pred_taken);
|
||||
iPred->update(tid, seqNum, predict_record.pc, false,
|
||||
predict_record.predTaken, *target,
|
||||
getBranchType(inst),
|
||||
predict_record.indirectHistory);
|
||||
}
|
||||
|
||||
predHist[tid].push_front(predict_record);
|
||||
@@ -321,8 +319,10 @@ BPredUnit::update(const InstSeqNum &done_sn, ThreadID tid)
|
||||
predHist[tid].back().inst,
|
||||
predHist[tid].back().target);
|
||||
|
||||
// Commite also Indirect predictor and RAS
|
||||
if (iPred) {
|
||||
iPred->commit(done_sn, tid, predHist[tid].back().indirectHistory);
|
||||
iPred->commit(tid, predHist[tid].back().seqNum,
|
||||
predHist[tid].back().indirectHistory);
|
||||
}
|
||||
|
||||
predHist[tid].pop_back();
|
||||
@@ -334,10 +334,6 @@ BPredUnit::squash(const InstSeqNum &squashed_sn, ThreadID tid)
|
||||
{
|
||||
History &pred_hist = predHist[tid];
|
||||
|
||||
if (iPred) {
|
||||
iPred->squash(squashed_sn, tid);
|
||||
}
|
||||
|
||||
while (!pred_hist.empty() &&
|
||||
pred_hist.front().seqNum > squashed_sn) {
|
||||
if (pred_hist.front().wasCall && pred_hist.front().pushedRAS) {
|
||||
@@ -369,7 +365,8 @@ BPredUnit::squash(const InstSeqNum &squashed_sn, ThreadID tid)
|
||||
// This call should delete the bpHistory.
|
||||
squash(tid, pred_hist.front().bpHistory);
|
||||
if (iPred) {
|
||||
iPred->deleteIndirectInfo(tid, pred_hist.front().indirectHistory);
|
||||
iPred->squash(tid, pred_hist.front().seqNum,
|
||||
pred_hist.front().indirectHistory);
|
||||
}
|
||||
|
||||
DPRINTF(Branch, "[tid:%i] [squash sn:%llu] "
|
||||
@@ -453,9 +450,13 @@ BPredUnit::squash(const InstSeqNum &squashed_sn,
|
||||
pred_hist.front().bpHistory, true, pred_hist.front().inst,
|
||||
corr_target.instAddr());
|
||||
|
||||
// Correct Indirect predictor -------------------
|
||||
if (iPred) {
|
||||
iPred->changeDirectionPrediction(tid,
|
||||
pred_hist.front().indirectHistory, actually_taken);
|
||||
iPred->update(tid, squashed_sn, (*hist_it).pc,
|
||||
true, actually_taken, corr_target,
|
||||
getBranchType(pred_hist.front().inst),
|
||||
(*hist_it).indirectHistory);
|
||||
|
||||
}
|
||||
|
||||
if (actually_taken) {
|
||||
@@ -470,11 +471,6 @@ BPredUnit::squash(const InstSeqNum &squashed_sn,
|
||||
}
|
||||
if (hist_it->wasIndirect) {
|
||||
++stats.indirectMispredicted;
|
||||
if (iPred) {
|
||||
iPred->recordTarget(
|
||||
hist_it->seqNum, pred_hist.front().indirectHistory,
|
||||
corr_target, tid);
|
||||
}
|
||||
} else {
|
||||
DPRINTF(Branch,"[tid:%i] [squash sn:%llu] "
|
||||
"BTB Update called for [sn:%llu] "
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2014 ARM Limited
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2023 The University of Edinburgh
|
||||
* 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.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
@@ -26,11 +36,16 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* @file
|
||||
* Indirect target predictor interface
|
||||
*/
|
||||
|
||||
#ifndef __CPU_PRED_INDIRECT_BASE_HH__
|
||||
#define __CPU_PRED_INDIRECT_BASE_HH__
|
||||
|
||||
#include "arch/generic/pcstate.hh"
|
||||
#include "cpu/inst_seq.hh"
|
||||
#include "cpu/pred/branch_type.hh"
|
||||
#include "params/IndirectPredictor.hh"
|
||||
#include "sim/sim_object.hh"
|
||||
|
||||
@@ -51,21 +66,57 @@ class IndirectPredictor : public SimObject
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool lookup(Addr br_addr, PCStateBase& br_target,
|
||||
ThreadID tid) = 0;
|
||||
virtual void recordIndirect(Addr br_addr, Addr tgt_addr,
|
||||
InstSeqNum seq_num, ThreadID tid) = 0;
|
||||
virtual void commit(InstSeqNum seq_num, ThreadID tid,
|
||||
void * indirect_history) = 0;
|
||||
virtual void squash(InstSeqNum seq_num, ThreadID tid) = 0;
|
||||
virtual void recordTarget(InstSeqNum seq_num, void * indirect_history,
|
||||
const PCStateBase& target, ThreadID tid) = 0;
|
||||
virtual void genIndirectInfo(ThreadID tid, void* & indirect_history) = 0;
|
||||
virtual void updateDirectionInfo(ThreadID tid, bool actually_taken) = 0;
|
||||
virtual void deleteIndirectInfo(ThreadID tid, void * indirect_history) = 0;
|
||||
virtual void changeDirectionPrediction(ThreadID tid,
|
||||
void * indirect_history,
|
||||
bool actually_taken) = 0;
|
||||
virtual void reset() {};
|
||||
|
||||
/**
|
||||
* Predicts the indirect target of an indirect branch.
|
||||
* @param tid Thread ID of the branch.
|
||||
* @param sn The sequence number of the branch.
|
||||
* @param pc The branch PC address.
|
||||
* @param i_history The pointer to the history object.
|
||||
* @return For a hit the predictor returns a pointer to the target PCState
|
||||
* otherwise a nullptr is returned.
|
||||
*/
|
||||
virtual const PCStateBase* lookup(ThreadID tid, InstSeqNum sn,
|
||||
Addr pc, void * &i_history) = 0;
|
||||
|
||||
/**
|
||||
* Updates the indirect predictor with history information of a branch.
|
||||
* Is called right after the prediction which updates the state
|
||||
* speculatively. In case the branch was mispredicted the function
|
||||
* is called again with the corrected information.
|
||||
* The function is called for ALL branches as some predictors incooperate
|
||||
* all branches in their history.
|
||||
* @param tid Thread ID
|
||||
* @param sn The sequence number of the branch.
|
||||
* @param pc The branch PC address.
|
||||
* @param squash Whether the update is called at a misprediction
|
||||
* @param taken Whether a conditional branch was taken
|
||||
* @param target The target address if this branch.
|
||||
* @param br_type The branch instruction type.
|
||||
* @param i_history The pointer to the history object.
|
||||
*/
|
||||
virtual void update(ThreadID tid, InstSeqNum sn, Addr pc, bool squash,
|
||||
bool taken, const PCStateBase& target,
|
||||
BranchType br_type, void * &i_history) = 0;
|
||||
|
||||
/**
|
||||
* Squashes a branch. If the branch modified the history
|
||||
* reverts the modification.
|
||||
* @param tid Thread ID
|
||||
* @param sn The sequence number of the branch.
|
||||
* @param i_history The pointer to the history object.
|
||||
*/
|
||||
virtual void squash(ThreadID tid, InstSeqNum sn, void * &i_history) = 0;
|
||||
|
||||
/**
|
||||
* A branch gets finally commited. Updates the internal state of
|
||||
* the indirect predictor (counter and target information).
|
||||
* @param tid Thread ID
|
||||
* @param sn The sequence number of the branch.
|
||||
* @param i_history The pointer to the history object.
|
||||
*/
|
||||
virtual void commit(ThreadID tid, InstSeqNum sn, void * &i_history) = 0;
|
||||
};
|
||||
|
||||
} // namespace branch_prediction
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2014 ARM Limited
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2022-2023 The University of Edinburgh
|
||||
* 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.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
@@ -46,9 +56,11 @@ SimpleIndirectPredictor::SimpleIndirectPredictor(
|
||||
numWays(params.indirectWays),
|
||||
tagBits(params.indirectTagSize),
|
||||
pathLength(params.indirectPathLength),
|
||||
speculativePathLength(params.speculativePathLength),
|
||||
instShift(params.instShiftAmt),
|
||||
ghrNumBits(params.indirectGHRBits),
|
||||
ghrMask((1 << params.indirectGHRBits)-1)
|
||||
ghrMask((1 << params.indirectGHRBits)-1),
|
||||
stats(this)
|
||||
{
|
||||
if (!isPowerOf2(numSets)) {
|
||||
panic("Indirect predictor requires power of 2 number of sets");
|
||||
@@ -64,172 +76,292 @@ SimpleIndirectPredictor::SimpleIndirectPredictor(
|
||||
fatal_if(ghrNumBits > (sizeof(ThreadInfo::ghr)*8), "ghr_size is too big");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::genIndirectInfo(ThreadID tid,
|
||||
void* & indirect_history)
|
||||
SimpleIndirectPredictor::reset()
|
||||
{
|
||||
// record the GHR as it was before this prediction
|
||||
DPRINTF(Indirect, "Reset Indirect predictor\n");
|
||||
|
||||
for (auto& ti : threadInfo) {
|
||||
ti.ghr = 0;
|
||||
ti.pathHist.clear();
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < numSets; i++) {
|
||||
for (unsigned j = 0; j < numWays; j++) {
|
||||
targetCache[i][j].tag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::genIndirectInfo(ThreadID tid, void* &i_history)
|
||||
{
|
||||
// Record the GHR as it was before this prediction
|
||||
// It will be used to recover the history in case this prediction is
|
||||
// wrong or belongs to bad path
|
||||
indirect_history = new unsigned(threadInfo[tid].ghr);
|
||||
IndirectHistory* history = new IndirectHistory;
|
||||
history->ghr = threadInfo[tid].ghr;
|
||||
i_history = static_cast<void*>(history);
|
||||
}
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::updateDirectionInfo(
|
||||
ThreadID tid, bool actually_taken)
|
||||
SimpleIndirectPredictor::updateDirectionInfo(ThreadID tid, bool taken,
|
||||
Addr pc, Addr target)
|
||||
{
|
||||
// Direction history
|
||||
threadInfo[tid].ghr <<= 1;
|
||||
threadInfo[tid].ghr |= actually_taken;
|
||||
threadInfo[tid].ghr |= taken;
|
||||
threadInfo[tid].ghr &= ghrMask;
|
||||
}
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::changeDirectionPrediction(ThreadID tid,
|
||||
void * indirect_history, bool actually_taken)
|
||||
|
||||
|
||||
// Interface methods ------------------------------
|
||||
const PCStateBase *
|
||||
SimpleIndirectPredictor::lookup(ThreadID tid, InstSeqNum sn,
|
||||
Addr pc, void * &i_history)
|
||||
{
|
||||
unsigned * previousGhr = static_cast<unsigned *>(indirect_history);
|
||||
threadInfo[tid].ghr = ((*previousGhr) << 1) + actually_taken;
|
||||
threadInfo[tid].ghr &= ghrMask;
|
||||
assert(i_history==nullptr);
|
||||
|
||||
genIndirectInfo(tid, i_history);
|
||||
IndirectHistory *history = static_cast<IndirectHistory*>(i_history);
|
||||
|
||||
history->pcAddr = pc;
|
||||
history->was_indirect = true;
|
||||
|
||||
/** Do the prediction for indirect branches (no returns) */
|
||||
PCStateBase* target = nullptr;
|
||||
history->hit = lookup(tid, pc, target, history);
|
||||
return target;
|
||||
}
|
||||
|
||||
bool
|
||||
SimpleIndirectPredictor::lookup(Addr br_addr, PCStateBase& target,
|
||||
ThreadID tid)
|
||||
SimpleIndirectPredictor::lookup(ThreadID tid, Addr br_addr,
|
||||
PCStateBase * &target,
|
||||
IndirectHistory * &history)
|
||||
{
|
||||
Addr set_index = getSetIndex(br_addr, threadInfo[tid].ghr, tid);
|
||||
Addr tag = getTag(br_addr);
|
||||
|
||||
assert(set_index < numSets);
|
||||
history->set_index = getSetIndex(br_addr, tid);
|
||||
history->tag = getTag(br_addr);
|
||||
assert(history->set_index < numSets);
|
||||
stats.lookups++;
|
||||
|
||||
DPRINTF(Indirect, "Looking up %x (set:%d)\n", br_addr, set_index);
|
||||
const auto &iset = targetCache[set_index];
|
||||
DPRINTF(Indirect, "Looking up PC:%#x, (set:%d, tag:%d), "
|
||||
"ghr:%#x, pathHist sz:%#x\n",
|
||||
history->pcAddr, history->set_index, history->tag,
|
||||
history->ghr, threadInfo[tid].pathHist.size());
|
||||
|
||||
const auto &iset = targetCache[history->set_index];
|
||||
for (auto way = iset.begin(); way != iset.end(); ++way) {
|
||||
// tag may be 0 and match the default in way->tag, so we also have to
|
||||
// check that way->target has been initialized.
|
||||
if (way->tag == tag && way->target) {
|
||||
if (way->tag == history->tag && way->target) {
|
||||
DPRINTF(Indirect, "Hit %x (target:%s)\n", br_addr, *way->target);
|
||||
set(target, *way->target);
|
||||
return true;
|
||||
history->hit = true;
|
||||
stats.hits++;
|
||||
return history->hit;
|
||||
}
|
||||
}
|
||||
DPRINTF(Indirect, "Miss %x\n", br_addr);
|
||||
return false;
|
||||
history->hit = false;
|
||||
stats.misses++;
|
||||
return history->hit;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::commit(ThreadID tid, InstSeqNum sn, void * &i_history)
|
||||
{
|
||||
if (i_history == nullptr) return;
|
||||
// we do not need to recover the GHR, so delete the information
|
||||
IndirectHistory *history = static_cast<IndirectHistory*>(i_history);
|
||||
|
||||
DPRINTF(Indirect, "Committing seq:%d, PC:%#x, ghr:%#x, pathHist sz:%lu\n",
|
||||
sn, history->pcAddr, history->ghr,
|
||||
threadInfo[tid].pathHist.size());
|
||||
|
||||
delete history;
|
||||
i_history = nullptr;
|
||||
|
||||
/** Delete histories if the history grows to much */
|
||||
while (threadInfo[tid].pathHist.size()
|
||||
>= (pathLength + speculativePathLength)) {
|
||||
|
||||
threadInfo[tid].pathHist.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::recordIndirect(Addr br_addr, Addr tgt_addr,
|
||||
InstSeqNum seq_num, ThreadID tid)
|
||||
SimpleIndirectPredictor::update(ThreadID tid, InstSeqNum sn, Addr pc,
|
||||
bool squash, bool taken, const PCStateBase& target,
|
||||
BranchType br_type, void * &i_history)
|
||||
{
|
||||
DPRINTF(Indirect, "Recording %x seq:%d\n", br_addr, seq_num);
|
||||
HistoryEntry entry(br_addr, tgt_addr, seq_num);
|
||||
threadInfo[tid].pathHist.push_back(entry);
|
||||
// If there is no history we did not use the indirect predictor yet.
|
||||
// Create one
|
||||
if (i_history==nullptr) {
|
||||
genIndirectInfo(tid, i_history);
|
||||
}
|
||||
IndirectHistory *history = static_cast<IndirectHistory*>(i_history);
|
||||
assert(history!=nullptr);
|
||||
|
||||
DPRINTF(Indirect, "Update sn:%i PC:%#x, squash:%i, ghr:%#x,path sz:%i\n",
|
||||
sn, pc, squash, history->ghr, threadInfo[tid].pathHist.size());
|
||||
|
||||
/** If update was called during squash we need to fix the indirect
|
||||
* path history and the global path history.
|
||||
* We restore the state before this branch incorrectly updated it
|
||||
* and perform the update afterwards again.
|
||||
*/
|
||||
history->was_indirect = isIndirectNoReturn(br_type);
|
||||
if (squash) {
|
||||
|
||||
/** restore global history */
|
||||
threadInfo[tid].ghr = history->ghr;
|
||||
|
||||
/** For indirect branches recalculate index and tag */
|
||||
if (history->was_indirect) {
|
||||
if (!threadInfo[tid].pathHist.empty()) {
|
||||
threadInfo[tid].pathHist.pop_back();
|
||||
}
|
||||
|
||||
history->set_index = getSetIndex(history->pcAddr, tid);
|
||||
history->tag = getTag(history->pcAddr);
|
||||
|
||||
DPRINTF(Indirect, "Record Target seq:%d, PC:%#x, TGT:%#x, "
|
||||
"ghr:%#x, (set:%x, tag:%x)\n",
|
||||
sn, history->pcAddr, target, history->ghr,
|
||||
history->set_index, history->tag);
|
||||
}
|
||||
}
|
||||
|
||||
// Only indirect branches are recorded in the path history
|
||||
if (history->was_indirect) {
|
||||
|
||||
DPRINTF(Indirect, "Recording %x seq:%d\n", history->pcAddr, sn);
|
||||
threadInfo[tid].pathHist.emplace_back(
|
||||
history->pcAddr, target.instAddr(), sn);
|
||||
|
||||
stats.indirectRecords++;
|
||||
}
|
||||
|
||||
// All branches update the global history
|
||||
updateDirectionInfo(tid,taken, history->pcAddr, target.instAddr());
|
||||
|
||||
// Finally if update is called during at squash we know the target
|
||||
// we predicted was wrong therefore we update the target.
|
||||
// We only record the target if the branch was indirect and taken
|
||||
if (squash && history->was_indirect && taken)
|
||||
recordTarget(tid, sn, target, history);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::commit(InstSeqNum seq_num, ThreadID tid,
|
||||
void * indirect_history)
|
||||
SimpleIndirectPredictor::squash(ThreadID tid, InstSeqNum sn, void * &i_history)
|
||||
{
|
||||
DPRINTF(Indirect, "Committing seq:%d\n", seq_num);
|
||||
ThreadInfo &t_info = threadInfo[tid];
|
||||
if (i_history == nullptr) return;
|
||||
|
||||
// we do not need to recover the GHR, so delete the information
|
||||
unsigned * previousGhr = static_cast<unsigned *>(indirect_history);
|
||||
delete previousGhr;
|
||||
IndirectHistory *history = static_cast<IndirectHistory*>(i_history);
|
||||
|
||||
if (t_info.pathHist.empty()) return;
|
||||
DPRINTF(Indirect, "Squashing seq:%d, PC:%#x, indirect:%i, "
|
||||
"ghr:%#x, pathHist sz:%#x\n",
|
||||
sn, history->pcAddr, history->was_indirect,
|
||||
history->ghr,
|
||||
threadInfo[tid].pathHist.size());
|
||||
|
||||
if (t_info.headHistEntry < t_info.pathHist.size() &&
|
||||
t_info.pathHist[t_info.headHistEntry].seqNum <= seq_num) {
|
||||
if (t_info.headHistEntry >= pathLength) {
|
||||
t_info.pathHist.pop_front();
|
||||
} else {
|
||||
++t_info.headHistEntry;
|
||||
|
||||
// Revert the global history register.
|
||||
threadInfo[tid].ghr = history->ghr;
|
||||
|
||||
// If we record this branch as indirect branch
|
||||
// remove it from the history.
|
||||
// Restore the old head in the history.
|
||||
if (history->was_indirect) {
|
||||
|
||||
// Should not be empty
|
||||
if (threadInfo[tid].pathHist.size() < pathLength) {
|
||||
stats.speculativeOverflows++;
|
||||
}
|
||||
|
||||
if (!threadInfo[tid].pathHist.empty()) {
|
||||
threadInfo[tid].pathHist.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
delete history;
|
||||
i_history = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::squash(InstSeqNum seq_num, ThreadID tid)
|
||||
{
|
||||
DPRINTF(Indirect, "Squashing seq:%d\n", seq_num);
|
||||
ThreadInfo &t_info = threadInfo[tid];
|
||||
auto squash_itr = t_info.pathHist.begin();
|
||||
while (squash_itr != t_info.pathHist.end()) {
|
||||
if (squash_itr->seqNum > seq_num) {
|
||||
break;
|
||||
}
|
||||
++squash_itr;
|
||||
}
|
||||
if (squash_itr != t_info.pathHist.end()) {
|
||||
DPRINTF(Indirect, "Squashing series starting with sn:%d\n",
|
||||
squash_itr->seqNum);
|
||||
}
|
||||
t_info.pathHist.erase(squash_itr, t_info.pathHist.end());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Internal functions ------------------------------
|
||||
|
||||
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::deleteIndirectInfo(ThreadID tid,
|
||||
void * indirect_history)
|
||||
SimpleIndirectPredictor::recordTarget(ThreadID tid, InstSeqNum sn,
|
||||
const PCStateBase& target, IndirectHistory * &history)
|
||||
{
|
||||
unsigned * previousGhr = static_cast<unsigned *>(indirect_history);
|
||||
threadInfo[tid].ghr = *previousGhr;
|
||||
|
||||
delete previousGhr;
|
||||
}
|
||||
|
||||
void
|
||||
SimpleIndirectPredictor::recordTarget(
|
||||
InstSeqNum seq_num, void * indirect_history, const PCStateBase& target,
|
||||
ThreadID tid)
|
||||
{
|
||||
ThreadInfo &t_info = threadInfo[tid];
|
||||
|
||||
unsigned * ghr = static_cast<unsigned *>(indirect_history);
|
||||
|
||||
// Should have just squashed so this branch should be the oldest
|
||||
auto hist_entry = *(t_info.pathHist.rbegin());
|
||||
// Temporarily pop it off the history so we can calculate the set
|
||||
t_info.pathHist.pop_back();
|
||||
Addr set_index = getSetIndex(hist_entry.pcAddr, *ghr, tid);
|
||||
Addr tag = getTag(hist_entry.pcAddr);
|
||||
hist_entry.targetAddr = target.instAddr();
|
||||
t_info.pathHist.push_back(hist_entry);
|
||||
// and it should be predicted as indirect.
|
||||
assert(!threadInfo[tid].pathHist.empty());
|
||||
assert(history->was_indirect);
|
||||
|
||||
assert(set_index < numSets);
|
||||
if (threadInfo[tid].pathHist.rbegin()->pcAddr != history->pcAddr) {
|
||||
DPRINTF(Indirect, "History seems to be corrupted. %#x != %#x\n",
|
||||
history->pcAddr,
|
||||
threadInfo[tid].pathHist.rbegin()->pcAddr);
|
||||
}
|
||||
|
||||
auto &iset = targetCache[set_index];
|
||||
DPRINTF(Indirect, "Record Target seq:%d, PC:%#x, TGT:%#x, "
|
||||
"ghr:%#x, (set:%x, tag:%x)\n",
|
||||
sn, history->pcAddr, target.instAddr(), history->ghr,
|
||||
history->set_index, history->tag);
|
||||
|
||||
assert(history->set_index < numSets);
|
||||
stats.targetRecords++;
|
||||
|
||||
// Update the target cache
|
||||
auto &iset = targetCache[history->set_index];
|
||||
for (auto way = iset.begin(); way != iset.end(); ++way) {
|
||||
if (way->tag == tag) {
|
||||
DPRINTF(Indirect, "Updating Target (seq: %d br:%x set:%d target:"
|
||||
"%s)\n", seq_num, hist_entry.pcAddr, set_index, target);
|
||||
if (way->tag == history->tag) {
|
||||
DPRINTF(Indirect,
|
||||
"Updating Target (seq: %d br:%x set:%d target:%s)\n",
|
||||
sn, history->pcAddr, history->set_index, target);
|
||||
set(way->target, target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DPRINTF(Indirect, "Allocating Target (seq: %d br:%x set:%d target:%s)\n",
|
||||
seq_num, hist_entry.pcAddr, set_index, target);
|
||||
sn, history->pcAddr, history->set_index, target);
|
||||
|
||||
// Did not find entry, random replacement
|
||||
auto &way = iset[rand() % numWays];
|
||||
way.tag = tag;
|
||||
way.tag = history->tag;
|
||||
set(way.target, target);
|
||||
}
|
||||
|
||||
|
||||
inline Addr
|
||||
SimpleIndirectPredictor::getSetIndex(Addr br_addr, unsigned ghr, ThreadID tid)
|
||||
{
|
||||
ThreadInfo &t_info = threadInfo[tid];
|
||||
|
||||
|
||||
inline Addr
|
||||
SimpleIndirectPredictor::getSetIndex(Addr br_addr, ThreadID tid)
|
||||
{
|
||||
Addr hash = br_addr >> instShift;
|
||||
if (hashGHR) {
|
||||
hash ^= ghr;
|
||||
hash ^= threadInfo[tid].ghr;
|
||||
}
|
||||
if (hashTargets) {
|
||||
unsigned hash_shift = floorLog2(numSets) / pathLength;
|
||||
for (int i = t_info.pathHist.size()-1, p = 0;
|
||||
for (int i = threadInfo[tid].pathHist.size()-1, p = 0;
|
||||
i >= 0 && p < pathLength; i--, p++) {
|
||||
hash ^= (t_info.pathHist[i].targetAddr >>
|
||||
hash ^= (threadInfo[tid].pathHist[i].targetAddr >>
|
||||
(instShift + p*hash_shift));
|
||||
}
|
||||
}
|
||||
@@ -242,5 +374,26 @@ SimpleIndirectPredictor::getTag(Addr br_addr)
|
||||
return (br_addr >> instShift) & ((0x1<<tagBits)-1);
|
||||
}
|
||||
|
||||
|
||||
SimpleIndirectPredictor::IndirectStats::IndirectStats(
|
||||
statistics::Group *parent)
|
||||
: statistics::Group(parent),
|
||||
ADD_STAT(lookups, statistics::units::Count::get(),
|
||||
"Number of lookups"),
|
||||
ADD_STAT(hits, statistics::units::Count::get(),
|
||||
"Number of hits of a tag"),
|
||||
ADD_STAT(misses, statistics::units::Count::get(),
|
||||
"Number of misses"),
|
||||
ADD_STAT(targetRecords, statistics::units::Count::get(),
|
||||
"Number of targets that where recorded/installed in the cache"),
|
||||
ADD_STAT(indirectRecords, statistics::units::Count::get(),
|
||||
"Number of indirect branches/calls recorded in the"
|
||||
" indirect hist"),
|
||||
ADD_STAT(speculativeOverflows, statistics::units::Count::get(),
|
||||
"Number of times more than the allowed capacity for speculative "
|
||||
"branches/calls where in flight and destroy the path history")
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace branch_prediction
|
||||
} // namespace gem5
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2014 ARM Limited
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2022-2023 The University of Edinburgh
|
||||
* 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.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
@@ -31,6 +41,7 @@
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "base/statistics.hh"
|
||||
#include "cpu/inst_seq.hh"
|
||||
#include "cpu/pred/indirect.hh"
|
||||
#include "params/SimpleIndirectPredictor.hh"
|
||||
@@ -46,19 +57,23 @@ class SimpleIndirectPredictor : public IndirectPredictor
|
||||
public:
|
||||
SimpleIndirectPredictor(const SimpleIndirectPredictorParams ¶ms);
|
||||
|
||||
bool lookup(Addr br_addr, PCStateBase& br_target, ThreadID tid);
|
||||
void recordIndirect(Addr br_addr, Addr tgt_addr, InstSeqNum seq_num,
|
||||
ThreadID tid);
|
||||
void commit(InstSeqNum seq_num, ThreadID tid, void * indirect_history);
|
||||
void squash(InstSeqNum seq_num, ThreadID tid);
|
||||
void recordTarget(InstSeqNum seq_num, void * indirect_history,
|
||||
const PCStateBase& target, ThreadID tid);
|
||||
void genIndirectInfo(ThreadID tid, void* & indirect_history);
|
||||
void updateDirectionInfo(ThreadID tid, bool actually_taken);
|
||||
void deleteIndirectInfo(ThreadID tid, void * indirect_history);
|
||||
void changeDirectionPrediction(ThreadID tid, void * indirect_history,
|
||||
bool actually_taken);
|
||||
/** Indirect predictor interface */
|
||||
void reset() override;
|
||||
|
||||
const PCStateBase * lookup(ThreadID tid, InstSeqNum sn,
|
||||
Addr pc, void * &iHistory) override;
|
||||
void update(ThreadID tid, InstSeqNum sn, Addr pc, bool squash,
|
||||
bool taken, const PCStateBase& target,
|
||||
BranchType br_type, void * &iHistory) override;
|
||||
void squash(ThreadID tid, InstSeqNum sn, void * &iHistory) override;
|
||||
void commit(ThreadID tid, InstSeqNum sn, void * &iHistory) override;
|
||||
|
||||
|
||||
|
||||
/** ------------------
|
||||
* The actual predictor
|
||||
* -------------------
|
||||
* */
|
||||
private:
|
||||
const bool hashGHR;
|
||||
const bool hashTargets;
|
||||
@@ -66,6 +81,7 @@ class SimpleIndirectPredictor : public IndirectPredictor
|
||||
const unsigned numWays;
|
||||
const unsigned tagBits;
|
||||
const unsigned pathLength;
|
||||
const unsigned speculativePathLength;
|
||||
const unsigned instShift;
|
||||
const unsigned ghrNumBits;
|
||||
const unsigned ghrMask;
|
||||
@@ -78,27 +94,88 @@ class SimpleIndirectPredictor : public IndirectPredictor
|
||||
|
||||
std::vector<std::vector<IPredEntry> > targetCache;
|
||||
|
||||
Addr getSetIndex(Addr br_addr, unsigned ghr, ThreadID tid);
|
||||
Addr getTag(Addr br_addr);
|
||||
|
||||
|
||||
struct HistoryEntry
|
||||
{
|
||||
HistoryEntry(Addr br_addr, Addr tgt_addr, InstSeqNum seq_num)
|
||||
: pcAddr(br_addr), targetAddr(tgt_addr), seqNum(seq_num) { }
|
||||
HistoryEntry() : pcAddr(0), targetAddr(0), seqNum(0) { }
|
||||
Addr pcAddr;
|
||||
Addr targetAddr;
|
||||
InstSeqNum seqNum;
|
||||
};
|
||||
|
||||
/** Indirect branch history information
|
||||
* Used for prediction, update and recovery
|
||||
*/
|
||||
struct IndirectHistory
|
||||
{
|
||||
/* data */
|
||||
Addr pcAddr;
|
||||
Addr targetAddr;
|
||||
InstSeqNum seqNum;
|
||||
|
||||
Addr set_index;
|
||||
Addr tag;
|
||||
bool hit;
|
||||
unsigned ghr;
|
||||
uint64_t pathHist;
|
||||
|
||||
bool was_indirect;
|
||||
|
||||
IndirectHistory()
|
||||
: pcAddr(MaxAddr),
|
||||
targetAddr(MaxAddr),
|
||||
was_indirect(false)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Per thread path and global history registers*/
|
||||
struct ThreadInfo
|
||||
{
|
||||
// Path history register
|
||||
std::deque<HistoryEntry> pathHist;
|
||||
unsigned headHistEntry = 0;
|
||||
// Global direction history register
|
||||
unsigned ghr = 0;
|
||||
};
|
||||
|
||||
std::vector<ThreadInfo> threadInfo;
|
||||
|
||||
|
||||
// ---- Internal functions ----- //
|
||||
bool lookup(ThreadID tid, Addr br_addr,
|
||||
PCStateBase * &target, IndirectHistory * &history);
|
||||
void recordTarget(ThreadID tid, InstSeqNum sn,
|
||||
const PCStateBase& target, IndirectHistory * &history);
|
||||
|
||||
// Helper functions to generate and modify the
|
||||
// direction info
|
||||
void genIndirectInfo(ThreadID tid, void* &iHistory);
|
||||
void updateDirectionInfo(ThreadID tid, bool taken, Addr pc, Addr target);
|
||||
|
||||
// Helper to compute set and tag
|
||||
inline Addr getSetIndex(Addr br_addr, ThreadID tid);
|
||||
inline Addr getTag(Addr br_addr);
|
||||
|
||||
inline bool isIndirectNoReturn(BranchType type) {
|
||||
return (type == BranchType::CallIndirect) ||
|
||||
(type == BranchType::IndirectUncond);
|
||||
}
|
||||
|
||||
protected:
|
||||
struct IndirectStats : public statistics::Group
|
||||
{
|
||||
IndirectStats(statistics::Group *parent);
|
||||
// STATS
|
||||
statistics::Scalar lookups;
|
||||
statistics::Scalar hits;
|
||||
statistics::Scalar misses;
|
||||
statistics::Scalar targetRecords;
|
||||
statistics::Scalar indirectRecords;
|
||||
statistics::Scalar speculativeOverflows;
|
||||
|
||||
} stats;
|
||||
};
|
||||
|
||||
} // namespace branch_prediction
|
||||
|
||||
Reference in New Issue
Block a user