Skip to main content

logicaffeine_compile/codegen/
context.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt::Write;
3
4use crate::ast::logic::LogicExpr;
5use crate::ast::stmt::Stmt;
6use crate::intern::{Interner, Symbol};
7
8use super::{codegen_assertion, codegen_expr};
9
10// =============================================================================
11// Refinement Type Enforcement
12// =============================================================================
13
14/// Tracks refinement type constraints across scopes for mutation enforcement.
15///
16/// When a variable with a refinement type is defined, its constraint is registered
17/// in the current scope. When that variable is mutated via `Set`, the assertion is
18/// re-emitted to ensure the invariant is preserved.
19///
20/// # Scope Management
21///
22/// The context maintains a stack of scopes to handle nested blocks:
23///
24/// ```text
25/// ┌─────────────────────────────┐
26/// │ Global Scope               │ ← x: { it > 0 }
27/// │  ┌──────────────────────┐  │
28/// │  │ Zone Scope           │  │ ← y: { it < 100 }
29/// │  │  ┌────────────────┐  │  │
30/// │  │  │ If Block Scope │  │  │ ← z: { it != 0 }
31/// │  │  └────────────────┘  │  │
32/// │  └──────────────────────┘  │
33/// └─────────────────────────────┘
34/// ```
35///
36/// # Variable Type Tracking
37///
38/// The context also tracks variable types for capability resolution. This allows
39/// statements like `Check that user can publish the document` to resolve "the document"
40/// to a variable named `doc` of type `Document`.
41pub struct RefinementContext<'a> {
42    /// Stack of scopes. Each scope maps variable Symbol to (bound_var, predicate).
43    scopes: Vec<HashMap<Symbol, (Symbol, &'a LogicExpr<'a>)>>,
44
45    /// Maps variable name Symbol to Rust type name (for capability resolution and optimization).
46    variable_types: HashMap<Symbol, String>,
47
48    /// Stack of scopes tracking which bindings came from boxed enum fields.
49    /// When these are used in expressions, they need to be dereferenced with `*`.
50    boxed_binding_scopes: Vec<HashSet<Symbol>>,
51
52    /// Tracks variables that are known to be String type.
53    /// Used for proper string concatenation codegen (format! vs +).
54    string_vars: HashSet<Symbol>,
55
56    /// Maps function Symbol to its return type string.
57    /// Used to infer variable types from function call results.
58    fn_returns: HashMap<Symbol, String>,
59
60    /// Variables live immediately after the current top-level statement.
61    ///
62    /// `None` = no liveness information available → OPT-1C must conservatively clone.
63    /// `Some(set)` = liveness computed; variables NOT in `set` are dead after this statement.
64    ///
65    /// Set by the caller of `codegen_stmt` before each top-level statement in a function body.
66    /// Consumed (cleared to `None`) at the start of `codegen_stmt` so that recursive calls for
67    /// nested blocks conservatively clone.
68    live_vars_after: Option<HashSet<Symbol>>,
69
70    /// Collection variables that escape the function (passed to calls, returned).
71    /// Variables NOT in this set can use Vec<T> instead of LogosSeq<T> for zero-overhead indexing.
72    escaping_vars: HashSet<Symbol>,
73
74    /// Oracle facts computed on the SAME statement slice codegen walks, so
75    /// the pointer-keyed loop alias snapshots match. Borrow hoisting reads
76    /// the distinctness queries from here; `None` (tests, unset) hoists
77    /// nothing.
78    oracle: Option<std::rc::Rc<crate::optimize::OracleFacts>>,
79
80    /// O3 scalarization: next fill index for each `[T; N]`-scalarized Seq.
81    /// Incremented as the init pushes are emitted (`x[0] = …`, `x[1] = …`).
82    array_fill_pos: HashMap<Symbol, usize>,
83
84
85    /// O2 de-Rc: Seq variables proven to never need reference semantics, so
86    /// they are emitted as a plain `Vec<T>` (no Rc/RefCell). The Let arm flips
87    /// the declaration; access sites then dispatch on the `Vec<T>` type.
88    de_rc_vars: HashSet<Symbol>,
89    /// Phase 4: the function being codegen'd returns an owned `Vec<T>` (not
90    /// `LogosSeq<T>`), so a `Return` of a borrowed-slice param emits `.to_vec()`
91    /// without the `LogosSeq::from_vec(...)` wrapper.
92    returns_vec: bool,
93    /// The current function is typed `-> LogosInt` (its `Int` return can exceed i64).
94    returns_bigint: bool,
95
96    /// Wave-2 A1 buffer-fill conversion: reused buffers (the inner partner of a
97    /// `Set outer to inner` ping-pong swap) that are refilled by a COUNTED push
98    /// loop. They are emitted as a SIZED `Vec` (`.resize` per round, not
99    /// `Vec::new()`+`.clear()`), and the loop's `Push v to buf` becomes an
100    /// INDEXED WRITE `buf[counter] = v` — removing the per-iteration `len`
101    /// mutation that blocks vectorization of the DP scan.
102    buffer_reuse_fill: HashSet<Symbol>,
103    /// Loop-local scratch buffers whose per-iteration allocation has been
104    /// hoisted out of the enclosing `while`: the buffer is declared once
105    /// (`Vec::new()`) before the loop, and the per-iteration full-copy fill
106    /// (`buf = src[..].to_vec()`) is lowered to `buf.clear(); buf.extend_from_slice(&src[..])`
107    /// — reusing the allocation instead of mallocing/freeing each iteration.
108    /// Populated by the `Stmt::While` handler after `detect_scratch_hoist_in_body`
109    /// proves the buffer is de-Rc'd (uniquely owned), declared in the body, and
110    /// non-escaping; cleared when the loop closes.
111    scratch_hoist: HashSet<Symbol>,
112    /// Active fill loop: `(buffer, counter_name)` while emitting the body of a
113    /// counted loop that refills a `buffer_reuse_fill` buffer. The `Push`
114    /// codegen reads this to emit `buffer[counter] = v` instead of `.push(v)`.
115    fill_loop: Option<(Symbol, String)>,
116    /// Strings built by cursor-lockstep appends in a counted loop (string_search's
117    /// `text`): declared as a pre-zeroed byte buffer and written at the cursor
118    /// (`*text.as_mut_vec().get_unchecked_mut(cursor) = b`) instead of `push`,
119    /// then truncated to the cursor after the loop. Keyed by the string symbol →
120    /// the cursor variable name. See `codegen/peephole.rs::try_emit_indexed_string_build`.
121    indexed_string_builds: HashMap<Symbol, String>,
122    /// Append-only worklists (BFS/DFS frontiers) proven bounded by a monotone
123    /// visited-set guard: emitted as a pre-sized buffer + a register tail
124    /// (`q[tail]=x; tail+=1`) instead of `Vec::push` — C's exact frontier code.
125    /// Keyed by the worklist symbol; see `codegen/worklist.rs`.
126    worklists: HashMap<Symbol, super::worklist::WorklistInfo>,
127    /// Affine read-only arrays (CSR offset arrays): a Seq built `push f(i) to A`
128    /// with affine `f`, IV from 0 step 1, never mutated after. Deleted entirely;
129    /// every `item k of A` becomes `coeff*(k-1)+offset` and `length of A` the
130    /// trip count. Keyed by the array symbol; see `codegen/affine_array.rs`.
131    affine_arrays: HashMap<Symbol, super::affine_array::AffineArrayInfo>,
132    /// Constant-table locals kept but emitted as stack arrays `[T; N]` (zero heap,
133    /// direct index) instead of a per-call `Vec`. See `codegen/affine_array.rs`.
134    const_tables: HashMap<Symbol, super::affine_array::ConstTableInfo>,
135    /// Fixed-size, non-escaping scratch buffers built by a constant-trip fill loop,
136    /// emitted in place as `[T; N]` via `from_fn` instead of a per-entry `Vec`. See
137    /// `codegen/affine_array.rs::detect_scratch_buffers`.
138    scratch_buffers: HashMap<Symbol, super::affine_array::ScratchInfo>,
139    /// Zero-init fixed-size buffers mutated by indexed writes (`Set item i of buf`),
140    /// emitted as a `[T; N]` stack array (`[0; N]` + indexed stores). See
141    /// `codegen/affine_array.rs::detect_indexed_buffers`.
142    indexed_buffers: HashMap<Symbol, super::affine_array::IndexedBufInfo>,
143    /// LOOP-built `[T; N]` return buffers (the digest): symbol → RUNTIME fill-cursor variable name. The
144    /// `Let` declares `[0; N]` + the cursor; each `Push` becomes `out[cursor]=v; cursor+=1`.
145    loop_fill_arrays: HashMap<Symbol, String>,
146    /// `Seq of Int` sequences proven to hold only `i32`-range values → stored as
147    /// `Vec<i32>` (half the footprint). Keyed by the sequence symbol; carries any
148    /// runtime guard (`% m` divisor bound). See `codegen/narrow.rs`. Gated by
149    /// `LOGOS_NARROW`, so empty unless that flag is set.
150    narrowed: HashMap<Symbol, super::narrow::NarrowInfo>,
151
152    /// Non-aliased local `Map of Int to Int` variables proven safe to lower to
153    /// the specialized open-addressing `LogosI64Map` (no `Rc<RefCell>`, no
154    /// clone) instead of `LogosMap<i64, i64>`; see `codegen/i64_map.rs`.
155    i64_maps: HashSet<Symbol>,
156    /// The subset of `i64_maps` whose value is never read (`contains` + `insert`
157    /// only) — lowered to the keys-only `LogosI64Set` instead of `LogosI64Map`.
158    i64_sets: HashSet<Symbol>,
159    /// The subset of `i64_maps` whose key domain is PROVEN bounded within the
160    /// map's `with capacity` hint → lowered to a direct-addressed flat array
161    /// (`LogosDenseI64Map`/`…NoPresence`/`LogosDenseI64Set`). Carries the proven
162    /// window offset `lo` and which representation to emit; see the dense gate in
163    /// `codegen/i64_map.rs`. Empty unless that gate fires (and it can be forced off
164    /// with `LOGOS_DENSE_MAP=0`).
165    dense_i64: HashMap<Symbol, super::i64_map::DenseMapInfo>,
166    /// Non-dense `Map of Int to Int` locals whose keys+values provably fit i32 →
167    /// lowered to the half-width `LogosI32Map` / quarter-width `LogosI32Set`. A
168    /// memory-traffic fallback for hash maps the dense gate cannot capture; empty
169    /// unless the narrowing gate fires (forced off with `LOGOS_NARROW_MAP=0`).
170    i32_maps: HashSet<Symbol>,
171    i32_sets: HashSet<Symbol>,
172    /// Push-built de-Rc Vecs that should be declared `Vec::with_capacity(cap)`
173    /// rather than `Vec::new()` — keyed `sym -> capacity expr` — because a later
174    /// counted loop index-reads them up to a proven bound (so growth reallocs
175    /// are pure overhead C avoids by sizing the buffer exactly).
176    vec_presize: HashMap<Symbol, String>,
177    /// Loop-invariant positive divisors lowered to a precomputed `LogosDivU64`
178    /// magic multiply — keyed `divisor sym -> helper variable name`. The `% n` /
179    /// `/ n` sites read this to emit `helper.rem(..)` / `helper.div(..)` instead
180    /// of a hardware division (O9 libdivide).
181    fast_div: HashMap<Symbol, String>,
182    /// `mutable` collection parameters (value semantics): passed by shared
183    /// `&LogosSeq`/`&LogosMap` so an in-place mutation reaches the caller's
184    /// allocation. These are the ONE case where a `LogosSeq`/`LogosMap` mutation
185    /// must NOT copy-on-write — the by-reference propagation is the whole point —
186    /// so the `cow()` emitter skips them (and a `&LogosSeq` cannot call the
187    /// `&mut self` `cow()` anyway).
188    mutable_collection_params: HashSet<Symbol>,
189    /// Integer variables whose assignments can overflow `i64` (a bignum constant,
190    /// a promotable-derived value, or a multiplicative/doubling accumulator) — the
191    /// `Let`/`Set` emitter stores these as the overflow-promoting `LogosInt` rather
192    /// than a bare `i64`. Computed once per body by `bigint_promote`.
193    promotable_int_candidates: HashSet<Symbol>,
194}
195
196impl<'a> RefinementContext<'a> {
197    pub fn new() -> Self {
198        Self {
199            scopes: vec![HashMap::new()],
200            variable_types: HashMap::new(),
201            boxed_binding_scopes: vec![HashSet::new()],
202            string_vars: HashSet::new(),
203            live_vars_after: None,
204            escaping_vars: HashSet::new(),
205            fn_returns: HashMap::new(),
206            oracle: None,
207            array_fill_pos: HashMap::new(),
208            de_rc_vars: HashSet::new(),
209            returns_vec: false,
210            returns_bigint: false,
211            buffer_reuse_fill: HashSet::new(),
212            scratch_hoist: HashSet::new(),
213            fill_loop: None,
214            indexed_string_builds: HashMap::new(),
215            worklists: HashMap::new(),
216            affine_arrays: HashMap::new(),
217            const_tables: HashMap::new(),
218            scratch_buffers: HashMap::new(),
219            indexed_buffers: HashMap::new(),
220            loop_fill_arrays: HashMap::new(),
221            narrowed: HashMap::new(),
222            i64_maps: HashSet::new(),
223            i64_sets: HashSet::new(),
224            dense_i64: HashMap::new(),
225            i32_maps: HashSet::new(),
226            i32_sets: HashSet::new(),
227            vec_presize: HashMap::new(),
228            fast_div: HashMap::new(),
229            mutable_collection_params: HashSet::new(),
230            promotable_int_candidates: HashSet::new(),
231        }
232    }
233
234    /// Create a RefinementContext seeded from a TypeEnv.
235    pub fn from_type_env(type_env: &crate::analysis::types::TypeEnv) -> Self {
236        Self {
237            scopes: vec![HashMap::new()],
238            variable_types: type_env.to_legacy_variable_types(),
239            boxed_binding_scopes: vec![HashSet::new()],
240            string_vars: type_env.to_legacy_string_vars(),
241            live_vars_after: None,
242            escaping_vars: HashSet::new(),
243            fn_returns: HashMap::new(),
244            oracle: None,
245            array_fill_pos: HashMap::new(),
246            de_rc_vars: HashSet::new(),
247            returns_vec: false,
248            returns_bigint: false,
249            buffer_reuse_fill: HashSet::new(),
250            scratch_hoist: HashSet::new(),
251            fill_loop: None,
252            indexed_string_builds: HashMap::new(),
253            worklists: HashMap::new(),
254            affine_arrays: HashMap::new(),
255            const_tables: HashMap::new(),
256            scratch_buffers: HashMap::new(),
257            indexed_buffers: HashMap::new(),
258            loop_fill_arrays: HashMap::new(),
259            narrowed: HashMap::new(),
260            i64_maps: HashSet::new(),
261            i64_sets: HashSet::new(),
262            dense_i64: HashMap::new(),
263            i32_maps: HashSet::new(),
264            i32_sets: HashSet::new(),
265            vec_presize: HashMap::new(),
266            fast_div: HashMap::new(),
267            mutable_collection_params: HashSet::new(),
268            promotable_int_candidates: HashSet::new(),
269        }
270    }
271
272    /// Attach the oracle fact table for borrow-hoist distinctness queries.
273    pub fn set_oracle(&mut self, oracle: std::rc::Rc<crate::optimize::OracleFacts>) {
274        self.oracle = Some(oracle);
275    }
276
277    /// The attached oracle facts, if any.
278    pub fn oracle(&self) -> Option<&crate::optimize::OracleFacts> {
279        self.oracle.as_deref()
280    }
281
282    /// Begin tracking a scalarized `[T; N]` array's fill position at 0.
283    pub(super) fn init_array_fill(&mut self, sym: Symbol) {
284        self.array_fill_pos.insert(sym, 0);
285    }
286
287    /// Return the next fill index for a scalarized array and advance it.
288    pub(super) fn next_array_fill(&mut self, sym: Symbol) -> usize {
289        let slot = self.array_fill_pos.entry(sym).or_insert(0);
290        let k = *slot;
291        *slot += 1;
292        k
293    }
294
295    /// Is this symbol a scalarized `[T; N]` array (registered with a `[` type)?
296    pub(super) fn is_scalarized_array(&self, sym: Symbol) -> bool {
297        self.variable_types
298            .get(&sym)
299            .map_or(false, |t| t.starts_with('['))
300    }
301
302    /// A1: mark a reused buffer to be filled by indexed write (not push).
303    pub(super) fn register_buffer_reuse_fill(&mut self, sym: Symbol) {
304        self.buffer_reuse_fill.insert(sym);
305    }
306
307    /// A1: is this buffer one whose counted push-refill becomes indexed writes?
308    pub(super) fn is_buffer_reuse_fill(&self, sym: Symbol) -> bool {
309        self.buffer_reuse_fill.contains(&sym)
310    }
311
312    /// Scratch-hoist: mark a loop-local buffer whose allocation was hoisted
313    /// before the enclosing `while`. Its per-iteration full-copy fill is then
314    /// lowered to `clear()` + `extend_from_slice` (reuse, not realloc).
315    pub(super) fn register_scratch_hoist(&mut self, sym: Symbol) {
316        self.scratch_hoist.insert(sym);
317    }
318
319    /// Scratch-hoist: is this buffer's allocation hoisted (refill via reuse)?
320    pub(super) fn is_scratch_hoist(&self, sym: Symbol) -> bool {
321        self.scratch_hoist.contains(&sym)
322    }
323
324    /// Scratch-hoist: stop treating `sym` as hoisted once its loop closes, so a
325    /// same-named buffer in a sibling loop is not mis-rewritten.
326    pub(super) fn clear_scratch_hoist(&mut self, sym: Symbol) {
327        self.scratch_hoist.remove(&sym);
328    }
329
330    /// A1: enter a fill loop — `Push v to buffer` inside it becomes
331    /// `buffer[counter] = v`. `counter` is the (0-based) loop counter name.
332    pub(super) fn set_fill_loop(&mut self, buffer: Symbol, counter: String) {
333        self.fill_loop = Some((buffer, counter));
334    }
335
336    /// A1: the active fill loop `(buffer, counter_name)`, if any.
337    pub(super) fn fill_loop(&self) -> Option<(Symbol, &str)> {
338        self.fill_loop.as_ref().map(|(b, c)| (*b, c.as_str()))
339    }
340
341    /// A1: leave the current fill loop.
342    pub(super) fn clear_fill_loop(&mut self) {
343        self.fill_loop = None;
344    }
345
346    /// Mark `text` as a cursor-indexed string build with the given cursor var name:
347    /// its `Set text to text + X` appends become `text[cursor] = …` writes.
348    pub(super) fn register_indexed_string_build(&mut self, text: Symbol, cursor: String) {
349        self.indexed_string_builds.insert(text, cursor);
350    }
351
352    /// The cursor variable name for a cursor-indexed string build, if `text` is one.
353    pub(super) fn indexed_string_build(&self, text: Symbol) -> Option<&str> {
354        self.indexed_string_builds.get(&text).map(String::as_str)
355    }
356
357    /// Record the recognized append-only worklists for this function body.
358    pub(super) fn set_worklists(
359        &mut self,
360        w: HashMap<Symbol, super::worklist::WorklistInfo>,
361    ) {
362        self.worklists = w;
363    }
364
365    /// The worklist conversion for `sym` (pre-sized buffer + register tail), if
366    /// it was recognized.
367    pub(super) fn worklist(&self, sym: Symbol) -> Option<&super::worklist::WorklistInfo> {
368        self.worklists.get(&sym)
369    }
370
371    /// Record the recognized affine read-only arrays for this function body.
372    pub(super) fn set_affine_arrays(
373        &mut self,
374        a: HashMap<Symbol, super::affine_array::AffineArrayInfo>,
375    ) {
376        self.affine_arrays = a;
377    }
378
379    /// The affine-array scalarization for `sym` (deleted array + closed-form
380    /// reads), if it was recognized.
381    pub(super) fn affine_array(&self, sym: Symbol) -> Option<&super::affine_array::AffineArrayInfo> {
382        self.affine_arrays.get(&sym)
383    }
384
385    /// Record the constant-table locals emitted as stack arrays `[T; N]`.
386    pub(super) fn set_const_tables(
387        &mut self,
388        c: HashMap<Symbol, super::affine_array::ConstTableInfo>,
389    ) {
390        self.const_tables = c;
391    }
392
393    /// The constant-table stack-array info for `sym`, if it was recognized.
394    pub(super) fn const_table(&self, sym: Symbol) -> Option<&super::affine_array::ConstTableInfo> {
395        self.const_tables.get(&sym)
396    }
397
398    /// Record the fixed-size scratch buffers emitted in place as `[T; N]` via `from_fn`.
399    pub(super) fn set_scratch_buffers(
400        &mut self,
401        s: HashMap<Symbol, super::affine_array::ScratchInfo>,
402    ) {
403        self.scratch_buffers = s;
404    }
405
406    /// The scratch-buffer scalarization info for `sym`, if it was recognized.
407    pub(super) fn scratch_buffer(&self, sym: Symbol) -> Option<&super::affine_array::ScratchInfo> {
408        self.scratch_buffers.get(&sym)
409    }
410
411    /// Record the zero-init indexed-write fixed-size buffers emitted as `[T; N]` stack arrays.
412    pub(super) fn set_indexed_buffers(&mut self, m: HashMap<Symbol, super::affine_array::IndexedBufInfo>) {
413        self.indexed_buffers = m;
414    }
415
416    /// Is `sym` a zero-init indexed-write fixed-size buffer (`[T; N]` stack array)?
417    pub(super) fn is_indexed_buffer(&self, sym: Symbol) -> bool {
418        self.indexed_buffers.contains_key(&sym)
419    }
420
421    /// Register a loop-built `[T; N]` return buffer with its runtime fill-cursor variable name.
422    pub(super) fn set_loop_fill_array(&mut self, sym: Symbol, counter: String) {
423        self.loop_fill_arrays.insert(sym, counter);
424    }
425
426    /// The runtime fill-cursor variable name for `sym`, if it is a loop-built `[T; N]` return buffer.
427    pub(super) fn loop_fill_cursor(&self, sym: Symbol) -> Option<&str> {
428        self.loop_fill_arrays.get(&sym).map(|s| s.as_str())
429    }
430
431    /// Record the `Seq of Int` sequences narrowed to `Vec<i32>`.
432    pub(super) fn set_narrowed(&mut self, n: HashMap<Symbol, super::narrow::NarrowInfo>) {
433        self.narrowed = n;
434    }
435
436    /// Whether `sym` is stored as `Vec<i32>` (loads sign-extend, stores truncate).
437    pub(super) fn is_narrowed(&self, sym: Symbol) -> bool {
438        self.narrowed.contains_key(&sym)
439    }
440
441    /// The runtime guards `sym`'s narrowing depends on (asserted at its decl).
442    pub(super) fn narrow_guards(&self, sym: Symbol) -> &[String] {
443        self.narrowed.get(&sym).map(|i| i.guards.as_slice()).unwrap_or(&[])
444    }
445
446    /// Record the `Map of Int to Int` locals lowered to `LogosI64Map`.
447    pub(super) fn set_i64_maps(&mut self, m: HashSet<Symbol>) {
448        self.i64_maps = m;
449    }
450
451    /// Record the subset lowered to the keys-only `LogosI64Set` (value unread).
452    pub(super) fn set_i64_sets(&mut self, s: HashSet<Symbol>) {
453        self.i64_sets = s;
454    }
455
456    /// Is this Map variable lowered to the specialized `LogosI64Map`
457    /// (open-addressing, no Rc/RefCell)?
458    pub(super) fn is_i64_map(&self, sym: Symbol) -> bool {
459        self.i64_maps.contains(&sym)
460    }
461
462    /// Is this Map variable lowered to the keys-only `LogosI64Set`?
463    pub(super) fn is_i64_set(&self, sym: Symbol) -> bool {
464        self.i64_sets.contains(&sym)
465    }
466
467    /// Record the `Map of Int to Int` locals proven dense (direct-addressed
468    /// array), keyed `sym -> {lo, kind}`. These are a subset of `i64_maps`.
469    pub(super) fn set_dense_i64(&mut self, m: HashMap<Symbol, super::i64_map::DenseMapInfo>) {
470        self.dense_i64 = m;
471    }
472
473    /// Which dense representation this Map variable lowers to, if any.
474    pub(super) fn dense_kind(&self, sym: Symbol) -> Option<super::i64_map::DenseKind> {
475        self.dense_i64.get(&sym).map(|i| i.kind)
476    }
477
478    /// The proven dense window info (`lo`, `kind`) for this Map variable, if any.
479    pub(super) fn dense_info(&self, sym: Symbol) -> Option<super::i64_map::DenseMapInfo> {
480        self.dense_i64.get(&sym).copied()
481    }
482
483    /// Record the non-dense `Map of Int to Int` locals narrowed to i32 storage,
484    /// split into value-read maps (`LogosI32Map`) and keys-only sets (`LogosI32Set`).
485    pub(super) fn set_i32_maps(&mut self, maps: HashSet<Symbol>, sets: HashSet<Symbol>) {
486        self.i32_maps = maps;
487        self.i32_sets = sets;
488    }
489
490    /// Is this Map variable lowered to the i32-narrowed `LogosI32Map`?
491    pub(super) fn is_i32_map(&self, sym: Symbol) -> bool {
492        self.i32_maps.contains(&sym)
493    }
494
495    /// Is this Map variable lowered to the i32-narrowed keys-only `LogosI32Set`?
496    pub(super) fn is_i32_set(&self, sym: Symbol) -> bool {
497        self.i32_sets.contains(&sym)
498    }
499
500    /// Record the push-built Vecs to pre-size (`sym -> capacity expr`).
501    pub(super) fn set_vec_presize(&mut self, m: HashMap<Symbol, String>) {
502        self.vec_presize = m;
503    }
504
505    /// The `with_capacity` argument for a push-built de-Rc Vec, if it is
506    /// index-read up to a proven bound (else `None` → plain `Vec::new()`).
507    pub(super) fn get_vec_presize(&self, sym: Symbol) -> Option<&String> {
508        self.vec_presize.get(&sym)
509    }
510
511    /// Record the loop-invariant positive divisors to lower to `LogosDivU64`
512    /// (`divisor sym -> helper variable name`).
513    pub(super) fn set_fast_div(&mut self, m: HashMap<Symbol, String>) {
514        self.fast_div = m;
515    }
516
517    /// The whole `divisor sym -> helper name` map, threaded into expression
518    /// codegen so each `% n` / `/ n` site can emit the magic multiply.
519    pub(super) fn get_fast_div(&self) -> &HashMap<Symbol, String> {
520        &self.fast_div
521    }
522
523    /// Mark `sym` as a `mutable` collection parameter (passed by `&LogosSeq`/
524    /// `&LogosMap`): its in-place mutations propagate to the caller by design, so
525    /// the `cow()` emitter skips it.
526    pub(super) fn register_mutable_collection_param(&mut self, sym: Symbol) {
527        self.mutable_collection_params.insert(sym);
528    }
529
530    /// Is `sym` a `mutable` collection parameter (mutate-in-place, no copy-on-write)?
531    pub(super) fn is_mutable_collection_param(&self, sym: Symbol) -> bool {
532        self.mutable_collection_params.contains(&sym)
533    }
534
535    /// Set the de-Rc'd Seq variables (emitted as plain `Vec<T>`).
536    pub fn set_de_rc_vars(&mut self, vars: HashSet<Symbol>) {
537        self.de_rc_vars = vars;
538    }
539
540    /// Phase 4: mark that the current function returns an owned `Vec<T>`.
541    pub fn set_returns_vec(&mut self, v: bool) {
542        self.returns_vec = v;
543    }
544
545    /// Does the current function return an owned `Vec<T>` (Phase 4)?
546    pub(super) fn returns_vec(&self) -> bool {
547        self.returns_vec
548    }
549
550    /// Mark that the current function returns an overflow-promoting `LogosInt`
551    /// (its `Int` return can exceed i64), so `Return` emits an un-narrowed value.
552    pub(super) fn set_returns_bigint(&mut self, v: bool) {
553        self.returns_bigint = v;
554    }
555
556    /// Does the current function return a `LogosInt`?
557    pub(super) fn returns_bigint(&self) -> bool {
558        self.returns_bigint
559    }
560
561    /// Is this Seq variable de-Rc'd to a plain `Vec<T>` (no Rc/RefCell)?
562    pub(super) fn is_de_rc(&self, sym: Symbol) -> bool {
563        self.de_rc_vars.contains(&sym)
564    }
565
566    /// Seed the set of integer variables whose assignments can overflow `i64`
567    /// (computed by `bigint_promote::promotable_int_vars`). The `Let` emitter
568    /// promotes such a variable to `LogosInt` when its initializer is integer.
569    pub(super) fn set_promotable_candidates(&mut self, vars: HashSet<Symbol>) {
570        self.promotable_int_candidates = vars;
571    }
572
573    /// Is this variable a promotion candidate (overflow-capable integer)?
574    pub(super) fn is_promotable_candidate(&self, sym: Symbol) -> bool {
575        self.promotable_int_candidates.contains(&sym)
576    }
577
578    /// Is this variable currently stored as the overflow-promoting `LogosInt`
579    /// (registered with the `|__bigint` sentinel)?
580    pub(super) fn is_bigint_var(&self, sym: Symbol) -> bool {
581        self.variable_types.get(&sym).map_or(false, |t| t.contains("__bigint"))
582    }
583
584    /// Set the escaping vars for local Vec optimization.
585    pub fn set_escaping_vars(&mut self, vars: HashSet<Symbol>) {
586        self.escaping_vars = vars;
587    }
588
589    /// Check if a variable escapes (and thus must remain LogosSeq).
590    pub fn var_escapes(&self, sym: &Symbol) -> bool {
591        self.escaping_vars.contains(sym)
592    }
593
594    /// Register a function's return type.
595    pub fn register_fn_return(&mut self, fn_sym: Symbol, return_type: String) {
596        self.fn_returns.insert(fn_sym, return_type);
597    }
598
599    /// Get a function's return type.
600    pub fn get_fn_return(&self, fn_sym: &Symbol) -> Option<&String> {
601        self.fn_returns.get(fn_sym)
602    }
603
604    /// Set the live-after set for the next statement about to be generated.
605    ///
606    /// Must be called before each top-level `codegen_stmt` call in a function body.
607    /// `codegen_stmt` will consume this (clearing it to `None`) so recursive nested calls
608    /// conservatively clone.
609    pub fn set_live_vars_after(&mut self, live: HashSet<Symbol>) {
610        self.live_vars_after = Some(live);
611    }
612
613    /// Take (and clear) the live-after set.  Called once at the start of `codegen_stmt`.
614    ///
615    /// Returns `None` when no liveness information was provided (conservative path).
616    pub fn take_live_vars_after(&mut self) -> Option<HashSet<Symbol>> {
617        self.live_vars_after.take()
618    }
619
620    pub(super) fn push_scope(&mut self) {
621        self.scopes.push(HashMap::new());
622        self.boxed_binding_scopes.push(HashSet::new());
623    }
624
625    pub(super) fn pop_scope(&mut self) {
626        self.scopes.pop();
627        self.boxed_binding_scopes.pop();
628    }
629
630    /// Register a binding that came from a boxed enum field.
631    /// These need `*` dereferencing when used in expressions.
632    pub(super) fn register_boxed_binding(&mut self, var: Symbol) {
633        if let Some(scope) = self.boxed_binding_scopes.last_mut() {
634            scope.insert(var);
635        }
636    }
637
638    /// Check if a variable is a boxed binding (needs dereferencing).
639    pub(super) fn is_boxed_binding(&self, var: Symbol) -> bool {
640        for scope in self.boxed_binding_scopes.iter().rev() {
641            if scope.contains(&var) {
642                return true;
643            }
644        }
645        false
646    }
647
648    /// Register a variable as having String type.
649    pub(super) fn register_string_var(&mut self, var: Symbol) {
650        self.string_vars.insert(var);
651    }
652
653    /// Check if a variable is known to be a String.
654    pub(super) fn is_string_var(&self, var: Symbol) -> bool {
655        self.string_vars.contains(&var)
656    }
657
658    /// Get a reference to the string_vars set for expression codegen.
659    pub(super) fn get_string_vars(&self) -> &HashSet<Symbol> {
660        &self.string_vars
661    }
662
663    pub(super) fn register(&mut self, var: Symbol, bound_var: Symbol, predicate: &'a LogicExpr<'a>) {
664        if let Some(scope) = self.scopes.last_mut() {
665            scope.insert(var, (bound_var, predicate));
666        }
667    }
668
669    pub(super) fn get_constraint(&self, var: Symbol) -> Option<(Symbol, &'a LogicExpr<'a>)> {
670        for scope in self.scopes.iter().rev() {
671            if let Some(entry) = scope.get(&var) {
672                return Some(*entry);
673            }
674        }
675        None
676    }
677
678    /// Register a variable with its type for capability resolution.
679    pub(super) fn register_variable_type(&mut self, var: Symbol, type_name: String) {
680        self.variable_types.insert(var, type_name);
681    }
682
683    /// Get variable type map for expression codegen optimization.
684    pub(super) fn get_variable_types(&self) -> &HashMap<Symbol, String> {
685        &self.variable_types
686    }
687
688    /// Get mutable variable type map for restoring types after hoisting scope.
689    pub(super) fn get_variable_types_mut(&mut self) -> &mut HashMap<Symbol, String> {
690        &mut self.variable_types
691    }
692
693    /// Find a variable name by its type (for resolving "the document" to "doc").
694    pub(super) fn find_variable_by_type(&self, type_name: &str, interner: &Interner) -> Option<String> {
695        let type_lower = type_name.to_lowercase();
696        for (var_sym, var_type) in &self.variable_types {
697            if var_type.to_lowercase() == type_lower {
698                return Some(interner.resolve(*var_sym).to_string());
699            }
700        }
701        None
702    }
703}
704
705/// Emits a debug_assert for a refinement predicate, substituting the bound variable.
706pub(super) fn emit_refinement_check(
707    var_name: &str,
708    bound_var: Symbol,
709    predicate: &LogicExpr,
710    interner: &Interner,
711    indent_str: &str,
712    output: &mut String,
713) {
714    let assertion = codegen_assertion(predicate, interner);
715    let bound = interner.resolve(bound_var);
716    let check = if bound == var_name {
717        assertion
718    } else {
719        replace_word(&assertion, bound, var_name)
720    };
721    writeln!(output, "{}debug_assert!({});", indent_str, check).unwrap();
722}
723
724/// Word-boundary replacement to substitute bound variable with actual variable.
725pub(super) fn replace_word(text: &str, from: &str, to: &str) -> String {
726    let mut result = String::with_capacity(text.len());
727    let mut word = String::new();
728    for c in text.chars() {
729        if c.is_alphanumeric() || c == '_' {
730            word.push(c);
731        } else {
732            if !word.is_empty() {
733                result.push_str(if word == from { to } else { &word });
734                word.clear();
735            }
736            result.push(c);
737        }
738    }
739    if !word.is_empty() {
740        result.push_str(if word == from { to } else { &word });
741    }
742    result
743}
744
745// =============================================================================
746// Mount+Sync Detection for Distributed<T>
747// =============================================================================
748
749/// Tracks which variables have Mount and/or Sync statements.
750///
751/// This is used to detect when a variable needs `Distributed<T>` instead of
752/// separate persistence and synchronization wrappers. A variable that is both
753/// mounted and synced can use the unified `Distributed<T>` type.
754///
755/// # Detection Flow
756///
757/// ```text
758/// Pre-scan all statements
759///       ↓
760/// Found "Mount x at path"  →  x.mounted = true, x.mount_path = Some(path)
761/// Found "Sync x on topic"  →  x.synced = true, x.sync_topic = Some(topic)
762///       ↓
763/// If x.mounted && x.synced  →  Use Distributed<T> with both
764/// ```
765#[derive(Debug, Default)]
766pub struct VariableCapabilities {
767    /// Variable has a Mount statement (persistence).
768    pub(super) mounted: bool,
769    /// Variable has a Sync statement (network synchronization).
770    pub(super) synced: bool,
771    /// Path expression for Mount (as generated code string).
772    pub(super) mount_path: Option<String>,
773    /// Topic expression for Sync (as generated code string).
774    pub(super) sync_topic: Option<String>,
775}
776
777/// Helper to create an empty VariableCapabilities map (for tests).
778pub fn empty_var_caps() -> HashMap<Symbol, VariableCapabilities> {
779    HashMap::new()
780}
781
782/// Pre-scan statements to detect variables that have both Mount and Sync.
783/// Returns a map from variable Symbol to its capabilities.
784pub(super) fn analyze_variable_capabilities<'a>(
785    stmts: &[Stmt<'a>],
786    interner: &Interner,
787) -> HashMap<Symbol, VariableCapabilities> {
788    let mut caps: HashMap<Symbol, VariableCapabilities> = HashMap::new();
789    let empty_synced = HashSet::new();
790
791    for stmt in stmts {
792        match stmt {
793            Stmt::Mount { var, path } => {
794                let entry = caps.entry(*var).or_default();
795                entry.mounted = true;
796                entry.mount_path = Some(codegen_expr(path, interner, &empty_synced));
797            }
798            Stmt::Sync { var, topic } => {
799                let entry = caps.entry(*var).or_default();
800                entry.synced = true;
801                entry.sync_topic = Some(codegen_expr(topic, interner, &empty_synced));
802            }
803            // Recursively check nested blocks (Block<'a> is &[Stmt<'a>])
804            Stmt::If { then_block, else_block, .. } => {
805                let nested = analyze_variable_capabilities(then_block, interner);
806                for (var, cap) in nested {
807                    let entry = caps.entry(var).or_default();
808                    if cap.mounted { entry.mounted = true; entry.mount_path = cap.mount_path; }
809                    if cap.synced { entry.synced = true; entry.sync_topic = cap.sync_topic; }
810                }
811                if let Some(else_b) = else_block {
812                    let nested = analyze_variable_capabilities(else_b, interner);
813                    for (var, cap) in nested {
814                        let entry = caps.entry(var).or_default();
815                        if cap.mounted { entry.mounted = true; entry.mount_path = cap.mount_path; }
816                        if cap.synced { entry.synced = true; entry.sync_topic = cap.sync_topic; }
817                    }
818                }
819            }
820            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
821                let nested = analyze_variable_capabilities(body, interner);
822                for (var, cap) in nested {
823                    let entry = caps.entry(var).or_default();
824                    if cap.mounted { entry.mounted = true; entry.mount_path = cap.mount_path; }
825                    if cap.synced { entry.synced = true; entry.sync_topic = cap.sync_topic; }
826                }
827            }
828            _ => {}
829        }
830    }
831
832    caps
833}