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}