Skip to main content

seqc/codegen/
state.rs

1//! CodeGen State and Core Types
2//!
3//! This module contains the CodeGen struct definition and core types
4//! used across the code generation modules.
5
6use crate::ast::UnionDef;
7use crate::ffi::FfiBindings;
8use crate::types::Type;
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12use super::specialization::SpecSignature;
13
14/// Sentinel value for unreachable predecessors in phi nodes.
15/// Used when a branch ends with a tail call (which emits ret directly).
16pub(super) const UNREACHABLE_PREDECESSOR: &str = "unreachable";
17
18/// Maximum number of values to keep in virtual registers (Issue #189).
19/// Values beyond this are spilled to memory.
20///
21/// Tuned for common patterns:
22/// - Binary ops need 2 values (`a b i.+`)
23/// - Dup patterns need 3 values (`a dup i.* b i.+`)
24/// - Complex expressions may use 4 (`a b i.+ c d i.* i.-`)
25///
26/// Larger values increase register pressure with diminishing returns,
27/// as most operations trigger spills (control flow, function calls, etc.).
28pub(super) const MAX_VIRTUAL_STACK: usize = 4;
29
30/// Tracks whether a statement is in tail position.
31///
32/// A statement is in tail position when its result is directly returned
33/// from the function without further processing. For tail calls, we can
34/// use LLVM's `musttail` to guarantee tail call optimization.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub(super) enum TailPosition {
37    /// This is the last operation before return - can use musttail
38    Tail,
39    /// More operations follow - use regular call
40    NonTail,
41}
42
43/// Result of generating code for an if-statement branch.
44pub(super) struct BranchResult {
45    /// The stack variable after executing the branch
46    pub stack_var: String,
47    /// Whether the branch emitted a tail call (and thus a ret)
48    pub emitted_tail_call: bool,
49    /// The predecessor block label for the phi node (or UNREACHABLE_PREDECESSOR)
50    pub predecessor: String,
51}
52
53/// Mangle a Seq word name into a valid LLVM IR identifier.
54///
55/// LLVM IR identifiers can contain: letters, digits, underscores, dollars, periods.
56/// Seq words can contain: letters, digits, hyphens, question marks, arrows, etc.
57///
58/// We escape special characters using underscore-based encoding:
59/// - `-` (hyphen) -> `_` (hyphens not valid in LLVM IR identifiers)
60/// - `?` -> `_Q_` (question)
61/// - `>` -> `_GT_` (greater than, for ->)
62/// - `<` -> `_LT_` (less than)
63/// - `!` -> `_BANG_`
64/// - `*` -> `_STAR_`
65/// - `/` -> `_SLASH_`
66/// - `+` -> `_PLUS_`
67/// - `=` -> `_EQ_`
68/// - `.` -> `_DOT_`
69pub(super) fn mangle_name(name: &str) -> String {
70    let mut result = String::new();
71    for c in name.chars() {
72        match c {
73            '?' => result.push_str("_Q_"),
74            '>' => result.push_str("_GT_"),
75            '<' => result.push_str("_LT_"),
76            '!' => result.push_str("_BANG_"),
77            '*' => result.push_str("_STAR_"),
78            '/' => result.push_str("_SLASH_"),
79            '+' => result.push_str("_PLUS_"),
80            '=' => result.push_str("_EQ_"),
81            // Hyphens converted to underscores (hyphens not valid in LLVM IR)
82            '-' => result.push('_'),
83            // Keep these as-is (valid in LLVM IR)
84            '_' | '.' | '$' => result.push(c),
85            // Alphanumeric kept as-is
86            c if c.is_alphanumeric() => result.push(c),
87            // Any other character gets hex-encoded
88            _ => result.push_str(&format!("_x{:02X}_", c as u32)),
89        }
90    }
91    result
92}
93
94/// Result of generating a quotation: wrapper and impl function names
95/// For closures, both names are the same (no TCO support yet)
96pub(super) struct QuotationFunctions {
97    /// C-convention wrapper function (for runtime calls)
98    pub wrapper: String,
99    /// tailcc implementation function (for TCO via musttail)
100    pub impl_: String,
101}
102
103/// Snapshot of the enclosing function's mutable codegen state while a nested
104/// quotation or closure is being generated. Returned by
105/// `enter_quotation_scope` and consumed by `exit_quotation_scope`, which
106/// commits the nested IR to `quotation_functions` and restores these fields.
107pub(super) struct QuotationScope {
108    pub output: String,
109    pub virtual_stack: Vec<VirtualValue>,
110    pub word_name: Option<String>,
111    pub aux_slots: Vec<String>,
112    pub aux_sp: usize,
113    /// Snapshot of the enclosing word's DISubprogram. Cleared while a
114    /// quotation body is being emitted (the quotation lives in its own
115    /// LLVM function with no subprogram, so any `!dbg` attached inside
116    /// would be unverifiable), and restored when the scope exits.
117    pub dbg_subprogram_id: Option<usize>,
118}
119
120/// A value held in an LLVM virtual register instead of memory (Issue #189).
121///
122/// This optimization keeps recently-pushed values in SSA variables,
123/// avoiding memory stores/loads for common patterns like `2 3 i.+`.
124/// Values are spilled to memory at control flow points and function calls.
125#[derive(Clone, Debug)]
126pub(super) enum VirtualValue {
127    /// Integer value in an SSA variable (i64)
128    Int {
129        ssa_var: String,
130        #[allow(dead_code)] // Used for constant folding in Phase 2
131        value: i64,
132    },
133    /// Float value in an SSA variable (double)
134    Float { ssa_var: String },
135    /// Boolean value in an SSA variable (i64: 0 or 1)
136    Bool { ssa_var: String },
137}
138
139pub struct CodeGen {
140    pub(super) output: String,
141    pub(super) string_globals: String,
142    pub(super) temp_counter: usize,
143    pub(super) string_counter: usize,
144    pub(super) block_counter: usize, // For generating unique block labels
145    pub(super) quot_counter: usize,  // For generating unique quotation function names
146    pub(super) string_constants: HashMap<Vec<u8>, String>, // byte payload -> global name
147    pub(super) quotation_functions: String, // Accumulates generated quotation functions
148    pub(super) type_map: HashMap<usize, Type>, // Maps quotation ID to inferred type (from typechecker)
149    pub(super) external_builtins: HashMap<String, String>, // seq_name -> symbol (for external builtins)
150    pub(super) inside_closure: bool, // Track if we're generating code inside a closure (disables TCO)
151    pub(super) inside_main: bool, // Track if we're generating code for main (uses C convention, no musttail)
152    pub(super) inside_quotation: bool, // Track if we're generating code for a quotation (uses C convention, no musttail)
153    pub(super) unions: Vec<UnionDef>,  // Union type definitions for pattern matching
154    pub(super) ffi_bindings: FfiBindings, // FFI function bindings
155    pub(super) ffi_wrapper_code: String, // Generated FFI wrapper functions
156    /// Pure inline test mode: bypasses scheduler, returns top of stack as exit code.
157    /// Used for testing pure integer programs without FFI dependencies.
158    pub(super) pure_inline_test: bool,
159    // Symbol interning for O(1) equality (Issue #166)
160    pub(super) symbol_globals: String, // LLVM IR for static symbol globals
161    pub(super) symbol_counter: usize,  // Counter for unique symbol names
162    pub(super) symbol_constants: HashMap<String, String>, // symbol name -> global name (deduplication)
163    /// Per-statement type info for optimization (Issue #186)
164    /// Maps (word_name, statement_index) -> top-of-stack type before statement
165    pub(super) statement_types: HashMap<(String, usize), Type>,
166    /// Resolved arithmetic sugar: maps (line, column) -> concrete op name
167    /// E.g., `+` at line 5, column 3 -> `"i.+"` if typechecker resolved it for Int operands
168    pub(super) resolved_sugar: HashMap<(usize, usize), String>,
169    /// Current word being compiled (for statement type lookup)
170    pub(super) current_word_name: Option<String>,
171    /// Current statement index within the word (for statement type lookup)
172    pub(super) current_stmt_index: usize,
173    /// Nesting depth for type lookup - only depth 0 can use type info
174    /// Nested contexts (if/else, loops) increment this to disable lookups
175    pub(super) codegen_depth: usize,
176    /// True if the previous statement was a trivially-copyable literal (Issue #195)
177    /// Used to optimize `dup` after literal push (e.g., `42 dup`)
178    pub(super) prev_stmt_is_trivial_literal: bool,
179    /// If previous statement was IntLiteral, stores its value (Issue #192)
180    /// Used to optimize `roll`/`pick` with constant N (e.g., `2 roll` -> rot)
181    pub(super) prev_stmt_int_value: Option<i64>,
182    /// Virtual register stack for top N values (Issue #189)
183    /// Values here are in SSA variables, not yet written to memory.
184    /// The memory stack pointer tracks where memory ends; virtual values are "above" it.
185    pub(super) virtual_stack: Vec<VirtualValue>,
186    /// Specialized word signatures for register-based codegen
187    /// Maps word name -> specialized signature
188    pub(super) specialized_words: HashMap<String, SpecSignature>,
189    /// Per-word aux stack slot counts from typechecker (Issue #350)
190    /// Maps word_name -> number of %Value allocas needed
191    pub(super) aux_slot_counts: HashMap<String, usize>,
192    /// Per-quotation aux stack slot counts from typechecker (Issue #393)
193    /// Maps quotation_id -> number of %Value allocas needed for that quotation
194    pub(super) quotation_aux_slot_counts: HashMap<usize, usize>,
195    /// LLVM alloca names for current word's aux slots (Issue #350)
196    pub(super) current_aux_slots: Vec<String>,
197    /// Compile-time index into aux slots (Issue #350)
198    pub(super) current_aux_sp: usize,
199    /// Whether to emit per-word atomic call counters (--instrument)
200    pub(super) instrument: bool,
201    /// Enable loop lowering for self-tail-recursive words (--loop-opt).
202    /// See `docs/design/LOOP_LOWERING.md`.
203    pub(super) loop_opt_enabled: bool,
204    /// Iterations between cooperative yields inside a lowered loop.
205    /// Must be a power of two (used as an AND mask). Default 1024.
206    pub(super) loop_yield_cadence: u32,
207    /// Call graph built from the program, used by loop lowering to classify
208    /// self-recursive words. `None` disables loop lowering regardless of
209    /// `loop_opt_enabled`.
210    pub(super) call_graph: Option<crate::call_graph::CallGraph>,
211    /// True if the user's `main` word has effect `( -- Int )`.
212    /// Determines whether `seq_main` writes the top-of-stack int to the
213    /// global exit code before freeing the stack. (Issue #355)
214    pub(super) main_returns_int: bool,
215    /// Maps word name -> sequential ID for instrumentation counters
216    pub(super) word_instrument_ids: HashMap<String, usize>,
217    // -------------------------------------------------------------------
218    // Debug info (DWARF) — see codegen/debug_info.rs.
219    //
220    // When enabled, emits LLVM `!DICompileUnit`, `!DIFile`, `!DISubprogram`,
221    // and per-call `!DILocation` metadata so panics in the runtime resolve
222    // back to .seq source lines via the standard Rust backtrace path.
223    // Zero runtime cost — pure metadata. The clang invocation must pass
224    // `-g` to preserve these into the final binary's DWARF section.
225    // -------------------------------------------------------------------
226    /// Source file the program was compiled from (for DIFile). When
227    /// `None`, debug info is disabled.
228    pub(super) dbg_source: Option<PathBuf>,
229    /// Accumulated DWARF metadata definitions (`!N = !DI...`). Appended to
230    /// the end of the IR file alongside the module flags.
231    pub(super) dbg_metadata: String,
232    /// Counter for unique metadata IDs. Started at 1000 to leave headroom
233    /// for any future module-level metadata that may want lower ids.
234    pub(super) dbg_md_counter: usize,
235    /// ID of the per-program `!DICompileUnit`. Set during program prologue
236    /// when debug info is enabled.
237    pub(super) dbg_cu_id: Option<usize>,
238    /// ID of the shared `!DIFile` for the source file.
239    pub(super) dbg_file_id: Option<usize>,
240    /// ID of the shared `!DISubroutineType` reused by every subprogram —
241    /// our generated functions all have the same opaque ptr-in/ptr-out
242    /// signature from a debugger's point of view.
243    pub(super) dbg_subroutine_type_id: Option<usize>,
244    /// `!DISubprogram` ID for the function currently being emitted, if any.
245    /// Call sites use this as the scope for their `!DILocation` records.
246    pub(super) current_dbg_subprogram_id: Option<usize>,
247    /// IDs of the two `!llvm.module.flags` records ("Dwarf Version",
248    /// "Debug Info Version"). Allocated through `dbg_alloc_id` at program
249    /// init so they never collide with later subprogram/location records.
250    pub(super) dbg_module_flag_ids: Option<(usize, usize)>,
251}
252
253impl Default for CodeGen {
254    fn default() -> Self {
255        Self::new()
256    }
257}
258
259impl CodeGen {
260    pub fn new() -> Self {
261        CodeGen {
262            output: String::new(),
263            string_globals: String::new(),
264            temp_counter: 0,
265            string_counter: 0,
266            block_counter: 0,
267            inside_closure: false,
268            inside_main: false,
269            inside_quotation: false,
270            quot_counter: 0,
271            string_constants: HashMap::new(),
272            quotation_functions: String::new(),
273            type_map: HashMap::new(),
274            external_builtins: HashMap::new(),
275            unions: Vec::new(),
276            ffi_bindings: FfiBindings::new(),
277            ffi_wrapper_code: String::new(),
278            pure_inline_test: false,
279            symbol_globals: String::new(),
280            symbol_counter: 0,
281            symbol_constants: HashMap::new(),
282            statement_types: HashMap::new(),
283            resolved_sugar: HashMap::new(),
284            current_word_name: None,
285            current_stmt_index: 0,
286            codegen_depth: 0,
287            prev_stmt_is_trivial_literal: false,
288            prev_stmt_int_value: None,
289            virtual_stack: Vec::new(),
290            specialized_words: HashMap::new(),
291            aux_slot_counts: HashMap::new(),
292            quotation_aux_slot_counts: HashMap::new(),
293            current_aux_slots: Vec::new(),
294            current_aux_sp: 0,
295            instrument: false,
296            loop_opt_enabled: false,
297            loop_yield_cadence: 1024,
298            call_graph: None,
299            word_instrument_ids: HashMap::new(),
300            main_returns_int: false,
301            dbg_source: None,
302            dbg_metadata: String::new(),
303            dbg_md_counter: 1000,
304            dbg_cu_id: None,
305            dbg_file_id: None,
306            dbg_subroutine_type_id: None,
307            current_dbg_subprogram_id: None,
308            dbg_module_flag_ids: None,
309        }
310    }
311
312    /// Enable DWARF debug info generation, anchored at the given source file.
313    ///
314    /// Must be called before `codegen_program*`. With debug info enabled,
315    /// every user-defined word gets a `!DISubprogram` and every call site
316    /// with a span gets a `!DILocation` — so a runtime panic backtrace
317    /// resolves the Seq frame to `.seq:line:col`. Zero runtime overhead.
318    pub fn set_source_file(&mut self, path: PathBuf) {
319        self.dbg_source = Some(path);
320    }
321
322    /// Create a CodeGen for pure inline testing.
323    /// Bypasses the scheduler, returning top of stack as exit code.
324    /// Only supports operations that are fully inlined (integers, arithmetic, stack ops).
325    pub fn new_pure_inline_test() -> Self {
326        let mut cg = Self::new();
327        cg.pure_inline_test = true;
328        cg
329    }
330
331    /// Set per-word aux stack slot counts from typechecker (Issue #350)
332    pub fn set_aux_slot_counts(&mut self, counts: HashMap<String, usize>) {
333        self.aux_slot_counts = counts;
334    }
335
336    /// Set per-quotation aux stack slot counts from typechecker (Issue #393)
337    pub fn set_quotation_aux_slot_counts(&mut self, counts: HashMap<usize, usize>) {
338        self.quotation_aux_slot_counts = counts;
339    }
340
341    /// Set resolved arithmetic sugar mappings from the typechecker
342    pub fn set_resolved_sugar(&mut self, sugar: HashMap<(usize, usize), String>) {
343        self.resolved_sugar = sugar;
344    }
345
346    /// Look up the resolved name for an arithmetic sugar op by source location
347    pub(super) fn resolve_sugar_at(&self, line: usize, column: usize) -> Option<&str> {
348        self.resolved_sugar.get(&(line, column)).map(|s| s.as_str())
349    }
350
351    /// Enable loop lowering and set the yield cadence (power of two).
352    pub fn set_loop_opt(&mut self, enabled: bool, cadence: u32) {
353        self.loop_opt_enabled = enabled;
354        self.loop_yield_cadence = cadence;
355    }
356
357    /// Provide the program call graph for self-recursion classification.
358    pub fn set_call_graph(&mut self, cg: crate::call_graph::CallGraph) {
359        self.call_graph = Some(cg);
360    }
361}