use super::{HostPlan, PagedAttentionError};
pub(super) const QUERY_BLOCK_ROWS: usize = 32;
pub(super) fn build(host: &HostPlan, max_words: usize) -> Result<Vec<u32>, PagedAttentionError> {
let fixed = host.pages.checked_add(4)
.and_then(|n| host.sequences.checked_mul(4).and_then(|s| n.checked_add(s)))
.and_then(|n| n.checked_add(2))
.and_then(|n| n.checked_add(host.queries))
.filter(|&n| n <= max_words && n <= u32::MAX as usize)
.ok_or(PagedAttentionError("ordered history metadata exceeds workspace budget"))?;
let mut counts = vec![0usize; host.sequences];
for row in 0..host.queries { counts[host.words[row] as usize] += 1; }
let mut entries = 0usize;
let mut blocks = 0usize;
for (s, &count) in counts.iter().enumerate() {
if count != 0 {
let length = host.words[2 * host.queries + s] as usize;
entries = entries.checked_add(length.div_ceil(host.page_size))
.ok_or(PagedAttentionError("ordered history index overflow"))?;
}
blocks = blocks.checked_add(count.div_ceil(QUERY_BLOCK_ROWS))
.ok_or(PagedAttentionError("ordered visibility index overflow"))?;
}
let total = entries.checked_mul(2).and_then(|n| fixed.checked_add(n))
.and_then(|n| n.checked_add(blocks))
.filter(|&n| n <= max_words && n <= u32::MAX as usize)
.ok_or(PagedAttentionError("ordered history metadata exceeds workspace budget"))?;
let entries_base = 4 + host.pages;
let sequence_base = entries_base + 2 * entries;
let query_base = sequence_base + host.sequences + 1;
let visibility_base = query_base + host.queries;
let block_offsets_base = visibility_base + 2 * host.sequences;
let block_maxima_base = block_offsets_base + host.sequences + 1;
debug_assert_eq!(block_maxima_base + blocks, total);
let mut links = Vec::with_capacity(entries);
for (s, &count) in counts.iter().enumerate() {
if count == 0 { continue; }
let length = host.words[2 * host.queries + s] as usize;
for logical in 0..length.div_ceil(host.page_size) {
let page = host.words[2 * host.queries + host.sequences + s * host.table_width + logical];
links.push((page, s as u32, logical as u32));
}
}
links.sort_unstable(); let mut rows: Vec<_> = (0..host.queries)
.map(|row| (host.words[row], row as u32)).collect();
rows.sort_unstable(); let mut out = vec![0u32; total];
out[0] = entries_base as u32; out[1] = sequence_base as u32; out[2] = query_base as u32;
for &(page, _, _) in &links { out[3 + page as usize + 1] += 1; }
for page in 0..host.pages { out[3 + page + 1] += out[3 + page]; }
for (i, &(_, sequence, logical)) in links.iter().enumerate() {
out[entries_base + 2 * i] = sequence;
out[entries_base + 2 * i + 1] = logical;
}
for &(sequence, _) in &rows { out[sequence_base + sequence as usize + 1] += 1; }
for sequence in 0..host.sequences {
out[sequence_base + sequence + 1] += out[sequence_base + sequence];
}
for (i, &(_, row)) in rows.iter().enumerate() { out[query_base + i] = row; }
let mut block_cursor = 0usize;
for sequence in 0..host.sequences {
let begin = out[sequence_base + sequence] as usize;
let end = out[sequence_base + sequence + 1] as usize;
let mut monotonic = true;
let mut previous = 0u32;
let mut maximum = 0u32;
out[block_offsets_base + sequence] = block_cursor as u32;
for i in begin..end {
let pos = host.words[host.queries + out[query_base + i] as usize];
if i != begin && pos < previous { monotonic = false; }
previous = pos; maximum = maximum.max(pos);
let block = block_cursor + (i - begin) / QUERY_BLOCK_ROWS;
out[block_maxima_base + block] = out[block_maxima_base + block].max(pos);
}
out[visibility_base + sequence] = u32::from(monotonic);
out[visibility_base + host.sequences + sequence] = maximum;
block_cursor += (end - begin).div_ceil(QUERY_BLOCK_ROWS);
}
out[block_offsets_base + host.sequences] = block_cursor as u32;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn selected(host: &HostPlan, index: &[u32], seq: usize, token: u32, causal: bool) -> Vec<u32> {
let sb=index[1] as usize; let qb=index[2] as usize;
let vb=qb+host.queries; let ob=vb+2*host.sequences; let mb=ob+host.sequences+1;
let begin=index[sb+seq] as usize; let end=index[sb+seq+1] as usize;
let mono=index[vb+seq]!=0;
if causal && token>index[vb+host.sequences+seq] { return vec![]; }
let mut cursor=begin;
if causal && mono && cursor<end && host.words[host.queries+index[qb+cursor] as usize]<token {
let mut right=end;
while cursor<right {
let mid=cursor+(right-cursor)/2;
if host.words[host.queries+index[qb+mid] as usize]<token {cursor=mid+1;} else {right=mid;}
}
}
let mut out=vec![];
while cursor<end {
let mut stop=end;
if causal && !mono {
let b=(cursor-begin)/QUERY_BLOCK_ROWS;
stop=end.min(begin+(b+1)*QUERY_BLOCK_ROWS);
if index[mb+index[ob+seq] as usize+b]<token {cursor=stop;continue;}
}
while cursor<stop {
let row=index[qb+cursor];
if !causal || mono || host.words[host.queries+row as usize]>=token {out.push(row);}
cursor+=1;
}
}
out
}
#[test] fn shared_pages_and_unsorted_rows() {
let h=HostPlan::new(2,4,&[vec![2,0],vec![2,3]],&[3,4],&[1,0,1],&[3,2,1]).unwrap();
let x=build(&h,100).unwrap();
assert_eq!(&x[3..8],&[0,1,1,3,4]);
let e=x[0] as usize; assert_eq!(&x[e..e+8],&[0,1,0,0,1,0,1,1]);
let q=x[2] as usize; assert_eq!(&x[q..q+h.queries],&[1,0,2]);
assert_eq!(selected(&h,&x,1,2,true),vec![0]);
}
#[test] fn unused_pages_and_empty_queries() {
let h=HostPlan::new(4,3,&[vec![]],&[0],&[],&[]).unwrap();
let x=build(&h,100).unwrap(); assert_eq!(&x[3..7],&[0,0,0,0]);
assert!(selected(&h,&x,0,0,true).is_empty());
}
#[test] fn size_guard_before_allocation() {
let h=HostPlan::new(2,100,&[vec![0]],&[1],&[0],&[0]).unwrap();
assert!(build(&h,10).is_err());
}
#[test] fn exact_budget_accepts_one_less_rejects() {
let h=HostPlan::new(4,1,&[vec![0]],&[4],&[0,0],&[1,3]).unwrap();
let x=build(&h,100).unwrap(); assert_eq!(build(&h,x.len()).unwrap(),x);
assert!(build(&h,x.len()-1).is_err());
}
#[test] fn no_query_sequence_has_no_page_links() {
let h=HostPlan::new(4,2,&[vec![0],vec![1]],&[4,4],&[0],&[2]).unwrap();
let x=build(&h,100).unwrap(); assert_eq!(&x[3..6],&[0,1,1]);
}
#[test] fn monotonic_duplicates_include_boundary() {
let h=HostPlan::new(8,1,&[vec![0]],&[8],&[0;5],&[0,2,2,6,7]).unwrap();
let x=build(&h,100).unwrap();
assert_eq!(selected(&h,&x,0,2,true),vec![1,2,3,4]);
assert!(selected(&h,&x,0,8,true).is_empty());
assert_eq!(selected(&h,&x,0,8,false),vec![0,1,2,3,4]);
}
#[test] fn unsorted_blocks_keep_original_accumulation_order() {
let mut positions:Vec<u32>=(0..97).map(|x| ((x*7)%97) as u32).collect();
positions[32..64].fill(0);
let h=HostPlan::new(128,1,&[vec![0]],&[128],&vec![0;97],&positions).unwrap();
let x=build(&h,1000).unwrap();
for token in 0..130 {
let expected:Vec<_>=positions.iter().enumerate().filter(|(_,p)| **p>=token).map(|(i,_)|i as u32).collect();
assert_eq!(selected(&h,&x,0,token,true),expected);
}
}
#[test] fn randomized_visibility_keeps_exact_row_sequence() {
let mut seed=0x9e3779b9u32;
let mut rand=|| {seed=seed.wrapping_mul(1664525).wrapping_add(1013904223);seed};
for _ in 0..300 {
let n=(rand()%180) as usize;
let ids:Vec<_>=(0..n).map(|_|rand()%3).collect();
let positions:Vec<_>=(0..n).map(|_|rand()%99).collect();
let h=HostPlan::new(128,3,&[vec![0],vec![0],vec![2]],&[99;3],&ids,&positions).unwrap();
let x=build(&h,10000).unwrap();
for seq in 0..3 { for token in [0,1,31,32,63,98,99,u32::MAX] { for causal in [true,false] {
let expected:Vec<_>=(0..n).filter(|&r|ids[r]==seq as u32 && (!causal||positions[r]>=token)).map(|r|r as u32).collect();
assert_eq!(selected(&h,&x,seq,token,causal),expected);
}}}
}
}
}