cpu: Refactor indirect predictor
Simplify indirect predictor interface. Several of the existing functions where merged together into four clear once. Those four are similar to the main direction predictor interface. 'lookup', 'update', 'squash' and 'commit'. This makes the interface much more clear, allows better functionality isolation and makes it simpler to develop new predictor models. A new parameter is added to allow additional buffer space for speculative path history. Change-Id: I6d6b43965b2986ef959953a64c428e50bc68d38e Signed-off-by: David Schall <david.schall@ed.ac.uk>
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