use std::collections::{HashMap, HashSet};
use crate::arena::Arena;
use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
use crate::intern::{Interner, Symbol};
#[derive(Hash, Eq, PartialEq, Clone)]
enum ExprKey {
Ident(Symbol),
LitInt(i64),
LitBool(bool),
LitText(Symbol),
BinaryOp(BinaryOpKind, Box<ExprKey>, Box<ExprKey>),
Length(Box<ExprKey>),
Not(Box<ExprKey>),
FieldAccess(Box<ExprKey>, Symbol),
Index(Box<ExprKey>, Box<ExprKey>),
Contains(Box<ExprKey>, Box<ExprKey>),
}
fn compute_key(expr: &Expr) -> Option<ExprKey> {
match expr {
Expr::Identifier(sym) => Some(ExprKey::Ident(*sym)),
Expr::Literal(Literal::Number(n)) => Some(ExprKey::LitInt(*n)),
Expr::Literal(Literal::Boolean(b)) => Some(ExprKey::LitBool(*b)),
Expr::Literal(Literal::Text(s)) => Some(ExprKey::LitText(*s)),
Expr::BinaryOp { op, left, right } => {
let lk = compute_key(left)?;
let rk = compute_key(right)?;
Some(ExprKey::BinaryOp(*op, Box::new(lk), Box::new(rk)))
}
Expr::Length { collection } => {
let ck = compute_key(collection)?;
Some(ExprKey::Length(Box::new(ck)))
}
Expr::Not { operand } => {
let ok = compute_key(operand)?;
Some(ExprKey::Not(Box::new(ok)))
}
Expr::FieldAccess { object, field } => {
let ok = compute_key(object)?;
Some(ExprKey::FieldAccess(Box::new(ok), *field))
}
Expr::Index { collection, index } => {
let ck = compute_key(collection)?;
let ik = compute_key(index)?;
Some(ExprKey::Index(Box::new(ck), Box::new(ik)))
}
Expr::Contains { collection, value } => {
let ck = compute_key(collection)?;
let vk = compute_key(value)?;
Some(ExprKey::Contains(Box::new(ck), Box::new(vk)))
}
_ => None,
}
}
fn is_non_trivial(expr: &Expr) -> bool {
!matches!(expr, Expr::Identifier(_) | Expr::Literal(_))
}
fn collect_key_symbols(key: &ExprKey, out: &mut HashSet<Symbol>) {
match key {
ExprKey::Ident(sym) => { out.insert(*sym); }
ExprKey::LitInt(_) | ExprKey::LitBool(_) | ExprKey::LitText(_) => {}
ExprKey::BinaryOp(_, l, r) | ExprKey::Index(l, r) | ExprKey::Contains(l, r) => {
collect_key_symbols(l, out);
collect_key_symbols(r, out);
}
ExprKey::Length(inner) | ExprKey::Not(inner) => {
collect_key_symbols(inner, out);
}
ExprKey::FieldAccess(obj, _) => {
collect_key_symbols(obj, out);
}
}
}
struct CseState {
cache: HashMap<ExprKey, Symbol>,
deps: HashMap<Symbol, Vec<ExprKey>>,
counter: u32,
}
impl CseState {
fn new() -> Self {
Self {
cache: HashMap::new(),
deps: HashMap::new(),
counter: 0,
}
}
fn insert(&mut self, key: ExprKey, sym: Symbol) {
let mut syms = HashSet::new();
collect_key_symbols(&key, &mut syms);
for s in syms {
self.deps.entry(s).or_default().push(key.clone());
}
self.cache.insert(key, sym);
}
fn invalidate(&mut self, sym: Symbol) {
if let Some(keys) = self.deps.remove(&sym) {
for key in keys {
self.cache.remove(&key);
}
}
}
fn lookup(&self, key: &ExprKey) -> Option<Symbol> {
self.cache.get(key).copied()
}
}
fn count_subexpressions(expr: &Expr, counts: &mut HashMap<ExprKey, usize>) {
if let Some(key) = compute_key(expr) {
if is_non_trivial(expr) {
*counts.entry(key).or_insert(0) += 1;
}
}
match expr {
Expr::BinaryOp { left, right, .. } => {
count_subexpressions(left, counts);
count_subexpressions(right, counts);
}
Expr::Length { collection } | Expr::Not { operand: collection } => {
count_subexpressions(collection, counts);
}
Expr::FieldAccess { object, .. } => {
count_subexpressions(object, counts);
}
Expr::Index { collection, index } | Expr::Contains { collection, value: index } => {
count_subexpressions(collection, counts);
count_subexpressions(index, counts);
}
_ => {}
}
}
fn rewrite_expr<'a>(
expr: &'a Expr<'a>,
intra_counts: &HashMap<ExprKey, usize>,
local_cache: &mut HashMap<ExprKey, Symbol>,
state: &mut CseState,
hoisted: &mut Vec<Stmt<'a>>,
interner: &mut Interner,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
) -> &'a Expr<'a> {
let key = compute_key(expr);
if let Some(ref k) = key {
if let Some(sym) = local_cache.get(k) {
return expr_arena.alloc(Expr::Identifier(*sym));
}
if is_non_trivial(expr) {
if let Some(sym) = state.lookup(k) {
return expr_arena.alloc(Expr::Identifier(sym));
}
}
}
let new_expr: &'a Expr<'a> = match expr {
Expr::BinaryOp { op, left, right } => {
let new_left = rewrite_expr(left, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
let new_right = rewrite_expr(right, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*left, new_left) && std::ptr::eq(*right, new_right) {
expr
} else {
expr_arena.alloc(Expr::BinaryOp { op: *op, left: new_left, right: new_right })
}
}
Expr::Length { collection } => {
let new_coll = rewrite_expr(collection, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*collection, new_coll) { expr }
else { expr_arena.alloc(Expr::Length { collection: new_coll }) }
}
Expr::Not { operand } => {
let new_op = rewrite_expr(operand, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*operand, new_op) { expr }
else { expr_arena.alloc(Expr::Not { operand: new_op }) }
}
Expr::FieldAccess { object, field } => {
let new_obj = rewrite_expr(object, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*object, new_obj) { expr }
else { expr_arena.alloc(Expr::FieldAccess { object: new_obj, field: *field }) }
}
Expr::Index { collection, index } => {
let new_coll = rewrite_expr(collection, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
let new_idx = rewrite_expr(index, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*collection, new_coll) && std::ptr::eq(*index, new_idx) { expr }
else { expr_arena.alloc(Expr::Index { collection: new_coll, index: new_idx }) }
}
Expr::Contains { collection, value } => {
let new_coll = rewrite_expr(collection, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
let new_val = rewrite_expr(value, intra_counts, local_cache, state, hoisted, interner, expr_arena, stmt_arena);
if std::ptr::eq(*collection, new_coll) && std::ptr::eq(*value, new_val) { expr }
else { expr_arena.alloc(Expr::Contains { collection: new_coll, value: new_val }) }
}
_ => expr,
};
if let Some(ref k) = key {
if is_non_trivial(new_expr) {
let count = intra_counts.get(k).copied().unwrap_or(0);
if count > 1 {
let tmp_name = format!("__cse_{}", state.counter);
state.counter += 1;
let tmp_sym = interner.intern(&tmp_name);
let let_stmt = stmt_arena.alloc(Stmt::Let {
var: tmp_sym,
ty: None,
value: new_expr,
mutable: false,
});
hoisted.push(let_stmt.clone());
local_cache.insert(k.clone(), tmp_sym);
state.insert(k.clone(), tmp_sym);
return expr_arena.alloc(Expr::Identifier(tmp_sym));
}
}
}
new_expr
}
fn cse_expr_cross_stmt<'a>(
expr: &'a Expr<'a>,
state: &CseState,
expr_arena: &'a Arena<Expr<'a>>,
) -> &'a Expr<'a> {
if let Some(key) = compute_key(expr) {
if is_non_trivial(expr) {
if let Some(sym) = state.lookup(&key) {
return expr_arena.alloc(Expr::Identifier(sym));
}
}
}
expr
}
fn cse_block<'a>(
stmts: Vec<Stmt<'a>>,
state: &mut CseState,
expr_arena: &'a Arena<Expr<'a>>,
stmt_arena: &'a Arena<Stmt<'a>>,
interner: &mut Interner,
) -> Vec<Stmt<'a>> {
let mut result = Vec::with_capacity(stmts.len());
for stmt in stmts {
match stmt {
Stmt::Let { var, ty, value, mutable } => {
let mut intra_counts = HashMap::new();
count_subexpressions(value, &mut intra_counts);
let has_intra_dupes = intra_counts.values().any(|&c| c > 1);
if has_intra_dupes {
let mut hoisted = Vec::new();
let mut local_cache = HashMap::new();
let new_value = rewrite_expr(
value, &intra_counts, &mut local_cache,
state, &mut hoisted, interner, expr_arena, stmt_arena,
);
result.extend(hoisted);
if let Some(key) = compute_key(new_value) {
if is_non_trivial(new_value) {
state.insert(key, var);
}
}
result.push(Stmt::Let { var, ty, value: new_value, mutable });
} else {
let new_value = cse_expr_cross_stmt(value, state, expr_arena);
if let Some(key) = compute_key(value) {
if is_non_trivial(value) {
state.insert(key, var);
}
}
result.push(Stmt::Let { var, ty, value: new_value, mutable });
}
}
Stmt::Set { target, value } => {
state.invalidate(target);
result.push(Stmt::Set { target, value });
}
Stmt::Push { value, collection } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
result.push(Stmt::Push { value, collection });
}
Stmt::Pop { collection, into } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
result.push(Stmt::Pop { collection, into });
}
Stmt::Add { value, collection } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
result.push(Stmt::Add { value, collection });
}
Stmt::Remove { value, collection } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
result.push(Stmt::Remove { value, collection });
}
Stmt::SetIndex { collection, index, value } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
result.push(Stmt::SetIndex { collection, index, value });
}
Stmt::SetField { object, field, value } => {
if let Expr::Identifier(sym) = object {
state.invalidate(*sym);
}
result.push(Stmt::SetField { object, field, value });
}
Stmt::If { cond, then_block, else_block } => {
let saved_cache = state.cache.clone();
let saved_deps = state.deps.clone();
let new_then = cse_block(then_block.to_vec(), state, expr_arena, stmt_arena, interner);
let new_then_block: Block = stmt_arena.alloc_slice(new_then);
state.cache = saved_cache.clone();
state.deps = saved_deps.clone();
let new_else = else_block.map(|eb| {
let processed = cse_block(eb.to_vec(), state, expr_arena, stmt_arena, interner);
let b: Block = stmt_arena.alloc_slice(processed);
b
});
state.cache = saved_cache;
state.deps = saved_deps;
invalidate_block_writes(then_block, state);
if let Some(eb) = else_block {
invalidate_block_writes(eb, state);
}
result.push(Stmt::If { cond, then_block: new_then_block, else_block: new_else });
}
Stmt::While { cond, body, decreasing } => {
let saved_cache = state.cache.clone();
let saved_deps = state.deps.clone();
let new_body = cse_block(body.to_vec(), state, expr_arena, stmt_arena, interner);
let new_body_block: Block = stmt_arena.alloc_slice(new_body);
state.cache = saved_cache;
state.deps = saved_deps;
invalidate_block_writes(body, state);
result.push(Stmt::While { cond, body: new_body_block, decreasing });
}
Stmt::Repeat { pattern, iterable, body } => {
let saved_cache = state.cache.clone();
let saved_deps = state.deps.clone();
let new_body = cse_block(body.to_vec(), state, expr_arena, stmt_arena, interner);
let new_body_block: Block = stmt_arena.alloc_slice(new_body);
state.cache = saved_cache;
state.deps = saved_deps;
invalidate_block_writes(body, state);
result.push(Stmt::Repeat { pattern, iterable, body: new_body_block });
}
Stmt::FunctionDef { name, generics, params, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
let mut fn_state = CseState::new();
fn_state.counter = state.counter;
let new_body = cse_block(body.to_vec(), &mut fn_state, expr_arena, stmt_arena, interner);
state.counter = fn_state.counter;
let new_body_block: Block = stmt_arena.alloc_slice(new_body);
result.push(Stmt::FunctionDef {
name, generics, params, body: new_body_block, return_type,
is_native, native_path, is_exported, export_target, opt_flags,
});
}
other => result.push(other),
}
}
result
}
fn invalidate_block_writes(stmts: &[Stmt], state: &mut CseState) {
for stmt in stmts {
match stmt {
Stmt::Set { target, .. } => state.invalidate(*target),
Stmt::Push { collection, .. } | Stmt::Pop { collection, .. }
| Stmt::Add { collection, .. } | Stmt::Remove { collection, .. } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
}
Stmt::SetIndex { collection, .. } | Stmt::SetField { object: collection, .. } => {
if let Expr::Identifier(sym) = collection {
state.invalidate(*sym);
}
}
Stmt::If { then_block, else_block, .. } => {
invalidate_block_writes(then_block, state);
if let Some(eb) = else_block {
invalidate_block_writes(eb, state);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
invalidate_block_writes(body, state);
}
Stmt::Zone { body, .. } => {
invalidate_block_writes(body, state);
}
_ => {}
}
}
}
use crate::ast::stmt::Block;
pub fn cse_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 state = CseState::new();
cse_block(stmts, &mut state, expr_arena, stmt_arena, interner)
}