#![allow(dead_code)]
use crate::vbyte;
pub const DEFAULT_CARRY_CAP: usize = 1_000_000;
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize)]
pub enum CarryLayout {
#[default]
IndexOnly,
IndexPlusScalars { widths: Vec<u8> },
AddrFrontier,
}
#[derive(Debug, Clone)]
pub struct Carry {
layout: CarryLayout,
cap: usize,
truncated: bool,
idx: Vec<u32>,
cols: Vec<Vec<u64>>,
addrs: std::collections::BTreeSet<u64>,
}
impl Carry {
pub fn index_only(cap: usize) -> Self {
Self {
layout: CarryLayout::IndexOnly,
cap,
truncated: false,
idx: Vec::new(),
cols: Vec::new(),
addrs: std::collections::BTreeSet::new(),
}
}
pub fn layout(&self) -> &CarryLayout {
&self.layout
}
pub fn truncated(&self) -> bool {
self.truncated
}
pub fn len(&self) -> usize {
self.idx.len()
}
pub fn is_empty(&self) -> bool {
self.idx.is_empty()
}
pub fn push_index(&mut self, dense_idx: u32) {
if self.idx.len() >= self.cap {
self.truncated = true;
return;
}
self.idx.push(dense_idx);
}
pub fn indices(&self) -> Vec<u32> {
let mut buf = Vec::new();
encode_indices_delta(&self.idx, &mut buf);
decode_indices_delta(&buf, self.idx.len())
}
pub fn index_plus_scalars(cap: usize, widths: Vec<u8>) -> Self {
let n = widths.len();
Self {
layout: CarryLayout::IndexPlusScalars { widths },
cap,
truncated: false,
idx: Vec::new(),
cols: vec![Vec::new(); n],
addrs: std::collections::BTreeSet::new(),
}
}
pub fn push_row(&mut self, dense_idx: u32, vals: &[u64]) {
let CarryLayout::IndexPlusScalars { widths } = &self.layout else {
panic!(
"push_row is only valid for IndexPlusScalars, got {:?}",
self.layout
);
};
assert_eq!(
vals.len(),
widths.len(),
"push_row expected {} scalar column(s) but got {}",
widths.len(),
vals.len()
);
if self.idx.len() >= self.cap {
self.truncated = true;
return;
}
for (k, (&v, &w)) in vals.iter().zip(widths.iter()).enumerate() {
let max = if w >= 8 {
u64::MAX
} else {
(1u64 << (w as u32 * 8)) - 1
};
assert!(
v <= max,
"scalar column {k}: value {v} does not fit declared width {w} byte(s) (max {max})"
);
self.cols[k].push(v);
}
self.idx.push(dense_idx);
}
pub fn scalar_column(&self, k: usize) -> Vec<u64> {
let CarryLayout::IndexPlusScalars { widths } = &self.layout else {
panic!("scalar_column is only valid for IndexPlusScalars");
};
let w = widths[k] as usize;
let mut buf = Vec::with_capacity(self.cols[k].len() * w);
for &v in &self.cols[k] {
buf.extend_from_slice(&v.to_be_bytes()[8 - w..]);
}
let mut out = Vec::with_capacity(self.cols[k].len());
for chunk in buf.chunks_exact(w) {
let mut acc = 0u64;
for &b in chunk {
acc = (acc << 8) | b as u64;
}
out.push(acc);
}
out
}
pub fn addr_frontier(cap: usize) -> Self {
Self {
layout: CarryLayout::AddrFrontier,
cap,
truncated: false,
idx: Vec::new(),
cols: Vec::new(),
addrs: std::collections::BTreeSet::new(),
}
}
pub fn push_addr(&mut self, addr: u64) {
if self.addrs.contains(&addr) {
return;
}
if self.addrs.len() >= self.cap {
self.truncated = true;
return;
}
self.addrs.insert(addr);
}
pub fn addresses(&self) -> Vec<u64> {
let sorted: Vec<u64> = self.addrs.iter().copied().collect();
let mut buf = Vec::new();
vbyte::encode_delta_u64(&sorted, &mut buf);
vbyte::decode_delta_u64(&buf, sorted.len())
}
}
fn encode_indices_delta(idx: &[u32], out: &mut Vec<u8>) {
let mut prev = 0u32;
for &v in idx {
vbyte::encode(v.wrapping_sub(prev), out);
prev = v;
}
}
fn decode_indices_delta(buf: &[u8], count: usize) -> Vec<u32> {
let mut out = Vec::with_capacity(count);
let mut prev = 0u32;
let mut i = 0;
while i < buf.len() && out.len() < count {
let (d, n) = vbyte::decode_one(&buf[i..]);
prev = prev.wrapping_add(d);
out.push(prev);
i += n;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_only_roundtrips_sorted_indices() {
let mut c = Carry::index_only(1_000_000);
for &i in &[3u32, 3, 10, 4096, 4097, 999_999] {
c.push_index(i);
}
assert!(!c.truncated());
assert_eq!(c.indices(), vec![3, 3, 10, 4096, 4097, 999_999]);
}
#[test]
fn cap_trips_truncated_and_bounds_len() {
let mut c = Carry::index_only(2);
c.push_index(1);
c.push_index(2);
assert!(!c.truncated(), "at-cap but not over yet");
c.push_index(3); assert!(c.truncated());
assert_eq!(c.len(), 2, "cap must bound stored indices");
assert_eq!(c.indices(), vec![1, 2]);
}
#[test]
fn empty_carry_roundtrips_to_empty() {
let c = Carry::index_only(10);
assert!(c.is_empty());
assert!(!c.truncated());
assert_eq!(c.indices(), Vec::<u32>::new());
}
#[test]
fn descending_pushes_still_roundtrip_exactly() {
let mut c = Carry::index_only(100);
for &i in &[100u32, 5, 5, 42, 0] {
c.push_index(i);
}
assert_eq!(c.indices(), vec![100, 5, 5, 42, 0]);
}
#[test]
fn index_plus_scalars_roundtrips_columns() {
let mut c = Carry::index_plus_scalars(1000, vec![1, 4]);
c.push_row(10, &[7, 0x0001_0000]);
c.push_row(11, &[255, 42]);
assert!(!c.truncated());
assert_eq!(c.indices(), vec![10, 11]);
assert_eq!(c.scalar_column(0), vec![7, 255]);
assert_eq!(c.scalar_column(1), vec![0x0001_0000, 42]);
}
#[test]
fn scalar_width_overflow_panics_with_message() {
let mut c = Carry::index_plus_scalars(10, vec![1]);
let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
c.push_row(0, &[256]); }));
assert!(err.is_err(), "over-wide scalar must panic");
}
#[test]
fn addr_frontier_dedups_and_sorts() {
let mut c = Carry::addr_frontier(100);
for &a in &[0x5000u64, 0x1000, 0x5000, 0x1000, 0x9abc] {
c.push_addr(a);
}
assert_eq!(c.addresses(), vec![0x1000, 0x5000, 0x9abc]);
assert!(!c.truncated());
}
#[test]
fn addr_frontier_cap_counts_distinct() {
let mut c = Carry::addr_frontier(2);
c.push_addr(0x10);
c.push_addr(0x10); c.push_addr(0x20); c.push_addr(0x30); assert!(c.truncated());
assert_eq!(c.addresses(), vec![0x10, 0x20]);
}
}