use vj_abi::{Region, SimHandle, SimTime};
use crate::process::{PredicateFn, StmtId};
use crate::state::{EdgeKind, FourStateValue, StateCellId};
pub mod assert_control;
pub mod class_event;
mod control;
mod dedup;
mod deferred;
mod finals;
mod guard;
pub mod host;
mod io_gate;
mod kernel_adapter;
pub mod mailbox;
pub mod maturation;
mod nba;
mod notify;
mod ops;
mod order;
pub mod proc_assert;
pub mod process_rng;
mod process_table;
mod queues;
mod region_callback;
mod repeat;
mod reschedule_req_proposal;
mod resume;
pub mod semaphore;
mod sensitivity;
mod subscribe;
pub mod thread_hooks;
mod violation;
mod wheel;
pub use control::*;
pub use dedup::*;
pub use deferred::{
deferred_call_for, deferred_cancel_for, deferred_report, deferred_report_for,
set_deferred_writer,
};
pub use finals::*;
pub use guard::*;
pub use io_gate::*;
pub use kernel_adapter::*;
pub use nba::*;
pub use notify::*;
pub use ops::*;
pub use order::*;
pub use process_table::*;
pub use queues::*;
pub use region_callback::*;
pub use repeat::*;
pub use resume::*;
pub use sensitivity::*;
pub use subscribe::*;
pub use thread_hooks::{
ThreadEvent, ThreadHook, fire_thread_event, set_thread_hook, thread_hook_installed,
};
pub use violation::{
DEFERRED_MODE_CANCEL, DEFERRED_MODE_FINAL, DEFERRED_MODE_OBSERVED, ViolationType,
set_violation_writer, violation_report, violation_report_for,
};
pub use wheel::*;
pub type ProcessId = u32;
pub type ActivationId = u64;
pub type SimTick = u64;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct SchedTime {
pub ticks: SimTick,
}
const _: () = assert!(core::mem::size_of::<SchedTime>() == 8);
impl SchedTime {
pub fn to_abi_display(self, delta: u32) -> vj_abi::SimTime {
vj_abi::SimTime {
sim_units: self.ticks as i64,
delta,
}
}
}
pub fn region_ordinal(region: Region) -> u32 {
match region {
Region::Active => Region::Active as u32,
Region::Inactive => Region::Inactive as u32,
Region::Nba => Region::Nba as u32,
Region::Reactive => Region::Reactive as u32,
Region::Postponed => Region::Postponed as u32,
Region::Preponed => Region::Preponed as u32,
Region::Observed => Region::Observed as u32,
}
}
#[derive(Clone, Copy, Debug)]
pub enum ResumeArg {
Start,
TimeElapsed,
EventFired {
term_index: usize,
},
ForkChildDone {
child: ProcessId,
},
Disabled,
AllForkChildrenDone,
WaitOrderSatisfied,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum JoinKind {
All,
Any,
None,
}
#[derive(Clone, Debug)]
pub enum EventTerm {
Edge {
cell: StateCellId,
edge: EdgeKind,
iff: Option<PredicateFn>,
},
ImplicitAny(Vec<StateCellId>),
Select {
cell: StateCellId,
edge: EdgeKind,
lsb: u32,
width: u32,
},
NamedEventTrigger(StateCellId),
Repeat {
count: u64,
inner: Box<EventTerm>,
},
}
#[derive(Clone, Debug, Default)]
pub struct EventWaitSet {
pub terms: Vec<EventTerm>,
pub rechecks_on_wake: bool,
}
#[derive(Clone, Debug)]
pub enum Suspend {
Delay(u64),
WaitEvents(EventWaitSet),
ForkJoin(JoinKind, Vec<SpawnSpec>),
WaitFork,
WaitOrder(Vec<ProcessId>),
WaitForGvt,
ByRequest,
Done,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum ProcessState {
Finished = 0,
Running = 1,
Waiting = 2,
Suspended = 3,
Killed = 4,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ProcessRequest {
Kill(ProcessId),
Suspend(ProcessId),
Resume(ProcessId),
Await(ProcessId),
SyncBlock,
SyncWake(ProcessId),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SpawnSpec {
pub kernel: u32,
pub entry_pc: u32,
pub parent: ProcessId,
pub disable_name: Vec<String>,
}
pub trait CompiledProcess: Send {
fn resume(&mut self, state: SimHandle, arg: ResumeArg, ops: &dyn SchedulerOps) -> Suspend;
fn mint_fork_child(&self, _spec: &SpawnSpec) -> Option<Box<dyn CompiledProcess>> {
None
}
fn disable_instance(&self) -> Option<u32> {
None
}
fn disable_exit(&self, _label: &str) -> Option<u32> {
None
}
fn owns_disable_scope(&self, _label: &str) -> bool {
false
}
fn redirect_resume(&mut self, _pc: u32) -> bool {
false
}
}
pub type DeferredIoCallback = Box<dyn FnOnce(&mut dyn IoCtx) + Send>;
pub trait SchedulerOps {
fn now(&self) -> SchedTime;
fn current_region(&self) -> Region;
fn stage_nba(&self, cell: StateCellId, value: FourStateValue, source: ProcessId);
fn commit_blocking_write(&self, cell: StateCellId, value: FourStateValue);
fn pending_ticks(&self) -> Vec<u64> {
Vec::new()
}
fn waiting_processes(&self, _cell: StateCellId) -> Vec<ProcessId> {
Vec::new()
}
fn request_finish(&self, code: i32);
fn defer_io(&self, at: SchedTime, region: Region, cb: DeferredIoCallback);
fn register_region_callback(&self, region: Region, at: SchedTime, cb: Box<dyn RegionCallback>);
fn disable_fork(&self, _proc: ProcessId) {}
fn spawn_detached(&self, _proc: Box<dyn CompiledProcess>) {}
fn disable_fork_block(&self, _proc: ProcessId) {}
fn disable_named(&self, _requester: ProcessId, _label: &str) -> bool {
false
}
fn process_request(&self, _requester: ProcessId, _req: ProcessRequest) -> bool {
false
}
fn process_state(&self, _target: ProcessId) -> ProcessState {
ProcessState::Running
}
fn process_track(&self, _proc: ProcessId) {}
fn wake_process(&self, _proc: ProcessId, _term: usize) {}
fn set_monitor_watch(&self, _cells: Vec<StateCellId>) {}
fn notify_blocking_row_change(&self, _cell: StateCellId) {}
fn notify_blocking_wide_change(
&self,
_cell: StateCellId,
_edge: EdgeKind,
_delta: Option<crate::state::ChangeDelta>,
) {
}
fn wants_delta(&self, _cell: StateCellId) -> bool {
false
}
fn stage_nba_masked(
&self,
cell: StateCellId,
row: Option<u64>,
value: FourStateValue,
mask: Vec<u64>,
source: ProcessId,
) {
let _ = (row, value, mask, source);
unreachable!("stage_nba_masked({cell:?}) on a SchedulerOps without an NBA log")
}
fn stage_nba_masked_delayed(
&self,
cell: StateCellId,
row: Option<u64>,
value: FourStateValue,
mask: Vec<u64>,
source: ProcessId,
delay: u64,
) {
let _ = (row, value, mask, source, delay);
unreachable!("stage_nba_masked_delayed({cell:?}) on a SchedulerOps without an NBA log")
}
fn stage_nba_coll(
&self,
cell: StateCellId,
row: Option<u64>,
elem: Vec<u64>,
coll: nba::CollWrite,
source: ProcessId,
delay: u64,
) {
let _ = (row, elem, coll, source, delay);
unreachable!("stage_nba_coll({cell:?}) on a SchedulerOps without an NBA log")
}
fn stage_nba_obj(&self, w: nba::ObjWrite, source: ProcessId, delay: u64) {
let _ = (w, source, delay);
unreachable!("stage_nba_obj on a SchedulerOps without an NBA log")
}
fn note_reactive_drive(&self) {}
fn note_clocking_event(&self, counter: StateCellId) {
let _ = counter;
}
fn stage_nba_pulse(&self, cell: StateCellId, row: Option<u64>, source: ProcessId, delay: u64) {
let _ = (row, source, delay);
unreachable!("stage_nba_pulse({cell:?}) on a SchedulerOps without an NBA log")
}
fn stage_nba_window(
&self,
cell: StateCellId,
window: u32,
value: FourStateValue,
mask: Vec<u64>,
source: ProcessId,
delay: u64,
) {
let _ = (window, value, mask, source, delay);
unreachable!("stage_nba_window({cell:?}) on a SchedulerOps without an NBA log")
}
}
pub trait IoCtx {}
#[derive(Clone, Debug, Default)]
pub struct StaticSensitivity {
pub edges: Vec<(StateCellId, Option<EdgeKind>)>,
}
#[derive(Clone, Copy, Debug)]
pub struct PendingActivation {
pub id: ActivationId,
pub proc: ProcessId,
pub arg: ResumeArg,
pub reconv_iter: u32,
pub logical_order: (SchedTime, Region, u64),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct BreakpointId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct CheckpointHandle(pub u64);
#[derive(Clone, Debug)]
pub enum BreakCondition {
Time(SimTime),
StmtHit {
stmt: StmtId,
cond: Option<PredicateFn>,
},
Watch {
cell: StateCellId,
edge: EdgeKind,
cond: Option<PredicateFn>,
},
ProcessSuspend(ProcessId),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SuspendReason {
WaitTime,
WaitEvent,
WaitFork,
Blocked,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ProcessRunState {
Running,
Suspended(SuspendReason),
}
#[derive(Clone, Copy, Debug)]
pub struct ProcessSnapshot {
pub id: ProcessId,
pub state: ProcessRunState,
pub current: StmtId,
}
#[derive(Clone, Debug)]
pub struct KernelSnapshot {
pub gvt: SimTime,
pub processes: Vec<ProcessSnapshot>,
}
#[derive(Clone, Copy, Debug)]
pub struct SpeculationBound {
pub max_lookahead_cycles: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RewindError {
PastFossilHorizon,
LayoutMismatch,
}
pub trait KernelControl: Send {
fn arm(&mut self, cond: BreakCondition) -> BreakpointId;
fn disarm(&mut self, id: BreakpointId);
fn snapshot(&self) -> KernelSnapshot;
fn gvt(&self) -> SimTime;
fn checkpoint(&mut self) -> CheckpointHandle;
fn rewind(&mut self, cp: CheckpointHandle) -> Result<(), RewindError>;
fn set_speculation_bound(&mut self, bound: SpeculationBound);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pending_activation_logical_order_is_a_plain_copy_tuple() {
let a = PendingActivation {
id: 42,
proc: 3,
arg: ResumeArg::Start,
reconv_iter: 0,
logical_order: (SchedTime { ticks: 10 }, Region::Active, 7),
};
let b = a;
assert_eq!(a.proc, b.proc);
assert_eq!(a.id, b.id);
assert_eq!(a.logical_order.2, 7);
}
#[test]
fn reconv_iter_distinguishes_same_process_same_tick_different_passes() {
let base = SchedTime { ticks: 10 };
let pass0 = PendingActivation {
id: 1,
proc: 3,
arg: ResumeArg::TimeElapsed,
reconv_iter: 0,
logical_order: (base, Region::Active, 7),
};
let pass1 = PendingActivation {
reconv_iter: 1,
..pass0
};
assert_eq!(pass0.logical_order, pass1.logical_order);
assert_ne!(
pass0.reconv_iter, pass1.reconv_iter,
"same 3-tuple logical_order must still be distinguishable via reconv_iter"
);
}
#[test]
fn sched_time_has_no_delta_field_and_is_integer_only() {
let t = SchedTime { ticks: 10 };
let t2 = SchedTime { ticks: t.ticks + 5 };
assert_eq!(t2.ticks, 15);
assert_eq!(core::mem::size_of::<SchedTime>(), 8);
}
#[test]
fn sched_time_to_abi_display_only_populates_delta_from_the_given_counter() {
let t = SchedTime { ticks: 7 };
let a = t.to_abi_display(3);
assert_eq!(
a,
vj_abi::SimTime {
sim_units: 7,
delta: 3,
}
);
let b = t.to_abi_display(9);
assert_eq!(a.sim_units, b.sim_units);
assert_ne!(
a.delta, b.delta,
"delta must come from the caller's counter, not be derived from ticks"
);
}
#[test]
fn pending_activation_logical_order_uses_sched_time_not_abi_simtime() {
let a = PendingActivation {
id: 1,
proc: 0,
arg: ResumeArg::Start,
reconv_iter: 0,
logical_order: (SchedTime { ticks: 1 }, Region::Active, 0),
};
assert_eq!(a.logical_order.0.ticks, 1);
}
#[test]
fn region_has_exactly_seven_variants_preponed_and_observed_added_re_inactive_re_nba_still_not()
{
let all = [
Region::Active,
Region::Inactive,
Region::Nba,
Region::Reactive,
Region::Postponed,
Region::Preponed,
Region::Observed,
];
for r in all {
let _ = region_ordinal(r);
}
assert_eq!(all.len(), 7);
}
#[test]
fn region_ordinal_matches_discriminant_for_every_variant() {
for r in [
Region::Active,
Region::Inactive,
Region::Nba,
Region::Reactive,
Region::Postponed,
Region::Preponed,
Region::Observed,
] {
assert_eq!(region_ordinal(r), r as u32);
}
}
#[test]
fn sc064_type_aliases_have_exact_underlying_representations() {
assert_eq!(core::mem::size_of::<SimTick>(), 8);
assert_eq!(core::mem::size_of::<ProcessId>(), 4);
assert_eq!(core::mem::size_of::<ActivationId>(), 8);
assert_eq!(core::mem::size_of::<crate::state::StateCellId>(), 8);
assert_eq!(crate::state::StateCellId(42).0, 42);
}
#[test]
fn sc127_a_mock_process_may_suspend_from_a_point_that_stands_in_for_a_dpi_imported_task_callback()
{
struct NoopArena;
impl crate::state::SignalAccess for NoopArena {
fn read(&self, _cell: crate::state::StateCellId) -> FourStateValue {
FourStateValue::Narrow(0)
}
fn write(&mut self, _cell: crate::state::StateCellId, _value: FourStateValue) -> bool {
false
}
fn subscribe(
&mut self,
_cell: crate::state::StateCellId,
_role: crate::state::ListenerRole,
_sink: Box<dyn crate::state::ValueChangeSink + Send>,
) {
}
}
struct NoopIoSink;
impl IoSink for NoopIoSink {
fn enqueue(&self, _at: SchedTime, _region: Region, _seq: u64, _cb: DeferredIoCallback) {
}
}
fn dpi_frame_3(delay: u64) -> Suspend {
Suspend::Delay(delay)
}
fn dpi_frame_2(delay: u64) -> Suspend {
dpi_frame_3(delay)
}
fn dpi_frame_1(delay: u64) -> Suspend {
dpi_frame_2(delay)
}
struct DpiCallbackWaiter {
log: std::sync::Arc<std::sync::Mutex<Vec<ResumeArg>>>,
}
impl CompiledProcess for DpiCallbackWaiter {
fn resume(
&mut self,
_state: SimHandle,
arg: ResumeArg,
_ops: &dyn SchedulerOps,
) -> Suspend {
self.log.lock().unwrap().push(arg);
match arg {
ResumeArg::Start => dpi_frame_1(5),
_ => Suspend::Done,
}
}
}
let log = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let mut engine = Engine::new(Box::new(NoopArena), Box::new(NoopIoSink));
engine.register_process(
Box::new(DpiCallbackWaiter { log: log.clone() }),
ProcessKind::Initial,
None,
0,
);
let result = engine.run_until(None);
assert!(
matches!(result, RunResult::Completed),
"a delay suspended from deep inside a DPI-callback-shaped call stack must \
still let the run reach quiescence like any other process, got {:?}",
result
);
assert_eq!(engine.now(), SchedTime { ticks: 5 });
let log = log.lock().unwrap();
assert_eq!(
log.len(),
2,
"must resume exactly twice: Start (which suspends via the DPI-shaped \
callback) and TimeElapsed (which completes)"
);
assert!(matches!(log[0], ResumeArg::Start));
assert!(
matches!(log[1], ResumeArg::TimeElapsed),
"the deeply-nested Suspend::Delay(5) must resume the process via an \
ordinary TimeElapsed wakeup -- no DPI-specific ResumeArg variant exists \
or is needed"
);
}
#[test]
fn wait_for_gvt_fits_the_suspend_enums_existing_match_shape() {
fn label(s: &Suspend) -> &'static str {
match s {
Suspend::Delay(_) => "delay",
Suspend::WaitEvents(_) => "wait_events",
Suspend::ForkJoin(_, _) => "fork_join",
Suspend::WaitFork => "wait_fork",
Suspend::WaitOrder(_) => "wait_order",
Suspend::WaitForGvt => "wait_for_gvt",
Suspend::ByRequest => "by_request",
Suspend::Done => "done",
}
}
assert_eq!(label(&Suspend::WaitForGvt), "wait_for_gvt");
assert_eq!(label(&Suspend::Delay(0)), "delay");
assert_eq!(
label(&Suspend::WaitEvents(EventWaitSet::default())),
"wait_events"
);
assert_eq!(
label(&Suspend::ForkJoin(JoinKind::All, Vec::new())),
"fork_join"
);
assert_eq!(label(&Suspend::WaitFork), "wait_fork");
assert_eq!(label(&Suspend::WaitOrder(Vec::new())), "wait_order");
assert_eq!(label(&Suspend::Done), "done");
}
}