use std::collections::HashMap;
use crate::arena::Arena;
use crate::ast::stmt::{BinaryOpKind, Block, Expr, Literal, Stmt};
use crate::intern::{Interner, Symbol};
fn reflection_preserves_attacks() -> bool {
use logicaffeine_kernel::lia::{fourier_motzkin_unsat, Constraint, LinearExpr, Rational};
let v = LinearExpr::var;
let (r1, c1, r2, c2, m) = (v(1), v(2), v(3), v(4), v(5));
let valid_ge0 = |e: &LinearExpr| {
fourier_motzkin_unsat(&[Constraint {
expr: e.add(&LinearExpr::constant(Rational::from_i64(1))),
strict: false,
}])
};
let eq = |a: &LinearExpr, b: &LinearExpr| {
let d = a.sub(b);
valid_ge0(&d) && valid_ge0(&d.neg())
};
let refl_col = m.sub(&c1).sub(&m.sub(&c2)); let orig_col_neg = c2.sub(&c1); let refl_fwd = r1.sub(&m.sub(&c1)).sub(&r2.sub(&m.sub(&c2)));
let orig_bwd = r1.add(&c1).sub(&r2.add(&c2));
let refl_bwd = r1.add(&m.sub(&c1)).sub(&r2.add(&m.sub(&c2)));
let orig_fwd = r1.sub(&c1).sub(&r2.sub(&c2));
eq(&refl_col, &orig_col_neg) && eq(&refl_fwd, &orig_bwd) && eq(&refl_bwd, &orig_fwd)
}
fn reflection_closure_proven() -> bool {
use std::sync::OnceLock;
static PROVEN: OnceLock<bool> = OnceLock::new();
*PROVEN.get_or_init(|| {
logicaffeine_kernel::bitvector::reflection_symmetry_proven() && reflection_preserves_attacks()
})
}
fn as_ident(e: &Expr) -> Option<Symbol> {
match e {
Expr::Identifier(s) => Some(*s),
_ => None,
}
}
fn as_int(e: &Expr) -> Option<i64> {
match e {
Expr::Literal(Literal::Number(n)) => Some(*n),
_ => None,
}
}
fn is_binop<'a>(e: &'a Expr<'a>, op: BinaryOpKind) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
match e {
Expr::BinaryOp { op: o, left, right } if *o == op => Some((left, right)),
_ => None,
}
}
fn match_or_with_bit(e: &Expr, bit: Symbol) -> Option<Symbol> {
let (l, r) = is_binop(e, BinaryOpKind::BitOr)?;
if as_ident(r) == Some(bit) {
as_ident(l)
} else if as_ident(l) == Some(bit) {
as_ident(r)
} else {
None
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum ArgKind {
Plain,
ShiftLeft,
ShiftRight,
}
fn classify_state_arg(e: &Expr, bit: Symbol) -> Option<(Symbol, ArgKind)> {
if let Some((base, amt)) = is_binop(e, BinaryOpKind::Shl) {
if as_int(amt) == Some(1) {
return match_or_with_bit(base, bit).map(|p| (p, ArgKind::ShiftLeft));
}
}
if let Some((base, amt)) = is_binop(e, BinaryOpKind::Shr) {
if as_int(amt) == Some(1) {
return match_or_with_bit(base, bit).map(|p| (p, ArgKind::ShiftRight));
}
}
match_or_with_bit(e, bit).map(|p| (p, ArgKind::Plain))
}
fn is_step(e: &Expr, k: Symbol) -> bool {
is_binop(e, BinaryOpKind::Add).is_some_and(|(l, r)| {
(as_ident(l) == Some(k) && as_int(r) == Some(1))
|| (as_int(l) == Some(1) && as_ident(r) == Some(k))
})
}
fn match_bit_loop<'a>(
cond: &Expr,
body: Block<'a>,
) -> Option<(Symbol, Symbol, Symbol, &'a Expr<'a>)> {
let (xl, xr) = is_binop(cond, BinaryOpKind::NotEq)?;
let x = as_ident(xl)?;
if as_int(xr) != Some(0) {
return None;
}
if body.len() != 3 {
return None;
}
let bit = match &body[0] {
Stmt::Let { var, value, .. } => {
let (a, b) = is_binop(value, BinaryOpKind::BitAnd)?;
let neg = |e: &Expr| is_binop(e, BinaryOpKind::Subtract)
.is_some_and(|(z, y)| as_int(z) == Some(0) && as_ident(y) == Some(x));
if !((as_ident(a) == Some(x) && neg(b)) || (as_ident(b) == Some(x) && neg(a))) {
return None;
}
*var
}
_ => return None,
};
match &body[1] {
Stmt::Set { target, value } if *target == x => {
let (a, b) = is_binop(value, BinaryOpKind::BitXor)?;
let ok = (as_ident(a) == Some(x) && as_ident(b) == Some(bit))
|| (as_ident(a) == Some(bit) && as_ident(b) == Some(x));
if !ok {
return None;
}
}
_ => return None,
}
match &body[2] {
Stmt::Set { target, value } => {
let (a, b) = is_binop(value, BinaryOpKind::Add)?;
if as_ident(a) == Some(*target) {
Some((x, bit, *target, b))
} else if as_ident(b) == Some(*target) {
Some((x, bit, *target, a))
} else {
None
}
}
_ => None,
}
}
#[derive(Clone, Copy)]
enum ChoiceKind<'a> {
BitTrick { x: Symbol },
Range { col: Symbol, limit: &'a Expr<'a>, cmp: BinaryOpKind },
}
#[derive(Clone, Copy)]
struct ChoiceLoop<'a> {
kind: ChoiceKind<'a>,
bit: Symbol,
acc: Symbol,
call: &'a Expr<'a>,
guard: Option<&'a Expr<'a>>,
}
fn shr1<'a>(sym: Symbol, ea: &'a Arena<Expr<'a>>) -> &'a Expr<'a> {
ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Shr,
left: ea.alloc(Expr::Identifier(sym)),
right: int_lit(1, ea),
})
}
fn match_range_loop<'a>(cond: &'a Expr<'a>, body: Block<'a>) -> Option<ChoiceLoop<'a>> {
let (cmp, cl, cr) = match cond {
Expr::BinaryOp { op: op @ (BinaryOpKind::Lt | BinaryOpKind::LtEq), left, right } => {
(*op, *left, *right)
}
_ => return None,
};
let col = as_ident(cl)?;
let limit = cr;
let accum_from = |target: Symbol, value: &'a Expr<'a>| -> Option<&'a Expr<'a>> {
let (a, b) = is_binop(value, BinaryOpKind::Add)?;
let call = if as_ident(a) == Some(target) {
b
} else if as_ident(b) == Some(target) {
a
} else {
return None;
};
matches!(call, Expr::Call { .. }).then_some(call)
};
let mut bit: Option<Symbol> = None;
let mut found: Option<(Symbol, &'a Expr<'a>, Option<&'a Expr<'a>>)> = None;
let mut saw_incr = false;
for s in body {
match s {
Stmt::Let { var, value, .. } => {
if let Some((l, r)) = is_binop(value, BinaryOpKind::Shl) {
if as_int(l) == Some(1) && as_ident(r) == Some(col) {
bit = Some(*var);
}
}
}
Stmt::Set { target, value } if *target == col && is_step(value, col) => {
saw_incr = true;
}
Stmt::Set { target, value } => {
if let Some(call) = accum_from(*target, value) {
found = Some((*target, call, None));
}
}
Stmt::If { cond: guard, then_block, else_block: None } if then_block.len() == 1 => {
if let Stmt::Set { target, value } = &then_block[0] {
if let Some(call) = accum_from(*target, value) {
found = Some((*target, call, Some(*guard)));
}
}
}
_ => {}
}
}
let bit = bit?;
let (acc, call, guard) = found?;
if !saw_incr {
return None;
}
Some(ChoiceLoop { kind: ChoiceKind::Range { col, limit, cmp }, bit, acc, call, guard })
}
fn match_choice_loop<'a>(cond: &'a Expr<'a>, body: Block<'a>) -> Option<ChoiceLoop<'a>> {
if let Some((x, bit, acc, call)) = match_bit_loop(cond, body) {
return Some(ChoiceLoop { kind: ChoiceKind::BitTrick { x }, bit, acc, call, guard: None });
}
match_range_loop(cond, body)
}
fn is_reflection_search(g: Symbol, params: &[Symbol], body: Block) -> bool {
if body.is_empty() {
return false;
}
let (row, n) = match &body[0] {
Stmt::If { cond, then_block, else_block: None } => {
let Some((l, r)) = is_binop(cond, BinaryOpKind::Eq) else { return false };
let (Some(row), Some(n)) = (as_ident(l), as_ident(r)) else { return false };
if then_block.len() != 1 || !matches!(&then_block[0], Stmt::Return { value: Some(v) } if as_int(v).is_some())
{
return false;
}
(row, n)
}
_ => return false,
};
for s in body {
if let Stmt::While { cond, body: lb, .. } = s {
if let Some(cl) = match_choice_loop(cond, lb) {
if let Expr::Call { function, args } = cl.call {
if *function == g && args.len() == params.len() && is_step(args[0], row) {
let mut plain = 0;
let mut left = 0;
let mut right = 0;
let mut other = 0;
for a in &args[1..] {
if as_ident(a) == Some(n) {
continue; }
match classify_state_arg(a, cl.bit) {
Some((_, ArgKind::Plain)) => plain += 1,
Some((_, ArgKind::ShiftLeft)) => left += 1,
Some((_, ArgKind::ShiftRight)) => right += 1,
None => other += 1,
}
}
return plain == 1 && left == 1 && right == 1 && other == 0;
}
}
}
}
}
false
}
struct Entry<'a> {
n: Symbol,
while_idx: usize,
loop_info: ChoiceLoop<'a>,
avail_let_idx: Option<usize>,
avail_init: Option<&'a Expr<'a>>,
}
fn recognize_entry<'a>(
params: &[Symbol],
body: Block<'a>,
searches: &HashMap<Symbol, ()>,
) -> Option<Entry<'a>> {
if params.len() != 1 {
return None;
}
let n = params[0];
let mut found: Option<(usize, ChoiceLoop<'a>)> = None;
for (i, s) in body.iter().enumerate() {
if let Stmt::While { cond, body: lb, .. } = s {
if let Some(cl) = match_choice_loop(cond, lb) {
if let Expr::Call { function, args } = cl.call {
if searches.contains_key(function)
&& !args.is_empty()
&& as_int(args[0]) == Some(1)
&& args.iter().any(|a| as_ident(a) == Some(n))
{
found = Some((i, cl));
break;
}
}
}
}
}
let (while_idx, loop_info) = found?;
let acc = loop_info.acc;
if !body[..while_idx]
.iter()
.any(|s| matches!(s, Stmt::Let { var, value, .. } if *var == acc && as_int(value) == Some(0)))
{
return None;
}
if !matches!(body.last(), Some(Stmt::Return { value: Some(v) }) if as_ident(v) == Some(acc)) {
return None;
}
let (avail_let_idx, avail_init) = match loop_info.kind {
ChoiceKind::BitTrick { x } => {
let idx = body[..while_idx]
.iter()
.rposition(|s| matches!(s, Stmt::Let { var, .. } if *var == x))?;
let init = match &body[idx] {
Stmt::Let { value, .. } => *value,
_ => return None,
};
(Some(idx), Some(init))
}
ChoiceKind::Range { col, limit, cmp } => {
if cmp != BinaryOpKind::Lt || as_ident(limit) != Some(n) {
return None;
}
if !body[..while_idx].iter().any(|s| {
matches!(s, Stmt::Let { var, value, .. } if *var == col && as_int(value) == Some(0))
}) {
return None;
}
(None, None)
}
};
Some(Entry { n, while_idx, loop_info, avail_let_idx, avail_init })
}
fn subst_ident<'a>(e: &'a Expr<'a>, from: Symbol, to: Symbol, ea: &'a Arena<Expr<'a>>) -> &'a Expr<'a> {
match e {
Expr::Identifier(s) if *s == from => ea.alloc(Expr::Identifier(to)),
Expr::Identifier(_) | Expr::Literal(_) => e,
Expr::BinaryOp { op, left, right } => ea.alloc(Expr::BinaryOp {
op: *op,
left: subst_ident(left, from, to, ea),
right: subst_ident(right, from, to, ea),
}),
Expr::Not { operand } => ea.alloc(Expr::Not { operand: subst_ident(operand, from, to, ea) }),
Expr::Call { function, args } => ea.alloc(Expr::Call {
function: *function,
args: args.iter().map(|a| subst_ident(a, from, to, ea)).collect(),
}),
_ => e,
}
}
fn int_lit<'a>(v: i64, ea: &'a Arena<Expr<'a>>) -> &'a Expr<'a> {
ea.alloc(Expr::Literal(Literal::Number(v)))
}
fn rewrite_entry<'a>(
e: &Entry<'a>,
body: Block<'a>,
ea: &'a Arena<Expr<'a>>,
sa: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> Vec<Stmt<'a>> {
let mut out: Vec<Stmt<'a>> = Vec::with_capacity(body.len() + 4);
for (i, s) in body.iter().enumerate() {
if Some(i) == e.avail_let_idx {
let init = e.avail_init.expect("avail_init is present whenever avail_let_idx is");
let low_mask = ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Subtract,
left: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Shl,
left: int_lit(1, ea),
right: shr1(e.n, ea),
}),
right: int_lit(1, ea),
});
let new_init = ea.alloc(Expr::BinaryOp { op: BinaryOpKind::BitAnd, left: init, right: low_mask });
if let Stmt::Let { var, ty, mutable, .. } = s {
out.push(Stmt::Let { var: *var, ty: *ty, value: new_init, mutable: *mutable });
} else {
out.push(s.clone());
}
} else if i == e.while_idx {
match e.loop_info.kind {
ChoiceKind::Range { col, cmp, .. } => {
if let Stmt::While { body: lb, decreasing, .. } = s {
let new_cond = ea.alloc(Expr::BinaryOp {
op: cmp,
left: ea.alloc(Expr::Identifier(col)),
right: shr1(e.n, ea),
});
out.push(Stmt::While { cond: new_cond, body: *lb, decreasing: *decreasing });
} else {
out.push(s.clone());
}
}
ChoiceKind::BitTrick { .. } => out.push(s.clone()),
}
let acc = e.loop_info.acc;
out.push(Stmt::Set {
target: acc,
value: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Multiply,
left: ea.alloc(Expr::Identifier(acc)),
right: int_lit(2, ea),
}),
});
let mid = interner.intern("__sym_mid");
let mid_let = Stmt::Let {
var: mid,
ty: None,
value: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Shl,
left: int_lit(1, ea),
right: shr1(e.n, ea),
}),
mutable: false,
};
let mid_call = subst_ident(e.loop_info.call, e.loop_info.bit, mid, ea);
let add_mid = Stmt::Set {
target: acc,
value: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Add,
left: ea.alloc(Expr::Identifier(acc)),
right: mid_call,
}),
};
let middle_body: Vec<Stmt<'a>> = match e.loop_info.guard {
Some(g) => {
let mid_guard = subst_ident(g, e.loop_info.bit, mid, ea);
vec![
mid_let,
Stmt::If {
cond: mid_guard,
then_block: sa.alloc_slice(vec![add_mid]),
else_block: None,
},
]
}
None => vec![mid_let, add_mid],
};
let odd = ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::NotEq,
left: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Modulo,
left: ea.alloc(Expr::Identifier(e.n)),
right: int_lit(2, ea),
}),
right: int_lit(0, ea),
});
out.push(Stmt::If { cond: odd, then_block: sa.alloc_slice(middle_body), else_block: None });
} else {
out.push(s.clone());
}
}
out
}
fn value_bijection_preserves_equality() -> bool {
use logicaffeine_proof::permgroup::{orbits, schreier_sims};
for k in 2..=6usize {
let gens: Vec<Vec<usize>> = (0..k - 1)
.map(|i| {
let mut p: Vec<usize> = (0..k).collect();
p.swap(i, i + 1);
p
})
.collect();
let bsgs = schreier_sims(k, &gens);
let fact: u128 = (1..=k as u128).product();
if bsgs.order() != fact {
return false;
}
let orbs = orbits(k, &gens);
if orbs.len() != 1 || orbs[0].len() != k {
return false;
}
let Some(elems) = bsgs.elements(fact as usize) else { return false };
for sigma in &elems {
for a in 0..k {
for b in 0..k {
if (a == b) != (sigma[a] == sigma[b]) {
return false;
}
}
}
}
}
true
}
fn value_symmetry_closure_proven() -> bool {
use std::sync::OnceLock;
static PROVEN: OnceLock<bool> = OnceLock::new();
*PROVEN.get_or_init(value_bijection_preserves_equality)
}
fn expr_contains_sym(e: &Expr, sym: Symbol) -> bool {
match e {
Expr::Identifier(s) => *s == sym,
Expr::Literal(_) => false,
Expr::BinaryOp { left, right, .. } => {
expr_contains_sym(left, sym) || expr_contains_sym(right, sym)
}
Expr::Not { operand } => expr_contains_sym(operand, sym),
Expr::Call { args, .. } => args.iter().any(|a| expr_contains_sym(a, sym)),
_ => true,
}
}
fn expr_sym_only_in_equality(e: &Expr, sym: Symbol) -> bool {
match e {
Expr::Identifier(s) => *s != sym,
Expr::Literal(_) => true,
Expr::BinaryOp { op: BinaryOpKind::Eq | BinaryOpKind::NotEq, left, right } => {
eq_side_ok(left, sym) && eq_side_ok(right, sym)
}
Expr::BinaryOp { left, right, .. } => {
expr_sym_only_in_equality(left, sym) && expr_sym_only_in_equality(right, sym)
}
Expr::Not { operand } => expr_sym_only_in_equality(operand, sym),
Expr::Call { args, .. } => args.iter().all(|a| expr_sym_only_in_equality(a, sym)),
_ => !expr_contains_sym(e, sym),
}
}
fn eq_side_ok(e: &Expr, sym: Symbol) -> bool {
match e {
Expr::Identifier(_) => true,
_ => expr_sym_only_in_equality(e, sym),
}
}
fn param_used_only_in_equality(sym: Symbol, body: Block) -> bool {
body.iter().all(|s| match s {
Stmt::Let { value, .. } => expr_sym_only_in_equality(value, sym),
Stmt::Set { value, .. } => expr_sym_only_in_equality(value, sym),
Stmt::Return { value } => value.map_or(true, |v| expr_sym_only_in_equality(v, sym)),
Stmt::If { cond, then_block, else_block } => {
expr_sym_only_in_equality(cond, sym)
&& param_used_only_in_equality(sym, then_block)
&& else_block.map_or(true, |b| param_used_only_in_equality(sym, b))
}
Stmt::While { cond, body, .. } => {
expr_sym_only_in_equality(cond, sym) && param_used_only_in_equality(sym, body)
}
_ => false,
})
}
fn match_value_search_loop(
f: Symbol,
params: &[Symbol],
level: Symbol,
cond: &Expr,
body: Block,
) -> Option<(usize, Symbol)> {
let c = match cond {
Expr::BinaryOp { op: BinaryOpKind::Lt | BinaryOpKind::LtEq, left, .. } => as_ident(left)?,
_ => return None,
};
if body.len() != 2 {
return None;
}
let mut saw_incr = false;
let mut found: Option<(usize, Symbol)> = None;
for s in body {
match s {
Stmt::Set { target, value } if *target == c && is_step(value, c) => saw_incr = true,
Stmt::If { cond: guard, then_block, else_block: None } if then_block.len() == 1 => {
let prev = match guard {
Expr::BinaryOp { op: BinaryOpKind::Eq | BinaryOpKind::NotEq, left, right } => {
if as_ident(left) == Some(c) {
as_ident(right)
} else if as_ident(right) == Some(c) {
as_ident(left)
} else {
None
}
}
_ => None,
}?;
if prev == c || !params.contains(&prev) {
return None;
}
let Stmt::Set { target: acc, value } = &then_block[0] else { return None };
let (a, b) = is_binop(value, BinaryOpKind::Add)?;
let call = if as_ident(a) == Some(*acc) {
b
} else if as_ident(b) == Some(*acc) {
a
} else {
return None;
};
let Expr::Call { function, args } = call else { return None };
if *function != f || args.len() != params.len() || !is_step(args[0], level) {
return None;
}
let positions: Vec<usize> = args
.iter()
.enumerate()
.filter(|(_, a)| as_ident(a) == Some(c))
.map(|(i, _)| i)
.collect();
if positions.len() != 1 || positions[0] == 0 {
return None;
}
found = Some((positions[0], prev));
}
_ => return None,
}
}
if !saw_incr {
return None;
}
found
}
fn value_search_val_pos(f: Symbol, params: &[Symbol], body: Block) -> Option<usize> {
if body.is_empty() {
return None;
}
let level = match &body[0] {
Stmt::If { cond, then_block, else_block: None } => {
let level = match cond {
Expr::BinaryOp {
op: BinaryOpKind::Gt | BinaryOpKind::GtEq | BinaryOpKind::Eq,
left,
..
} => as_ident(left)?,
_ => return None,
};
if then_block.len() != 1
|| !matches!(&then_block[0], Stmt::Return { value: Some(v) } if as_int(v).is_some())
{
return None;
}
level
}
_ => return None,
};
for s in body {
if let Stmt::While { cond, body: lb, .. } = s {
if let Some((valpos, prev)) = match_value_search_loop(f, params, level, cond, lb) {
if param_used_only_in_equality(prev, body) {
return Some(valpos);
}
}
}
}
None
}
struct ValueEntry<'a> {
while_idx: usize,
total: Symbol,
call: &'a Expr<'a>,
valpos: usize,
bound: &'a Expr<'a>,
}
fn recognize_value_entry<'a>(
body: Block<'a>,
val_searches: &HashMap<Symbol, usize>,
) -> Option<ValueEntry<'a>> {
for (i, s) in body.iter().enumerate() {
let Stmt::While { cond, body: lb, .. } = s else { continue };
let (c, bound) = match cond {
Expr::BinaryOp { op: BinaryOpKind::LtEq, left, right } => match as_ident(left) {
Some(c) => (c, *right),
None => continue,
},
_ => continue,
};
if lb.len() != 2 {
continue;
}
let mut saw_incr = false;
let mut acc_call: Option<(Symbol, &'a Expr<'a>)> = None;
let mut clean = true;
for st in *lb {
match st {
Stmt::Set { target, value } if *target == c && is_step(value, c) => {
saw_incr = true;
}
Stmt::Set { target, value } => match is_binop(value, BinaryOpKind::Add) {
Some((a, b)) if as_ident(a) == Some(*target) => acc_call = Some((*target, b)),
Some((a, b)) if as_ident(b) == Some(*target) => acc_call = Some((*target, a)),
_ => {
clean = false;
}
},
_ => {
clean = false;
}
}
}
if !clean || !saw_incr {
continue;
}
let Some((total, call)) = acc_call else { continue };
let Expr::Call { function, args } = call else { continue };
let valpos = match val_searches.get(function) {
Some(p) => *p,
None => continue,
};
if valpos >= args.len() || as_ident(args[valpos]) != Some(c) {
continue;
}
if args.iter().enumerate().any(|(j, a)| j != valpos && as_ident(a) == Some(c)) {
continue;
}
let starts_at_one = body[..i].iter().any(
|s| matches!(s, Stmt::Let { var, value, .. } if *var == c && as_int(value) == Some(1)),
);
let total_zero = body[..i].iter().any(
|s| matches!(s, Stmt::Let { var, value, .. } if *var == total && as_int(value) == Some(0)),
);
let returns_total =
matches!(body.last(), Some(Stmt::Return { value: Some(v) }) if as_ident(v) == Some(total));
if !starts_at_one || !total_zero || !returns_total {
continue;
}
return Some(ValueEntry { while_idx: i, total, call, valpos, bound });
}
None
}
fn rewrite_value_entry<'a>(
e: &ValueEntry<'a>,
body: Block<'a>,
ea: &'a Arena<Expr<'a>>,
interner: &mut Interner,
) -> Vec<Stmt<'a>> {
let vsym = interner.intern("__valuesym");
let Expr::Call { function, args } = e.call else { return body.to_vec() };
let new_args: Vec<&'a Expr<'a>> = args
.iter()
.enumerate()
.map(|(j, a)| if j == e.valpos { int_lit(1, ea) } else { *a })
.collect();
let fixed_call = ea.alloc(Expr::Call { function: *function, args: new_args });
let mut out: Vec<Stmt<'a>> = Vec::with_capacity(body.len() + 1);
for (i, s) in body.iter().enumerate() {
if i == e.while_idx {
out.push(Stmt::Let { var: vsym, ty: None, value: e.bound, mutable: false });
out.push(Stmt::Set {
target: e.total,
value: ea.alloc(Expr::BinaryOp {
op: BinaryOpKind::Multiply,
left: ea.alloc(Expr::Identifier(vsym)),
right: fixed_call,
}),
});
} else {
out.push(s.clone());
}
}
out
}
pub fn break_symmetry_stmts<'a>(
stmts: Vec<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> Vec<Stmt<'a>> {
let mut searches: HashMap<Symbol, ()> = HashMap::new();
let mut val_searches: HashMap<Symbol, usize> = HashMap::new();
for s in &stmts {
if let Stmt::FunctionDef {
name, params, body, is_native: false, generics, ..
} = s
{
if generics.is_empty() {
let psyms: Vec<Symbol> = params.iter().map(|(p, _)| *p).collect();
if is_reflection_search(*name, &psyms, body) {
searches.insert(*name, ());
} else if let Some(valpos) = value_search_val_pos(*name, &psyms, body) {
val_searches.insert(*name, valpos);
}
}
}
}
if searches.is_empty() && val_searches.is_empty() {
return stmts;
}
let refl_ok = !searches.is_empty() && reflection_closure_proven();
let value_ok = !val_searches.is_empty() && value_symmetry_closure_proven();
if !refl_ok && !value_ok {
return stmts;
}
stmts
.into_iter()
.map(|s| match &s {
Stmt::FunctionDef {
name,
generics,
params,
body,
return_type,
is_native: false,
native_path,
is_exported: false,
export_target,
opt_flags,
} if generics.is_empty()
&& opt_flags.is_on(crate::optimization::Opt::Symmetry)
&& !searches.contains_key(name)
&& !val_searches.contains_key(name) =>
{
let psyms: Vec<Symbol> = params.iter().map(|(p, _)| *p).collect();
let mut new_body: Option<Vec<Stmt<'a>>> = None;
if refl_ok {
if let Some(entry) = recognize_entry(&psyms, body, &searches) {
new_body =
Some(rewrite_entry(&entry, body, expr_arena, stmt_arena, interner));
}
}
if new_body.is_none() && value_ok {
if let Some(entry) = recognize_value_entry(body, &val_searches) {
new_body = Some(rewrite_value_entry(&entry, body, expr_arena, interner));
}
}
match new_body {
Some(nb) => Stmt::FunctionDef {
name: *name,
generics: generics.clone(),
params: params.clone(),
body: stmt_arena.alloc_slice(nb),
return_type: *return_type,
is_native: false,
native_path: *native_path,
is_exported: false,
export_target: *export_target,
opt_flags: opt_flags.clone(),
},
None => s,
}
}
_ => s,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reflection_attack_preservation_is_kernel_proved() {
assert!(
reflection_preserves_attacks(),
"kernel LIA failed to prove the reflection preserves the attack relation"
);
}
#[test]
fn full_closure_is_kernel_proven() {
assert!(reflection_closure_proven());
}
#[test]
fn value_symmetry_group_is_transitive_symmetric() {
assert!(
value_bijection_preserves_equality(),
"permgroup failed to certify the value symmetry as the transitive Sₖ"
);
assert!(value_symmetry_closure_proven());
}
}