use serde::{Deserialize, Serialize};
pub const TRACE_GROUP_BYTES: usize = 64;
const GROUPS_PER_SUMMARY: usize = 64;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceLayout {
pub flags_offset: usize,
pub group_count: usize,
pub summary_offset: usize,
pub summary_count: usize,
homes: Vec<(usize, usize)>,
}
impl TraceLayout {
pub(crate) fn new(offset: usize, stable_size: usize, mut homes: Vec<(usize, usize)>) -> Self {
homes.sort_unstable();
let mut merged: Vec<(usize, usize)> = Vec::new();
for (start, end) in homes {
if let Some(last) = merged.last_mut()
&& last.0 == start
{
last.1 = last.1.max(end);
} else {
merged.push((start, end));
}
}
let group_count = stable_size.div_ceil(TRACE_GROUP_BYTES);
let summary_count = group_count.div_ceil(GROUPS_PER_SUMMARY);
Self {
flags_offset: offset,
group_count,
summary_offset: offset + group_count,
summary_count,
homes: merged,
}
}
pub fn end_offset(&self) -> usize {
self.summary_offset + self.summary_count
}
pub fn validate(
&self,
stable_size: usize,
metadata_start: usize,
scratch_start: usize,
) -> bool {
self.group_count == stable_size.div_ceil(TRACE_GROUP_BYTES)
&& self.summary_count == self.group_count.div_ceil(GROUPS_PER_SUMMARY)
&& self.flags_offset >= metadata_start
&& self.flags_offset.checked_add(self.group_count) == Some(self.summary_offset)
&& self
.summary_offset
.checked_add(self.summary_count)
.is_some_and(|end| end <= scratch_start)
&& self
.homes
.iter()
.all(|&(start, end)| start <= end && end <= stable_size)
&& self.homes.windows(2).all(|homes| homes[0].1 <= homes[1].0)
}
pub fn notification_offsets(&self, stable_home: usize) -> [usize; 2] {
let group = stable_home / TRACE_GROUP_BYTES;
[
self.flags_offset + group,
self.summary_offset + group / GROUPS_PER_SUMMARY,
]
}
pub unsafe fn mark_home(&self, memory: *mut u8, home: usize) {
for offset in self.notification_offsets(home) {
unsafe {
*memory.add(offset) = 1;
}
}
}
pub unsafe fn mark_range(&self, memory: *mut u8, offset: usize, len: usize) {
if len == 0 {
return;
}
let first = self
.homes
.partition_point(|&(start, _)| start <= offset)
.saturating_sub(1);
for &(start, end) in &self.homes[first..] {
if start >= offset.saturating_add(len) {
break;
}
if end > offset {
unsafe {
self.mark_home(memory, start);
}
}
}
}
pub fn take(&self, memory: &mut [u8], groups: &mut Vec<usize>) {
groups.clear();
let (prefix, summaries) = memory.split_at_mut(self.summary_offset);
let flags = &mut prefix[self.flags_offset..][..self.group_count];
take_nonzero(&mut summaries[..self.summary_count], |summary| {
let start = summary * GROUPS_PER_SUMMARY;
let flags = &mut flags[start..];
let len = flags.len().min(GROUPS_PER_SUMMARY);
take_nonzero(&mut flags[..len], |index| groups.push(start + index));
});
}
}
fn take_nonzero(bytes: &mut [u8], mut visit: impl FnMut(usize)) {
let (words, tail) = bytes.as_chunks_mut::<8>();
for (index, bytes) in words.iter_mut().enumerate() {
let word = u64::from_le_bytes(*bytes);
if word == 0 {
continue;
}
bytes.fill(0);
const LOW_BITS: u64 = 0x7f7f_7f7f_7f7f_7f7f;
let mut active = (((word & LOW_BITS) + LOW_BITS) | word) & !LOW_BITS;
while active != 0 {
visit(index * 8 + active.trailing_zeros() as usize / 8);
active &= active - 1;
}
}
let base = words.len() * 8;
for (index, byte) in tail.iter_mut().enumerate() {
if *byte != 0 {
*byte = 0;
visit(base + index);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonzero_bytes_preserve_address_order() {
for alignment in 0..8 {
for len in 0..=512 {
let mut bytes = vec![0xa5; alignment + len + 8];
for (index, byte) in bytes[alignment..][..len].iter_mut().enumerate() {
*byte = if index % 2 == 0 { (index / 2) as u8 } else { 0 };
}
let expected: Vec<_> = bytes[alignment..][..len]
.iter()
.enumerate()
.filter_map(|(index, &byte)| (byte != 0).then_some(index))
.collect();
let mut actual = vec![];
take_nonzero(&mut bytes[alignment..][..len], |index| actual.push(index));
assert_eq!(actual, expected);
assert!(bytes[..alignment].iter().all(|&byte| byte == 0xa5));
assert!(bytes[alignment..][..len].iter().all(|&byte| byte == 0));
assert!(bytes[alignment + len..].iter().all(|&byte| byte == 0xa5));
}
}
}
#[test]
fn activity_boundaries_match_scalar_collection() {
for group_count in [
0, 1, 7, 8, 9, 63, 64, 65, 127, 128, 447, 448, 449, 511, 512, 513, 575, 576, 577, 1024,
] {
for alignment in 0..8 {
let stable_size = (group_count * TRACE_GROUP_BYTES).saturating_sub(3);
let trace = TraceLayout::new(stable_size + alignment, stable_size, vec![]);
assert_eq!(trace.group_count, group_count);
for pattern in 0..5 {
let mut memory = vec![0xa5; trace.end_offset() + 8];
memory[trace.flags_offset..trace.end_offset()].fill(0);
for group in 0..group_count {
let flag = match pattern {
0 => 0,
1 => 1,
2 => u8::from(group % 2 == 0),
3 => {
u8::from(group == 0 || group + 1 == group_count || group % 64 == 0)
}
_ => [0, 2, 0, 0x80, 0, 0xff, 1][group % 7],
};
memory[trace.flags_offset + group] = flag;
if flag != 0 {
memory[trace.summary_offset + group / GROUPS_PER_SUMMARY] = flag;
}
}
if trace.summary_count > 1 && matches!(pattern, 0 | 4) {
memory[trace.summary_offset] = 0xff;
memory[trace.summary_offset + 1] = 0;
}
let mut expected_memory = memory.clone();
let mut expected_groups = vec![];
for summary in 0..trace.summary_count {
if expected_memory[trace.summary_offset + summary] == 0 {
continue;
}
expected_memory[trace.summary_offset + summary] = 0;
for group in 0..group_count {
if group / GROUPS_PER_SUMMARY == summary
&& expected_memory[trace.flags_offset + group] != 0
{
expected_memory[trace.flags_offset + group] = 0;
expected_groups.push(group);
}
}
}
let mut groups = vec![usize::MAX];
trace.take(&mut memory, &mut groups);
assert_eq!(groups, expected_groups);
assert_eq!(memory, expected_memory);
groups.push(usize::MAX);
trace.take(&mut memory, &mut groups);
assert!(groups.is_empty());
assert_eq!(memory, expected_memory);
}
}
}
}
#[test]
fn aliases_partial_writes_and_separate_consumers() {
let trace = TraceLayout::new(
8192,
8192,
vec![(0, 512), (0, 64), (512, 520), (4096, 4104)],
);
let mut memory = vec![0; trace.end_offset()];
unsafe {
trace.mark_range(memory.as_mut_ptr(), 500, 2);
trace.mark_range(memory.as_mut_ptr(), 510, 4);
trace.mark_home(memory.as_mut_ptr(), 4096);
}
let mut groups = vec![];
trace.take(&mut memory, &mut groups);
assert_eq!(groups, [0, 8, 64]);
trace.take(&mut memory, &mut groups);
assert!(groups.is_empty());
}
}