use crate::common::{PAGE_SHIFT, PhysAddr, VirtAddr};
use crate::config::PageBoundary;
use crate::sim::packet::CacheLevel;
use crate::soc::cache::prefetch::stride::{StrideTracker, line_along, pc_index};
use crate::uarch::mmu::PrefetchTranslation;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LoadPrefetch {
pub line: VirtAddr,
pub paddr: PhysAddr,
pub into: CacheLevel,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PrefetchDrop {
PageBoundary,
TlbMiss,
Denied,
NotRam,
}
const LEVELS: [CacheLevel; 2] = [CacheLevel::L1D, CacheLevel::L2];
#[derive(Clone, Copy, Debug)]
struct Stream {
pc: VirtAddr,
tracker: StrideTracker,
ahead: [Option<u64>; 2],
}
impl Stream {
const fn new(pc: VirtAddr, vaddr: VirtAddr) -> Self {
Self { pc, tracker: StrideTracker::starting_at(vaddr.val()), ahead: [None; 2] }
}
const fn needs(&self, level: usize, line: u64, current: u64, stride: i64, reach: i64) -> bool {
let Some(ahead) = self.ahead[level] else { return true };
let direction = stride.signum();
let lead = (ahead as i64).wrapping_sub(current as i64).wrapping_mul(direction);
if lead <= 0 || lead > reach {
return true;
}
(line as i64).wrapping_sub(ahead as i64).wrapping_mul(direction) > 0
}
}
#[derive(Debug)]
pub struct LoadPrefetcher {
table: Vec<Option<Stream>>,
table_mask: usize,
line_bytes: u64,
l1_lines: usize,
l2_lines: usize,
page_boundary: PageBoundary,
}
impl LoadPrefetcher {
#[must_use]
pub fn new(
line_bytes: usize,
table_size: usize,
l1_lines: usize,
l2_lines: usize,
page_boundary: PageBoundary,
) -> Self {
let size = if table_size.is_power_of_two() { table_size } else { 64 };
Self {
table: vec![None; size],
table_mask: size - 1,
line_bytes: line_bytes as u64,
l1_lines,
l2_lines,
page_boundary,
}
}
#[must_use]
pub const fn page_boundary(&self) -> PageBoundary {
self.page_boundary
}
pub fn train(
&mut self,
pc: VirtAddr,
vaddr: VirtAddr,
mut place: impl FnMut(VirtAddr) -> Result<PhysAddr, PrefetchDrop>,
) -> Vec<LoadPrefetch> {
let (line_bytes, l1_lines, l2_lines) = (self.line_bytes, self.l1_lines, self.l2_lines);
let slot = &mut self.table[pc_index(pc, self.table_mask)];
if slot.is_none_or(|stream| stream.pc != pc) {
*slot = Some(Stream::new(pc, vaddr));
return Vec::new();
}
let Some(stream) = slot.as_mut() else { return Vec::new() };
let learned = stream.tracker.stride();
let confident = stream.tracker.train(vaddr.val());
if stream.tracker.stride() != learned {
stream.ahead = [None; 2];
}
let Some(stride) = confident else { return Vec::new() };
let ranges = [1..=l1_lines as i64, l1_lines as i64 + 1..=l2_lines as i64];
let furthest = l1_lines.max(l2_lines) as i64 + 1;
let reach = stride.abs().max(line_bytes as i64).saturating_mul(furthest);
let mut prefetches = Vec::new();
for (level, (into, steps)) in LEVELS.into_iter().zip(ranges).enumerate() {
for k in steps {
let line = line_along(vaddr.val(), stride, k, line_bytes);
if !stream.needs(level, line, vaddr.val(), stride, reach) {
continue;
}
let Ok(paddr) = place(VirtAddr::new(line)) else { break };
prefetches.push(LoadPrefetch { line: VirtAddr::new(line), paddr, into });
stream.ahead[level] = Some(line);
}
}
prefetches
}
}
#[derive(Debug)]
pub struct PagePlacer<F> {
trigger: VirtAddr,
trigger_paddr: PhysAddr,
page_bytes: u64,
translated: bool,
page_boundary: PageBoundary,
translate: F,
}
impl<F: Fn(VirtAddr) -> PrefetchTranslation> PagePlacer<F> {
pub fn new(
trigger: VirtAddr,
trigger_paddr: PhysAddr,
page_boundary: PageBoundary,
translate: F,
) -> Self {
let (page_bytes, translated) = match translate(trigger) {
PrefetchTranslation::Mapped { page, .. } => (page.bytes(), true),
PrefetchTranslation::Untranslated => (1 << PAGE_SHIFT, false),
PrefetchTranslation::Missing | PrefetchTranslation::Denied => (1 << PAGE_SHIFT, true),
};
Self { trigger, trigger_paddr, page_bytes, translated, page_boundary, translate }
}
pub fn place(&self, target: VirtAddr) -> Result<PhysAddr, PrefetchDrop> {
let page_mask = !(self.page_bytes - 1);
if target.val() & page_mask == self.trigger.val() & page_mask {
let offset = target.val().wrapping_sub(self.trigger.val());
return Ok(PhysAddr::new(self.trigger_paddr.val().wrapping_add(offset)));
}
match self.page_boundary {
PageBoundary::Stop => Err(PrefetchDrop::PageBoundary),
PageBoundary::CrossWithTlb if !self.translated => Ok(PhysAddr::new(target.val())),
PageBoundary::CrossWithTlb => match (self.translate)(target) {
PrefetchTranslation::Mapped { paddr, .. } => Ok(paddr),
PrefetchTranslation::Untranslated => Ok(PhysAddr::new(target.val())),
PrefetchTranslation::Missing => Err(PrefetchDrop::TlbMiss),
PrefetchTranslation::Denied => Err(PrefetchDrop::Denied),
},
}
}
}