use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
use logicaffeine_base::intern::{Interner, Symbol};
use std::collections::{HashMap, HashSet};
use super::worklist::{const_eval, for_each_stmt_expr, is_new_empty_int_seq, ok_read_only, visit_idents};
const I32_MAX: i64 = i32::MAX as i64;
const I32_MIN: i64 = i32::MIN as i64;
#[derive(Clone, Debug, Default)]
pub(crate) struct NarrowInfo {
pub guards: Vec<String>,
}
pub(crate) fn detect_narrowable<'a>(
body: &'a [Stmt<'a>],
de_rc: &HashSet<Symbol>,
interner: &Interner,
) -> HashMap<Symbol, NarrowInfo> {
let mut out = HashMap::new();
for stmt in body {
let Stmt::Let { var, value, .. } = stmt else { continue };
if !is_new_empty_int_seq(value) {
continue;
}
let c = *var;
if !de_rc.contains(&c) {
continue;
}
if let Some(info) = analyze(c, body, interner) {
out.insert(c, info);
}
}
out
}
pub(crate) fn narrowable_seqs_for_body(
body: &[Stmt],
interner: &Interner,
) -> HashSet<Symbol> {
let no_params: HashMap<Symbol, HashSet<usize>> = HashMap::new();
let no_fns: HashSet<Symbol> = HashSet::new();
let de_rc = super::detection::collect_de_rc_seqs(
body,
interner,
&no_params,
&no_params,
&no_fns,
false,
);
detect_narrowable(body, &de_rc, interner).into_keys().collect()
}
#[derive(Clone, Copy)]
struct LoopCtx {
iv: Option<Symbol>,
trip: Option<i64>,
}
struct Writes {
bail: bool,
lo: i64,
hi: i64,
guards: Vec<String>,
any: bool,
large: bool,
}
fn analyze<'a>(c: Symbol, body: &'a [Stmt<'a>], interner: &Interner) -> Option<NarrowInfo> {
if escapes(body, c) {
return None;
}
let mut w = Writes { bail: false, lo: 0, hi: 0, guards: Vec::new(), any: false, large: false };
let mut rmw: HashMap<Symbol, &'a Expr<'a>> = HashMap::new();
let mut defs: HashMap<Symbol, &'a Expr<'a>> = HashMap::new();
walk(body, c, &mut Vec::new(), &mut rmw, &mut defs, &mut w, interner);
if w.bail || !w.any || !w.large || w.lo < I32_MIN || w.hi > I32_MAX {
return None;
}
let mut seen = HashSet::new();
let guards = w.guards.into_iter().filter(|g| seen.insert(g.clone())).collect();
Some(NarrowInfo { guards })
}
fn walk<'a>(
stmts: &'a [Stmt<'a>],
c: Symbol,
loops: &mut Vec<LoopCtx>,
rmw: &mut HashMap<Symbol, &'a Expr<'a>>,
defs: &mut HashMap<Symbol, &'a Expr<'a>>,
w: &mut Writes,
interner: &Interner,
) {
for (pos, s) in stmts.iter().enumerate() {
match s {
Stmt::Let { var, value, .. } => {
defs.insert(*var, value);
match index_of(value, c) {
Some(idx) => {
rmw.insert(*var, idx);
}
None => {
rmw.remove(var);
}
}
}
Stmt::Push { collection, value } => {
if names(collection, c) {
classify(value, c, None, loops, rmw, defs, 0, &mut *w, interner);
}
}
Stmt::SetIndex { collection, index, value } => {
if names(collection, c) {
classify(value, c, Some(index), loops, rmw, defs, 0, w, interner);
}
}
Stmt::Set { target, .. } => {
if *target == c {
w.bail = true;
}
rmw.remove(target);
defs.remove(target);
}
Stmt::If { then_block, else_block, .. } => {
walk(then_block, c, loops, rmw, defs, w, interner);
if let Some(eb) = else_block {
walk(eb, c, loops, rmw, defs, w, interner);
}
}
Stmt::While { cond, body, .. } => {
loops.push(loop_ctx(cond, &stmts[..pos]));
let mut inner_rmw = rmw.clone();
let mut inner_defs = defs.clone();
for v in set_targets(body) {
inner_defs.remove(&v);
inner_rmw.remove(&v);
}
walk(body, c, loops, &mut inner_rmw, &mut inner_defs, w, interner);
loops.pop();
}
Stmt::Repeat { body, .. } => {
loops.push(LoopCtx { iv: None, trip: None });
let mut inner_rmw = rmw.clone();
let mut inner_defs = defs.clone();
for v in set_targets(body) {
inner_defs.remove(&v);
inner_rmw.remove(&v);
}
walk(body, c, loops, &mut inner_rmw, &mut inner_defs, w, interner);
loops.pop();
}
_ => {}
}
}
}
fn escapes(stmts: &[Stmt], c: Symbol) -> bool {
!stmts.iter().all(|s| use_is_local(s, c))
}
fn use_is_local(s: &Stmt, c: Symbol) -> bool {
match s {
Stmt::Push { collection, value } => {
(names(collection, c) || ok_read_only(collection, c)) && ok_read_only(value, c)
}
Stmt::SetIndex { collection, index, value } => {
(names(collection, c) || ok_read_only(collection, c))
&& ok_read_only(index, c)
&& ok_read_only(value, c)
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } => ok_read_only(value, c),
Stmt::SetField { object, value, .. } => ok_read_only(object, c) && ok_read_only(value, c),
Stmt::If { cond, then_block, else_block } => {
ok_read_only(cond, c)
&& then_block.iter().all(|x| use_is_local(x, c))
&& else_block.as_ref().map_or(true, |eb| eb.iter().all(|x| use_is_local(x, c)))
}
Stmt::While { cond, body, .. } => {
ok_read_only(cond, c) && body.iter().all(|x| use_is_local(x, c))
}
Stmt::Repeat { body, .. } => body.iter().all(|x| use_is_local(x, c)),
Stmt::Return { value: Some(v) } => ok_read_only(v, c),
Stmt::Return { value: None } => true,
Stmt::Show { object, recipient } | Stmt::Give { object, recipient } => {
ok_read_only(object, c) && ok_read_only(recipient, c)
}
Stmt::Call { args, .. } => args.iter().all(|a| ok_read_only(a, c)),
Stmt::RuntimeAssert { condition, .. } => ok_read_only(condition, c),
Stmt::Inspect { target, .. } => ok_read_only(target, c),
Stmt::Pop { collection, .. }
| Stmt::Remove { collection, .. }
| Stmt::Add { collection, .. } => !names(collection, c),
_ => !mentions_top(s, c),
}
}
fn mentions_top(s: &Stmt, c: Symbol) -> bool {
let mut found = false;
for_each_stmt_expr(s, &mut |e| {
visit_idents(e, &mut |sym| {
if sym == c {
found = true;
}
})
});
found
}
fn set_targets(stmts: &[Stmt]) -> HashSet<Symbol> {
let mut out = HashSet::new();
fn rec(stmts: &[Stmt], out: &mut HashSet<Symbol>) {
for s in stmts {
match s {
Stmt::Set { target, .. } => {
out.insert(*target);
}
Stmt::If { then_block, else_block, .. } => {
rec(then_block, out);
if let Some(eb) = else_block {
rec(eb, out);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => rec(body, out),
_ => {}
}
}
}
rec(stmts, &mut out);
out
}
fn classify<'a>(
value: &Expr<'a>,
c: Symbol,
at: Option<&Expr<'a>>,
loops: &[LoopCtx],
rmw: &HashMap<Symbol, &'a Expr<'a>>,
defs: &HashMap<Symbol, &'a Expr<'a>>,
depth: u32,
w: &mut Writes,
interner: &Interner,
) {
if depth == 0 && loops.iter().any(|l| l.trip.is_none()) {
w.large = true;
}
if let Expr::Identifier(v) = value {
if depth < 8 {
if let Some(def) = defs.get(v) {
classify(def, c, at, loops, rmw, defs, depth + 1, w, interner);
return;
}
}
w.bail = true;
return;
}
if let Some(k) = const_eval(value) {
if k < I32_MIN || k > I32_MAX {
w.bail = true;
} else {
w.lo = w.lo.min(k);
w.hi = w.hi.max(k);
w.any = true;
}
return;
}
if let Expr::BinaryOp { op: BinaryOpKind::Modulo, right, .. } = value {
match right {
Expr::Literal(Literal::Number(m)) if *m > 0 && *m <= I32_MAX => {
w.lo = w.lo.min(-(*m - 1));
w.hi = w.hi.max(*m - 1);
w.any = true;
return;
}
Expr::Identifier(msym) => {
let mn = sanitize(interner.resolve(*msym));
w.guards.push(format!("({mn}) > 0 && ({mn}) <= {I32_MAX}"));
w.lo = w.lo.min(I32_MIN);
w.hi = w.hi.max(I32_MAX);
w.any = true;
return;
}
_ => {}
}
}
if let Some(idx) = at {
if let Some(d) = accumulator_delta(value, c, idx, rmw) {
if d > 0 {
if let Some(mult) = slot_multiplier(idx, loops) {
w.hi = w.hi.saturating_add(d.saturating_mul(mult));
w.lo = w.lo.min(0);
w.any = true;
return;
}
}
}
}
w.bail = true;
}
fn accumulator_delta<'a>(
value: &Expr<'a>,
c: Symbol,
idx: &Expr<'a>,
rmw: &HashMap<Symbol, &'a Expr<'a>>,
) -> Option<i64> {
let Expr::BinaryOp { op: BinaryOpKind::Add, left, right } = value else { return None };
let reads_slot = |e: &Expr<'a>| -> bool {
if let Some(read_idx) = index_of(e, c) {
return expr_eq(read_idx, idx);
}
if let Expr::Identifier(v) = e {
return rmw.get(v).map_or(false, |r| expr_eq(r, idx));
}
false
};
if reads_slot(left) {
return const_eval(right);
}
if reads_slot(right) {
return const_eval(left);
}
None
}
fn slot_multiplier(idx: &Expr, loops: &[LoopCtx]) -> Option<i64> {
let idx_vars = ident_set(idx);
let mut mult: i64 = 1;
let mut index_loop_seen = false;
for l in loops {
match l.iv {
Some(iv) if idx_vars.contains(&iv) => index_loop_seen = true,
_ => mult = mult.checked_mul(l.trip?)?,
}
}
if !index_loop_seen {
return None;
}
Some(mult)
}
fn loop_ctx(cond: &Expr, prior: &[Stmt]) -> LoopCtx {
let Expr::BinaryOp { op, left, right } = cond else { return LoopCtx { iv: None, trip: None } };
let inclusive = match op {
BinaryOpKind::Lt => false,
BinaryOpKind::LtEq => true,
_ => return LoopCtx { iv: None, trip: None },
};
let Expr::Identifier(iv) = left else { return LoopCtx { iv: None, trip: None } };
let iv = *iv;
let bound = const_eval(right).or_else(|| match right {
Expr::Identifier(b) => iv_start(prior, *b),
_ => None,
});
let trip = match (iv_start(prior, iv), bound) {
(Some(s), Some(b)) => {
let t = if inclusive { b - s + 1 } else { b - s };
Some(t.max(0))
}
_ => None,
};
LoopCtx { iv: Some(iv), trip }
}
fn iv_start(prior: &[Stmt], iv: Symbol) -> Option<i64> {
for s in prior.iter().rev() {
match s {
Stmt::Let { var, value, .. } if *var == iv => return const_eval(value),
Stmt::Set { target, value } if *target == iv => return const_eval(value),
_ => {}
}
}
None
}
fn index_of<'a>(e: &Expr<'a>, sym: Symbol) -> Option<&'a Expr<'a>> {
if let Expr::Index { collection, index } = e {
if names(collection, sym) {
return Some(index);
}
}
None
}
fn names(e: &Expr, sym: Symbol) -> bool {
matches!(e, Expr::Identifier(s) if *s == sym)
}
fn ident_set(e: &Expr) -> HashSet<Symbol> {
let mut out = HashSet::new();
visit_idents(e, &mut |s| {
out.insert(s);
});
out
}
fn sanitize(name: &str) -> String {
name.chars().map(|c| if c.is_alphanumeric() || c == '_' { c } else { '_' }).collect()
}
fn expr_eq(a: &Expr, b: &Expr) -> bool {
match (a, b) {
(Expr::Identifier(x), Expr::Identifier(y)) => x == y,
(Expr::Literal(Literal::Number(x)), Expr::Literal(Literal::Number(y))) => x == y,
(
Expr::BinaryOp { op: o1, left: l1, right: r1 },
Expr::BinaryOp { op: o2, left: l2, right: r2 },
) => o1 == o2 && expr_eq(l1, l2) && expr_eq(r1, r2),
(Expr::Index { collection: c1, index: i1 }, Expr::Index { collection: c2, index: i2 }) => {
expr_eq(c1, c2) && expr_eq(i1, i2)
}
_ => false,
}
}