use std::collections::{HashMap, HashSet};
use crate::arena::Arena;
use crate::ast::stmt::{Expr, Literal, MatchArm, Stmt};
use crate::intern::{Interner, Symbol};
use crate::loop_shape::{
const_eval_i64, extract_counted_repeat, extract_counted_while, CountedLoop,
};
use super::partial_eval::substitute_block;
const TRIP_THRESHOLD: usize = 16;
const TOTAL_EXPANSION_BUDGET: usize = 4096;
pub fn unroll_stmts<'a>(
stmts: Vec<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> (Vec<Stmt<'a>>, bool) {
unroll_entry(stmts, false, expr_arena, stmt_arena, interner)
}
pub fn unroll_stmts_run<'a>(
stmts: Vec<Stmt<'a>>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> (Vec<Stmt<'a>>, bool) {
unroll_entry(stmts, true, expr_arena, stmt_arena, interner)
}
fn unroll_entry<'a>(
stmts: Vec<Stmt<'a>>,
entry_in_loop: bool,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> (Vec<Stmt<'a>>, bool) {
let scalarizable = crate::codegen::detection::scalarizable_seq_symbols(&stmts, interner);
let rotate_syms: HashSet<Symbol> = ["rotl", "rotr"].iter().filter_map(|n| interner.lookup(n)).collect();
if scalarizable.is_empty() && rotate_syms.is_empty() {
return (stmts, false);
}
let mut budget = TOTAL_EXPANSION_BUDGET;
let out = unroll_block(
&stmts,
entry_in_loop,
&mut budget,
&scalarizable,
&rotate_syms,
expr_arena,
stmt_arena,
);
if budget == TOTAL_EXPANSION_BUDGET {
(stmts, false)
} else {
(out, true)
}
}
struct UnrollPlan {
values: Vec<i64>,
final_val: i64,
}
fn plan_unroll(
cl: &CountedLoop,
in_loop: bool,
budget: usize,
scalarizable: &HashSet<Symbol>,
rotate_syms: &HashSet<Symbol>,
) -> Option<UnrollPlan> {
let start = const_eval_i64(cl.start)?;
let limit = const_eval_i64(cl.limit)?;
let span = if cl.inclusive {
limit.checked_sub(start)?.checked_add(1)?
} else {
limit.checked_sub(start)?
};
let tc = span.max(0);
let tcu = tc as usize;
if tcu > TRIP_THRESHOLD || tcu > budget {
return None;
}
if body_has_push(cl.body_without_increment) {
return None;
}
if !body_substitutable(cl.body_without_increment) || owns_break(cl.body_without_increment) {
return None;
}
let crypto = loop_rotates_by_ivar(cl.body_without_increment, cl.var, rotate_syms);
if !crypto {
if !in_loop {
return None;
}
let mut roots = HashSet::new();
collect_indexed_roots(cl.body_without_increment, &mut roots);
if roots.is_empty() || !roots.iter().all(|r| scalarizable.contains(r)) {
return None;
}
}
let final_val = start.checked_add(tc)?;
let values: Vec<i64> = (0..tcu).map(|k| start + k as i64).collect();
Some(UnrollPlan { values, final_val })
}
fn unroll_block<'a>(
block: &[Stmt<'a>],
in_loop: bool,
budget: &mut usize,
scalarizable: &HashSet<Symbol>,
rotate_syms: &HashSet<Symbol>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
) -> Vec<Stmt<'a>> {
let refs: Vec<&Stmt<'a>> = block.iter().collect();
let mut out: Vec<Stmt<'a>> = Vec::with_capacity(block.len());
let mut idx = 0;
while idx < block.len() {
{
if let Some((cl, _consumed)) = extract_counted_while(&refs, idx) {
if let Some(plan) = plan_unroll(&cl, in_loop, *budget, scalarizable, rotate_syms) {
*budget -= plan.values.len();
out.push(block[idx].clone());
emit_unrolled(&cl, &plan, &mut out, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
let fin = expr_arena.alloc(Expr::Literal(Literal::Number(plan.final_val)));
out.push(Stmt::Set { target: cl.var, value: fin });
idx += 2;
continue;
}
}
if let Some(cl) = extract_counted_repeat(&block[idx]) {
if let Some(plan) = plan_unroll(&cl, in_loop, *budget, scalarizable, rotate_syms) {
*budget -= plan.values.len();
emit_unrolled(&cl, &plan, &mut out, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
idx += 1;
continue;
}
}
}
out.push(descend_stmt(&block[idx], in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena));
idx += 1;
}
out
}
fn emit_unrolled<'a>(
cl: &CountedLoop<'a>,
plan: &UnrollPlan,
out: &mut Vec<Stmt<'a>>,
budget: &mut usize,
scalarizable: &HashSet<Symbol>,
rotate_syms: &HashSet<Symbol>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
) {
for &v in &plan.values {
let lit: &'a Expr<'a> = expr_arena.alloc(Expr::Literal(Literal::Number(v)));
let mut subs: HashMap<Symbol, &'a Expr<'a>> = HashMap::new();
subs.insert(cl.var, lit);
let sub_body = substitute_block(cl.body_without_increment, &subs, expr_arena, stmt_arena);
let unrolled = unroll_block(&sub_body, true, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
out.extend(unrolled);
}
}
fn descend_stmt<'a>(
s: &Stmt<'a>,
in_loop: bool,
budget: &mut usize,
scalarizable: &HashSet<Symbol>,
rotate_syms: &HashSet<Symbol>,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
) -> Stmt<'a> {
match s {
Stmt::While { cond, body, decreasing } => {
let nb = unroll_block(body, true, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::While { cond: *cond, body: stmt_arena.alloc_slice(nb), decreasing: *decreasing }
}
Stmt::Repeat { pattern, iterable, body } => {
let nb = unroll_block(body, true, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::Repeat {
pattern: pattern.clone(),
iterable: *iterable,
body: stmt_arena.alloc_slice(nb),
}
}
Stmt::If { cond, then_block, else_block } => {
let nt = unroll_block(then_block, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
let ne = match else_block {
Some(eb) => {
let neb = unroll_block(eb, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Some(stmt_arena.alloc_slice(neb) as &[Stmt<'a>])
}
None => None,
};
Stmt::If { cond: *cond, then_block: stmt_arena.alloc_slice(nt), else_block: ne }
}
Stmt::Inspect { target, arms, has_otherwise } => {
let na = arms
.iter()
.map(|a| MatchArm {
enum_name: a.enum_name,
variant: a.variant,
bindings: a.bindings.clone(),
body: stmt_arena.alloc_slice(unroll_block(
a.body, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena,
)),
})
.collect();
Stmt::Inspect { target: *target, arms: na, has_otherwise: *has_otherwise }
}
Stmt::FunctionDef {
name,
generics,
params,
body,
return_type,
is_native,
native_path,
is_exported,
export_target,
opt_flags,
} => {
let nb = unroll_block(body, false, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::FunctionDef {
name: *name,
generics: generics.clone(),
params: params.clone(),
body: stmt_arena.alloc_slice(nb),
return_type: *return_type,
is_native: *is_native,
native_path: *native_path,
is_exported: *is_exported,
export_target: *export_target,
opt_flags: opt_flags.clone(),
}
}
Stmt::Zone { name, capacity, source_file, body } => {
let nb = unroll_block(body, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::Zone {
name: *name,
capacity: *capacity,
source_file: *source_file,
body: stmt_arena.alloc_slice(nb),
}
}
Stmt::Concurrent { tasks } => {
let nb = unroll_block(tasks, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::Concurrent { tasks: stmt_arena.alloc_slice(nb) }
}
Stmt::Parallel { tasks } => {
let nb = unroll_block(tasks, in_loop, budget, scalarizable, rotate_syms, expr_arena, stmt_arena);
Stmt::Parallel { tasks: stmt_arena.alloc_slice(nb) }
}
other => other.clone(),
}
}
fn body_substitutable(stmts: &[Stmt]) -> bool {
stmts.iter().all(stmt_substitutable)
}
fn stmt_substitutable(s: &Stmt) -> bool {
match s {
Stmt::Let { .. }
| Stmt::Set { .. }
| Stmt::Return { .. }
| Stmt::Show { .. }
| Stmt::Call { .. }
| Stmt::SetIndex { .. }
| Stmt::Push { .. }
| Stmt::SetField { .. }
| Stmt::Give { .. }
| Stmt::Add { .. }
| Stmt::Remove { .. }
| Stmt::RuntimeAssert { .. }
| Stmt::Break => true,
Stmt::If { then_block, else_block, .. } => {
body_substitutable(then_block)
&& match else_block {
Some(eb) => body_substitutable(eb),
None => true,
}
}
Stmt::While { body, .. } => body_substitutable(body),
Stmt::Repeat { body, .. } => body_substitutable(body),
Stmt::Inspect { arms, .. } => arms.iter().all(|a| body_substitutable(a.body)),
_ => false,
}
}
fn owns_break(stmts: &[Stmt]) -> bool {
stmts.iter().any(|s| match s {
Stmt::Break => true,
Stmt::If { then_block, else_block, .. } => {
owns_break(then_block) || matches!(else_block, Some(eb) if owns_break(eb))
}
Stmt::Inspect { arms, .. } => arms.iter().any(|a| owns_break(a.body)),
_ => false,
})
}
fn body_has_push(stmts: &[Stmt]) -> bool {
stmts.iter().any(|s| match s {
Stmt::Push { .. } => true,
Stmt::If { then_block, else_block, .. } => {
body_has_push(then_block) || matches!(else_block, Some(eb) if body_has_push(eb))
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => body_has_push(body),
Stmt::Inspect { arms, .. } => arms.iter().any(|a| body_has_push(a.body)),
_ => false,
})
}
fn loop_rotates_by_ivar(body: &[Stmt], ivar: Symbol, rotate_syms: &HashSet<Symbol>) -> bool {
body.iter().any(|s| stmt_rotates_by_ivar(s, ivar, rotate_syms))
}
fn stmt_rotates_by_ivar(s: &Stmt, ivar: Symbol, rot: &HashSet<Symbol>) -> bool {
match s {
Stmt::Let { value, .. } | Stmt::Set { value, .. } | Stmt::Show { object: value, .. } => {
expr_rotates_by_ivar(value, ivar, rot)
}
Stmt::SetIndex { collection, index, value } => {
expr_rotates_by_ivar(collection, ivar, rot)
|| expr_rotates_by_ivar(index, ivar, rot)
|| expr_rotates_by_ivar(value, ivar, rot)
}
Stmt::SetField { object, value, .. } => {
expr_rotates_by_ivar(object, ivar, rot) || expr_rotates_by_ivar(value, ivar, rot)
}
Stmt::Push { value, collection } => {
expr_rotates_by_ivar(value, ivar, rot) || expr_rotates_by_ivar(collection, ivar, rot)
}
Stmt::Return { value } => matches!(value, Some(v) if expr_rotates_by_ivar(v, ivar, rot)),
Stmt::Call { args, .. } => args.iter().any(|a| expr_rotates_by_ivar(a, ivar, rot)),
Stmt::RuntimeAssert { condition, .. } => expr_rotates_by_ivar(condition, ivar, rot),
Stmt::If { cond, then_block, else_block } => {
expr_rotates_by_ivar(cond, ivar, rot)
|| loop_rotates_by_ivar(then_block, ivar, rot)
|| matches!(else_block, Some(eb) if loop_rotates_by_ivar(eb, ivar, rot))
}
Stmt::While { cond, body, .. } => {
expr_rotates_by_ivar(cond, ivar, rot) || loop_rotates_by_ivar(body, ivar, rot)
}
Stmt::Repeat { iterable, body, .. } => {
expr_rotates_by_ivar(iterable, ivar, rot) || loop_rotates_by_ivar(body, ivar, rot)
}
Stmt::Inspect { target, arms, .. } => {
expr_rotates_by_ivar(target, ivar, rot)
|| arms.iter().any(|a| loop_rotates_by_ivar(a.body, ivar, rot))
}
_ => false,
}
}
fn expr_rotates_by_ivar(e: &Expr, ivar: Symbol, rot: &HashSet<Symbol>) -> bool {
match e {
Expr::Call { function, args } => {
if rot.contains(function) {
if let Some(shift) = args.get(1) {
if expr_mentions(shift, ivar) {
return true;
}
}
}
args.iter().any(|a| expr_rotates_by_ivar(a, ivar, rot))
}
Expr::CallExpr { callee, args } => {
expr_rotates_by_ivar(callee, ivar, rot) || args.iter().any(|a| expr_rotates_by_ivar(a, ivar, rot))
}
Expr::BinaryOp { left, right, .. }
| Expr::Union { left, right }
| Expr::Intersection { left, right }
| Expr::Range { start: left, end: right } => {
expr_rotates_by_ivar(left, ivar, rot) || expr_rotates_by_ivar(right, ivar, rot)
}
Expr::Index { collection, index } => {
expr_rotates_by_ivar(collection, ivar, rot) || expr_rotates_by_ivar(index, ivar, rot)
}
Expr::Slice { collection, start, end } => {
expr_rotates_by_ivar(collection, ivar, rot)
|| expr_rotates_by_ivar(start, ivar, rot)
|| expr_rotates_by_ivar(end, ivar, rot)
}
Expr::Not { operand } => expr_rotates_by_ivar(operand, ivar, rot),
Expr::Length { collection } => expr_rotates_by_ivar(collection, ivar, rot),
Expr::Contains { collection, value } => {
expr_rotates_by_ivar(collection, ivar, rot) || expr_rotates_by_ivar(value, ivar, rot)
}
Expr::FieldAccess { object, .. } => expr_rotates_by_ivar(object, ivar, rot),
Expr::Copy { expr } => expr_rotates_by_ivar(expr, ivar, rot),
Expr::Give { value } | Expr::OptionSome { value } => expr_rotates_by_ivar(value, ivar, rot),
Expr::List(items) | Expr::Tuple(items) => items.iter().any(|i| expr_rotates_by_ivar(i, ivar, rot)),
_ => false,
}
}
fn expr_mentions(e: &Expr, sym: Symbol) -> bool {
match e {
Expr::Identifier(s) => *s == sym,
Expr::Call { args, .. } => args.iter().any(|a| expr_mentions(a, sym)),
Expr::CallExpr { callee, args } => {
expr_mentions(callee, sym) || args.iter().any(|a| expr_mentions(a, sym))
}
Expr::BinaryOp { left, right, .. }
| Expr::Union { left, right }
| Expr::Intersection { left, right }
| Expr::Range { start: left, end: right } => {
expr_mentions(left, sym) || expr_mentions(right, sym)
}
Expr::Index { collection, index } => expr_mentions(collection, sym) || expr_mentions(index, sym),
Expr::Slice { collection, start, end } => {
expr_mentions(collection, sym) || expr_mentions(start, sym) || expr_mentions(end, sym)
}
Expr::Not { operand } => expr_mentions(operand, sym),
Expr::Length { collection } => expr_mentions(collection, sym),
Expr::Contains { collection, value } => expr_mentions(collection, sym) || expr_mentions(value, sym),
Expr::FieldAccess { object, .. } => expr_mentions(object, sym),
Expr::Copy { expr } => expr_mentions(expr, sym),
Expr::Give { value } | Expr::OptionSome { value } => expr_mentions(value, sym),
Expr::WithCapacity { value, capacity } => expr_mentions(value, sym) || expr_mentions(capacity, sym),
Expr::List(items) | Expr::Tuple(items) => items.iter().any(|i| expr_mentions(i, sym)),
Expr::InterpolatedString(parts) => parts.iter().any(|p| {
matches!(p, crate::ast::stmt::StringPart::Expr { value, .. } if expr_mentions(value, sym))
}),
_ => false,
}
}
fn root_symbol(e: &Expr) -> Option<Symbol> {
match e {
Expr::Identifier(s) => Some(*s),
Expr::Index { collection, .. } => root_symbol(collection),
Expr::Slice { collection, .. } => root_symbol(collection),
Expr::FieldAccess { object, .. } => root_symbol(object),
_ => None,
}
}
fn collect_indexed_roots(stmts: &[Stmt], out: &mut HashSet<Symbol>) {
for s in stmts {
stmt_indexed_roots(s, out);
}
}
fn stmt_indexed_roots(s: &Stmt, out: &mut HashSet<Symbol>) {
match s {
Stmt::SetIndex { collection, index, value } => {
if let Some(r) = root_symbol(collection) {
out.insert(r);
}
expr_indexed_roots(collection, out);
expr_indexed_roots(index, out);
expr_indexed_roots(value, out);
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } => expr_indexed_roots(value, out),
Stmt::Push { value, collection } => {
expr_indexed_roots(value, out);
expr_indexed_roots(collection, out);
}
Stmt::Show { object, .. } => expr_indexed_roots(object, out),
Stmt::SetField { object, value, .. } => {
expr_indexed_roots(object, out);
expr_indexed_roots(value, out);
}
Stmt::Give { object, recipient } => {
expr_indexed_roots(object, out);
expr_indexed_roots(recipient, out);
}
Stmt::Add { value, collection } | Stmt::Remove { value, collection } => {
expr_indexed_roots(value, out);
expr_indexed_roots(collection, out);
}
Stmt::RuntimeAssert { condition, .. } => expr_indexed_roots(condition, out),
Stmt::Return { value } => {
if let Some(v) = value {
expr_indexed_roots(v, out);
}
}
Stmt::Call { args, .. } => {
for a in args {
expr_indexed_roots(a, out);
}
}
Stmt::If { cond, then_block, else_block } => {
expr_indexed_roots(cond, out);
collect_indexed_roots(then_block, out);
if let Some(eb) = else_block {
collect_indexed_roots(eb, out);
}
}
Stmt::While { cond, body, .. } => {
expr_indexed_roots(cond, out);
collect_indexed_roots(body, out);
}
Stmt::Repeat { iterable, body, .. } => {
expr_indexed_roots(iterable, out);
collect_indexed_roots(body, out);
}
Stmt::Inspect { target, arms, .. } => {
expr_indexed_roots(target, out);
for a in arms {
collect_indexed_roots(a.body, out);
}
}
_ => {}
}
}
fn expr_indexed_roots(e: &Expr, out: &mut HashSet<Symbol>) {
match e {
Expr::Index { collection, index } => {
if let Some(r) = root_symbol(collection) {
out.insert(r);
}
expr_indexed_roots(collection, out);
expr_indexed_roots(index, out);
}
Expr::Slice { collection, start, end } => {
if let Some(r) = root_symbol(collection) {
out.insert(r);
}
expr_indexed_roots(collection, out);
expr_indexed_roots(start, out);
expr_indexed_roots(end, out);
}
Expr::BinaryOp { left, right, .. } => {
expr_indexed_roots(left, out);
expr_indexed_roots(right, out);
}
Expr::Not { operand } => expr_indexed_roots(operand, out),
Expr::Length { collection } => expr_indexed_roots(collection, out),
Expr::Contains { collection, value } => {
expr_indexed_roots(collection, out);
expr_indexed_roots(value, out);
}
Expr::Union { left, right } | Expr::Intersection { left, right } => {
expr_indexed_roots(left, out);
expr_indexed_roots(right, out);
}
Expr::Call { args, .. } | Expr::CallExpr { args, .. } => {
for a in args {
expr_indexed_roots(a, out);
}
}
Expr::FieldAccess { object, .. } => expr_indexed_roots(object, out),
Expr::Copy { expr } => expr_indexed_roots(expr, out),
Expr::Give { value } | Expr::OptionSome { value } => expr_indexed_roots(value, out),
Expr::WithCapacity { value, capacity } => {
expr_indexed_roots(value, out);
expr_indexed_roots(capacity, out);
}
Expr::Range { start, end } => {
expr_indexed_roots(start, out);
expr_indexed_roots(end, out);
}
Expr::List(items) | Expr::Tuple(items) => {
for i in items {
expr_indexed_roots(i, out);
}
}
Expr::InterpolatedString(parts) => {
for p in parts {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
expr_indexed_roots(value, out);
}
}
}
_ => {}
}
}