use std::collections::{HashMap, HashSet};
use crate::analysis::types::RustNames;
use crate::ast::stmt::{BinaryOpKind, Expr, Stmt};
use crate::intern::{Interner, Symbol};
use crate::optimize::OracleFacts;
use super::detection::collect_mutable_vars;
pub(super) fn helper_name(divisor_ident: &str) -> String {
format!("__lcdiv_{}", divisor_ident)
}
pub(super) fn detect_fast_div(
stmts: &[Stmt],
oracle: Option<&OracleFacts>,
interner: &Interner,
) -> HashMap<Symbol, String> {
let oracle = match oracle {
Some(o) => o,
None => return HashMap::new(),
};
let mutated = collect_mutable_vars(stmts);
let mut w = Walk {
oracle,
mutated: &mutated,
sound: HashSet::new(),
unsound: HashSet::new(),
};
w.block(stmts, &Scope::default());
let names = RustNames::new(interner);
w.sound
.difference(&w.unsound)
.map(|&n| (n, helper_name(&names.ident(n))))
.collect()
}
#[derive(Clone, Default)]
struct Scope {
pos: HashSet<Symbol>,
nonneg: HashSet<Symbol>,
}
struct Walk<'a> {
oracle: &'a OracleFacts,
mutated: &'a HashSet<Symbol>,
sound: HashSet<Symbol>,
unsound: HashSet<Symbol>,
}
impl<'a> Walk<'a> {
fn block(&mut self, stmts: &[Stmt], sc: &Scope) {
for s in stmts {
self.stmt(s, sc);
}
}
fn stmt(&mut self, s: &Stmt, sc: &Scope) {
match s {
Stmt::While { cond, body, .. } => {
self.expr(cond, sc);
let mut inner = sc.clone();
if let Expr::BinaryOp { op: BinaryOpKind::Lt, left, right } = cond {
if let (Expr::Identifier(c), Expr::Identifier(n)) = (left, right) {
if self.oracle.expr_int_range(left).is_some_and(|(lo, _)| lo >= 0) {
inner.pos.insert(*n);
inner.nonneg.insert(*c);
}
}
}
self.block(body, &inner);
}
Stmt::Repeat { iterable, body, .. } => {
self.expr(iterable, sc);
self.block(body, sc);
}
Stmt::If { cond, then_block, else_block, .. } => {
self.expr(cond, sc);
self.block(then_block, sc);
if let Some(e) = else_block {
self.block(e, sc);
}
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } => self.expr(value, sc),
Stmt::Return { value: Some(e) } => self.expr(e, sc),
Stmt::Show { object, .. } | Stmt::Give { object, .. } => self.expr(object, sc),
Stmt::Push { value, collection, .. } => {
self.expr(value, sc);
self.expr(collection, sc);
}
Stmt::Add { value, .. } | Stmt::Remove { value, .. } => self.expr(value, sc),
Stmt::SetIndex { collection, index, value } => {
self.expr(collection, sc);
self.expr(index, sc);
self.expr(value, sc);
}
Stmt::SetField { value, .. } => self.expr(value, sc),
Stmt::Call { args, .. } => {
for a in args {
self.expr(a, sc);
}
}
Stmt::RuntimeAssert { condition, .. } => self.expr(condition, sc),
_ => {}
}
}
fn expr(&mut self, e: &Expr, sc: &Scope) {
match e {
Expr::BinaryOp { op: BinaryOpKind::Modulo | BinaryOpKind::Divide, left, right } => {
if let Expr::Identifier(n) = right {
self.consider(*n, left, sc);
}
self.expr(left, sc);
self.expr(right, sc);
}
Expr::BinaryOp { left, right, .. } => {
self.expr(left, sc);
self.expr(right, sc);
}
Expr::Not { operand } => self.expr(operand, sc),
Expr::Index { collection, index } => {
self.expr(collection, sc);
self.expr(index, sc);
}
Expr::Length { collection } => self.expr(collection, sc),
Expr::Call { args, .. } => {
for a in args {
self.expr(a, sc);
}
}
Expr::Contains { collection, value } => {
self.expr(collection, sc);
self.expr(value, sc);
}
_ => {}
}
}
fn consider(&mut self, n: Symbol, dividend: &Expr, sc: &Scope) {
if self.mutated.contains(&n) {
self.unsound.insert(n);
return;
}
let n_ge_1 = sc.pos.contains(&n);
let dividend_ge_0 = self.expr_nonneg(dividend, sc);
if n_ge_1 && dividend_ge_0 {
self.sound.insert(n);
} else {
self.unsound.insert(n);
}
}
fn expr_nonneg(&self, e: &Expr, sc: &Scope) -> bool {
match e {
Expr::Literal(crate::ast::stmt::Literal::Number(k)) => *k >= 0,
Expr::Identifier(v) => sc.nonneg.contains(v),
Expr::Length { .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Multiply, left, right } => {
self.expr_nonneg(left, sc) && self.expr_nonneg(right, sc)
}
Expr::BinaryOp { op: BinaryOpKind::Modulo | BinaryOpKind::Divide, left, .. } => {
self.expr_nonneg(left, sc)
}
_ => self.oracle.expr_int_range(e).is_some_and(|(lo, _)| lo >= 0),
}
}
}