use std::collections::{HashMap, VecDeque};
use logicaffeine_compile::ast::stmt::{BinaryOpKind, Block, Expr, Literal, SelectBranch, Stmt};
use logicaffeine_compile::Interner;
use logicaffeine_verify::{BitVecOp, VerifyExpr};
type FuncTable<'a> = HashMap<String, (Vec<String>, Block<'a>)>;
struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
fn new(seed: u64) -> Self {
SplitMix64 { state: seed }
}
fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: usize) -> usize {
if n <= 1 {
return 0;
}
(self.next_u64() % n as u64) as usize
}
}
pub const INT_WIDTH: u32 = 64;
#[derive(Debug, Clone)]
pub enum SymValue {
Int(VerifyExpr),
Bool(VerifyExpr),
Chan(usize),
}
#[derive(Debug, Clone)]
pub struct SymSummary {
pub outputs: Vec<SymValue>,
pub errored: VerifyExpr,
}
#[derive(Debug, Clone)]
pub struct Unsupported(pub String);
fn unsupported<T>(reason: impl Into<String>) -> Result<T, Unsupported> {
Err(Unsupported(reason.into()))
}
struct State {
env: HashMap<String, SymValue>,
outputs: Vec<SymValue>,
errored: VerifyExpr,
channels: HashMap<usize, VecDeque<SymValue>>,
chan_counter: usize,
rng: Option<SplitMix64>,
}
impl State {
fn new() -> Self {
State {
env: HashMap::new(),
outputs: Vec::new(),
errored: VerifyExpr::bool(false),
channels: HashMap::new(),
chan_counter: 0,
rng: None,
}
}
}
pub fn execute(stmts: &[Stmt], interner: &Interner) -> Result<SymSummary, Unsupported> {
let funcs = collect_funcs(stmts, interner);
let mut state = State::new();
exec_block(&mut state, stmts, interner, &funcs)?;
Ok(SymSummary {
outputs: state.outputs,
errored: state.errored,
})
}
pub fn execute_seeded(stmts: &[Stmt], interner: &Interner, seed: u64) -> Result<SymSummary, Unsupported> {
let funcs = collect_funcs(stmts, interner);
let mut state = State::new();
state.rng = Some(SplitMix64::new(seed));
exec_block(&mut state, stmts, interner, &funcs)?;
Ok(SymSummary {
outputs: state.outputs,
errored: state.errored,
})
}
fn collect_funcs<'a>(stmts: &'a [Stmt<'a>], interner: &Interner) -> FuncTable<'a> {
let mut table = FuncTable::new();
for stmt in stmts {
if let Stmt::FunctionDef { name, params, body, is_native: false, .. } = stmt {
let param_names = params.iter().map(|(p, _)| interner.resolve(*p).to_string()).collect();
table.insert(interner.resolve(*name).to_string(), (param_names, *body));
}
}
table
}
fn exec_block(
state: &mut State,
stmts: &[Stmt],
interner: &Interner,
funcs: &FuncTable,
) -> Result<(), Unsupported> {
for stmt in stmts {
exec_stmt(state, stmt, interner, funcs)?;
}
Ok(())
}
fn resolve_chan(state: &mut State, expr: &Expr, interner: &Interner) -> Result<usize, Unsupported> {
match eval(state, expr, interner)? {
SymValue::Chan(id) => Ok(id),
_ => unsupported("pipe operand is not a channel"),
}
}
fn exec_stmt(
state: &mut State,
stmt: &Stmt,
interner: &Interner,
funcs: &FuncTable,
) -> Result<(), Unsupported> {
match stmt {
Stmt::FunctionDef { .. } => Ok(()),
Stmt::CreatePipe { var, .. } => {
let id = state.chan_counter;
state.chan_counter += 1;
state.channels.insert(id, VecDeque::new());
state.env.insert(interner.resolve(*var).to_string(), SymValue::Chan(id));
Ok(())
}
Stmt::SendPipe { value, pipe } => {
let v = eval(state, value, interner)?;
let id = resolve_chan(state, pipe, interner)?;
state.channels.get_mut(&id).expect("channel id is allocated").push_back(v);
Ok(())
}
Stmt::ReceivePipe { var, pipe } => {
let id = resolve_chan(state, pipe, interner)?;
let v = match state.channels.get_mut(&id).and_then(|q| q.pop_front()) {
Some(v) => v,
None => return unsupported("receive on an empty channel (not statically resolvable)"),
};
state.env.insert(interner.resolve(*var).to_string(), v);
Ok(())
}
Stmt::LaunchTask { function, args } => {
let fname = interner.resolve(*function).to_string();
let (params, body) = match funcs.get(&fname) {
Some(f) => f.clone(),
None => return unsupported(format!("launch of unknown task '{fname}'")),
};
if params.len() != args.len() {
return unsupported(format!("task '{fname}' arity mismatch"));
}
let arg_vals = args
.iter()
.map(|a| eval(state, a, interner))
.collect::<Result<Vec<_>, _>>()?;
let saved = std::mem::take(&mut state.env);
for (p, v) in params.iter().zip(arg_vals) {
state.env.insert(p.clone(), v);
}
let r = exec_block(state, body, interner, funcs);
state.env = saved;
r
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
exec_block(state, tasks, interner, funcs)
}
Stmt::Select { branches } => {
if state.rng.is_none() {
return unsupported("Select requires seeded mode (nondeterministic)");
}
let mut ready: Vec<usize> = Vec::new();
for (i, b) in branches.iter().enumerate() {
if let SelectBranch::Receive { pipe, .. } = b {
let id = resolve_chan(state, pipe, interner)?;
if state.channels.get(&id).map(|q| !q.is_empty()).unwrap_or(false) {
ready.push(i);
}
}
}
let winner = if !ready.is_empty() {
let k = state.rng.as_mut().unwrap().below(ready.len());
ready[k]
} else {
match branches.iter().position(|b| matches!(b, SelectBranch::Timeout { .. })) {
Some(i) => i,
None => return unsupported("Select with no ready arm and no timeout would block"),
}
};
match &branches[winner] {
SelectBranch::Receive { var, pipe, body } => {
let id = resolve_chan(state, pipe, interner)?;
let v = state
.channels
.get_mut(&id)
.and_then(|q| q.pop_front())
.ok_or_else(|| Unsupported("Select winner channel empty".into()))?;
state.env.insert(interner.resolve(*var).to_string(), v);
exec_block(state, body, interner, funcs)
}
SelectBranch::Timeout { body, .. } => exec_block(state, body, interner, funcs),
}
}
Stmt::Let { var, value, .. } => {
let v = eval(state, value, interner)?;
state.env.insert(interner.resolve(*var).to_string(), v);
Ok(())
}
Stmt::Set { target, value } => {
let name = interner.resolve(*target).to_string();
if !state.env.contains_key(&name) {
return unsupported(format!("Set to variable '{name}' not in scope"));
}
let v = eval(state, value, interner)?;
state.env.insert(name, v);
Ok(())
}
Stmt::Show { object, recipient } => {
if let Expr::Identifier(sym) = recipient {
if interner.resolve(*sym) == "show" {
let v = eval(state, object, interner)?;
state.outputs.push(v);
return Ok(());
}
}
unsupported("Show to a non-console recipient")
}
other => unsupported(format!("statement {}", stmt_kind(other))),
}
}
fn eval(state: &mut State, expr: &Expr, interner: &Interner) -> Result<SymValue, Unsupported> {
match expr {
Expr::Literal(Literal::Number(n)) => {
Ok(SymValue::Int(VerifyExpr::bv_const(INT_WIDTH, *n as u64)))
}
Expr::Literal(Literal::Boolean(b)) => Ok(SymValue::Bool(VerifyExpr::bool(*b))),
Expr::Literal(_) => unsupported("non-Int/Bool literal"),
Expr::Identifier(sym) => {
let name = interner.resolve(*sym);
state
.env
.get(name)
.cloned()
.ok_or_else(|| Unsupported(format!("reference to unbound variable '{name}'")))
}
Expr::BinaryOp { op, left, right } => {
let l = eval(state, left, interner)?;
let r = eval(state, right, interner)?;
match op {
BinaryOpKind::Divide | BinaryOpKind::Modulo => {
let (a, b) = match (l, r) {
(SymValue::Int(a), SymValue::Int(b)) => (a, b),
_ => return unsupported("division on non-Int operands"),
};
let div_by_zero = VerifyExpr::bv_binary(
BitVecOp::Eq,
b.clone(),
VerifyExpr::bv_const(INT_WIDTH, 0),
);
state.errored = VerifyExpr::or(state.errored.clone(), div_by_zero);
let bvop = if matches!(op, BinaryOpKind::Divide) {
BitVecOp::SDiv
} else {
BitVecOp::SRem
};
Ok(SymValue::Int(VerifyExpr::bv_binary(bvop, a, b)))
}
_ => apply_binop(*op, l, r),
}
}
Expr::Not { operand } => match eval(state, operand, interner)? {
SymValue::Bool(e) => Ok(SymValue::Bool(VerifyExpr::not(e))),
SymValue::Int(e) => Ok(SymValue::Int(VerifyExpr::bv_binary(
BitVecOp::Xor,
e,
VerifyExpr::bv_const(INT_WIDTH, u64::MAX),
))),
SymValue::Chan(_) => unsupported("`not` on a channel"),
},
other => unsupported(format!("expression {}", expr_kind(other))),
}
}
fn apply_binop(op: BinaryOpKind, l: SymValue, r: SymValue) -> Result<SymValue, Unsupported> {
use BinaryOpKind::*;
use SymValue::{Bool, Int};
match (op, l, r) {
(Add, Int(a), Int(b)) => Ok(Int(VerifyExpr::bv_binary(BitVecOp::Add, a, b))),
(Subtract, Int(a), Int(b)) => Ok(Int(VerifyExpr::bv_binary(BitVecOp::Sub, a, b))),
(Multiply, Int(a), Int(b)) => Ok(Int(VerifyExpr::bv_binary(BitVecOp::Mul, a, b))),
(Lt, Int(a), Int(b)) => Ok(Bool(VerifyExpr::bv_binary(BitVecOp::SLt, a, b))),
(Gt, Int(a), Int(b)) => Ok(Bool(VerifyExpr::bv_binary(BitVecOp::SLt, b, a))),
(LtEq, Int(a), Int(b)) => Ok(Bool(VerifyExpr::bv_binary(BitVecOp::SLe, a, b))),
(GtEq, Int(a), Int(b)) => Ok(Bool(VerifyExpr::bv_binary(BitVecOp::SLe, b, a))),
(Eq, Int(a), Int(b)) => Ok(Bool(VerifyExpr::bv_binary(BitVecOp::Eq, a, b))),
(Eq, Bool(a), Bool(b)) => Ok(Bool(VerifyExpr::iff(a, b))),
(NotEq, Int(a), Int(b)) => Ok(Bool(VerifyExpr::not(VerifyExpr::bv_binary(
BitVecOp::Eq,
a,
b,
)))),
(NotEq, Bool(a), Bool(b)) => Ok(Bool(VerifyExpr::not(VerifyExpr::iff(a, b)))),
(And, Bool(a), Bool(b)) => Ok(Bool(VerifyExpr::and(a, b))),
(And, Int(a), Int(b)) => Ok(Int(VerifyExpr::bv_binary(BitVecOp::And, a, b))),
(Or, Bool(a), Bool(b)) => Ok(Bool(VerifyExpr::or(a, b))),
(Or, Int(a), Int(b)) => Ok(Int(VerifyExpr::bv_binary(BitVecOp::Or, a, b))),
(op, _, _) => unsupported(format!("operator {op:?} on these operand types")),
}
}
fn stmt_kind(s: &Stmt) -> &'static str {
match s {
Stmt::If { .. } => "If",
Stmt::While { .. } => "While",
Stmt::Repeat { .. } => "Repeat",
Stmt::Return { .. } => "Return",
Stmt::Inspect { .. } => "Inspect",
Stmt::FunctionDef { .. } => "FunctionDef",
Stmt::Call { .. } => "Call",
_ => "<other>",
}
}
fn expr_kind(e: &Expr) -> &'static str {
match e {
Expr::Call { .. } => "Call",
Expr::Index { .. } => "Index",
Expr::FieldAccess { .. } => "FieldAccess",
Expr::List(_) => "List",
Expr::InterpolatedString(_) => "InterpolatedString",
_ => "<other>",
}
}