use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use crate::analysis::types::RustNames;
use crate::ast::stmt::{Expr, Stmt};
use crate::intern::{Interner, Symbol};
use super::context::RefinementContext;
use super::peephole::body_modifies_var;
thread_local! {
static FORCE_DISABLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn force_disable_for_test(disabled: bool) {
FORCE_DISABLE.with(|c| c.set(disabled));
}
pub fn hoisting_disabled() -> bool {
FORCE_DISABLE.with(|c| c.get())
|| !crate::optimize::active_config().is_on(crate::optimization::Opt::HoistBorrows)
}
fn could_alias_seq(ty: Option<&String>) -> bool {
let t = match ty {
Some(t) => t,
None => return true,
};
let base = t.split("|__hl:").next().unwrap_or(t.as_str());
let definitely_not = matches!(
base,
"i64" | "f64" | "bool" | "char" | "u8" | "usize" | "i32" | "u64"
| "String" | "&str" | "()" | "__zero_based_i64" | "__single_char_u8"
) || base.starts_with("LogosMap")
|| base.starts_with("LogosI64Map")
|| base.starts_with("LogosI64Set")
|| base.starts_with("HashMap")
|| base.starts_with("FxHashMap")
|| base.starts_with("std::collections::HashMap")
|| base.starts_with("rustc_hash::FxHashMap");
!definitely_not
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum HoistKind {
Shared,
MutSlice,
MutVec,
}
pub(crate) struct HoistEntry {
pub sym: Symbol,
pub kind: HoistKind,
pub elem_ty: String,
pub old_type: String,
pub is_vec: bool,
}
fn is_pure_scalar_builtin(name: &str, argc: usize) -> bool {
matches!(
(name, argc),
("sqrt", 1) | ("abs", 1) | ("floor", 1) | ("ceil", 1) | ("round", 1)
| ("pow", 2) | ("min", 2) | ("max", 2)
)
}
struct AccessRoles<'i> {
read: HashSet<Symbol>,
written: HashSet<Symbol>,
resized: HashSet<Symbol>,
other: HashSet<Symbol>,
rebound: HashSet<Symbol>,
order: Vec<Symbol>,
bail: bool,
interner: &'i Interner,
}
impl<'i> AccessRoles<'i> {
fn new(interner: &'i Interner) -> Self {
AccessRoles {
read: HashSet::new(),
written: HashSet::new(),
resized: HashSet::new(),
other: HashSet::new(),
rebound: HashSet::new(),
order: Vec::new(),
bail: false,
interner,
}
}
fn note(&mut self, sym: Symbol) {
if !self.order.contains(&sym) {
self.order.push(sym);
}
}
fn read(&mut self, sym: Symbol) {
self.note(sym);
self.read.insert(sym);
}
fn written(&mut self, sym: Symbol) {
self.note(sym);
self.written.insert(sym);
}
fn resized(&mut self, sym: Symbol) {
self.note(sym);
self.resized.insert(sym);
}
fn other(&mut self, sym: Symbol) {
self.note(sym);
self.other.insert(sym);
}
}
fn scan_value_expr(e: &Expr, roles: &mut AccessRoles) {
match e {
Expr::Identifier(s) => roles.other(*s),
Expr::Literal(_) | Expr::OptionNone => {}
Expr::Index { collection, index } => {
scan_collection_pos(collection, roles);
scan_value_expr(index, roles);
}
Expr::Slice { collection, start, end } => {
scan_collection_pos(collection, roles);
scan_value_expr(start, roles);
scan_value_expr(end, roles);
}
Expr::Length { collection } => scan_collection_pos(collection, roles),
Expr::Contains { collection, value } => {
scan_collection_pos(collection, roles);
scan_value_expr(value, roles);
}
Expr::BinaryOp { left, right, .. } => {
scan_value_expr(left, roles);
scan_value_expr(right, roles);
}
Expr::Not { operand } => scan_value_expr(operand, roles),
Expr::Range { start, end } => {
scan_value_expr(start, roles);
scan_value_expr(end, roles);
}
Expr::List(items) | Expr::Tuple(items) => {
for it in items {
scan_value_expr(it, roles);
}
}
Expr::New { init_fields, .. } => {
for (_, v) in init_fields {
scan_value_expr(v, roles);
}
}
Expr::NewVariant { fields, .. } => {
for (_, v) in fields {
scan_value_expr(v, roles);
}
}
Expr::FieldAccess { object, .. } => scan_value_expr(object, roles),
Expr::Copy { expr } => scan_value_expr(expr, roles),
Expr::WithCapacity { value, capacity } => {
scan_value_expr(value, roles);
scan_value_expr(capacity, roles);
}
Expr::OptionSome { value } => scan_value_expr(value, roles),
Expr::Give { value } => scan_value_expr(value, roles),
Expr::InterpolatedString(parts) => {
for p in parts {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
scan_value_expr(value, roles);
}
}
}
Expr::Union { left, right } | Expr::Intersection { left, right } => {
scan_value_expr(left, roles);
scan_value_expr(right, roles);
}
Expr::Call { function, args } => {
if is_pure_scalar_builtin(roles.interner.resolve(*function), args.len()) {
for a in args {
scan_value_expr(a, roles);
}
} else {
roles.bail = true;
}
}
_ => roles.bail = true,
}
}
fn scan_collection_pos(e: &Expr, roles: &mut AccessRoles) {
if let Expr::Identifier(s) = e {
roles.read(*s);
} else {
scan_value_expr(e, roles);
}
}
fn scan_stmts(stmts: &[Stmt], roles: &mut AccessRoles) {
for stmt in stmts {
if roles.bail {
return;
}
match stmt {
Stmt::Let { var, value, .. } => {
roles.rebound.insert(*var);
scan_value_expr(value, roles);
}
Stmt::Set { target, value } => {
let _ = target;
scan_value_expr(value, roles);
}
Stmt::SetIndex { collection, index, value } => {
if let Expr::Identifier(s) = collection {
roles.written(*s);
} else {
scan_value_expr(collection, roles);
}
scan_value_expr(index, roles);
scan_value_expr(value, roles);
}
Stmt::Push { value, collection }
| Stmt::Add { value, collection }
| Stmt::Remove { value, collection } => {
scan_value_expr(value, roles);
if let Expr::Identifier(s) = collection {
roles.resized(*s);
} else {
scan_value_expr(collection, roles);
}
}
Stmt::Pop { collection, into } => {
if let Expr::Identifier(s) = collection {
roles.resized(*s);
} else {
scan_value_expr(collection, roles);
}
if let Some(v) = into {
roles.rebound.insert(*v);
}
}
Stmt::If { cond, then_block, else_block } => {
scan_value_expr(cond, roles);
scan_stmts(then_block, roles);
if let Some(eb) = else_block {
scan_stmts(eb, roles);
}
}
Stmt::While { cond, body, .. } => {
scan_value_expr(cond, roles);
scan_stmts(body, roles);
}
Stmt::Repeat { pattern, iterable, body } => {
if let Expr::Identifier(s) = iterable {
roles.other(*s);
} else {
scan_value_expr(iterable, roles);
}
if let crate::ast::stmt::Pattern::Identifier(s) = pattern {
roles.rebound.insert(*s);
}
scan_stmts(body, roles);
}
Stmt::Show { object, recipient } => {
scan_value_expr(object, roles);
scan_value_expr(recipient, roles);
}
Stmt::Return { value } => {
if let Some(v) = value {
scan_value_expr(v, roles);
}
}
Stmt::Break => {}
Stmt::SetField { object, value, .. } => {
scan_value_expr(object, roles);
scan_value_expr(value, roles);
}
Stmt::Inspect { target, arms, .. } => {
scan_value_expr(target, roles);
for arm in arms {
for (_, binding) in &arm.bindings {
roles.rebound.insert(*binding);
}
scan_stmts(arm.body, roles);
}
}
Stmt::RuntimeAssert { condition, .. } => scan_value_expr(condition, roles),
_ => roles.bail = true,
}
}
}
pub(crate) fn plan_borrow_hoist<'a>(
loop_stmt: &Stmt<'a>,
cond: Option<&Expr<'a>>,
body: &[Stmt<'a>],
ctx: &RefinementContext<'a>,
interner: &Interner,
) -> Vec<HoistEntry> {
let oracle = match ctx.oracle() {
Some(o) => o,
None => return Vec::new(),
};
let mut roles = AccessRoles::new(interner);
if let Some(c) = cond {
scan_value_expr(c, &mut roles);
}
scan_stmts(body, &mut roles);
if roles.bail {
return Vec::new();
}
let types = ctx.get_variable_types();
let mut hoisted: Vec<(Symbol, HoistKind, String, String)> = Vec::new();
let mut derc_hoisted: Vec<(Symbol, HoistKind, String, String)> = Vec::new();
for sym in &roles.order {
let sym = *sym;
if roles.other.contains(&sym) || roles.rebound.contains(&sym) {
continue;
}
let resized = roles.resized.contains(&sym);
let written = roles.written.contains(&sym);
let read = roles.read.contains(&sym);
if !resized && !written && !read {
continue;
}
let full_ty = match types.get(&sym) {
Some(t) => t.clone(),
None => continue,
};
if full_ty.contains("|__hl:") {
continue;
}
let base_ty = full_ty.split("|__hl:").next().unwrap_or(&full_ty);
let elem_ty = if let Some(e) = base_ty.strip_prefix("LogosSeq<").and_then(|s| s.strip_suffix('>')) {
e.to_string()
} else if let Some(e) = base_ty.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
e.to_string()
} else {
continue;
};
let is_vec = ctx.is_de_rc(sym) || base_ty.starts_with("Vec<");
if body_modifies_var(body, sym) {
continue;
}
let kind = if resized {
HoistKind::MutVec
} else if written {
HoistKind::MutSlice
} else {
HoistKind::Shared
};
if is_vec {
derc_hoisted.push((sym, kind, elem_ty, full_ty));
} else {
hoisted.push((sym, kind, elem_ty, full_ty));
}
}
let all_touched: Vec<Symbol> = roles
.order
.iter()
.copied()
.filter(|s| could_alias_seq(types.get(s)))
.collect();
loop {
let hoisted_syms: HashSet<Symbol> = hoisted.iter().map(|(s, _, _, _)| *s).collect();
let mut_hoisted: Vec<Symbol> = hoisted
.iter()
.filter(|(_, k, _, _)| *k != HoistKind::Shared)
.map(|(s, _, _, _)| *s)
.collect();
let unhoisted_mut: Vec<Symbol> = all_touched
.iter()
.filter(|s| {
!hoisted_syms.contains(s)
&& (roles.written.contains(s)
|| roles.resized.contains(s)
|| roles.other.contains(s))
})
.cloned()
.collect();
let keep: Vec<bool> = hoisted
.iter()
.map(|(sym, kind, _, _)| {
if *kind != HoistKind::Shared {
all_touched
.iter()
.filter(|t| **t != *sym)
.all(|t| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *t))
} else {
mut_hoisted
.iter()
.filter(|s| **s != *sym)
.all(|s| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *s))
&& unhoisted_mut
.iter()
.all(|t| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *t))
}
})
.collect();
if keep.iter().all(|k| *k) {
break;
}
let mut it = keep.into_iter();
hoisted.retain(|_| it.next().unwrap());
}
let entries: Vec<HoistEntry> = derc_hoisted
.into_iter()
.map(|(sym, kind, elem_ty, old_type)| HoistEntry { sym, kind, elem_ty, old_type, is_vec: true })
.chain(
hoisted
.into_iter()
.map(|(sym, kind, elem_ty, old_type)| HoistEntry { sym, kind, elem_ty, old_type, is_vec: false }),
)
.collect();
if !entries.is_empty() {
crate::optimize::mark_fired(crate::optimization::Opt::HoistBorrows);
}
entries
}
pub(crate) fn emit_hoist_open(
entries: &[HoistEntry],
interner: &Interner,
indent_str: &str,
ctx: &mut RefinementContext,
output: &mut String,
) {
if entries.is_empty() {
return;
}
let names = RustNames::new(interner);
writeln!(output, "{}{{", indent_str).unwrap();
for e in entries {
let n = names.ident(e.sym);
match (e.kind, e.is_vec) {
(HoistKind::Shared, false) => {
writeln!(output, "{} let __{}_g = {}.borrow();", indent_str, n, n).unwrap();
writeln!(output, "{} let {} = &__{}_g[..];", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("&[{}]", e.elem_ty));
}
(HoistKind::MutSlice, false) => {
writeln!(output, "{} let mut __{}_g = {}.borrow_mut();", indent_str, n, n).unwrap();
writeln!(output, "{} let {} = &mut __{}_g[..];", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("&mut [{}]", e.elem_ty));
}
(HoistKind::MutVec, false) => {
writeln!(output, "{} let mut __{}_g = {}.borrow_mut();", indent_str, n, n).unwrap();
writeln!(output, "{} let {} = &mut *__{}_g;", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("Vec<{}>", e.elem_ty));
}
(HoistKind::Shared, true) => {
writeln!(output, "{} let {} = &{}[..];", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("&[{}]", e.elem_ty));
}
(HoistKind::MutSlice, true) => {
writeln!(output, "{} let {} = &mut {}[..];", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("&mut [{}]", e.elem_ty));
}
(HoistKind::MutVec, true) => {
writeln!(output, "{} let mut {} = &mut {};", indent_str, n, n).unwrap();
ctx.register_variable_type(e.sym, format!("Vec<{}>", e.elem_ty));
}
}
}
}
pub(crate) fn emit_hoist_close(
entries: &[HoistEntry],
indent_str: &str,
ctx: &mut RefinementContext,
output: &mut String,
) {
if entries.is_empty() {
return;
}
for e in entries {
ctx.register_variable_type(e.sym, e.old_type.clone());
}
writeln!(output, "{}}}", indent_str).unwrap();
}