1use std::collections::{HashMap, HashSet};
44use std::fmt::Write;
45
46use crate::analysis::types::RustNames;
47use crate::ast::stmt::{Expr, Stmt};
48use crate::intern::{Interner, Symbol};
49
50use super::context::RefinementContext;
51use super::peephole::body_modifies_var;
52
53thread_local! {
54 static FORCE_DISABLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
58}
59
60pub fn force_disable_for_test(disabled: bool) {
63 FORCE_DISABLE.with(|c| c.set(disabled));
64}
65
66pub fn hoisting_disabled() -> bool {
69 FORCE_DISABLE.with(|c| c.get())
70 || !crate::optimize::active_config().is_on(crate::optimization::Opt::HoistBorrows)
71}
72
73fn could_alias_seq(ty: Option<&String>) -> bool {
78 let t = match ty {
79 Some(t) => t,
80 None => return true,
81 };
82 let base = t.split("|__hl:").next().unwrap_or(t.as_str());
83 let definitely_not = matches!(
84 base,
85 "i64" | "f64" | "bool" | "char" | "u8" | "usize" | "i32" | "u64"
86 | "String" | "&str" | "()" | "__zero_based_i64" | "__single_char_u8"
87 ) || base.starts_with("LogosMap")
88 || base.starts_with("LogosI64Map")
89 || base.starts_with("LogosI64Set")
90 || base.starts_with("HashMap")
91 || base.starts_with("FxHashMap")
92 || base.starts_with("std::collections::HashMap")
93 || base.starts_with("rustc_hash::FxHashMap");
94 !definitely_not
95}
96
97#[derive(Clone, Copy, PartialEq)]
99pub(crate) enum HoistKind {
100 Shared,
102 MutSlice,
104 MutVec,
107}
108
109pub(crate) struct HoistEntry {
111 pub sym: Symbol,
112 pub kind: HoistKind,
113 pub elem_ty: String,
114 pub old_type: String,
115 pub is_vec: bool,
120}
121
122fn is_pure_scalar_builtin(name: &str, argc: usize) -> bool {
127 matches!(
128 (name, argc),
129 ("sqrt", 1) | ("abs", 1) | ("floor", 1) | ("ceil", 1) | ("round", 1)
130 | ("pow", 2) | ("min", 2) | ("max", 2)
131 )
132}
133
134struct AccessRoles<'i> {
135 read: HashSet<Symbol>,
137 written: HashSet<Symbol>,
139 resized: HashSet<Symbol>,
143 other: HashSet<Symbol>,
147 rebound: HashSet<Symbol>,
151 order: Vec<Symbol>,
153 bail: bool,
155 interner: &'i Interner,
157}
158
159impl<'i> AccessRoles<'i> {
160 fn new(interner: &'i Interner) -> Self {
161 AccessRoles {
162 read: HashSet::new(),
163 written: HashSet::new(),
164 resized: HashSet::new(),
165 other: HashSet::new(),
166 rebound: HashSet::new(),
167 order: Vec::new(),
168 bail: false,
169 interner,
170 }
171 }
172
173 fn note(&mut self, sym: Symbol) {
174 if !self.order.contains(&sym) {
175 self.order.push(sym);
176 }
177 }
178 fn read(&mut self, sym: Symbol) {
179 self.note(sym);
180 self.read.insert(sym);
181 }
182 fn written(&mut self, sym: Symbol) {
183 self.note(sym);
184 self.written.insert(sym);
185 }
186 fn resized(&mut self, sym: Symbol) {
187 self.note(sym);
188 self.resized.insert(sym);
189 }
190 fn other(&mut self, sym: Symbol) {
191 self.note(sym);
192 self.other.insert(sym);
193 }
194}
195
196fn scan_value_expr(e: &Expr, roles: &mut AccessRoles) {
200 match e {
201 Expr::Identifier(s) => roles.other(*s),
202 Expr::Literal(_) | Expr::OptionNone => {}
203 Expr::Index { collection, index } => {
204 scan_collection_pos(collection, roles);
205 scan_value_expr(index, roles);
206 }
207 Expr::Slice { collection, start, end } => {
208 scan_collection_pos(collection, roles);
209 scan_value_expr(start, roles);
210 scan_value_expr(end, roles);
211 }
212 Expr::Length { collection } => scan_collection_pos(collection, roles),
213 Expr::Contains { collection, value } => {
214 scan_collection_pos(collection, roles);
215 scan_value_expr(value, roles);
216 }
217 Expr::BinaryOp { left, right, .. } => {
218 scan_value_expr(left, roles);
219 scan_value_expr(right, roles);
220 }
221 Expr::Not { operand } => scan_value_expr(operand, roles),
222 Expr::Range { start, end } => {
223 scan_value_expr(start, roles);
224 scan_value_expr(end, roles);
225 }
226 Expr::List(items) | Expr::Tuple(items) => {
227 for it in items {
228 scan_value_expr(it, roles);
229 }
230 }
231 Expr::New { init_fields, .. } => {
232 for (_, v) in init_fields {
233 scan_value_expr(v, roles);
234 }
235 }
236 Expr::NewVariant { fields, .. } => {
237 for (_, v) in fields {
238 scan_value_expr(v, roles);
239 }
240 }
241 Expr::FieldAccess { object, .. } => scan_value_expr(object, roles),
242 Expr::Copy { expr } => scan_value_expr(expr, roles),
243 Expr::WithCapacity { value, capacity } => {
244 scan_value_expr(value, roles);
245 scan_value_expr(capacity, roles);
246 }
247 Expr::OptionSome { value } => scan_value_expr(value, roles),
248 Expr::Give { value } => scan_value_expr(value, roles),
249 Expr::InterpolatedString(parts) => {
250 for p in parts {
251 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
252 scan_value_expr(value, roles);
253 }
254 }
255 }
256 Expr::Union { left, right } | Expr::Intersection { left, right } => {
257 scan_value_expr(left, roles);
258 scan_value_expr(right, roles);
259 }
260 Expr::Call { function, args } => {
261 if is_pure_scalar_builtin(roles.interner.resolve(*function), args.len()) {
265 for a in args {
266 scan_value_expr(a, roles);
267 }
268 } else {
269 roles.bail = true;
270 }
271 }
272 _ => roles.bail = true,
274 }
275}
276
277fn scan_collection_pos(e: &Expr, roles: &mut AccessRoles) {
280 if let Expr::Identifier(s) = e {
281 roles.read(*s);
282 } else {
283 scan_value_expr(e, roles);
284 }
285}
286
287fn scan_stmts(stmts: &[Stmt], roles: &mut AccessRoles) {
288 for stmt in stmts {
289 if roles.bail {
290 return;
291 }
292 match stmt {
293 Stmt::Let { var, value, .. } => {
294 roles.rebound.insert(*var);
295 scan_value_expr(value, roles);
296 }
297 Stmt::Set { target, value } => {
298 let _ = target;
301 scan_value_expr(value, roles);
302 }
303 Stmt::SetIndex { collection, index, value } => {
304 if let Expr::Identifier(s) = collection {
305 roles.written(*s);
306 } else {
307 scan_value_expr(collection, roles);
308 }
309 scan_value_expr(index, roles);
310 scan_value_expr(value, roles);
311 }
312 Stmt::Push { value, collection }
313 | Stmt::Add { value, collection }
314 | Stmt::Remove { value, collection } => {
315 scan_value_expr(value, roles);
318 if let Expr::Identifier(s) = collection {
319 roles.resized(*s);
320 } else {
321 scan_value_expr(collection, roles);
322 }
323 }
324 Stmt::Pop { collection, into } => {
325 if let Expr::Identifier(s) = collection {
326 roles.resized(*s);
327 } else {
328 scan_value_expr(collection, roles);
329 }
330 if let Some(v) = into {
331 roles.rebound.insert(*v);
332 }
333 }
334 Stmt::If { cond, then_block, else_block } => {
335 scan_value_expr(cond, roles);
336 scan_stmts(then_block, roles);
337 if let Some(eb) = else_block {
338 scan_stmts(eb, roles);
339 }
340 }
341 Stmt::While { cond, body, .. } => {
342 scan_value_expr(cond, roles);
343 scan_stmts(body, roles);
344 }
345 Stmt::Repeat { pattern, iterable, body } => {
346 if let Expr::Identifier(s) = iterable {
347 roles.other(*s);
348 } else {
349 scan_value_expr(iterable, roles);
350 }
351 if let crate::ast::stmt::Pattern::Identifier(s) = pattern {
352 roles.rebound.insert(*s);
353 }
354 scan_stmts(body, roles);
355 }
356 Stmt::Show { object, recipient } => {
357 scan_value_expr(object, roles);
360 scan_value_expr(recipient, roles);
361 }
362 Stmt::Return { value } => {
363 if let Some(v) = value {
364 scan_value_expr(v, roles);
365 }
366 }
367 Stmt::Break => {}
368 Stmt::SetField { object, value, .. } => {
369 scan_value_expr(object, roles);
370 scan_value_expr(value, roles);
371 }
372 Stmt::Inspect { target, arms, .. } => {
373 scan_value_expr(target, roles);
374 for arm in arms {
375 for (_, binding) in &arm.bindings {
376 roles.rebound.insert(*binding);
377 }
378 scan_stmts(arm.body, roles);
379 }
380 }
381 Stmt::RuntimeAssert { condition, .. } => scan_value_expr(condition, roles),
382 _ => roles.bail = true,
384 }
385 }
386}
387
388pub(crate) fn plan_borrow_hoist<'a>(
391 loop_stmt: &Stmt<'a>,
392 cond: Option<&Expr<'a>>,
393 body: &[Stmt<'a>],
394 ctx: &RefinementContext<'a>,
395 interner: &Interner,
396) -> Vec<HoistEntry> {
397 let oracle = match ctx.oracle() {
400 Some(o) => o,
401 None => return Vec::new(),
402 };
403
404 let mut roles = AccessRoles::new(interner);
405 if let Some(c) = cond {
406 scan_value_expr(c, &mut roles);
407 }
408 scan_stmts(body, &mut roles);
409 if roles.bail {
410 return Vec::new();
411 }
412
413 let types = ctx.get_variable_types();
418 let mut hoisted: Vec<(Symbol, HoistKind, String, String)> = Vec::new();
419 let mut derc_hoisted: Vec<(Symbol, HoistKind, String, String)> = Vec::new();
423 for sym in &roles.order {
424 let sym = *sym;
425 if roles.other.contains(&sym) || roles.rebound.contains(&sym) {
426 continue;
427 }
428 let resized = roles.resized.contains(&sym);
429 let written = roles.written.contains(&sym);
430 let read = roles.read.contains(&sym);
431 if !resized && !written && !read {
432 continue;
433 }
434 let full_ty = match types.get(&sym) {
435 Some(t) => t.clone(),
436 None => continue,
437 };
438 if full_ty.contains("|__hl:") {
442 continue;
443 }
444 let base_ty = full_ty.split("|__hl:").next().unwrap_or(&full_ty);
445 let elem_ty = if let Some(e) = base_ty.strip_prefix("LogosSeq<").and_then(|s| s.strip_suffix('>')) {
450 e.to_string()
451 } else if let Some(e) = base_ty.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
452 e.to_string()
453 } else {
454 continue;
455 };
456 let is_vec = ctx.is_de_rc(sym) || base_ty.starts_with("Vec<");
457 if body_modifies_var(body, sym) {
460 continue;
461 }
462 let kind = if resized {
463 HoistKind::MutVec
464 } else if written {
465 HoistKind::MutSlice
466 } else {
467 HoistKind::Shared
468 };
469 if is_vec {
470 derc_hoisted.push((sym, kind, elem_ty, full_ty));
471 } else {
472 hoisted.push((sym, kind, elem_ty, full_ty));
473 }
474 }
475
476 let all_touched: Vec<Symbol> = roles
486 .order
487 .iter()
488 .copied()
489 .filter(|s| could_alias_seq(types.get(s)))
490 .collect();
491 loop {
492 let hoisted_syms: HashSet<Symbol> = hoisted.iter().map(|(s, _, _, _)| *s).collect();
493 let mut_hoisted: Vec<Symbol> = hoisted
494 .iter()
495 .filter(|(_, k, _, _)| *k != HoistKind::Shared)
496 .map(|(s, _, _, _)| *s)
497 .collect();
498 let unhoisted_mut: Vec<Symbol> = all_touched
499 .iter()
500 .filter(|s| {
501 !hoisted_syms.contains(s)
502 && (roles.written.contains(s)
503 || roles.resized.contains(s)
504 || roles.other.contains(s))
505 })
506 .cloned()
507 .collect();
508 let keep: Vec<bool> = hoisted
509 .iter()
510 .map(|(sym, kind, _, _)| {
511 if *kind != HoistKind::Shared {
512 all_touched
515 .iter()
516 .filter(|t| **t != *sym)
517 .all(|t| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *t))
518 } else {
519 mut_hoisted
522 .iter()
523 .filter(|s| **s != *sym)
524 .all(|s| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *s))
525 && unhoisted_mut
526 .iter()
527 .all(|t| oracle.loop_handles_definitely_distinct(loop_stmt, *sym, *t))
528 }
529 })
530 .collect();
531 if keep.iter().all(|k| *k) {
532 break;
533 }
534 let mut it = keep.into_iter();
535 hoisted.retain(|_| it.next().unwrap());
536 }
537
538 let entries: Vec<HoistEntry> = derc_hoisted
539 .into_iter()
540 .map(|(sym, kind, elem_ty, old_type)| HoistEntry { sym, kind, elem_ty, old_type, is_vec: true })
541 .chain(
542 hoisted
543 .into_iter()
544 .map(|(sym, kind, elem_ty, old_type)| HoistEntry { sym, kind, elem_ty, old_type, is_vec: false }),
545 )
546 .collect();
547 if !entries.is_empty() {
550 crate::optimize::mark_fired(crate::optimization::Opt::HoistBorrows);
551 }
552 entries
553}
554
555pub(crate) fn emit_hoist_open(
559 entries: &[HoistEntry],
560 interner: &Interner,
561 indent_str: &str,
562 ctx: &mut RefinementContext,
563 output: &mut String,
564) {
565 if entries.is_empty() {
566 return;
567 }
568 let names = RustNames::new(interner);
569 writeln!(output, "{}{{", indent_str).unwrap();
570 for e in entries {
571 let n = names.ident(e.sym);
572 match (e.kind, e.is_vec) {
573 (HoistKind::Shared, false) => {
575 writeln!(output, "{} let __{}_g = {}.borrow();", indent_str, n, n).unwrap();
576 writeln!(output, "{} let {} = &__{}_g[..];", indent_str, n, n).unwrap();
577 ctx.register_variable_type(e.sym, format!("&[{}]", e.elem_ty));
578 }
579 (HoistKind::MutSlice, false) => {
580 writeln!(output, "{} let mut __{}_g = {}.borrow_mut();", indent_str, n, n).unwrap();
581 writeln!(output, "{} let {} = &mut __{}_g[..];", indent_str, n, n).unwrap();
582 ctx.register_variable_type(e.sym, format!("&mut [{}]", e.elem_ty));
583 }
584 (HoistKind::MutVec, false) => {
585 writeln!(output, "{} let mut __{}_g = {}.borrow_mut();", indent_str, n, n).unwrap();
590 writeln!(output, "{} let {} = &mut *__{}_g;", indent_str, n, n).unwrap();
591 ctx.register_variable_type(e.sym, format!("Vec<{}>", e.elem_ty));
592 }
593 (HoistKind::Shared, true) => {
595 writeln!(output, "{} let {} = &{}[..];", indent_str, n, n).unwrap();
596 ctx.register_variable_type(e.sym, format!("&[{}]", e.elem_ty));
597 }
598 (HoistKind::MutSlice, true) => {
599 writeln!(output, "{} let {} = &mut {}[..];", indent_str, n, n).unwrap();
600 ctx.register_variable_type(e.sym, format!("&mut [{}]", e.elem_ty));
601 }
602 (HoistKind::MutVec, true) => {
603 writeln!(output, "{} let mut {} = &mut {};", indent_str, n, n).unwrap();
608 ctx.register_variable_type(e.sym, format!("Vec<{}>", e.elem_ty));
609 }
610 }
611 }
612}
613
614pub(crate) fn emit_hoist_close(
616 entries: &[HoistEntry],
617 indent_str: &str,
618 ctx: &mut RefinementContext,
619 output: &mut String,
620) {
621 if entries.is_empty() {
622 return;
623 }
624 for e in entries {
625 ctx.register_variable_type(e.sym, e.old_type.clone());
626 }
627 writeln!(output, "{}}}", indent_str).unwrap();
628}