use crate::config::StoreSetConfig;
use crate::uarch::pipeline::rob::RobTag;
use super::predictor::{MemDepPredictor, MemPrediction};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StoreSetId(pub u16);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SsitIndex(pub u32);
impl SsitIndex {
#[inline]
pub const fn from_pc(pc: u64, table_size: usize) -> Self {
Self(((pc >> 2) as u32) % (table_size as u32))
}
}
#[derive(Debug)]
pub struct StoreSetPredictor {
ssit: Vec<Option<StoreSetId>>,
lfst: Vec<Option<RobTag>>,
mem_ops: u64,
clear_period: u64,
}
impl StoreSetPredictor {
pub fn new(config: &StoreSetConfig) -> Self {
Self {
ssit: vec![None; config.ssit_size],
lfst: vec![None; config.lfst_size],
mem_ops: 0,
clear_period: config.clear_period,
}
}
const fn new_set_id(&self, load_pc: u64) -> StoreSetId {
StoreSetId(((load_pc ^ (load_pc >> 10)) % self.lfst.len() as u64) as u16)
}
#[inline]
const fn ssit_index(&self, pc: u64) -> SsitIndex {
SsitIndex::from_pc(pc, self.ssit.len())
}
}
impl MemDepPredictor for StoreSetPredictor {
fn predict(&mut self, pc: u64, _rob_tag: RobTag, _is_store: bool) -> MemPrediction {
let idx = self.ssit_index(pc);
let Some(set_id) = self.ssit[idx.0 as usize] else {
return MemPrediction::NoDep;
};
self.lfst[set_id.0 as usize].map_or(MemPrediction::NoDep, MemPrediction::DepOn)
}
fn register_store(&mut self, store_pc: u64, rob_tag: RobTag) {
let idx = self.ssit_index(store_pc);
if let Some(set_id) = self.ssit[idx.0 as usize] {
self.lfst[set_id.0 as usize] = Some(rob_tag);
}
}
fn train(&mut self, load_pc: u64, store_pc: u64) {
let load_idx = self.ssit_index(load_pc);
let store_idx = self.ssit_index(store_pc);
let load_set = self.ssit[load_idx.0 as usize];
let store_set = self.ssit[store_idx.0 as usize];
match (load_set, store_set) {
(None, None) => {
let id = self.new_set_id(load_pc);
self.ssit[load_idx.0 as usize] = Some(id);
self.ssit[store_idx.0 as usize] = Some(id);
}
(None, Some(sid)) => {
self.ssit[load_idx.0 as usize] = Some(sid);
}
(Some(lid), None) => {
self.ssit[store_idx.0 as usize] = Some(lid);
}
(Some(lid), Some(sid)) => {
if sid > lid {
self.ssit[store_idx.0 as usize] = Some(lid);
} else {
self.ssit[load_idx.0 as usize] = Some(sid);
}
}
}
}
fn rebuild_lfst_entry(&mut self, store_pc: u64, rob_tag: RobTag) {
let idx = self.ssit_index(store_pc);
if let Some(set_id) = self.ssit[idx.0 as usize] {
let slot = &mut self.lfst[set_id.0 as usize];
match *slot {
None => *slot = Some(rob_tag),
Some(current) => {
if rob_tag.is_newer_than(current) {
*slot = Some(rob_tag);
}
}
}
}
}
fn flush(&mut self) {
self.lfst.fill(None);
}
fn flush_after(&mut self, keep_tag: RobTag) {
for entry in &mut self.lfst {
if let Some(tag) = *entry
&& tag.is_newer_than(keep_tag)
{
*entry = None;
}
}
}
fn note_mem_op(&mut self) {
self.mem_ops += 1;
if self.clear_period > 0 && self.mem_ops > self.clear_period {
self.mem_ops = 0;
self.ssit.fill(None);
self.lfst.fill(None);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::StoreSetConfig;
fn test_predictor() -> StoreSetPredictor {
StoreSetPredictor::new(&StoreSetConfig { ssit_size: 64, lfst_size: 16, clear_period: 0 })
}
#[test]
fn test_unknown_pc_returns_no_dep() {
let mut p = test_predictor();
assert_eq!(p.predict(0x1000, RobTag(1), false), MemPrediction::NoDep);
assert_eq!(p.predict(0x2000, RobTag(2), false), MemPrediction::NoDep);
}
#[test]
fn test_train_creates_set_then_dep_on() {
let mut p = test_predictor();
let load_pc = 0x1000;
let store_pc = 0x2000;
p.train(load_pc, store_pc);
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::NoDep);
let store_tag = RobTag(5);
p.register_store(store_pc, store_tag);
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(store_tag));
}
#[test]
fn test_store_store_chain() {
let mut p = test_predictor();
let store_pc = 0x2000;
let load_pc = 0x1000;
p.train(load_pc, store_pc);
let s1 = RobTag(1);
assert_eq!(p.predict(store_pc, s1, true), MemPrediction::NoDep);
p.register_store(store_pc, s1);
let s2 = RobTag(2);
assert_eq!(p.predict(store_pc, s2, true), MemPrediction::DepOn(s1));
p.register_store(store_pc, s2);
let l1 = RobTag(3);
assert_eq!(p.predict(load_pc, l1, false), MemPrediction::DepOn(s2));
}
#[test]
fn test_flush_clears_lfst_keeps_ssit() {
let mut p = test_predictor();
let load_pc = 0x1000;
let store_pc = 0x2000;
let store_tag = RobTag(5);
p.train(load_pc, store_pc);
p.register_store(store_pc, store_tag);
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(store_tag));
p.flush();
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::NoDep);
let new_tag = RobTag(10);
p.register_store(store_pc, new_tag);
assert_eq!(p.predict(load_pc, RobTag(15), false), MemPrediction::DepOn(new_tag));
}
#[test]
fn test_flush_after_partial() {
let mut p = test_predictor();
let store_pc_a = 0x1004; let store_pc_b = 0x1008; let load_pc_a = 0x100C; let load_pc_b = 0x1010;
p.train(load_pc_a, store_pc_a);
p.train(load_pc_b, store_pc_b);
let old_tag = RobTag(2);
let new_tag = RobTag(8);
p.register_store(store_pc_a, old_tag);
p.register_store(store_pc_b, new_tag);
p.flush_after(RobTag(5));
assert_eq!(p.predict(load_pc_a, RobTag(10), false), MemPrediction::DepOn(old_tag));
assert_eq!(p.predict(load_pc_b, RobTag(10), false), MemPrediction::NoDep);
}
#[test]
fn test_merge_sets() {
let mut p = test_predictor();
let load_pc = 0x1000;
let store_a = 0x2000;
let store_b = 0x3000;
p.train(load_pc, store_a);
p.train(0x4000, store_b);
p.train(load_pc, store_b);
let tag_b = RobTag(10);
p.register_store(store_b, tag_b);
assert_eq!(p.predict(load_pc, RobTag(15), false), MemPrediction::DepOn(tag_b));
}
#[test]
fn test_rebuild_lfst_entry() {
let mut p = test_predictor();
let store_pc = 0x2000;
let load_pc = 0x1000;
p.train(load_pc, store_pc);
p.rebuild_lfst_entry(store_pc, RobTag(5));
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(RobTag(5)));
p.rebuild_lfst_entry(store_pc, RobTag(3));
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(RobTag(5)));
p.rebuild_lfst_entry(store_pc, RobTag(8));
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(RobTag(8)));
}
#[test]
fn tables_are_wiped_after_the_clear_period_of_memory_ops() {
let mut p = StoreSetPredictor::new(&StoreSetConfig {
ssit_size: 64,
lfst_size: 16,
clear_period: 100,
});
let load_pc = 0x1000;
let store_pc = 0x2000;
p.train(load_pc, store_pc);
p.register_store(store_pc, RobTag(5));
for _ in 0..100 {
p.note_mem_op();
}
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::DepOn(RobTag(5)));
p.note_mem_op();
assert_eq!(p.predict(load_pc, RobTag(10), false), MemPrediction::NoDep);
p.register_store(store_pc, RobTag(11));
assert_eq!(p.predict(load_pc, RobTag(12), false), MemPrediction::NoDep);
}
#[test]
fn a_new_set_is_numbered_from_the_load_pc() {
let mut p = test_predictor();
let load_pc: u64 = 0x1234_5678;
p.train(load_pc, 0x2000);
let expected = StoreSetId(((load_pc ^ (load_pc >> 10)) % 16) as u16);
assert_eq!(p.ssit[p.ssit_index(load_pc).0 as usize], Some(expected));
assert_eq!(p.ssit[p.ssit_index(0x2000).0 as usize], Some(expected));
}
#[test]
fn merging_two_sets_keeps_the_lower_id() {
let mut p = test_predictor();
let (load_a, store_a) = (0x1004, 0x2004);
let (load_b, store_b) = (0x1010, 0x2010);
p.train(load_a, store_a);
p.train(load_b, store_b);
let set_a = p.ssit[p.ssit_index(load_a).0 as usize].unwrap();
let set_b = p.ssit[p.ssit_index(load_b).0 as usize].unwrap();
assert_ne!(set_a, set_b);
p.train(load_a, store_b);
let lower = set_a.min(set_b);
assert_eq!(p.ssit[p.ssit_index(load_a).0 as usize], Some(lower));
assert_eq!(p.ssit[p.ssit_index(store_b).0 as usize], Some(lower));
}
}