use crate::common::InstSeq;
use crate::uarch::bpred::BranchPredictor;
use crate::uarch::pipeline::rob::RobTag;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SquashCause {
Branch,
System,
MemoryOrder,
Coherence,
}
impl SquashCause {
#[must_use]
pub const fn replays(self) -> bool {
matches!(self, Self::MemoryOrder | Self::Coherence)
}
}
#[derive(Clone, Copy, Debug)]
pub struct BranchRepair {
pub seq: InstSeq,
pub taken: bool,
pub target: u64,
}
impl BranchRepair {
pub fn apply(&self, predictor: &mut BranchPredictor) {
predictor.mispredict(self.seq, self.taken, self.target);
}
}
#[derive(Clone, Copy, Debug)]
pub struct Redirect {
pub target: u64,
pub cause: SquashCause,
pub repair: Option<BranchRepair>,
}
impl Redirect {
#[must_use]
pub const fn to(target: u64, cause: SquashCause) -> Self {
Self { target, cause, repair: None }
}
#[must_use]
pub const fn mispredict(target: u64, repair: BranchRepair) -> Self {
Self { target, cause: SquashCause::Branch, repair: Some(repair) }
}
}
#[derive(Clone, Copy, Debug)]
pub struct PendingSquash {
pub keep_tag: Option<RobTag>,
pub redirect: Redirect,
pub apply_at: u64,
}
impl PendingSquash {
#[must_use]
pub fn squashes(&self, tag: RobTag) -> bool {
self.keep_tag.is_none_or(|keep| tag.is_newer_than(keep))
}
#[must_use]
pub const fn is_older_than(&self, other: &Self) -> bool {
match (self.keep_tag, other.keep_tag) {
(None, Some(_)) => true,
(Some(mine), Some(theirs)) => mine.is_older_than(theirs),
(None | Some(_), None) => false,
}
}
#[must_use]
pub const fn takes_precedence_over(&self, other: &Self) -> bool {
if self.is_older_than(other) {
return true;
}
let same_window = match (self.keep_tag, other.keep_tag) {
(Some(mine), Some(theirs)) => mine.0 == theirs.0,
(None, None) => true,
_ => false,
};
same_window && other.redirect.cause.replays() && !self.redirect.cause.replays()
}
#[must_use]
pub const fn is_due(&self, now: u64) -> bool {
self.apply_at <= now
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pending(keep_tag: Option<u32>) -> PendingSquash {
PendingSquash {
keep_tag: keep_tag.map(RobTag),
redirect: Redirect::to(0, SquashCause::System),
apply_at: 0,
}
}
#[test]
fn squashes_everything_younger_than_the_kept_tag() {
let squash = pending(Some(5));
assert!(!squash.squashes(RobTag(4)));
assert!(!squash.squashes(RobTag(5)));
assert!(squash.squashes(RobTag(6)));
}
#[test]
fn a_squash_without_a_kept_tag_squashes_the_whole_window() {
assert!(pending(None).squashes(RobTag(0)));
}
fn keeping(keep_tag: u32, cause: SquashCause) -> PendingSquash {
PendingSquash {
keep_tag: Some(RobTag(keep_tag)),
redirect: Redirect::to(0, cause),
apply_at: 0,
}
}
#[test]
fn a_redirect_from_the_kept_instruction_beats_a_replay_of_the_next() {
let violation = keeping(5, SquashCause::MemoryOrder);
let mispredict = keeping(5, SquashCause::Branch);
assert!(mispredict.takes_precedence_over(&violation));
assert!(!violation.takes_precedence_over(&mispredict));
}
#[test]
fn a_squash_that_removes_more_takes_precedence_whatever_its_cause() {
let older_violation = keeping(3, SquashCause::Coherence);
let younger_mispredict = keeping(7, SquashCause::Branch);
assert!(older_violation.takes_precedence_over(&younger_mispredict));
assert!(!younger_mispredict.takes_precedence_over(&older_violation));
}
#[test]
fn the_squash_keeping_less_is_the_older_one() {
assert!(pending(Some(3)).is_older_than(&pending(Some(7))));
assert!(!pending(Some(7)).is_older_than(&pending(Some(3))));
assert!(pending(None).is_older_than(&pending(Some(0))));
assert!(!pending(Some(0)).is_older_than(&pending(None)));
assert!(!pending(Some(3)).is_older_than(&pending(Some(3))));
}
}