#![allow(dead_code)]
use vj_abi::SimHandle;
use crate::process::ArenaRange;
use crate::sched::{JoinKind, SimTick};
use crate::state::{EdgeKind, StateCellId};
pub const KIND_DELAY: u8 = 0;
pub const KIND_EDGE: u8 = 1;
pub const KIND_EDGE_LIST: u8 = 2;
pub const KIND_FORK_JOIN: u8 = 3;
pub const KIND_DONE: u8 = 4;
pub const KIND_REPEAT_EDGE: u8 = 5;
pub const KIND_WAIT_FORK: u8 = 6;
pub const KIND_BY_REQUEST: u8 = 7;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
#[allow(dead_code)]
pub struct RescheduleReqLayoutProposal {
pub kind: u8,
pub edge_kind: u8,
pub fork_join_kind: u8,
pub _reserved: u8,
pub a: u32,
pub b: u32,
pub edge_signal: u64,
}
const _: () = assert!(core::mem::size_of::<RescheduleReqLayoutProposal>() == 24);
const _: () = assert!(core::mem::align_of::<RescheduleReqLayoutProposal>() == 8);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct InvalidKindByte(pub u8);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ProposalKind {
Delay,
Edge,
EdgeList,
ForkJoin,
Done,
RepeatEdge,
WaitFork,
ByRequest,
}
impl RescheduleReqLayoutProposal {
pub fn from_delay(ticks: SimTick) -> Self {
Self {
kind: KIND_DELAY,
edge_kind: 0,
fork_join_kind: 0,
_reserved: 0,
a: ticks as u32,
b: (ticks >> 32) as u32,
edge_signal: 0,
}
}
pub fn from_single_edge(edge: EdgeKind, signal: StateCellId) -> Self {
Self {
kind: KIND_EDGE,
edge_kind: edge as u8,
fork_join_kind: 0,
_reserved: 0,
a: 0,
b: 0,
edge_signal: signal.0,
}
}
pub fn from_single_edge_with_guard(
edge: EdgeKind,
signal: StateCellId,
guard_fn: extern "C" fn(SimHandle) -> bool,
) -> Self {
let ptr = guard_fn as usize as u64;
Self {
kind: KIND_EDGE,
edge_kind: edge as u8,
fork_join_kind: 0,
_reserved: 0,
a: ptr as u32,
b: (ptr >> 32) as u32,
edge_signal: signal.0,
}
}
pub fn guard_fn(&self) -> Option<extern "C" fn(SimHandle) -> bool> {
if self.kind != KIND_EDGE {
return None;
}
let ptr = (u64::from(self.a)) | (u64::from(self.b) << 32);
if ptr == 0 {
return None;
}
Some(unsafe {
core::mem::transmute::<usize, extern "C" fn(SimHandle) -> bool>(ptr as usize)
})
}
pub fn from_repeat_edge(count: u64, edge: EdgeKind, signal: StateCellId) -> Self {
Self {
kind: KIND_REPEAT_EDGE,
edge_kind: edge as u8,
fork_join_kind: 0,
_reserved: 0,
a: count as u32,
b: (count >> 32) as u32,
edge_signal: signal.0,
}
}
pub fn from_edge_list(range: ArenaRange) -> Self {
Self {
kind: KIND_EDGE_LIST,
edge_kind: 0,
fork_join_kind: 0,
_reserved: 0,
a: range.base,
b: range.count,
edge_signal: 0,
}
}
pub fn from_fork_join(join_kind: JoinKind, range: ArenaRange) -> Self {
Self {
kind: KIND_FORK_JOIN,
edge_kind: 0,
fork_join_kind: join_kind as u8,
_reserved: 0,
a: range.base,
b: range.count,
edge_signal: 0,
}
}
pub fn done() -> Self {
Self {
kind: KIND_DONE,
edge_kind: 0,
fork_join_kind: 0,
_reserved: 0,
a: 0,
b: 0,
edge_signal: 0,
}
}
pub fn wait_fork() -> Self {
Self {
kind: KIND_WAIT_FORK,
..Self::done()
}
}
pub fn by_request() -> Self {
Self {
kind: KIND_BY_REQUEST,
..Self::done()
}
}
pub fn to_bytes(self) -> [u8; 24] {
let mut out = [0u8; 24];
out[0] = self.kind;
out[1] = self.edge_kind;
out[2] = self.fork_join_kind;
out[3] = self._reserved;
out[4..8].copy_from_slice(&self.a.to_le_bytes());
out[8..12].copy_from_slice(&self.b.to_le_bytes());
out[16..24].copy_from_slice(&self.edge_signal.to_le_bytes());
out
}
fn kind_enum(&self) -> Result<ProposalKind, InvalidKindByte> {
match self.kind {
KIND_DELAY => Ok(ProposalKind::Delay),
KIND_EDGE => Ok(ProposalKind::Edge),
KIND_EDGE_LIST => Ok(ProposalKind::EdgeList),
KIND_FORK_JOIN => Ok(ProposalKind::ForkJoin),
KIND_DONE => Ok(ProposalKind::Done),
KIND_REPEAT_EDGE => Ok(ProposalKind::RepeatEdge),
KIND_WAIT_FORK => Ok(ProposalKind::WaitFork),
KIND_BY_REQUEST => Ok(ProposalKind::ByRequest),
other => Err(InvalidKindByte(other)),
}
}
pub fn describe(&self) -> Result<&'static str, InvalidKindByte> {
Ok(match self.kind_enum()? {
ProposalKind::Delay => "Delay",
ProposalKind::Edge => "Edge",
ProposalKind::EdgeList => "EdgeList",
ProposalKind::ForkJoin => "ForkJoin",
ProposalKind::Done => "Done",
ProposalKind::RepeatEdge => "RepeatEdge",
ProposalKind::WaitFork => "WaitFork",
ProposalKind::ByRequest => "ByRequest",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::process::ArenaRange;
use crate::sched::JoinKind;
use crate::state::{EdgeKind, StateCellId};
#[test]
fn layout_proposal_is_repr_c_and_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<RescheduleReqLayoutProposal>();
assert_eq!(core::mem::size_of::<RescheduleReqLayoutProposal>(), 24);
assert_eq!(core::mem::align_of::<RescheduleReqLayoutProposal>(), 8);
}
#[test]
fn layout_proposal_fits_the_frozen_reschedule_req_budget() {
assert!(
core::mem::size_of::<RescheduleReqLayoutProposal>()
<= vj_abi::eval::RESCHEDULE_REQ_SIZE
);
}
#[test]
fn layout_proposal_covers_every_suspend_variant() {
let delay = RescheduleReqLayoutProposal::from_delay(42);
assert_eq!(delay.kind, KIND_DELAY);
assert_eq!(delay.describe().unwrap(), "Delay");
let edge = RescheduleReqLayoutProposal::from_single_edge(EdgeKind::Posedge, StateCellId(7));
assert_eq!(edge.kind, KIND_EDGE);
assert_eq!(edge.edge_kind, EdgeKind::Posedge as u8);
assert_eq!(edge.edge_signal, 7);
assert_eq!(edge.describe().unwrap(), "Edge");
let edge_list =
RescheduleReqLayoutProposal::from_edge_list(ArenaRange { base: 3, count: 5 });
assert_eq!(edge_list.kind, KIND_EDGE_LIST);
assert_eq!(edge_list.a, 3);
assert_eq!(edge_list.b, 5);
assert_eq!(edge_list.describe().unwrap(), "EdgeList");
let fork_join = RescheduleReqLayoutProposal::from_fork_join(
JoinKind::All,
ArenaRange { base: 10, count: 2 },
);
assert_eq!(fork_join.kind, KIND_FORK_JOIN);
assert_eq!(fork_join.fork_join_kind, JoinKind::All as u8);
assert_eq!(fork_join.a, 10);
assert_eq!(fork_join.b, 2);
assert_eq!(fork_join.describe().unwrap(), "ForkJoin");
let done = RescheduleReqLayoutProposal::done();
assert_eq!(done.kind, KIND_DONE);
assert_eq!(done.describe().unwrap(), "Done");
let repeat_edge =
RescheduleReqLayoutProposal::from_repeat_edge(3, EdgeKind::Posedge, StateCellId(9));
assert_eq!(repeat_edge.kind, KIND_REPEAT_EDGE);
assert_eq!(repeat_edge.edge_kind, EdgeKind::Posedge as u8);
assert_eq!(repeat_edge.edge_signal, 9);
assert_eq!(repeat_edge.a, 3);
assert_eq!(repeat_edge.b, 0);
assert_eq!(repeat_edge.describe().unwrap(), "RepeatEdge");
let wait_fork = RescheduleReqLayoutProposal::wait_fork();
assert_eq!(wait_fork.kind, KIND_WAIT_FORK);
assert_eq!(wait_fork.describe().unwrap(), "WaitFork");
let by_request = RescheduleReqLayoutProposal::by_request();
assert_eq!(by_request.kind, KIND_BY_REQUEST);
assert_ne!(by_request.kind, KIND_DONE);
assert_ne!(by_request.kind, KIND_WAIT_FORK);
assert_eq!(by_request.describe().unwrap(), "ByRequest");
}
#[test]
fn repeat_edge_count_round_trips_through_a_b_split() {
let near_max: u64 = u64::MAX - 1;
let proposal = RescheduleReqLayoutProposal::from_repeat_edge(
near_max,
EdgeKind::Negedge,
StateCellId(1),
);
let reconstructed = ((proposal.b as u64) << 32) | (proposal.a as u64);
assert_eq!(reconstructed, near_max);
}
extern "C" fn test_guard_fn(_h: vj_abi::SimHandle) -> bool {
true
}
#[test]
fn guard_fn_round_trips_through_a_b_split() {
let with_guard = RescheduleReqLayoutProposal::from_single_edge_with_guard(
EdgeKind::Posedge,
StateCellId(3),
test_guard_fn,
);
let decoded = with_guard
.guard_fn()
.expect("a guard-carrying KIND_EDGE proposal must decode Some");
assert_eq!(
decoded as *const () as usize, test_guard_fn as *const () as usize,
"the decoded function pointer must be byte-identical to the original"
);
let without_guard =
RescheduleReqLayoutProposal::from_single_edge(EdgeKind::Posedge, StateCellId(3));
assert!(
without_guard.guard_fn().is_none(),
"a plain, guard-free KIND_EDGE proposal (a == b == 0) must decode to None"
);
}
#[test]
fn guard_fn_is_none_for_every_kind_other_than_edge() {
let delay = RescheduleReqLayoutProposal::from_delay(u64::MAX - 1);
assert!(
delay.guard_fn().is_none(),
"KIND_DELAY's own a/b (a real, nonzero tick-count split) must never decode as a \
guard function pointer"
);
}
#[test]
fn guard_fn_does_not_grow_the_proposal_past_24_bytes() {
assert_eq!(
core::mem::size_of::<RescheduleReqLayoutProposal>(),
24,
"adding guard_fn support must not grow RescheduleReqLayoutProposal -- it reuses \
the existing a/b slot, it does not add a new field"
);
}
#[test]
fn describe_reports_a_typed_error_for_an_out_of_range_kind_byte() {
let mut corrupt = RescheduleReqLayoutProposal::done();
corrupt.kind = 200;
assert_eq!(corrupt.describe(), Err(InvalidKindByte(200)));
}
#[test]
fn delay_round_trips_through_a_b_split() {
let near_max: SimTick = u64::MAX - 1;
let proposal = RescheduleReqLayoutProposal::from_delay(near_max);
let reconstructed = ((proposal.b as u64) << 32) | (proposal.a as u64);
assert_eq!(reconstructed, near_max);
}
#[test]
fn sc109_event_region_uses_the_abi_five_variant_set() {
use vj_abi::Region;
assert_eq!(core::mem::size_of::<Region>(), 4);
let all = [
Region::Active,
Region::Inactive,
Region::Nba,
Region::Reactive,
Region::Postponed,
];
assert_eq!(all.len(), 5);
for r in all {
let ordinal = crate::sched::region_ordinal(r);
assert!((0..=4).contains(&ordinal));
}
let mut ordinals: Vec<u32> = all
.iter()
.map(|&r| crate::sched::region_ordinal(r))
.collect();
ordinals.sort_unstable();
assert_eq!(ordinals, vec![0, 1, 2, 3, 4], "no gap in the 5-variant set");
}
}