logicaffeine_compile/concurrency/model.rs
1//! Concurrency memory model — the normative determinacy table.
2//!
3//! See `docs/CONCURRENCY_MODEL.md` and `work/FINISH_INTERPRETER.md` §3 for the full
4//! specification. The short version:
5//!
6//! - **Determinate fragment** = `LaunchTask` + FIFO `Pipe`/`Send`/`Receive` +
7//! data-independent `Concurrent`/`Parallel` + CRDT shared state. By Kahn's
8//! determinacy theorem (Kahn Process Networks, 1974) the observable output of
9//! such a program is *scheduling-independent*.
10//! - **Nondeterminate fragment** = the moment a program reaches a `Select`
11//! (`Await the first of:`), an `After` timeout branch, a `Try to send/receive`,
12//! or a `Stop` it can observe *which* event won a race — a function of timing,
13//! not of channel histories. Those four constructs are the determinacy frontier.
14
15use crate::ast::stmt::{SelectBranch, Stmt};
16
17/// Whole-program determinacy verdict.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Determinacy {
20 /// No reachable construct can introduce scheduling-dependent nondeterminism;
21 /// output is a pure function of input (Kahn determinacy + CRDT convergence).
22 Determinate,
23 /// At least one nondeterminism source is present.
24 Nondeterminate {
25 /// The constructs that forced nondeterminism, in source order.
26 witnesses: Vec<NondetWitness>,
27 },
28}
29
30impl Determinacy {
31 /// True iff the program is in the determinate fragment.
32 pub fn is_determinate(&self) -> bool {
33 matches!(self, Determinacy::Determinate)
34 }
35
36 /// The kinds of nondeterminism present (empty when determinate).
37 pub fn nondet_kinds(&self) -> Vec<NondetKind> {
38 match self {
39 Determinacy::Determinate => Vec::new(),
40 Determinacy::Nondeterminate { witnesses } => witnesses.iter().map(|w| w.kind).collect(),
41 }
42 }
43}
44
45/// A single nondeterminism source found in the program.
46///
47/// The concurrency AST nodes do not carry source spans (only `Escape`/`Require`
48/// do), so a witness records the *kind* of construct. Spans can be threaded
49/// through here when the AST grows them, without changing the classifier's shape.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct NondetWitness {
52 /// The construct that introduced nondeterminism.
53 pub kind: NondetKind,
54}
55
56/// The constructs that force nondeterminism — the determinate↔nondeterminate frontier.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum NondetKind {
59 /// `Await the first of:` — nondeterministic choice over the ready branch set.
60 Select,
61 /// `After N seconds:` — a timer event racing the other branches.
62 AfterTimer,
63 /// `Try to receive x from ch.` — outcome depends on instantaneous buffer occupancy.
64 TryRecv,
65 /// `Try to send v into ch.`
66 TrySend,
67 /// `Stop handle.` — cancellation at the task's next scheduling point.
68 StopTask,
69 /// Two or more concurrently-running threads (the main flow + spawned tasks +
70 /// `Concurrent`/`Parallel` blocks) can each write to the shared output sink,
71 /// so the *interleaving* of their `Show` lines is scheduling-dependent. This
72 /// is the one nondeterminism source Kahn determinacy does NOT cover — stdout
73 /// is a shared resource the channel-history argument never modelled.
74 ConcurrentPrint,
75}
76
77/// Append the *direct* nondeterminism witnesses contributed by a single statement.
78///
79/// This does NOT recurse into nested blocks — descending into the program is the
80/// classifier's job ([`super::classify`]). It only reports what `stmt` itself is.
81pub(crate) fn direct_nondet_witnesses(stmt: &Stmt, out: &mut Vec<NondetWitness>) {
82 match stmt {
83 Stmt::Select { branches } => {
84 out.push(NondetWitness { kind: NondetKind::Select });
85 for branch in branches {
86 if let SelectBranch::Timeout { .. } = branch {
87 out.push(NondetWitness { kind: NondetKind::AfterTimer });
88 }
89 }
90 }
91 Stmt::TryReceivePipe { .. } => out.push(NondetWitness { kind: NondetKind::TryRecv }),
92 Stmt::TrySendPipe { .. } => out.push(NondetWitness { kind: NondetKind::TrySend }),
93 Stmt::StopTask { .. } => out.push(NondetWitness { kind: NondetKind::StopTask }),
94 _ => {}
95 }
96}