Skip to main content

vj_sim/determinism/
policy.rs

1//! DETERMINISM::POLICY (Task 17, ledger DT-001/DT-013) — the v1 conservative
2//! default and the fail-closed DPI speculation gate.
3//!
4//! ARCHITECTURE §2/§4 item 1: "ship v1 with `LOOKAHEAD_CYCLES = 0` globally
5//! ... functionally a conservative deterministic partitioned engine
6//! (bit-reproducible, no rollback)" — optimistic speculation is fully
7//! disabled at first release. This module is the one place that constant and
8//! the DPI fail-closed classification live; nothing here waits on WOCB.
9
10use vj_abi::Region;
11
12use crate::analysis::DpiVpiTag;
13
14/// DT-001: "a global `LOOKAHEAD_CYCLES` config defaults to 0 for every
15/// region." One constant, one place — every region-specific hook
16/// ([`lookahead_cycles_for_region`]) reads through it rather than each
17/// inlining its own `0`, so a later wave can turn per-region optimism on
18/// (ARCHITECTURE §4 item 2) without an API break.
19pub const LOOKAHEAD_CYCLES: u32 = 0;
20
21/// Whether a speculative window may run past a given point without waiting
22/// for a conservative barrier. v1 never produces `Allow` from an unproven or
23/// unclassified DPI call — see [`dpi_speculation_policy`].
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
25pub enum SpeculationGate {
26    Allow,
27    Barrier,
28}
29
30/// DT-013: DPI purity classification for speculation is fail-closed —
31/// anything not *provably* pure/safe defaults to [`SpeculationGate::Barrier`].
32/// `None` means unclassified (`analyze()` hasn't run, or doesn't exist yet in
33/// this wave) and is treated exactly like a proven-unsafe tag, never like a
34/// proven-safe one. The `match` inside the `Some` arm is exhaustive (no
35/// wildcard): adding a 5th `DpiVpiTag` variant later without updating this
36/// function fails to compile instead of silently defaulting that new variant
37/// to `Allow`.
38pub fn dpi_speculation_policy(tag: Option<DpiVpiTag>) -> SpeculationGate {
39    match tag {
40        None => SpeculationGate::Barrier,
41        Some(t) => match t {
42            DpiVpiTag::Pure => SpeculationGate::Allow,
43            DpiVpiTag::DeferredSafe => SpeculationGate::Allow,
44            DpiVpiTag::Barrier => SpeculationGate::Barrier,
45            DpiVpiTag::SpeculationUnsafe => SpeculationGate::Barrier,
46        },
47    }
48}
49
50/// DT-001's per-region hook: today every region reads the same global
51/// [`LOOKAHEAD_CYCLES`], but the hook's existence (rather than every call
52/// site inlining the constant) is what lets a later wave enable per-region
53/// optimism (ARCHITECTURE §4 item 2) without an API break — ties to SC-130's
54/// review in Task 22.
55pub fn lookahead_cycles_for_region(_region: Region) -> u32 {
56    LOOKAHEAD_CYCLES
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::determinism::SequentialController;
63    use crate::sched::SchedTime;
64
65    #[test]
66    fn unclassified_dpi_defaults_to_barrier_never_pure() {
67        assert!(matches!(
68            dpi_speculation_policy(None),
69            SpeculationGate::Barrier
70        ));
71    }
72
73    #[test]
74    fn every_dpi_vpi_tag_variant_maps_correctly_fail_closed() {
75        let cases = [
76            (DpiVpiTag::Pure, SpeculationGate::Allow),
77            (DpiVpiTag::DeferredSafe, SpeculationGate::Allow),
78            (DpiVpiTag::Barrier, SpeculationGate::Barrier),
79            (DpiVpiTag::SpeculationUnsafe, SpeculationGate::Barrier),
80        ];
81        for (tag, expected) in cases {
82            assert_eq!(
83                dpi_speculation_policy(Some(tag)),
84                expected,
85                "DpiVpiTag::{tag:?} must map to {expected:?}"
86            );
87        }
88    }
89
90    #[test]
91    fn lookahead_cycles_is_zero_for_every_region_in_v1() {
92        for r in [
93            Region::Active,
94            Region::Inactive,
95            Region::Nba,
96            Region::Reactive,
97            Region::Postponed,
98        ] {
99            assert_eq!(
100                lookahead_cycles_for_region(r),
101                0,
102                "region {r:?} must have LOOKAHEAD_CYCLES == 0 in v1"
103            );
104        }
105    }
106
107    /// DT-001: "0 lookahead" and "the degenerate sequential controller" are
108    /// the same statement viewed two ways (ARCHITECTURE §4 item 1) — the
109    /// only `DeterminismController` reachable at default config is
110    /// `SequentialController`, whose own `gvt() == now` behavior is
111    /// consistent with a globally-zero lookahead (nothing ever runs ahead
112    /// of `now`, so there is nothing to roll back).
113    #[test]
114    fn the_only_determinism_controller_reachable_at_default_config_is_sequential() {
115        assert_eq!(lookahead_cycles_for_region(Region::Active), 0);
116
117        let controller = SequentialController::default_boxed();
118        let now = SchedTime { ticks: 11 };
119        let out = controller.partition(&[], Region::Active, now);
120        assert!(out.is_empty());
121        assert_eq!(
122            controller.gvt(),
123            now,
124            "the default controller's gvt() must equal now immediately after \
125             partition() — consistent with LOOKAHEAD_CYCLES == 0 (nothing ever \
126             runs ahead of now)"
127        );
128    }
129}