use std::collections::{HashMap, HashSet};
use crate::analysis::callgraph::calls_in_stmts;
use crate::ast::stmt::{Expr, SelectBranch, Stmt};
use crate::intern::Symbol;
use super::model::{direct_nondet_witnesses, Determinacy, NondetKind, NondetWitness};
pub fn classify_program(stmts: &[Stmt]) -> Determinacy {
let mut witnesses = Vec::new();
for stmt in stmts {
collect_witnesses_stmt(stmt, &mut witnesses);
}
collect_concurrent_print(stmts, &mut witnesses);
if witnesses.is_empty() {
Determinacy::Determinate
} else {
Determinacy::Nondeterminate { witnesses }
}
}
fn collect_witnesses_stmt(stmt: &Stmt, out: &mut Vec<NondetWitness>) {
direct_nondet_witnesses(stmt, out);
match stmt {
Stmt::If { then_block, else_block, .. } => {
for s in *then_block {
collect_witnesses_stmt(s, out);
}
if let Some(eb) = else_block {
for s in *eb {
collect_witnesses_stmt(s, out);
}
}
}
Stmt::While { body, .. }
| Stmt::Repeat { body, .. }
| Stmt::Zone { body, .. }
| Stmt::FunctionDef { body, .. } => {
for s in *body {
collect_witnesses_stmt(s, out);
}
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
for s in *tasks {
collect_witnesses_stmt(s, out);
}
}
Stmt::Inspect { arms, .. } => {
for arm in arms.iter() {
for s in arm.body.iter() {
collect_witnesses_stmt(s, out);
}
}
}
Stmt::Select { branches } => {
for branch in branches {
match branch {
SelectBranch::Receive { body, .. } | SelectBranch::Timeout { body, .. } => {
for s in body.iter() {
collect_witnesses_stmt(s, out);
}
}
}
}
}
_ => {}
}
}
fn collect_concurrent_print(stmts: &[Stmt], out: &mut Vec<NondetWitness>) {
let mut fn_bodies: HashMap<Symbol, &[Stmt]> = HashMap::new();
for_each_stmt(stmts, &mut |s| {
if let Stmt::FunctionDef { name, body, .. } = s {
fn_bodies.insert(*name, body);
}
});
let direct_show_fns: HashSet<Symbol> = fn_bodies
.iter()
.filter(|(_, body)| block_directly_shows(body))
.map(|(name, _)| *name)
.collect();
let fn_prints = |start: Symbol| -> bool {
let mut stack = vec![start];
let mut seen = HashSet::new();
while let Some(g) = stack.pop() {
if !seen.insert(g) {
continue;
}
if direct_show_fns.contains(&g) {
return true;
}
if let Some(body) = fn_bodies.get(&g) {
stack.extend(calls_in_stmts(body));
}
}
false
};
let mut printers = 0usize;
if block_directly_shows(stmts) || calls_in_stmts(stmts).into_iter().any(&fn_prints) {
printers += 1;
}
let mut launch_targets: Vec<Symbol> = Vec::new();
for_each_stmt(stmts, &mut |s| match s {
Stmt::LaunchTask { function, .. } | Stmt::LaunchTaskWithHandle { function, .. } => {
launch_targets.push(*function)
}
_ => {}
});
for f in launch_targets {
if printers >= 2 {
break;
}
if fn_prints(f) {
printers += 1;
}
}
if printers < 2 {
let mut blocks: Vec<&[Stmt]> = Vec::new();
for_each_stmt(stmts, &mut |s| {
if let Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } = s {
blocks.push(tasks);
}
});
printers += blocks.iter().map(|b| count_shows(b)).sum::<usize>();
}
if printers >= 2 {
out.push(NondetWitness { kind: NondetKind::ConcurrentPrint });
}
}
fn for_each_stmt<'a>(stmts: &'a [Stmt<'a>], f: &mut impl FnMut(&'a Stmt<'a>)) {
for s in stmts {
f(s);
match s {
Stmt::If { then_block, else_block, .. } => {
for_each_stmt(then_block, f);
if let Some(eb) = else_block {
for_each_stmt(eb, f);
}
}
Stmt::While { body, .. }
| Stmt::Repeat { body, .. }
| Stmt::Zone { body, .. }
| Stmt::FunctionDef { body, .. } => for_each_stmt(body, f),
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => for_each_stmt(tasks, f),
Stmt::Inspect { arms, .. } => {
for arm in arms.iter() {
for_each_stmt(arm.body, f);
}
}
Stmt::Select { branches } => {
for branch in branches {
match branch {
SelectBranch::Receive { body, .. } | SelectBranch::Timeout { body, .. } => {
for_each_stmt(body, f)
}
}
}
}
_ => {}
}
}
}
fn block_directly_shows(stmts: &[Stmt]) -> bool {
stmts.iter().any(stmt_directly_shows)
}
fn stmt_directly_shows(stmt: &Stmt) -> bool {
match stmt {
Stmt::Show { .. } => true,
Stmt::If { then_block, else_block, .. } => {
block_directly_shows(then_block)
|| else_block.map_or(false, |eb| block_directly_shows(eb))
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
block_directly_shows(body)
}
Stmt::Inspect { arms, .. } => arms.iter().any(|arm| block_directly_shows(arm.body)),
Stmt::Select { branches } => branches.iter().any(|branch| match branch {
SelectBranch::Receive { body, .. } | SelectBranch::Timeout { body, .. } => {
block_directly_shows(body)
}
}),
_ => false,
}
}
fn count_shows(stmts: &[Stmt]) -> usize {
let mut n = 0;
for_each_stmt(stmts, &mut |s| {
if matches!(s, Stmt::Show { .. }) {
n += 1;
}
});
n
}
pub fn branches_independent(tasks: &[Stmt]) -> bool {
let infos: Vec<BranchInfo> = tasks.iter().map(BranchInfo::of).collect();
for i in 0..infos.len() {
for j in (i + 1)..infos.len() {
if infos[i].conflicts_with(&infos[j]) {
return false;
}
}
}
true
}
pub fn branches_share_mutable_state(tasks: &[Stmt]) -> bool {
let infos: Vec<BranchInfo> = tasks.iter().map(BranchInfo::of).collect();
for i in 0..infos.len() {
for j in (i + 1)..infos.len() {
if infos[i].shares_mutable_state_with(&infos[j]) {
return true;
}
}
}
false
}
#[derive(Default)]
struct BranchInfo {
writes: HashSet<Symbol>,
refs: HashSet<Symbol>,
pipes: HashSet<Symbol>,
}
impl BranchInfo {
fn of(stmt: &Stmt) -> Self {
let mut info = BranchInfo::default();
info.scan_stmt(stmt);
info
}
fn conflicts_with(&self, other: &BranchInfo) -> bool {
!self.pipes.is_disjoint(&other.pipes) || self.shares_mutable_state_with(other)
}
fn shares_mutable_state_with(&self, other: &BranchInfo) -> bool {
!self.writes.is_disjoint(&other.writes)
|| !self.writes.is_disjoint(&other.refs)
|| !other.writes.is_disjoint(&self.refs)
}
fn scan_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Let { value, .. } => self.scan_expr(value),
Stmt::Set { target, value } => {
self.writes.insert(*target);
self.scan_expr(value);
}
Stmt::Push { collection, value } | Stmt::Add { collection, value } | Stmt::Remove { collection, value } => {
self.note_mutated(collection);
self.scan_expr(value);
}
Stmt::Pop { collection, .. } => self.note_mutated(collection),
Stmt::SetIndex { collection, index, value } => {
self.note_mutated(collection);
self.scan_expr(index);
self.scan_expr(value);
}
Stmt::Show { object, .. } => self.scan_expr(object),
Stmt::Return { value } => {
if let Some(v) = value {
self.scan_expr(v);
}
}
Stmt::Call { args, .. } => {
for a in args.iter() {
self.scan_expr(a);
}
}
Stmt::LaunchTask { args, .. } | Stmt::LaunchTaskWithHandle { args, .. } => {
for a in args.iter() {
self.scan_expr(a);
}
}
Stmt::SendPipe { value, pipe } | Stmt::TrySendPipe { value, pipe, .. } => {
self.note_pipe(pipe);
self.scan_expr(value);
}
Stmt::ReceivePipe { pipe, .. } | Stmt::TryReceivePipe { pipe, .. } => {
self.note_pipe(pipe);
}
Stmt::If { cond, then_block, else_block } => {
self.scan_expr(cond);
for s in *then_block {
self.scan_stmt(s);
}
if let Some(eb) = else_block {
for s in *eb {
self.scan_stmt(s);
}
}
}
Stmt::While { cond, body, .. } => {
self.scan_expr(cond);
for s in *body {
self.scan_stmt(s);
}
}
Stmt::Repeat { iterable, body, .. } => {
self.scan_expr(iterable);
for s in *body {
self.scan_stmt(s);
}
}
Stmt::Zone { body, .. } => {
for s in *body {
self.scan_stmt(s);
}
}
_ => {}
}
}
fn note_mutated(&mut self, collection: &Expr) {
if let Some(root) = root_symbol(collection) {
self.writes.insert(root);
}
}
fn note_pipe(&mut self, pipe: &Expr) {
if let Expr::Identifier(sym) = pipe {
self.pipes.insert(*sym);
}
}
fn scan_expr(&mut self, expr: &Expr) {
match expr {
Expr::Identifier(sym) => {
self.refs.insert(*sym);
}
Expr::BinaryOp { left, right, .. } => {
self.scan_expr(left);
self.scan_expr(right);
}
Expr::Call { args, .. } => {
for a in args.iter() {
self.scan_expr(a);
}
}
Expr::CallExpr { callee, args } => {
self.scan_expr(callee);
for a in args.iter() {
self.scan_expr(a);
}
}
Expr::Index { collection, index } => {
self.scan_expr(collection);
self.scan_expr(index);
}
Expr::FieldAccess { object, .. } => self.scan_expr(object),
Expr::Length { collection } => self.scan_expr(collection),
Expr::Not { operand } => self.scan_expr(operand),
Expr::List(items) | Expr::Tuple(items) => {
for i in items.iter() {
self.scan_expr(i);
}
}
_ => {}
}
}
}
fn root_symbol(expr: &Expr) -> Option<Symbol> {
match expr {
Expr::Identifier(sym) => Some(*sym),
Expr::FieldAccess { object, .. } => root_symbol(object),
Expr::Index { collection, .. } => root_symbol(collection),
_ => None,
}
}