logicaffeine_compile/concurrency/send_check.rs
1//! Send / escape analysis — the static soundness gate for the concurrency
2//! memory model (Phase 4 of `work/FINISH_INTERPRETER.md`).
3//!
4//! The memory model is message-passing + CRDT: tasks have isolated heaps, and the
5//! only cross-task sharing is channels (move semantics) and CRDT cells. This pass
6//! rejects programs that violate that discipline. This first increment implements
7//! the **data-race check** for `Simultaneously`/`Attempt all` blocks — branches
8//! that share mutable state (the same variable, or the same pipe with conflicting
9//! roles) would race once the branches genuinely run in parallel (M:N). The
10//! remaining checks (spawned-body free-variable mutation, use-after-send,
11//! non-sendable channel element types) and the wiring into every tier's run path
12//! land alongside the interpreter lowering.
13
14use super::classify::branches_share_mutable_state;
15use crate::ast::stmt::Stmt;
16
17/// A reason a program is rejected by the Send/escape analysis.
18///
19/// The concurrency AST nodes carry no spans (only `Escape`/`Require` do), so a
20/// diagnostic carries its message; spans can be threaded through when the AST
21/// grows them.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SendDiagnostic {
24 /// A Socratic explanation of the violation and how to fix it.
25 pub message: String,
26}
27
28/// Run the Send/escape analysis over a whole program. An empty result means the
29/// program respects the message-passing + CRDT discipline.
30pub fn check_send_escape(stmts: &[Stmt]) -> Vec<SendDiagnostic> {
31 let mut diags = Vec::new();
32 check_block(stmts, &mut diags);
33 diags
34}
35
36fn check_block(stmts: &[Stmt], diags: &mut Vec<SendDiagnostic>) {
37 for stmt in stmts {
38 match stmt {
39 Stmt::Parallel { tasks } | Stmt::Concurrent { tasks } => {
40 if branches_share_mutable_state(tasks) {
41 diags.push(SendDiagnostic {
42 message: "concurrent branches share mutable state across tasks — \
43 pass it through a Pipe or make it a CRDT"
44 .to_string(),
45 });
46 }
47 check_block(tasks, diags);
48 }
49 Stmt::If { then_block, else_block, .. } => {
50 check_block(then_block, diags);
51 if let Some(eb) = else_block {
52 check_block(eb, diags);
53 }
54 }
55 Stmt::While { body, .. }
56 | Stmt::Repeat { body, .. }
57 | Stmt::Zone { body, .. }
58 | Stmt::FunctionDef { body, .. } => check_block(body, diags),
59 Stmt::Inspect { arms, .. } => {
60 for arm in arms.iter() {
61 check_block(arm.body, diags);
62 }
63 }
64 _ => {}
65 }
66 }
67}