const CACHE_LINE: usize = 64;
pub const ENABLED: bool = cfg!(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
));
#[allow(clippy::missing_const_for_fn)]
#[inline(always)]
pub fn prefetch<T>(ptr: *const T) {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "x86_64", not(miri)))] {
unsafe {
core::arch::x86_64::_mm_prefetch::<{ core::arch::x86_64::_MM_HINT_T0 }>(ptr.cast());
}
} else if #[cfg(all(target_arch = "aarch64", not(miri)))] {
unsafe {
core::arch::asm!(
"prfm pldl1keep, [{ptr}]",
ptr = in(reg) ptr,
options(nostack, readonly, preserves_flags),
);
}
} else {
let _ = ptr;
}
}
}
#[inline]
pub fn prefetch_lines<T>(ptr: *const T, len: usize, max_lines: usize) {
if !ENABLED {
return;
}
for line in lines(ptr.cast(), len.saturating_mul(size_of::<T>()), max_lines) {
prefetch(line);
}
}
#[inline]
fn lines(ptr: *const u8, len: usize, max_lines: usize) -> impl Iterator<Item = *const u8> {
let misalign = ptr.addr() % CACHE_LINE;
let count = if len == 0 {
0
} else {
misalign.saturating_add(len).div_ceil(CACHE_LINE)
};
let first = ptr.wrapping_sub(misalign);
(0..count.min(max_lines)).map(move |line| first.wrapping_add(line * CACHE_LINE))
}
#[derive(Clone, Debug)]
pub struct Lookahead {
distance: usize,
unreturned: usize,
}
impl Lookahead {
pub const fn new(distance: usize) -> Self {
Self {
distance,
unreturned: usize::MAX,
}
}
pub fn advance<'a, T>(&mut self, remaining: &'a [T]) -> &'a [T] {
let reach = remaining.len().min(self.distance.saturating_add(1));
let returned = remaining.len().saturating_sub(self.unreturned);
self.unreturned = remaining.len() - reach;
&remaining[returned..reach]
}
}
#[cfg(test)]
#[commonware_macros::test_group("miri")]
mod tests {
use super::*;
fn line_offsets(start: usize, len: usize, max_lines: usize) -> Vec<usize> {
let base = core::ptr::without_provenance::<u8>(0x10000);
lines(base.wrapping_add(start), len, max_lines)
.map(|line| line.addr() - base.addr())
.collect()
}
#[test]
fn test_lines_cover_every_intersected_line() {
assert_eq!(line_offsets(0, 64, 8), [0]);
assert_eq!(line_offsets(0, 65, 8), [0, 64]);
assert_eq!(line_offsets(16, 64, 8), [0, 64]);
assert_eq!(line_offsets(16, 128, 8), [0, 64, 128]);
assert_eq!(line_offsets(63, 2, 8), [0, 64]);
assert!(line_offsets(16, 0, 8).is_empty());
assert_eq!(
line_offsets(16, 1024, 8),
[0, 64, 128, 192, 256, 320, 384, 448]
);
assert_eq!(line_offsets(1, usize::MAX, 2), [0, 64]);
assert!(line_offsets(1, usize::MAX, 0).is_empty());
}
#[test]
fn test_lookahead_returns_each_item_once_distance_ahead() {
for (total, distance) in [(0, 2), (1, 2), (5, 0), (5, 2), (5, 8), (5, usize::MAX)] {
let items: Vec<usize> = (0..total).collect();
let mut ahead = Lookahead::new(distance);
let mut scan = items.into_iter();
let mut hinted = Vec::new();
let mut consumed = 0;
loop {
hinted.extend_from_slice(ahead.advance(scan.as_slice()));
assert_eq!(
hinted.len(),
total.min(distance.saturating_add(consumed + 1)),
"{total} {distance}"
);
let Some(item) = scan.next() else { break };
assert_eq!(item, consumed);
consumed += 1;
}
assert_eq!(hinted, (0..total).collect::<Vec<_>>(), "{total} {distance}");
}
}
#[test]
fn test_lookahead_never_repeats_an_item() {
let items = [0, 1, 2, 3];
let mut ahead = Lookahead::new(1);
assert_eq!(ahead.advance(&items), [0, 1]);
assert!(ahead.advance(&items).is_empty());
assert_eq!(ahead.advance(&items[3..]), [3]);
assert!(ahead.advance(&items[4..]).is_empty());
}
#[test]
fn test_prefetch_accepts_any_address() {
prefetch(core::ptr::null::<u8>());
prefetch_lines(core::ptr::without_provenance::<u64>(usize::MAX - 8), 64, 8);
}
}