use std::array as StdArray;
use loom::sync::Arc;
use loom::sync::atomic::{AtomicU32, Ordering};
use loom::thread;
const EMPTY_PACKED: u32 = (u16::MAX as u32) << 16;
struct LoomInlineSuffixBag<const WIDTH: usize> {
slots: [AtomicU32; WIDTH],
}
impl<const WIDTH: usize> LoomInlineSuffixBag<WIDTH> {
fn new() -> Self {
Self {
slots: StdArray::from_fn(|_| AtomicU32::new(EMPTY_PACKED)),
}
}
fn has_suffix(&self, slot: usize) -> bool {
let packed = self.slots[slot].load(Ordering::Acquire);
let offset = (packed >> 16) as u16;
offset != u16::MAX
}
fn get_meta(&self, slot: usize) -> Option<(u16, u16)> {
let packed = self.slots[slot].load(Ordering::Acquire);
let offset = (packed >> 16) as u16;
let len = packed as u16;
if offset == u16::MAX {
None
} else {
Some((offset, len))
}
}
fn assign(&self, slot: usize, offset: u16, len: u16) {
let packed = ((offset as u32) << 16) | (len as u32);
self.slots[slot].store(packed, Ordering::Release);
}
fn clear(&self, slot: usize) {
self.slots[slot].store(EMPTY_PACKED, Ordering::Release);
}
}
#[test]
fn test_loom_concurrent_reads() {
loom::model(|| {
let bag: Arc<LoomInlineSuffixBag<4>> = Arc::new(LoomInlineSuffixBag::new());
bag.assign(0, 0, 10); bag.assign(1, 10, 20);
let b1 = Arc::clone(&bag);
let b2 = Arc::clone(&bag);
let t1 = thread::spawn(move || {
let has0 = b1.has_suffix(0);
let has1 = b1.has_suffix(1);
(has0, has1)
});
let t2 = thread::spawn(move || {
let meta0 = b2.get_meta(0);
let meta1 = b2.get_meta(1);
(meta0, meta1)
});
let (has0, has1) = t1.join().unwrap();
let (meta0, meta1) = t2.join().unwrap();
assert!(has0, "slot 0 should have suffix");
assert!(has1, "slot 1 should have suffix");
assert_eq!(meta0, Some((0, 10)));
assert_eq!(meta1, Some((10, 20)));
});
}
#[test]
fn test_loom_read_during_clear() {
loom::model(|| {
let bag: Arc<LoomInlineSuffixBag<4>> = Arc::new(LoomInlineSuffixBag::new());
bag.assign(0, 0, 10);
let b_writer = Arc::clone(&bag);
let b_reader = Arc::clone(&bag);
let writer = thread::spawn(move || {
b_writer.clear(0);
});
let reader = thread::spawn(move || {
let meta = b_reader.get_meta(0);
meta
});
writer.join().unwrap();
let result = reader.join().unwrap();
assert!(
result == Some((0, 10)) || result.is_none(),
"unexpected result: {:?}",
result
);
});
}
#[test]
fn test_loom_read_during_assign() {
loom::model(|| {
let bag: Arc<LoomInlineSuffixBag<4>> = Arc::new(LoomInlineSuffixBag::new());
let b_writer = Arc::clone(&bag);
let b_reader = Arc::clone(&bag);
let writer = thread::spawn(move || {
b_writer.assign(0, 100, 50);
});
let reader = thread::spawn(move || b_reader.get_meta(0));
writer.join().unwrap();
let result = reader.join().unwrap();
assert!(
result.is_none() || result == Some((100, 50)),
"unexpected result: {:?}",
result
);
});
}
#[test]
fn test_loom_multiple_writers_readers() {
loom::model(|| {
let bag: Arc<LoomInlineSuffixBag<4>> = Arc::new(LoomInlineSuffixBag::new());
bag.assign(0, 0, 10);
bag.assign(1, 10, 20);
let b1 = Arc::clone(&bag);
let b2 = Arc::clone(&bag);
let b3 = Arc::clone(&bag);
let w1 = thread::spawn(move || {
b1.clear(0);
});
let w2 = thread::spawn(move || {
b2.assign(1, 100, 5);
});
let reader = thread::spawn(move || {
let m0 = b3.get_meta(0);
let m1 = b3.get_meta(1);
(m0, m1)
});
w1.join().unwrap();
w2.join().unwrap();
let (m0, m1) = reader.join().unwrap();
assert!(
m0 == Some((0, 10)) || m0.is_none(),
"slot 0 unexpected: {:?}",
m0
);
assert!(
m1 == Some((10, 20)) || m1 == Some((100, 5)),
"slot 1 unexpected: {:?}",
m1
);
});
}
#[test]
fn test_loom_read_clear_read() {
loom::model(|| {
let bag: Arc<LoomInlineSuffixBag<4>> = Arc::new(LoomInlineSuffixBag::new());
bag.assign(0, 0, 10);
let b_writer = Arc::clone(&bag);
let b_reader = Arc::clone(&bag);
let writer = thread::spawn(move || {
b_writer.clear(0);
});
let reader = thread::spawn(move || {
let before = b_reader.get_meta(0);
loom::thread::yield_now();
let after = b_reader.get_meta(0);
(before, after)
});
writer.join().unwrap();
let (before, after) = reader.join().unwrap();
let valid = matches!(
(before, after),
(Some((0, 10)), Some((0, 10))) | (Some((0, 10)), None) | (None, None)
);
assert!(
valid,
"invalid state transition: before={:?}, after={:?}",
before, after
);
});
}