logicaffeine_compile/codegen/mod.rs
1//! Code generation from LOGOS AST to Rust source code.
2//!
3//! This module transforms the parsed and type-checked LOGOS program into
4//! idiomatic Rust code. The generated code uses `logicaffeine_data` types
5//! for runtime values and integrates with the kernel for proof verification.
6//!
7//! # Pipeline Position
8//!
9//! ```text
10//! ┌─────────────────────────────────────────────────────────┐
11//! │ LOGOS Source → Lexer → Parser → AST → Analysis → HERE │
12//! └─────────────────────────────────────────────────────────┘
13//! ↓
14//! Rust Source
15//! ```
16//!
17//! # Code Generation Rules
18//!
19//! | LOGOS Statement | Rust Output |
20//! |-----------------|-------------|
21//! | `Let x be 5.` | `let x = 5;` |
22//! | `Set x to 10.` | `x = 10;` |
23//! | `Give x to y.` | `let y = x;` (move) |
24//! | `Show x to show.` | `println!("{}", x);` |
25//! | `If x > 0 then...` | `if x > 0 { ... }` |
26//! | `Repeat while x > 0...` | `while x > 0 { ... }` |
27//! | `Zone "name"...` | `{ /* zone scope */ }` |
28//! | `Mount x at "path".` | `x.mount(vfs, "path").await;` |
29//! | `Sync x on "topic".` | `x.subscribe("topic").await;` |
30//!
31//! # Key Features
32//!
33//! - **Refinement Types**: Generates `debug_assert!` for type predicates
34//! - **Policy Enforcement**: Emits capability checks for access control
35//! - **Zone Safety**: Translates memory zones to Rust scopes
36//! - **CRDT Mutability**: Uses `.set()` for LWWRegister/MVRegister fields
37//! - **Async Detection**: Adds `#[tokio::main]` when async operations are present
38//! - **VFS Detection**: Injects `get_platform_vfs()` for file operations (io_uring on Linux, NativeVfs elsewhere)
39//! - **Mount+Sync Detection**: Uses `Distributed<T>` for combined persistence/sync
40//!
41//! # Refinement Context
42//!
43//! The [`RefinementContext`] tracks type predicates across scopes:
44//!
45//! ```text
46//! Let x: { it: Int | it > 0 } be 5. ← Register constraint
47//! ↓
48//! debug_assert!(5 > 0); ← Check at definition
49//! ↓
50//! Set x to 10. ← Re-check on mutation
51//! ↓
52//! debug_assert!(10 > 0); ← Re-emit assertion
53//! ```
54//!
55//! - When a variable with a refinement type is defined, its constraint is registered
56//! - When that variable is mutated, the assertion is re-emitted
57//! - Variable types are tracked for capability resolution
58//!
59//! # Entry Point
60//!
61//! The main entry point is [`codegen_program`], which generates a complete Rust
62//! program from a list of statements.
63
64// Module declarations
65pub(crate) mod bigint_promote;
66pub(crate) mod context;
67pub(crate) mod detection;
68pub(crate) mod types;
69mod policy;
70pub(crate) mod ffi;
71pub(crate) mod bindings;
72pub(crate) mod tce;
73pub(crate) mod marshal;
74pub(crate) mod peephole;
75pub(crate) mod hoist;
76pub(crate) mod stmt;
77pub(crate) mod expr;
78pub(crate) mod program;
79pub(crate) mod worklist;
80pub(crate) mod affine_array;
81pub(crate) mod narrow;
82pub(crate) mod i64_map;
83pub(crate) mod fast_div;
84pub(crate) mod entry_guard;
85pub(crate) mod strsearch;
86// Per-function codegen slicing for the AOT-native tier (HOTSWAP Axis-3 / P14a).
87pub(crate) mod slice;
88
89// ─── External API re-exports ────────────────────────────────────────────────
90// These preserve the public API used by compile.rs, ui_bridge.rs, and test files.
91
92pub use context::{RefinementContext, VariableCapabilities, empty_var_caps};
93pub use detection::{collect_async_functions, collect_pipe_sender_params, collect_pipe_vars};
94pub use program::{codegen_program, codegen_program_mapped, codegen_program_with_proven};
95pub use slice::function_slice;
96pub use marshal::codegen_native_tier_export;
97pub use hoist::force_disable_for_test as force_disable_borrow_hoist_for_test;
98pub use ffi::generate_c_header;
99pub use bindings::{generate_python_bindings, generate_typescript_bindings};
100pub use expr::{codegen_expr, codegen_assertion, codegen_term};
101pub use stmt::codegen_stmt;
102
103// ─── Mode-B (deterministic-replay) codegen gate ─────────────────────────────
104//
105// A compile-global flag: when set, a `Select` emits the *seeded* winner-pick
106// (sharing the interpreter's choice function) instead of a raw `tokio::select!`.
107// The DEFAULT (Mode A) is off, so default emission stays byte-identical to today
108// — the one bit Phase 8 adds. Codegen is single-threaded per compile, so a
109// thread-local is the natural home; `compile::compile_to_rust_deterministic`
110// sets it for the extent of one compile and restores it after.
111
112thread_local! {
113 static SEEDED_SELECT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
114}
115
116/// Is the deterministic-replay (Mode-B) `Select` lowering active for this compile?
117pub(crate) fn seeded_select_enabled() -> bool {
118 SEEDED_SELECT.with(|c| c.get())
119}
120
121/// Run `f` with Mode-B `Select` lowering enabled, restoring the previous mode
122/// afterwards (panic-safe). Returns `f`'s result.
123pub fn with_seeded_select<R>(f: impl FnOnce() -> R) -> R {
124 struct Restore(bool);
125 impl Drop for Restore {
126 fn drop(&mut self) {
127 SEEDED_SELECT.with(|c| c.set(self.0));
128 }
129 }
130 let _guard = SEEDED_SELECT.with(|c| {
131 let prev = c.get();
132 c.set(true);
133 Restore(prev)
134 });
135 f()
136}
137
138// ─── Internal cross-module re-exports ───────────────────────────────────────
139// These allow sibling submodules to use `use super::item` for items
140// that moved out of mod.rs into submodule files.
141
142pub(crate) use ffi::{
143 CAbiClass, classify_type_for_c_abi, has_wasm_exports, has_c_exports, has_c_exports_with_text,
144 codegen_c_accessors, collect_c_export_ref_structs, collect_c_export_reference_types,
145 collect_c_export_value_type_structs, codegen_logos_runtime_preamble, mangle_type_for_c,
146 map_type_to_c_header, map_field_type_to_c,
147};
148pub(crate) use expr::{
149 codegen_expr_with_async, codegen_expr_boxed, codegen_expr_boxed_with_strings,
150 codegen_expr_boxed_with_types, codegen_interpolated_string, codegen_literal,
151 codegen_expr_with_async_and_strings, is_definitely_string_expr_with_vars,
152 is_definitely_string_expr, is_definitely_numeric_expr, collect_string_concat_operands,
153};
154pub(crate) use stmt::{get_root_identifier, is_copy_type, has_copy_element_type, has_copy_value_type};
155pub(crate) use peephole::{
156 try_emit_for_range_pattern, try_emit_vec_fill_pattern, try_emit_swap_pattern,
157 try_emit_prefix_reverse,
158 try_emit_seq_copy_pattern, try_emit_seq_from_slice_pattern,
159 try_emit_bare_slice_push_pattern,
160 try_emit_vec_with_capacity_pattern, try_emit_merge_capacity_pattern,
161 try_emit_string_with_capacity_pattern,
162 try_emit_rotate_left_pattern,
163 try_emit_buffer_reuse_while,
164 body_mutates_collection, body_modifies_var, exprs_equal,
165};
166pub(crate) use types::is_recursive_field;
167
168/// Check if a name is a Rust keyword (needs `r#` escaping in generated code).
169pub(crate) fn is_rust_keyword(name: &str) -> bool {
170 matches!(name,
171 "as" | "async" | "await" | "break" | "const" | "continue" | "crate" |
172 "dyn" | "else" | "enum" | "extern" | "false" | "fn" | "for" | "if" |
173 "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" |
174 "pub" | "ref" | "return" | "self" | "Self" | "static" | "struct" |
175 "super" | "trait" | "true" | "type" | "unsafe" | "use" | "where" |
176 "while" | "abstract" | "become" | "box" | "do" | "final" | "macro" |
177 "override" | "priv" | "try" | "typeof" | "unsized" | "virtual" | "yield"
178 )
179}
180
181/// Escape a name as a Rust raw identifier if it's a keyword.
182/// e.g., "move" → "r#move", "add" → "add"
183pub(crate) fn escape_rust_ident(name: &str) -> String {
184 if is_rust_keyword(name) {
185 format!("r#{}", name)
186 } else {
187 name.to_string()
188 }
189}
190
191/// A call-site ABI role a function parameter can carry, keyed by parameter index.
192/// A single function may play SEVERAL at once — the canonical case is a readonly
193/// `Seq` param (de-Rc'd to `&[T]`) alongside a value-semantics `mutable` collection
194/// param (passed as `&LogosSeq`/`&LogosMap`).
195#[derive(Clone, Copy)]
196pub(crate) enum FnRole {
197 /// Readonly borrow (`&[T]`) — the `borrow_params_map` opt.
198 Borrow,
199 /// Element-mutating borrow (`&mut [T]`) — the `mut_borrow_params_map` opt.
200 MutBorrow,
201 /// Value-semantics `mutable` collection (`&LogosSeq`/`&LogosMap`).
202 ValueMutable,
203}
204
205/// Pack the per-parameter role index sets a function plays into ONE `variable_types`
206/// slot. The slot is single-valued, so registering each role as its own
207/// `fn_borrow:` / `fn_value_mutable:` string used to make them clobber each other —
208/// a function with both a readonly-borrow param and a `mutable` param would keep
209/// only the last-registered role, and every call site then mis-lowered the other
210/// (cloning an owned `LogosSeq` into a `&[T]` slot). This keeps all roles together.
211pub(crate) fn encode_fn_roles(
212 borrow: &std::collections::HashSet<usize>,
213 mut_borrow: &std::collections::HashSet<usize>,
214 value_mutable: &std::collections::HashSet<usize>,
215) -> String {
216 fn csv(s: &std::collections::HashSet<usize>) -> String {
217 let mut v: Vec<usize> = s.iter().copied().collect();
218 v.sort_unstable();
219 v.iter().map(|i| i.to_string()).collect::<Vec<_>>().join(",")
220 }
221 format!(
222 "fn_roles|b={}|m={}|v={}",
223 csv(borrow),
224 csv(mut_borrow),
225 csv(value_mutable)
226 )
227}
228
229/// The parameter indices for one [`FnRole`] out of a slot written by
230/// [`encode_fn_roles`]. Empty when the slot is absent or is an ordinary variable
231/// type rather than a role encoding.
232pub(crate) fn fn_role_indices(
233 encoded: Option<&String>,
234 role: FnRole,
235) -> std::collections::HashSet<usize> {
236 let empty = std::collections::HashSet::new();
237 let Some(rest) = encoded.and_then(|s| s.strip_prefix("fn_roles|")) else {
238 return empty;
239 };
240 let key = match role {
241 FnRole::Borrow => "b=",
242 FnRole::MutBorrow => "m=",
243 FnRole::ValueMutable => "v=",
244 };
245 for section in rest.split('|') {
246 if let Some(csv) = section.strip_prefix(key) {
247 return csv.split(',').filter_map(|i| i.parse().ok()).collect();
248 }
249 }
250 empty
251}
252
253/// True when a `variable_types` slot is a [`encode_fn_roles`] encoding (a function
254/// symbol's ABI role) rather than an ordinary variable type.
255pub(crate) fn is_fn_role_slot(ty: &str) -> bool {
256 ty.starts_with("fn_roles|")
257}