use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BindingSlotSet {
slots: SmallVec<[u32; 8]>,
}
impl BindingSlotSet {
pub fn insert(&mut self, slot: u32) -> bool {
match self.slots.binary_search(&slot) {
Ok(_) => false,
Err(pos) => {
self.slots.insert(pos, slot);
true
}
}
}
#[must_use]
pub fn contains(&self, slot: &u32) -> bool {
self.slots.binary_search(slot).is_ok()
}
#[must_use]
pub fn intersects(&self, other: &Self) -> bool {
let (small, large) = if self.slots.len() <= other.slots.len() {
(self, other)
} else {
(other, self)
};
small.slots.iter().any(|slot| large.contains(slot))
}
}
impl FromIterator<u32> for BindingSlotSet {
fn from_iter<T: IntoIterator<Item = u32>>(iter: T) -> Self {
let mut set = Self::default();
for slot in iter {
set.insert(slot);
}
set
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArmBindingSummary {
pub reads: BindingSlotSet,
pub writes: BindingSlotSet,
}
impl ArmBindingSummary {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl Default for ArmBindingSummary {
fn default() -> Self {
Self {
reads: BindingSlotSet::default(),
writes: BindingSlotSet::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArmIndependenceVerdict {
Independent,
SerializeRequired {
reason: ArmConflict,
},
}
impl std::fmt::Display for ArmIndependenceVerdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Independent => f.write_str("independent"),
Self::SerializeRequired { reason } => write!(f, "serialize-required:{reason}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArmConflict {
WriteWriteConflict,
ReadAfterWrite,
WriteAfterRead,
}
impl std::fmt::Display for ArmConflict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WriteWriteConflict => f.write_str("write-write-conflict"),
Self::ReadAfterWrite => f.write_str("read-after-write"),
Self::WriteAfterRead => f.write_str("write-after-read"),
}
}
}
#[must_use]
pub fn can_dispatch_concurrently(
a: &ArmBindingSummary,
b: &ArmBindingSummary,
) -> ArmIndependenceVerdict {
if a.writes.intersects(&b.writes) {
return ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteWriteConflict,
};
}
if a.writes.intersects(&b.reads) {
return ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::ReadAfterWrite,
};
}
if a.reads.intersects(&b.writes) {
return ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteAfterRead,
};
}
ArmIndependenceVerdict::Independent
}
#[cfg(test)]
mod tests {
use super::*;
fn summary(reads: &[u32], writes: &[u32]) -> ArmBindingSummary {
ArmBindingSummary {
reads: reads.iter().copied().collect(),
writes: writes.iter().copied().collect(),
}
}
#[test]
fn fully_disjoint_arms_are_independent() {
let a = summary(&[0, 1], &[2]);
let b = summary(&[3, 4], &[5]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::Independent
);
}
#[test]
fn empty_arms_are_independent() {
let a = summary(&[], &[]);
let b = summary(&[], &[]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::Independent
);
}
#[test]
fn shared_read_only_slot_is_independent() {
let a = summary(&[7], &[1]);
let b = summary(&[7], &[2]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::Independent
);
}
#[test]
fn write_write_conflict_serialises() {
let a = summary(&[], &[3]);
let b = summary(&[], &[3]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteWriteConflict,
}
);
}
#[test]
fn read_after_write_serialises() {
let a = summary(&[0], &[5]);
let b = summary(&[5], &[1]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::ReadAfterWrite,
}
);
}
#[test]
fn write_after_read_serialises() {
let a = summary(&[5], &[1]);
let b = summary(&[0], &[5]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteAfterRead,
}
);
}
#[test]
fn write_write_takes_precedence_over_other_conflicts() {
let a = summary(&[], &[1, 3]);
let b = summary(&[1], &[3]);
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteWriteConflict,
}
);
}
#[test]
fn verdict_is_symmetric_for_writes_and_reads() {
let a = summary(&[], &[10]);
let b = summary(&[], &[10]);
let verdict_ab = can_dispatch_concurrently(&a, &b);
let verdict_ba = can_dispatch_concurrently(&b, &a);
assert_eq!(verdict_ab, verdict_ba);
}
#[test]
fn one_empty_arm_leaves_independent_when_other_alone() {
let a = summary(&[1, 2, 3], &[4]);
let b = ArmBindingSummary::new();
assert_eq!(
can_dispatch_concurrently(&a, &b),
ArmIndependenceVerdict::Independent
);
assert_eq!(
can_dispatch_concurrently(&b, &a),
ArmIndependenceVerdict::Independent
);
}
#[test]
fn arm_independence_verdict_displays_summary() {
assert_eq!(format!("{}", ArmIndependenceVerdict::Independent), "independent");
assert_eq!(
format!(
"{}",
ArmIndependenceVerdict::SerializeRequired {
reason: ArmConflict::WriteAfterRead,
}
),
"serialize-required:write-after-read"
);
}
#[test]
fn arm_conflict_displays_reason_code() {
assert_eq!(
format!("{}", ArmConflict::WriteWriteConflict),
"write-write-conflict"
);
assert_eq!(format!("{}", ArmConflict::ReadAfterWrite), "read-after-write");
assert_eq!(format!("{}", ArmConflict::WriteAfterRead), "write-after-read");
}
}