use std::io::{self, Read, Write};
use crate::cvec::Codec;
use crate::vbyte;
pub struct IdMap {
block_base: Vec<u64>,
block_start: Vec<u32>,
offsets: Vec<u32>,
staging: Vec<u64>,
}
const BLOCK_SPAN: u64 = 1u64 << 32;
#[inline]
fn search_offsets(slice: &[u32], d: u32) -> Option<usize> {
let n = slice.len();
if n == 0 {
return None;
}
if n == 1 {
return if unsafe { *slice.get_unchecked(0) } == d {
Some(0)
} else {
None
};
}
let mut lo = 0usize;
let mut hi = n - 1;
let lo_val = unsafe { *slice.get_unchecked(lo) };
let hi_val = unsafe { *slice.get_unchecked(hi) };
if d < lo_val || d > hi_val {
return None;
}
for _ in 0..4 {
let range = hi - lo;
if range <= 128 {
break;
}
let lo_v = unsafe { *slice.get_unchecked(lo) } as u64;
let hi_v = unsafe { *slice.get_unchecked(hi) } as u64;
if hi_v == lo_v {
break; }
let offset = ((range as u64 * (d as u64 - lo_v) / (hi_v - lo_v)) as usize).min(range - 1);
let mid = (lo + offset).max(lo + 1).min(hi - 1);
let mid_v = unsafe { *slice.get_unchecked(mid) };
if mid_v == d {
return Some(mid);
} else if mid_v < d {
lo = mid + 1;
} else {
hi = mid.saturating_sub(1);
}
if lo > hi {
return None;
}
let lv = unsafe { *slice.get_unchecked(lo) };
let hv = unsafe { *slice.get_unchecked(hi) };
if d < lv || d > hv {
return None;
}
}
let sub = &slice[lo..=hi];
let sub_n = sub.len();
let mut base = 0usize;
let mut len = sub_n;
while len > 1 {
let half = len / 2;
let mid = base + half;
let q = (len - half) / 2;
let ptr = sub.as_ptr();
#[cfg(target_arch = "x86_64")]
unsafe {
use core::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
_mm_prefetch(ptr.add(base + q) as *const i8, _MM_HINT_T0);
_mm_prefetch(ptr.add(mid + q) as *const i8, _MM_HINT_T0);
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) ptr.add(base + q),
options(nostack, readonly)
);
core::arch::asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) ptr.add(mid + q),
options(nostack, readonly)
);
}
let take_upper = unsafe { *sub.get_unchecked(mid) } < d;
base = if take_upper { mid } else { base };
len -= half;
}
let pos = base + (unsafe { *sub.get_unchecked(base) } < d) as usize;
if pos < sub_n && unsafe { *sub.get_unchecked(pos) } == d {
Some(lo + pos)
} else {
None
}
}
#[allow(dead_code)]
impl IdMap {
pub fn new() -> Self {
Self {
block_base: Vec::new(),
block_start: Vec::new(),
offsets: Vec::new(),
staging: Vec::new(),
}
}
pub fn with_capacity(cap: usize) -> Self {
Self {
block_base: Vec::new(),
block_start: Vec::new(),
offsets: Vec::new(),
staging: Vec::with_capacity(cap),
}
}
pub fn push(&mut self, addr: u64) {
self.staging.push(addr);
}
pub fn sort_and_dedup(&mut self) {
self.staging.sort_unstable();
self.staging.dedup();
self.build_from_sorted();
}
#[inline]
pub fn push_sorted_addr(&mut self, addr: u64) {
debug_assert!(
self.staging.is_empty(),
"cannot mix push and push_sorted_addr"
);
if self.block_base.is_empty() {
self.block_base.push(addr);
self.block_start.push(0);
self.offsets.push(0);
return;
}
let mut base = *self.block_base.last().unwrap();
debug_assert!(
addr > self.addr_at(self.offsets.len() - 1),
"push_sorted_addr not ascending"
);
if addr - base >= BLOCK_SPAN {
base = addr;
self.block_base.push(base);
self.block_start.push(self.offsets.len() as u32);
}
self.offsets.push((addr - base) as u32);
}
pub fn reserve_offsets(&mut self, n: usize) {
self.offsets.reserve(n);
}
pub fn finalize_sorted(&mut self) {
if self.block_start.is_empty() {
self.block_start.push(0);
} else {
self.block_start.push(self.offsets.len() as u32);
}
}
fn build_from_sorted(&mut self) {
let len = self.staging.len();
self.block_base = Vec::new();
self.block_start = Vec::new();
self.offsets = Vec::with_capacity(len);
if len == 0 {
self.block_start.push(0);
self.staging = Vec::new();
return;
}
let mut base = self.staging[0];
self.block_base.push(base);
self.block_start.push(0);
for (i, &a) in self.staging.iter().enumerate() {
if a - base >= BLOCK_SPAN {
base = a;
self.block_base.push(base);
self.block_start.push(i as u32);
}
self.offsets.push((a - base) as u32);
}
self.block_start.push(len as u32); self.staging = Vec::new();
}
pub fn len(&self) -> usize {
self.offsets.len()
}
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
pub fn index_of(&self, addr: u64) -> Option<usize> {
if self.block_base.is_empty() || addr < self.block_base[0] {
return None;
}
let b = self.block_base.partition_point(|&base| base <= addr) - 1;
let delta = addr - self.block_base[b];
if delta >= BLOCK_SPAN {
return None; }
let d = delta as u32;
let lo = self.block_start[b] as usize;
let hi = self.block_start[b + 1] as usize;
search_offsets(&self.offsets[lo..hi], d).map(|pos| lo + pos)
}
pub fn addr_at(&self, i: usize) -> u64 {
let b = self.block_start.partition_point(|&s| (s as usize) <= i) - 1;
self.block_base[b] + self.offsets[i] as u64
}
pub fn compress(&self, codec: Codec) -> io::Result<(Vec<u8>, usize)> {
let len = self.len();
match codec {
Codec::None => {
let mut out = Vec::with_capacity(len * 8);
for i in 0..len {
out.extend_from_slice(&self.addr_at(i).to_le_bytes());
}
Ok((out, len))
}
Codec::Deflate9 => {
let mut vb: Vec<u8> = Vec::new();
let mut prev = 0u64;
for b in 0..self.block_base.len() {
let base = self.block_base[b];
let lo = self.block_start[b] as usize;
let hi = self.block_start[b + 1] as usize;
for &off in &self.offsets[lo..hi] {
let addr = base + off as u64;
vbyte::encode_u64(addr - prev, &mut vb);
prev = addr;
}
}
let mut e =
flate2::write::DeflateEncoder::new(Vec::new(), flate2::Compression::best());
e.write_all(&vb)?;
let blob = e.finish()?;
Ok((blob, len))
}
}
}
pub fn from_compressed(blob: &[u8], len: usize, codec: Codec) -> io::Result<Self> {
let mut m = IdMap::new();
m.reserve_offsets(len);
match codec {
Codec::None => {
debug_assert_eq!(blob.len(), len * 8);
for c in blob.chunks_exact(8) {
let addr = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]);
m.push_sorted_addr(addr);
}
}
Codec::Deflate9 => {
let mut prev = 0u64;
let mut carry = [0u8; 16]; let mut carry_len = 0usize;
let mut buf = vec![0u8; 65536];
let mut pushed = 0usize;
let mut decoder: Box<dyn io::Read> =
Box::new(flate2::read::DeflateDecoder::new(blob));
loop {
let n = decoder.read(&mut buf)?;
if n == 0 {
break;
}
let mut i = 0usize;
while i < n && pushed < len {
carry[carry_len] = buf[i];
carry_len += 1;
i += 1;
if carry[carry_len - 1] & 0x80 == 0 {
let (delta, _) = vbyte::decode_one_u64(&carry[..carry_len]);
prev += delta;
m.push_sorted_addr(prev);
pushed += 1;
carry_len = 0;
}
}
}
debug_assert_eq!(pushed, len);
}
}
m.finalize_sorted();
Ok(m)
}
}
impl Default for IdMap {
fn default() -> Self {
Self::new()
}
}
const IC_BITS: usize = 14;
const IC_SIZE: usize = 1 << IC_BITS;
pub struct IndexCache {
keys: Box<[u64; IC_SIZE]>,
vals: Box<[u32; IC_SIZE]>,
}
impl IndexCache {
pub fn new() -> Self {
Self {
keys: Box::new([0u64; IC_SIZE]),
vals: Box::new([0u32; IC_SIZE]),
}
}
#[inline(always)]
pub fn index_of(&mut self, id_map: &IdMap, addr: u64) -> Option<usize> {
if addr == 0 {
return None;
}
let slot = ((addr ^ (addr >> 20)) as usize) & (IC_SIZE - 1);
let k = unsafe { *self.keys.get_unchecked(slot) };
if k == addr {
let v = unsafe { *self.vals.get_unchecked(slot) };
return if v == u32::MAX {
None
} else {
Some(v as usize)
};
}
let result = id_map.index_of(addr);
unsafe {
*self.keys.get_unchecked_mut(slot) = addr;
*self.vals.get_unchecked_mut(slot) = result.map(|i| i as u32).unwrap_or(u32::MAX);
}
result
}
}
impl Default for IndexCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_and_lookup() {
let mut m = IdMap::new();
m.push(0x300);
m.push(0x100);
m.push(0x200);
m.push(0x100); m.sort_and_dedup();
assert_eq!(m.len(), 3);
assert_eq!(m.index_of(0x100), Some(0));
assert_eq!(m.index_of(0x200), Some(1));
assert_eq!(m.index_of(0x300), Some(2));
assert_eq!(m.index_of(0x400), None);
}
#[test]
fn empty_map() {
let mut m = IdMap::new();
m.sort_and_dedup();
assert_eq!(m.len(), 0);
assert!(m.is_empty());
assert_eq!(m.index_of(42), None);
}
#[test]
fn single_element() {
let mut m = IdMap::new();
m.push(999);
m.sort_and_dedup();
assert_eq!(m.len(), 1);
assert_eq!(m.index_of(999), Some(0));
assert_eq!(m.index_of(0), None);
}
#[test]
fn addr_roundtrip() {
let mut m = IdMap::new();
let addrs = vec![0x10u64, 0x30, 0x50, 0x20, 0x40];
for &a in &addrs {
m.push(a);
}
m.sort_and_dedup();
assert_eq!(m.addr_at(0), 0x10);
assert_eq!(m.addr_at(2), 0x30);
assert_eq!(m.addr_at(4), 0x50);
}
#[test]
fn multi_block_far_apart() {
let mut m = IdMap::new();
let a0 = 0x1000u64;
let a1 = 0x1000u64 + (1u64 << 33); let a2 = a1 + 0x40; let a3 = a1 + (1u64 << 34); for &a in &[a3, a0, a2, a1] {
m.push(a);
} m.sort_and_dedup();
assert_eq!(m.len(), 4);
assert_eq!(m.index_of(a0), Some(0));
assert_eq!(m.index_of(a1), Some(1));
assert_eq!(m.index_of(a2), Some(2));
assert_eq!(m.index_of(a3), Some(3));
assert_eq!(m.addr_at(0), a0);
assert_eq!(m.addr_at(1), a1);
assert_eq!(m.addr_at(2), a2);
assert_eq!(m.addr_at(3), a3);
assert_eq!(m.index_of(a0 + 0x8), None);
assert_eq!(m.index_of(a1 - 0x8), None);
}
#[test]
fn push_sorted_incremental_matches_batch() {
let addrs_sorted: Vec<u64> = {
let base = 0x7f00_0000_0000u64;
let mut v = Vec::new();
for k in 0..6u64 {
v.push(base + k * (1u64 << 33) + 0x10);
v.push(base + k * (1u64 << 33) + 0x20);
v.push(base + k * (1u64 << 33) + 0x30);
}
v.sort_unstable();
v.dedup();
v
};
let mut batch = IdMap::new();
for &a in &addrs_sorted {
batch.push(a);
}
batch.sort_and_dedup();
let mut incr = IdMap::new();
incr.reserve_offsets(addrs_sorted.len());
for &a in &addrs_sorted {
incr.push_sorted_addr(a);
}
incr.finalize_sorted();
assert_eq!(incr.len(), batch.len());
for i in 0..batch.len() {
let a = batch.addr_at(i);
assert_eq!(incr.addr_at(i), a);
assert_eq!(incr.index_of(a), Some(i));
}
assert_eq!(incr.index_of(addrs_sorted[0] - 1), None);
assert_eq!(incr.index_of(*addrs_sorted.last().unwrap() + 1), None);
}
#[test]
fn push_sorted_empty() {
let mut m = IdMap::new();
m.finalize_sorted();
assert_eq!(m.len(), 0);
assert!(m.is_empty());
assert_eq!(m.index_of(1), None);
}
#[test]
fn compress_roundtrip_multiblock() {
let mut m = IdMap::new();
let base = 0x7f00_0000_0000u64;
for k in 0..5u64 {
m.push(base + k * (1u64 << 33) + 0x10);
m.push(base + k * (1u64 << 33) + 0x20);
}
m.sort_and_dedup();
let n = m.len();
for codec in [Codec::None, Codec::Deflate9] {
let (blob, len) = m.compress(codec).unwrap();
let m2 = IdMap::from_compressed(&blob, len, codec).unwrap();
assert_eq!(m2.len(), n);
for i in 0..n {
let a = m.addr_at(i);
assert_eq!(m2.addr_at(i), a);
assert_eq!(m2.index_of(a), Some(i));
}
}
}
#[test]
fn single_block_boundaries() {
let base = 0x7f00_0000_0000u64;
let addrs: Vec<u64> = vec![base, base + 0x10, base + 0x1000, base + 0x1_0000];
let mut m = IdMap::new();
for &a in &addrs {
m.push(a);
}
m.sort_and_dedup();
assert_eq!(m.block_base.len(), 1, "all addrs must land in one block");
for (i, &a) in addrs.iter().enumerate() {
assert_eq!(m.index_of(a), Some(i));
assert_eq!(m.addr_at(i), a);
}
assert_eq!(m.index_of(base - 1), None);
assert_eq!(m.index_of(base + 0x1_0001), None);
assert_eq!(m.index_of(base + 1), None);
assert_eq!(m.index_of(base + 0x800), None);
}
#[test]
fn single_block_matches_naive_reference() {
let base = 0x1234_0000_0000u64;
let mut addrs: Vec<u64> = (0..500u64).map(|k| base + k.wrapping_mul(0x9E37)).collect();
addrs.sort_unstable();
addrs.dedup();
let mut m = IdMap::new();
for &a in &addrs {
m.push(a);
}
m.sort_and_dedup();
assert_eq!(m.block_base.len(), 1);
for (i, &a) in addrs.iter().enumerate() {
assert_eq!(m.index_of(a), Some(i));
assert_eq!(m.addr_at(i), a);
}
for probe in [base - 5, base + 0x9E37 / 2, addrs.last().unwrap() + 7] {
let want = addrs.binary_search(&probe).ok();
assert_eq!(m.index_of(probe), want);
}
}
#[test]
fn search_offsets_matches_stdlib_binary_search() {
let cases: Vec<Vec<u32>> = vec![
vec![],
vec![0],
vec![7],
vec![0, 1, 2, 3, 4, 5, 6, 7],
vec![1, 3, 5, 7, 9, 11],
vec![0, 100, 200, 5000, 5001, u32::MAX - 1, u32::MAX],
(0..257u32).collect(),
(0..1000u32).map(|k| k.wrapping_mul(37)).collect(),
];
for slice in &cases {
let mut probes: Vec<u32> = Vec::new();
for &v in slice {
probes.push(v);
probes.push(v.wrapping_sub(1));
probes.push(v.wrapping_add(1));
}
probes.push(0);
probes.push(u32::MAX);
for d in 0..64u32 {
probes.push(d.wrapping_mul(97));
}
for &d in &probes {
let want = slice.binary_search(&d).ok();
let got = search_offsets(slice, d);
assert_eq!(got, want, "slice={slice:?} probe={d}");
}
}
}
proptest::proptest! {
#[test]
fn prop_search_offsets_matches_stdlib(
raw in proptest::collection::vec(0u32.., 0..300),
probes in proptest::collection::vec(0u32.., 0..80),
) {
let mut slice: Vec<u32> = raw;
slice.sort_unstable();
slice.dedup();
for &d in &probes {
let want = slice.binary_search(&d).ok();
let got = search_offsets(&slice, d);
proptest::prop_assert_eq!(got, want);
}
for (i, &v) in slice.iter().enumerate() {
proptest::prop_assert_eq!(search_offsets(&slice, v), Some(i));
}
}
}
proptest::proptest! {
#[test]
fn prop_single_block_roundtrip(
raw in proptest::collection::vec(0u32.., 0..400),
probes in proptest::collection::vec(0u32.., 0..40),
) {
let base = 0x2000_0000_0000u64;
let mut addrs: Vec<u64> = raw.iter().map(|&x| base + x as u64).collect();
addrs.sort_unstable();
addrs.dedup();
let mut m = IdMap::new();
for &a in &addrs {
m.push(a);
}
m.sort_and_dedup();
if !addrs.is_empty() {
proptest::prop_assert_eq!(m.block_base.len(), 1);
}
for (i, &a) in addrs.iter().enumerate() {
proptest::prop_assert_eq!(m.index_of(a), Some(i));
proptest::prop_assert_eq!(m.addr_at(i), a);
}
for &p in &probes {
let addr = base + p as u64;
let want = addrs.binary_search(&addr).ok();
proptest::prop_assert_eq!(m.index_of(addr), want);
}
}
}
}