hibana 0.9.6

Choreography-derived runtime enforcement kernel for no_std Rust multiparty protocols
Documentation
use super::super::{PackedLocalEventRow, RoleLaneImage, ScopeId};
use crate::global::{
    compiled::images::{CompiledProgramRef, EventSemanticKind},
    typestate::{
        LocalAtomFacts, LocalConflict, LocalNode, LocalNodeMeta, PackedEventConflict,
        RouteChoiceMark, StateIndex,
    },
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum RoleLocalDirection {
    Local,
    Send,
    Recv,
}

#[inline(always)]
pub(super) const fn role_local_direction(role: u8, from: u8, to: u8) -> Option<RoleLocalDirection> {
    if from == role && to == role {
        Some(RoleLocalDirection::Local)
    } else if from == role {
        Some(RoleLocalDirection::Send)
    } else if to == role {
        Some(RoleLocalDirection::Recv)
    } else {
        None
    }
}

pub(super) const fn decode_resident_event_header(
    eff_index: u16,
    scope_raw: u16,
    flags: u8,
) -> Option<ScopeId> {
    if eff_index as usize >= crate::eff::meta::COMPACT_EVENT_IDENTITY_CAPACITY
        || (flags & !PackedLocalEventRow::FLAG_CHOICE_DETERMINANT) != 0
    {
        return None;
    }
    let scope = match ScopeId::decode_raw(scope_raw) {
        Some(scope) => scope,
        None => return None,
    };
    Some(scope)
}

#[inline(always)]
pub(super) const fn encode_optional_event_fact_row(row: usize) -> Option<u16> {
    if row < u16::MAX as usize {
        Some(row as u16)
    } else {
        None
    }
}

#[cfg(all(test, hibana_repo_tests))]
mod tests {
    use super::encode_optional_event_fact_row;

    #[test]
    fn optional_event_fact_row_preserves_the_reserved_absent_value() {
        assert_eq!(encode_optional_event_fact_row(0), Some(0));
        assert_eq!(
            encode_optional_event_fact_row(u16::MAX as usize - 1),
            Some(u16::MAX - 1)
        );
        assert_eq!(encode_optional_event_fact_row(u16::MAX as usize), None);
    }
}

impl PackedLocalEventRow {
    const FLAG_CHOICE_DETERMINANT: u8 = 1 << 0;

    #[inline(always)]
    pub(crate) const fn from_packed_parts(
        eff_index: u16,
        dependency_row: u16,
        conflict_row: u16,
        scope_raw: u16,
        frame_label: u8,
        flags: u8,
    ) -> Self {
        let scope = match decode_resident_event_header(eff_index, scope_raw, flags) {
            Some(scope) => scope,
            None => crate::invariant(),
        };
        Self {
            eff_index,
            dependency_row,
            conflict_row,
            scope,
            frame_label,
            flags,
        }
    }

    #[inline(always)]
    pub(crate) const fn scope(self) -> ScopeId {
        self.scope
    }

    #[inline(always)]
    pub(crate) const fn new(
        eff_idx: usize,
        scope: ScopeId,
        frame_label: u8,
        choice: RouteChoiceMark,
    ) -> Self {
        if eff_idx >= crate::eff::meta::COMPACT_EVENT_IDENTITY_CAPACITY {
            panic!("local event row eff index overflow");
        }
        let mut flags = 0u8;
        if choice.is_determinant() {
            flags |= Self::FLAG_CHOICE_DETERMINANT;
        }
        Self {
            eff_index: eff_idx as u16,
            dependency_row: u16::MAX,
            conflict_row: u16::MAX,
            scope,
            frame_label,
            flags,
        }
    }

    #[inline(always)]
    pub(crate) const fn with_dependency_row(mut self, row: usize) -> Self {
        self.dependency_row = match encode_optional_event_fact_row(row) {
            Some(row) => row,
            None => panic!("local event dependency row index overflow"),
        };
        self
    }

    #[inline(always)]
    pub(crate) const fn with_conflict_row(mut self, row: usize) -> Self {
        self.conflict_row = match encode_optional_event_fact_row(row) {
            Some(row) => row,
            None => panic!("local event conflict row index overflow"),
        };
        self
    }

    #[inline(always)]
    const fn is_empty(self) -> bool {
        self.eff_index == u16::MAX
    }

    #[inline(always)]
    const fn is_choice_determinant(self) -> bool {
        (self.flags & Self::FLAG_CHOICE_DETERMINANT) != 0
    }

    #[inline(always)]
    const fn choice_mark(self) -> RouteChoiceMark {
        if self.is_choice_determinant() {
            RouteChoiceMark::Determinant
        } else {
            RouteChoiceMark::Ordinary
        }
    }

    pub(crate) const fn to_node(
        self,
        role: u8,
        action_ordinal: usize,
        program: &CompiledProgramRef,
        conflict: PackedEventConflict,
    ) -> LocalNode {
        if self.is_empty() {
            crate::invariant();
        }
        let eff_idx = self.eff_index as usize;
        let atom = program.event_atom_at(eff_idx);
        let scope = self.scope;
        let semantic = EventSemanticKind::ProtocolEvent;
        let route_arm = match conflict.to_conflict() {
            Some(LocalConflict::RouteArm { arm, .. }) => Some(arm),
            Some(LocalConflict::Unconditional | LocalConflict::SharedRoute) | None => None,
        };
        let next = StateIndex::from_usize(action_ordinal + 1);
        let eff_index = crate::eff::EffIndex::from_dense_ordinal(eff_idx);
        let facts = LocalAtomFacts {
            eff_index,
            label: atom.label,
            frame_label: self.frame_label,
            origin: atom.origin,
            lane: atom.lane,
        };
        let meta = LocalNodeMeta {
            semantic,
            next,
            scope,
            route_arm,
            choice: self.choice_mark(),
        };
        match role_local_direction(role, atom.from, atom.to) {
            Some(RoleLocalDirection::Local) => LocalNode::local(facts, meta),
            Some(RoleLocalDirection::Send) => LocalNode::send(atom.to, facts, meta),
            Some(RoleLocalDirection::Recv) => LocalNode::recv(atom.from, facts, meta),
            None => crate::invariant(),
        }
    }
}

impl<'a> RoleLaneImage<'a> {
    #[inline(always)]
    pub(crate) const fn local_step_node(
        &self,
        step_idx: usize,
        role: u8,
        program: &CompiledProgramRef,
    ) -> Option<LocalNode> {
        match self.local_step_event(step_idx) {
            Some(event) => Some(event.to_node(
                role,
                step_idx,
                program,
                self.event_conflict_for_index(step_idx),
            )),
            None => None,
        }
    }
}