use super::maps::Map;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Call {
Insert(u64),
InsertIfAbsent(u64),
GetOrInsert(u64),
Remove,
Get,
Clear,
RemoveIfEven,
CompareAndRemove(u64),
ReplaceIfEven(u64),
CompareAndSwap(u64, u64),
ComputeAdd(u64),
ComputeToggle(u64),
}
pub const REFUSED: u64 = 1 << 62;
pub fn replaced(answer: Result<u64, Option<u64>>) -> Option<u64> {
match answer {
Ok(old) => Some(old),
Err(current) => current.map(|v| v | REFUSED),
}
}
pub fn perform<M: Map<u64, u64>>(m: &M, k: u64, call: Call) -> Option<u64> {
match call {
Call::Insert(v) => m.insert(k, v),
Call::InsertIfAbsent(v) => m.insert_if_absent(k, v),
Call::GetOrInsert(v) => Some(m.get_or_insert(k, v)),
Call::Remove => m.remove(&k),
Call::Get => m.get(&k),
Call::Clear => {
m.clear();
None
}
Call::RemoveIfEven => m.remove_if(&k, |v| v % 2 == 0),
Call::CompareAndRemove(e) => m.compare_and_remove(&k, &e),
Call::ReplaceIfEven(n) => replaced(m.replace_if(k, n, |v| v % 2 == 0)),
Call::CompareAndSwap(e, n) => replaced(m.compare_and_swap(k, &e, n)),
Call::ComputeAdd(d) => m.compute(k, |v| Some(v.map_or(d, |v| v + d))),
Call::ComputeToggle(n) => m.compute(k, |v| match v {
None => Some(n),
Some(_) => None,
}),
}
}
#[derive(Clone, Copy, Debug)]
pub struct Event {
pub call: Call,
pub answer: Option<u64>,
pub invoked: u64,
pub answered: u64,
}
fn apply(call: Call, state: Option<u64>) -> (Option<u64>, Option<u64>) {
match call {
Call::Insert(v) => (state, Some(v)),
Call::InsertIfAbsent(v) => match state {
None => (None, Some(v)),
Some(cur) => (Some(cur), state),
},
Call::GetOrInsert(v) => match state {
None => (Some(v), Some(v)),
Some(cur) => (Some(cur), state),
},
Call::Remove => (state, None),
Call::Get => (state, state),
Call::Clear => (None, None),
Call::RemoveIfEven => match state {
Some(v) if v % 2 == 0 => (state, None),
_ => (None, state),
},
Call::CompareAndRemove(e) => match state {
Some(v) if v == e => (state, None),
_ => (None, state),
},
Call::ReplaceIfEven(n) => match state {
Some(v) if v % 2 == 0 => (state, Some(n)),
Some(v) => (Some(v | REFUSED), state),
None => (None, None),
},
Call::CompareAndSwap(e, n) => match state {
Some(v) if v == e => (state, Some(n)),
Some(v) => (Some(v | REFUSED), state),
None => (None, None),
},
Call::ComputeAdd(d) => {
let next = Some(state.map_or(d, |v| v + d));
(next, next)
}
Call::ComputeToggle(n) => {
let next = match state {
None => Some(n),
Some(_) => None,
};
(next, next)
}
}
}
pub fn linearizable(history: &[Event], initial: Option<u64>) -> bool {
assert!(history.len() <= 63, "a history of at most 63 calls");
let full: u64 = (1u64 << history.len()) - 1;
let mut seen: HashSet<(u64, Option<u64>)> = HashSet::new();
let mut stack = vec![(0u64, initial)];
while let Some((done, state)) = stack.pop() {
if done == full {
return true;
}
if !seen.insert((done, state)) {
continue;
}
let horizon = history
.iter()
.enumerate()
.filter(|(i, _)| done & (1 << i) == 0)
.map(|(_, e)| e.answered)
.min()
.unwrap_or(u64::MAX);
for (i, e) in history.iter().enumerate() {
if done & (1 << i) != 0 || e.invoked > horizon {
continue;
}
let (answer, next) = apply(e.call, state);
if answer == e.answer {
stack.push((done | (1 << i), next));
}
}
}
false
}