#[cfg(all(not(loom), target_has_atomic = "64"))]
use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(loom)]
use loom::sync::atomic::{AtomicU64, Ordering};
#[cfg(all(not(target_has_atomic = "64"), not(loom)))]
use core::cell::Cell;
#[cfg(all(not(target_has_atomic = "64"), not(loom)))]
use critical_section::Mutex;
#[cfg(not(loom))]
static MONO_FLOOR: Floor = Floor::new();
pub(crate) struct Floor {
#[cfg(target_has_atomic = "64")]
cell: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
cell: Mutex<Cell<u64>>,
}
impl Floor {
#[cfg(loom)]
pub(crate) fn new() -> Self {
Self {
#[cfg(target_has_atomic = "64")]
cell: AtomicU64::new(0),
#[cfg(not(target_has_atomic = "64"))]
cell: Mutex::new(Cell::new(0)),
}
}
#[cfg(not(loom))]
pub(crate) const fn new() -> Self {
Self {
#[cfg(target_has_atomic = "64")]
cell: AtomicU64::new(0),
#[cfg(not(target_has_atomic = "64"))]
cell: Mutex::new(Cell::new(0)),
}
}
pub(crate) fn clamp(&self, x: u64) -> u64 {
#[cfg(target_has_atomic = "64")]
{
let prev = self.cell.fetch_max(x, Ordering::AcqRel);
if x > prev {
x
} else {
prev
}
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| {
let c = self.cell.borrow(cs);
let prev = c.get();
let v = if x > prev { x } else { prev };
c.set(v);
v
})
}
}
#[cfg(test)]
pub(crate) fn peek(&self) -> u64 {
#[cfg(target_has_atomic = "64")]
{
self.cell.load(Ordering::Acquire)
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| self.cell.borrow(cs).get())
}
}
}
#[must_use]
pub fn mono_floor(mono: u64) -> u64 {
#[cfg(loom)]
{
let _ = mono;
0
}
#[cfg(not(loom))]
{
MONO_FLOOR.clamp(mono)
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use super::Floor;
#[test]
fn loom_floor_fetch_max_never_decreases() {
loom::model(|| {
let floor = loom::sync::Arc::new(Floor::new());
let f1 = loom::sync::Arc::clone(&floor);
let f2 = loom::sync::Arc::clone(&floor);
let left = loom::sync::Arc::new(loom::sync::atomic::AtomicU64::new(0));
let left2 = loom::sync::Arc::clone(&left);
let right = loom::sync::Arc::new(loom::sync::atomic::AtomicU64::new(0));
let right2 = loom::sync::Arc::clone(&right);
let t1 = loom::thread::spawn(move || {
for x in [7_u64, 3, 9] {
let v = f1.clamp(x);
assert!(v >= x);
left.fetch_max(v, loom::sync::atomic::Ordering::AcqRel);
}
});
let t2 = loom::thread::spawn(move || {
for x in [8_u64, 1, 5] {
let v = f2.clamp(x);
assert!(v >= x);
right.fetch_max(v, loom::sync::atomic::Ordering::AcqRel);
}
});
assert!(t1.join().is_ok(), "worker 1 panicked");
assert!(t2.join().is_ok(), "worker 2 panicked");
assert_eq!(floor.peek(), 9);
assert!(left2.load(loom::sync::atomic::Ordering::Acquire) <= 9);
assert!(right2.load(loom::sync::atomic::Ordering::Acquire) <= 9);
});
}
}
#[cfg(test)]
mod tests {
use super::{mono_floor, Floor};
#[test]
fn floor_holds_regressions_single_threaded() {
let floor = Floor::new();
assert_eq!(floor.clamp(100), 100);
assert_eq!(floor.clamp(50), 100); assert_eq!(floor.clamp(100), 100); assert_eq!(floor.clamp(200), 200); assert_eq!(floor.clamp(199), 200); assert_eq!(floor.peek(), 200); }
#[test]
fn global_floor_clamps_regressions() {
let high = mono_floor(u64::MAX - 3);
assert_eq!(high, u64::MAX - 3);
assert_eq!(mono_floor(5), u64::MAX - 3, "regression must clamp");
assert_eq!(mono_floor(u64::MAX), u64::MAX);
assert_eq!(mono_floor(1), u64::MAX);
}
}