use std::collections::HashSet;
use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
use crate::optimization::Opt;
use crate::intern::{Interner, Symbol};
use crate::Arena;
pub(crate) fn direct_self_tail_args<'a>(
expr: &'a Expr<'a>,
func: Symbol,
pc: usize,
) -> Option<&'a [&'a Expr<'a>]> {
let Expr::Call { function, args } = expr else {
return None;
};
if *function != func || args.len() != pc {
return None;
}
Some(args.as_slice())
}
pub(crate) fn tail_pair_args<'a>(
s1: &Stmt<'a>,
s2: &Stmt<'a>,
func: Symbol,
pc: usize,
) -> Option<&'a [&'a Expr<'a>]> {
let (bound, call) = match s1 {
Stmt::Set { target, value } => (*target, *value),
Stmt::Let { var, value, .. } => (*var, *value),
_ => return None,
};
let Expr::Call { function, args } = call else {
return None;
};
if *function != func || args.len() != pc {
return None;
}
let Stmt::Return { value: Some(rv) } = s2 else {
return None;
};
let Expr::Identifier(rvar) = rv else {
return None;
};
if *rvar != bound {
return None;
}
Some(args.as_slice())
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum NonRecSide {
Left,
Right,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct AccumulatorInfo {
pub(crate) op: BinaryOpKind,
pub(crate) identity: i64,
pub(crate) non_recursive_side: NonRecSide,
}
fn expr_is_self_call(func: Symbol, e: &Expr) -> bool {
matches!(e, Expr::Call { function, .. } if *function == func)
}
fn expr_contains_self_call(func: Symbol, e: &Expr) -> bool {
match e {
Expr::Call { function, args } => {
*function == func || args.iter().any(|a| expr_contains_self_call(func, a))
}
Expr::BinaryOp { left, right, .. } => {
expr_contains_self_call(func, left) || expr_contains_self_call(func, right)
}
Expr::Index { collection, index } => {
expr_contains_self_call(func, collection) || expr_contains_self_call(func, index)
}
Expr::FieldAccess { object, .. } => expr_contains_self_call(func, object),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().any(|i| expr_contains_self_call(func, i))
}
Expr::Not { operand } => expr_contains_self_call(func, operand),
Expr::Length { collection } => expr_contains_self_call(func, collection),
_ => false,
}
}
fn has_non_return_self_calls(func: Symbol, body: &[Stmt]) -> bool {
body.iter().any(|s| match s {
Stmt::Return { .. } => false,
Stmt::If { cond, then_block, else_block } => {
expr_contains_self_call(func, cond)
|| has_non_return_self_calls(func, then_block)
|| else_block.is_some_and(|e| has_non_return_self_calls(func, e))
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } => expr_contains_self_call(func, value),
Stmt::Show { object, .. } => expr_contains_self_call(func, object),
Stmt::While { cond, body, .. } => {
expr_contains_self_call(func, cond) || has_non_return_self_calls(func, body)
}
Stmt::Repeat { body, .. } => has_non_return_self_calls(func, body),
Stmt::Call { function, args } => {
*function == func || args.iter().any(|a| expr_contains_self_call(func, a))
}
_ => false,
})
}
fn count_recursive_returns(func: Symbol, body: &[Stmt]) -> (usize, usize) {
let mut base = 0;
let mut recursive = 0;
for s in body {
match s {
Stmt::Return { value: Some(e) } => {
if expr_contains_self_call(func, e) {
recursive += 1;
} else {
base += 1;
}
}
Stmt::Return { value: None } => base += 1,
Stmt::If { then_block, else_block, .. } => {
let (tb, tr) = count_recursive_returns(func, then_block);
base += tb;
recursive += tr;
if let Some(e) = else_block {
let (eb, er) = count_recursive_returns(func, e);
base += eb;
recursive += er;
}
}
_ => {}
}
}
(base, recursive)
}
fn find_accumulator_return(func: Symbol, body: &[Stmt]) -> Option<AccumulatorInfo> {
for s in body {
match s {
Stmt::Return { value: Some(Expr::BinaryOp { op, left, right }) } => {
if !matches!(op, BinaryOpKind::Add | BinaryOpKind::Multiply) {
continue;
}
let identity = if matches!(op, BinaryOpKind::Add) { 0 } else { 1 };
let left_call = expr_is_self_call(func, left);
let right_call = expr_is_self_call(func, right);
if left_call && !expr_contains_self_call(func, right) {
return Some(AccumulatorInfo { op: *op, identity, non_recursive_side: NonRecSide::Right });
}
if right_call && !expr_contains_self_call(func, left) {
return Some(AccumulatorInfo { op: *op, identity, non_recursive_side: NonRecSide::Left });
}
}
Stmt::If { then_block, else_block, .. } => {
if let Some(info) = find_accumulator_return(func, then_block) {
return Some(info);
}
if let Some(e) = else_block {
if let Some(info) = find_accumulator_return(func, e) {
return Some(info);
}
}
}
_ => {}
}
}
None
}
pub(crate) fn detect_accumulator_pattern(func: Symbol, body: &[Stmt]) -> Option<AccumulatorInfo> {
if has_non_return_self_calls(func, body) {
return None;
}
let (base, recursive) = count_recursive_returns(func, body);
if recursive != 1 || base == 0 {
return None;
}
find_accumulator_return(func, body)
}
pub(crate) fn rewrite_accumulators<'a>(
stmts: &[Stmt<'a>],
stmt_arena: &'a Arena<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
interner: &mut Interner,
) -> Option<&'a [Stmt<'a>]> {
let mut out: Option<Vec<Stmt<'a>>> = None;
for (i, s) in stmts.iter().enumerate() {
let rewritten = accumulator_rewrite_of(s, stmt_arena, expr_arena, interner);
match (&mut out, rewritten) {
(Some(v), Some(fd)) => v.push(fd),
(Some(v), None) => v.push(s.clone()),
(None, Some(fd)) => {
let mut v: Vec<Stmt<'a>> = stmts[..i].to_vec();
v.push(fd);
out = Some(v);
}
(None, None) => {}
}
}
out.map(|v| stmt_arena.alloc_slice(v))
}
fn accumulator_rewrite_of<'a>(
s: &Stmt<'a>,
stmt_arena: &'a Arena<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
interner: &mut Interner,
) -> Option<Stmt<'a>> {
let Stmt::FunctionDef { name, params, body, is_native, is_exported, opt_flags, .. } = s else {
return None;
};
let body: &'a [Stmt<'a>] = *body;
if *is_native || *is_exported {
return None;
}
if !opt_flags.is_on(Opt::Tco) {
return None;
}
let acc = detect_accumulator_pattern(*name, body)?;
let param_syms: Vec<Symbol> = params.iter().map(|(p, _)| *p).collect();
let new_body = build_accumulator_loop(body, *name, ¶m_syms, acc, stmt_arena, expr_arena, interner);
let mut fd = s.clone();
if let Stmt::FunctionDef { body: b, .. } = &mut fd {
*b = new_body;
}
Some(fd)
}
fn build_accumulator_loop<'a>(
body: &'a [Stmt<'a>],
func: Symbol,
params: &[Symbol],
acc: AccumulatorInfo,
stmt_arena: &'a Arena<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
interner: &mut Interner,
) -> &'a [Stmt<'a>] {
let acc_sym = interner.intern("__acc");
let identity = expr_arena.alloc(Expr::Literal(Literal::Number(acc.identity)));
let let_acc = Stmt::Let { var: acc_sym, ty: None, value: identity, mutable: true };
let inner = rewrite_accumulator_body(body, func, params, acc, acc_sym, stmt_arena, expr_arena, interner);
let cond = expr_arena.alloc(Expr::Literal(Literal::Boolean(true)));
let while_stmt = Stmt::While { cond, body: inner, decreasing: None };
stmt_arena.alloc_slice(vec![let_acc, while_stmt])
}
fn rewrite_accumulator_body<'a>(
body: &'a [Stmt<'a>],
func: Symbol,
params: &[Symbol],
acc: AccumulatorInfo,
acc_sym: Symbol,
stmt_arena: &'a Arena<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
interner: &mut Interner,
) -> &'a [Stmt<'a>] {
let mut out: Vec<Stmt<'a>> = Vec::with_capacity(body.len() + params.len());
for s in body {
match s {
Stmt::Return { value: Some(e) } if expr_contains_self_call(func, e) => {
let Expr::BinaryOp { op, left, right } = *e else {
out.push(s.clone());
continue;
};
let (call_expr, nonrec): (&'a Expr<'a>, &'a Expr<'a>) = match acc.non_recursive_side {
NonRecSide::Left => (*right, *left),
NonRecSide::Right => (*left, *right),
};
let acc_id = expr_arena.alloc(Expr::Identifier(acc_sym));
let folded = expr_arena.alloc(Expr::BinaryOp { op: *op, left: acc_id, right: nonrec });
out.push(Stmt::Set { target: acc_sym, value: folded });
if let Expr::Call { args, .. } = call_expr {
let temps: Vec<Symbol> = (0..args.len())
.map(|i| interner.intern(&format!("__acc_arg{}", i)))
.collect();
for (t, arg) in temps.iter().zip(args.iter()) {
out.push(Stmt::Let { var: *t, ty: None, value: *arg, mutable: false });
}
for (p, t) in params.iter().zip(temps.iter()) {
let t_id = expr_arena.alloc(Expr::Identifier(*t));
out.push(Stmt::Set { target: *p, value: t_id });
}
}
}
Stmt::Return { value: Some(e) } => {
let acc_id = expr_arena.alloc(Expr::Identifier(acc_sym));
let ret = expr_arena.alloc(Expr::BinaryOp { op: acc.op, left: acc_id, right: *e });
out.push(Stmt::Return { value: Some(ret) });
}
Stmt::Return { value: None } => {
let acc_id = expr_arena.alloc(Expr::Identifier(acc_sym));
out.push(Stmt::Return { value: Some(acc_id) });
}
Stmt::If { cond, then_block, else_block } => {
let then_b = rewrite_accumulator_body(then_block, func, params, acc, acc_sym, stmt_arena, expr_arena, interner);
let else_b = (*else_block)
.map(|e| rewrite_accumulator_body(e, func, params, acc, acc_sym, stmt_arena, expr_arena, interner));
out.push(Stmt::If { cond: *cond, then_block: then_b, else_block: else_b });
}
other => out.push(other.clone()),
}
}
stmt_arena.alloc_slice(out)
}