1use std::fs::{File, OpenOptions};
41use std::mem::size_of;
42use std::path::Path;
43use std::sync::atomic::{AtomicU64, Ordering};
44
45use memmap2::{MmapMut, MmapOptions};
46
47pub const BITVEC_MAGIC: u64 = 0x4150_4256_4543_2031;
48pub const BITS_PER_WORD: usize = 64;
49
50#[repr(C, align(64))]
51pub struct BitVecHeader {
52 pub magic: u64,
53 pub capacity_bits: u64,
54 pub word_count: u64,
55 _pad: [u8; 40],
56}
57
58const _: () = {
59 assert!(size_of::<BitVecHeader>() == 64);
60};
61
62pub const fn bit_vec_file_size(capacity_bits: usize) -> usize {
63 let word_count = capacity_bits.div_ceil(BITS_PER_WORD);
64 size_of::<BitVecHeader>() + word_count * size_of::<AtomicU64>()
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum BitVecError {
69 OutOfBounds,
70 LayoutMismatch,
71 IoError(std::io::ErrorKind),
72}
73
74impl From<std::io::Error> for BitVecError {
75 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
76}
77
78pub struct SharedBitVec {
79 _file: File,
80 mmap: MmapMut,
81 capacity_bits: usize,
82 word_count: usize,
83 header_sidecar: subetha_core::HandshakeHeader,
84 ring_sidecar: Box<subetha_core::ObservationRing>,
85}
86
87unsafe impl Send for SharedBitVec {}
88unsafe impl Sync for SharedBitVec {}
89
90impl subetha_sidecar::AdaptiveInstance for SharedBitVec {
91 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
92 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
93 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
94 Box::new(subetha_sidecar::NoMigrationPolicy)
95 }
96}
97
98impl SharedBitVec {
99 pub fn create(
100 path: impl AsRef<Path>, capacity_bits: usize,
101 ) -> Result<Self, BitVecError> {
102 assert!(capacity_bits >= 1);
103 let word_count = capacity_bits.div_ceil(BITS_PER_WORD);
104 let total = bit_vec_file_size(capacity_bits);
105 let file = OpenOptions::new()
106 .read(true).write(true).create(true).truncate(true)
107 .open(path.as_ref())?;
108 file.set_len(total as u64)?;
109 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
110 let hdr = mmap.as_mut_ptr() as *mut BitVecHeader;
111 unsafe {
112 std::ptr::write_bytes(hdr as *mut u8, 0, size_of::<BitVecHeader>());
113 (*hdr).magic = BITVEC_MAGIC;
114 (*hdr).capacity_bits = capacity_bits as u64;
115 (*hdr).word_count = word_count as u64;
116 }
117 Ok(Self {
118 _file: file, mmap, capacity_bits, word_count,
119 header_sidecar: subetha_core::HandshakeHeader::new(),
120 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
121 })
122 }
123
124 pub fn open(
125 path: impl AsRef<Path>, expected_capacity_bits: usize,
126 ) -> Result<Self, BitVecError> {
127 let total = bit_vec_file_size(expected_capacity_bits);
128 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
129 if file.metadata()?.len() < total as u64 {
130 return Err(BitVecError::LayoutMismatch);
131 }
132 let mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
133 let hdr = unsafe { &*(mmap.as_ptr() as *const BitVecHeader) };
134 if hdr.magic != BITVEC_MAGIC || hdr.capacity_bits != expected_capacity_bits as u64 {
135 return Err(BitVecError::LayoutMismatch);
136 }
137 let word_count = hdr.word_count as usize;
138 Ok(Self {
139 _file: file, mmap, capacity_bits: expected_capacity_bits, word_count,
140 header_sidecar: subetha_core::HandshakeHeader::new(),
141 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
142 })
143 }
144
145 #[inline]
146 pub fn capacity_bits(&self) -> usize { self.capacity_bits }
147
148 #[inline]
149 pub fn capacity_words(&self) -> usize { self.word_count }
150
151 fn word(&self, word_idx: usize) -> &AtomicU64 {
152 let base = unsafe { self.mmap.as_ptr().add(size_of::<BitVecHeader>()) };
153 unsafe { &*(base.add(word_idx * size_of::<AtomicU64>()) as *const AtomicU64) }
154 }
155
156 #[inline]
157 fn check_bounds(&self, bit: usize) -> Result<(), BitVecError> {
158 if bit >= self.capacity_bits { Err(BitVecError::OutOfBounds) } else { Ok(()) }
159 }
160
161 pub fn set(&self, i: usize) -> Result<bool, BitVecError> {
163 if let Err(e) = self.check_bounds(i) {
164 self.ring_sidecar
165 .push_op(crate::sidecar_ops::bit_vec::OP_SET, 1);
166 return Err(e);
167 }
168 let (w, b) = (i / BITS_PER_WORD, i % BITS_PER_WORD);
169 let mask = 1u64 << b;
170 let prev = self.word(w).fetch_or(mask, Ordering::AcqRel);
171 self.ring_sidecar
172 .push_op(crate::sidecar_ops::bit_vec::OP_SET, 0);
173 Ok((prev & mask) != 0)
174 }
175
176 pub fn clear(&self, i: usize) -> Result<bool, BitVecError> {
178 if let Err(e) = self.check_bounds(i) {
179 self.ring_sidecar
180 .push_op(crate::sidecar_ops::bit_vec::OP_CLEAR, 1);
181 return Err(e);
182 }
183 let (w, b) = (i / BITS_PER_WORD, i % BITS_PER_WORD);
184 let mask = 1u64 << b;
185 let prev = self.word(w).fetch_and(!mask, Ordering::AcqRel);
186 self.ring_sidecar
187 .push_op(crate::sidecar_ops::bit_vec::OP_CLEAR, 0);
188 Ok((prev & mask) != 0)
189 }
190
191 pub fn toggle(&self, i: usize) -> Result<bool, BitVecError> {
193 if let Err(e) = self.check_bounds(i) {
194 self.ring_sidecar
195 .push_op(crate::sidecar_ops::bit_vec::OP_TOGGLE, 1);
196 return Err(e);
197 }
198 let (w, b) = (i / BITS_PER_WORD, i % BITS_PER_WORD);
199 let mask = 1u64 << b;
200 let prev = self.word(w).fetch_xor(mask, Ordering::AcqRel);
201 self.ring_sidecar
202 .push_op(crate::sidecar_ops::bit_vec::OP_TOGGLE, 0);
203 Ok((prev & mask) == 0)
205 }
206
207 pub fn get(&self, i: usize) -> Result<bool, BitVecError> {
209 if let Err(e) = self.check_bounds(i) {
210 self.ring_sidecar
211 .push_op(crate::sidecar_ops::bit_vec::OP_GET, 1);
212 return Err(e);
213 }
214 let (w, b) = (i / BITS_PER_WORD, i % BITS_PER_WORD);
215 let mask = 1u64 << b;
216 let v = (self.word(w).load(Ordering::Acquire) & mask) != 0;
217 self.ring_sidecar
218 .push_op(crate::sidecar_ops::bit_vec::OP_GET, 0);
219 Ok(v)
220 }
221
222 pub fn set_range(&self, lo: usize, hi: usize) -> Result<(), BitVecError> {
225 if lo > hi || hi > self.capacity_bits {
226 self.ring_sidecar
227 .push_op(crate::sidecar_ops::bit_vec::OP_RANGE, 1);
228 return Err(BitVecError::OutOfBounds);
229 }
230 if lo != hi { self.range_op(lo, hi, RangeOp::Set); }
231 self.ring_sidecar
232 .push_op(crate::sidecar_ops::bit_vec::OP_RANGE, 0);
233 Ok(())
234 }
235
236 pub fn clear_range(&self, lo: usize, hi: usize) -> Result<(), BitVecError> {
239 if lo > hi || hi > self.capacity_bits {
240 self.ring_sidecar
241 .push_op(crate::sidecar_ops::bit_vec::OP_RANGE, 1);
242 return Err(BitVecError::OutOfBounds);
243 }
244 if lo != hi { self.range_op(lo, hi, RangeOp::Clear); }
245 self.ring_sidecar
246 .push_op(crate::sidecar_ops::bit_vec::OP_RANGE, 0);
247 Ok(())
248 }
249
250 fn range_op(&self, lo: usize, hi: usize, op: RangeOp) {
251 let lo_word = lo / BITS_PER_WORD;
252 let hi_word = (hi - 1) / BITS_PER_WORD; let lo_bit = lo % BITS_PER_WORD;
254 let hi_bit_excl = hi - hi_word * BITS_PER_WORD;
255 if lo_word == hi_word {
256 let count = hi - lo;
258 let mask = if count == BITS_PER_WORD { u64::MAX }
259 else { ((1u64 << count) - 1) << lo_bit };
260 match op {
261 RangeOp::Set => { self.word(lo_word).fetch_or(mask, Ordering::AcqRel); }
262 RangeOp::Clear => { self.word(lo_word).fetch_and(!mask, Ordering::AcqRel); }
263 }
264 return;
265 }
266 let lo_mask = !((1u64 << lo_bit) - 1);
268 match op {
269 RangeOp::Set => { self.word(lo_word).fetch_or(lo_mask, Ordering::AcqRel); }
270 RangeOp::Clear => { self.word(lo_word).fetch_and(!lo_mask, Ordering::AcqRel); }
271 }
272 for w in (lo_word + 1)..hi_word {
274 let v = match op {
275 RangeOp::Set => u64::MAX,
276 RangeOp::Clear => 0,
277 };
278 self.word(w).store(v, Ordering::Release);
279 }
280 let hi_mask = if hi_bit_excl == BITS_PER_WORD { u64::MAX }
282 else { (1u64 << hi_bit_excl) - 1 };
283 match op {
284 RangeOp::Set => { self.word(hi_word).fetch_or(hi_mask, Ordering::AcqRel); }
285 RangeOp::Clear => { self.word(hi_word).fetch_and(!hi_mask, Ordering::AcqRel); }
286 }
287 }
288
289 pub fn count_ones(&self) -> usize {
291 let mut sum = 0usize;
292 for w in 0..self.word_count {
293 sum += self.word(w).load(Ordering::Acquire).count_ones() as usize;
294 }
295 self.ring_sidecar
296 .push_op(crate::sidecar_ops::bit_vec::OP_COUNT_ONES, 0);
297 sum
301 }
302
303 pub fn count_zeros(&self) -> usize {
305 self.capacity_bits.saturating_sub(self.count_ones())
306 }
307
308 pub fn is_all_set(&self) -> bool { self.count_ones() == self.capacity_bits }
309 pub fn is_all_clear(&self) -> bool { self.count_ones() == 0 }
310
311 pub fn set_all(&self) {
315 if self.word_count == 0 { return; }
316 let full_words = self.word_count - 1;
317 for w in 0..full_words {
318 self.word(w).store(u64::MAX, Ordering::Release);
319 }
320 let last_word_bits = self.capacity_bits - full_words * BITS_PER_WORD;
322 let last_mask = if last_word_bits == BITS_PER_WORD { u64::MAX }
323 else { (1u64 << last_word_bits) - 1 };
324 self.word(full_words).store(last_mask, Ordering::Release);
325 }
326
327 pub fn clear_all(&self) {
329 for w in 0..self.word_count {
330 self.word(w).store(0, Ordering::Release);
331 }
332 }
333
334 pub fn flush(&self) -> Result<(), BitVecError> {
335 self.mmap.flush()?;
336 Ok(())
337 }
338
339 pub fn flush_async(&self) -> Result<(), BitVecError> {
340 self.mmap.flush_async()?;
341 Ok(())
342 }
343}
344
345#[derive(Clone, Copy)]
346enum RangeOp { Set, Clear }
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use std::sync::Arc;
352 use std::thread;
353
354 fn tmp(name: &str) -> std::path::PathBuf {
355 let mut p = std::env::temp_dir();
356 let pid = std::process::id();
357 p.push(format!("subetha-bitvec-{name}-{pid}.bin"));
358 p
359 }
360
361 #[test]
362 fn create_initial_all_zero() {
363 let p = tmp("init");
364 let b = SharedBitVec::create(&p, 100).unwrap();
365 assert_eq!(b.capacity_bits(), 100);
366 assert_eq!(b.count_ones(), 0);
367 assert!(b.is_all_clear());
368 for i in 0..100 {
369 assert!(!b.get(i).unwrap(), "bit {i} should start clear");
370 }
371 std::fs::remove_file(&p).ok();
372 }
373
374 #[test]
375 fn set_get_round_trip() {
376 let p = tmp("rt");
377 let b = SharedBitVec::create(&p, 200).unwrap();
378 assert!(!b.set(5).unwrap());
379 assert!(!b.set(63).unwrap());
380 assert!(!b.set(64).unwrap());
381 assert!(!b.set(199).unwrap());
382 assert!(b.get(5).unwrap());
383 assert!(b.get(63).unwrap());
384 assert!(b.get(64).unwrap());
385 assert!(b.get(199).unwrap());
386 assert!(!b.get(6).unwrap());
387 assert!(!b.get(0).unwrap());
388 assert!(b.set(5).unwrap());
390 std::fs::remove_file(&p).ok();
391 }
392
393 #[test]
394 fn clear_works() {
395 let p = tmp("clear");
396 let b = SharedBitVec::create(&p, 64).unwrap();
397 b.set(10).unwrap();
398 b.set(20).unwrap();
399 assert!(b.clear(10).unwrap());
400 assert!(!b.get(10).unwrap());
401 assert!(b.get(20).unwrap());
402 assert!(!b.clear(0).unwrap());
404 std::fs::remove_file(&p).ok();
405 }
406
407 #[test]
408 fn toggle_alternates() {
409 let p = tmp("toggle");
410 let b = SharedBitVec::create(&p, 8).unwrap();
411 assert!(b.toggle(3).unwrap()); assert!(b.get(3).unwrap());
413 assert!(!b.toggle(3).unwrap()); assert!(!b.get(3).unwrap());
415 std::fs::remove_file(&p).ok();
416 }
417
418 #[test]
419 fn count_ones_accurate() {
420 let p = tmp("count");
421 let b = SharedBitVec::create(&p, 200).unwrap();
422 for i in [0, 7, 31, 63, 64, 100, 199] {
423 b.set(i).unwrap();
424 }
425 assert_eq!(b.count_ones(), 7);
426 assert_eq!(b.count_zeros(), 200 - 7);
427 std::fs::remove_file(&p).ok();
428 }
429
430 #[test]
431 fn set_range_single_word() {
432 let p = tmp("range-single");
433 let b = SharedBitVec::create(&p, 64).unwrap();
434 b.set_range(10, 20).unwrap();
435 for i in 0..64 {
436 let expected = (10..20).contains(&i);
437 assert_eq!(b.get(i).unwrap(), expected, "bit {i}");
438 }
439 std::fs::remove_file(&p).ok();
440 }
441
442 #[test]
443 fn set_range_spans_multiple_words() {
444 let p = tmp("range-multi");
445 let b = SharedBitVec::create(&p, 200).unwrap();
446 b.set_range(50, 150).unwrap();
447 for i in 0..200 {
448 let expected = (50..150).contains(&i);
449 assert_eq!(b.get(i).unwrap(), expected, "bit {i}");
450 }
451 assert_eq!(b.count_ones(), 100);
452 std::fs::remove_file(&p).ok();
453 }
454
455 #[test]
456 fn clear_range_after_set_all() {
457 let p = tmp("clear-range");
458 let b = SharedBitVec::create(&p, 200).unwrap();
459 b.set_all();
460 assert!(b.is_all_set());
461 b.clear_range(70, 130).unwrap();
462 for i in 0..200 {
463 let expected = !(70..130).contains(&i);
464 assert_eq!(b.get(i).unwrap(), expected, "bit {i}");
465 }
466 std::fs::remove_file(&p).ok();
467 }
468
469 #[test]
470 fn out_of_bounds_rejected() {
471 let p = tmp("oob");
472 let b = SharedBitVec::create(&p, 8).unwrap();
473 assert_eq!(b.set(8).err(), Some(BitVecError::OutOfBounds));
474 assert_eq!(b.set(999).err(), Some(BitVecError::OutOfBounds));
475 assert_eq!(b.get(8).err(), Some(BitVecError::OutOfBounds));
476 assert_eq!(b.set_range(0, 9).err(), Some(BitVecError::OutOfBounds));
477 std::fs::remove_file(&p).ok();
478 }
479
480 #[test]
481 fn set_all_and_clear_all() {
482 let p = tmp("all");
483 let b = SharedBitVec::create(&p, 130).unwrap();
484 b.set_all();
485 assert!(b.is_all_set());
486 for i in 0..130 { assert!(b.get(i).unwrap()); }
487 b.clear_all();
488 assert!(b.is_all_clear());
489 for i in 0..130 { assert!(!b.get(i).unwrap()); }
490 std::fs::remove_file(&p).ok();
491 }
492
493 #[test]
494 fn cross_handle_visibility() {
495 let p = tmp("cross-handle");
496 let writer = SharedBitVec::create(&p, 100).unwrap();
497 let reader = SharedBitVec::open(&p, 100).unwrap();
498 writer.set(42).unwrap();
499 writer.set(77).unwrap();
500 assert!(reader.get(42).unwrap());
501 assert!(reader.get(77).unwrap());
502 assert!(!reader.get(0).unwrap());
503 reader.clear(42).unwrap();
504 assert!(!writer.get(42).unwrap());
505 std::fs::remove_file(&p).ok();
506 }
507
508 #[test]
509 fn concurrent_setters_of_disjoint_bits_all_visible() {
510 let p = tmp("concurrent-disjoint");
512 let b = Arc::new(SharedBitVec::create(&p, 1000).unwrap());
513 let n_threads = 4;
514 let per_thread = 100;
515 let mut handles = vec![];
516 for t in 0..n_threads {
517 let b = b.clone();
518 handles.push(thread::spawn(move || {
519 for i in 0..per_thread {
520 let bit = t * per_thread + i;
521 b.set(bit).unwrap();
522 }
523 }));
524 }
525 for h in handles { h.join().unwrap(); }
526 assert_eq!(b.count_ones(), n_threads * per_thread);
527 for t in 0..n_threads {
528 for i in 0..per_thread {
529 assert!(b.get(t * per_thread + i).unwrap());
530 }
531 }
532 std::fs::remove_file(&p).ok();
533 }
534
535 #[test]
536 fn concurrent_setters_of_same_word_distinct_bits_all_visible() {
537 let p = tmp("concurrent-same-word");
540 let b = Arc::new(SharedBitVec::create(&p, 64).unwrap());
541 let mut handles = vec![];
542 for bit in 0..64 {
543 let b = b.clone();
544 handles.push(thread::spawn(move || {
545 b.set(bit).unwrap();
546 }));
547 }
548 for h in handles { h.join().unwrap(); }
549 assert!(b.is_all_set(), "every bit in the word should be set");
550 std::fs::remove_file(&p).ok();
551 }
552
553 #[test]
554 fn concurrent_setters_of_same_bit_idempotent() {
555 let p = tmp("concurrent-same-bit");
557 let b = Arc::new(SharedBitVec::create(&p, 100).unwrap());
558 let mut handles = vec![];
559 for _ in 0..16 {
560 let b = b.clone();
561 handles.push(thread::spawn(move || b.set(42).unwrap()));
562 }
563 let prev_values: Vec<bool> = handles.into_iter()
564 .map(|h| h.join().unwrap()).collect();
565 let false_count = prev_values.iter().filter(|&&v| !v).count();
567 let true_count = prev_values.iter().filter(|&&v| v).count();
568 assert_eq!(false_count, 1, "exactly one setter should see prev=false");
569 assert_eq!(true_count, 15);
570 assert!(b.get(42).unwrap());
571 assert_eq!(b.count_ones(), 1);
572 std::fs::remove_file(&p).ok();
573 }
574
575 #[test]
576 fn disk_persistence_survives_reopen() {
577 let p = tmp("disk");
578 {
579 let b = SharedBitVec::create(&p, 200).unwrap();
580 for i in [5, 50, 100, 150, 199] { b.set(i).unwrap(); }
581 b.flush().unwrap();
582 }
583 let b2 = SharedBitVec::open(&p, 200).unwrap();
584 assert_eq!(b2.count_ones(), 5);
585 for i in [5, 50, 100, 150, 199] { assert!(b2.get(i).unwrap()); }
586 for i in [0, 4, 6, 49, 51] { assert!(!b2.get(i).unwrap()); }
587 std::fs::remove_file(&p).ok();
588 }
589
590 #[test]
591 fn capacity_words_matches_packed_count() {
592 let p = tmp("words");
593 let b1 = SharedBitVec::create(&p, 64).unwrap();
594 assert_eq!(b1.capacity_words(), 1);
595 drop(b1);
596 std::fs::remove_file(&p).ok();
597
598 let p2 = tmp("words2");
599 let b2 = SharedBitVec::create(&p2, 65).unwrap();
600 assert_eq!(b2.capacity_words(), 2);
601 drop(b2);
602 std::fs::remove_file(&p2).ok();
603
604 let p3 = tmp("words3");
605 let b3 = SharedBitVec::create(&p3, 1000).unwrap();
606 assert_eq!(b3.capacity_words(), 16); drop(b3);
608 std::fs::remove_file(&p3).ok();
609 }
610
611 #[test]
612 fn allocation_bitmap_pattern() {
613 let p = tmp("alloc-pattern");
617 let b = SharedBitVec::create(&p, 1024).unwrap();
618 for i in 0..10 { assert!(!b.set(i).unwrap()); }
620 assert_eq!(b.count_ones(), 10);
621 assert!(b.clear(5).unwrap());
623 assert_eq!(b.count_ones(), 9);
624 assert!(!b.set(5).unwrap());
626 assert_eq!(b.count_ones(), 10);
627 std::fs::remove_file(&p).ok();
628 }
629}