Skip to main content

seqc/codegen/
program.rs

1//! Program Code Generation
2//!
3//! This module contains the main entry points for generating LLVM IR
4//! from a complete Seq program.
5
6use super::{
7    CodeGen, CodeGenError, emit_runtime_decls, ffi_c_args, ffi_return_type, get_target_triple,
8};
9use crate::ast::{Program, WordDef};
10use crate::config::CompilerConfig;
11use crate::ffi::FfiBindings;
12use crate::types::{StackType, Type};
13use std::collections::HashMap;
14use std::fmt::Write as _;
15
16/// Detect whether `main` was declared with effect `( -- Int )`.
17///
18/// Returns true if main's declared output is a single Int (with no row
19/// variable below it). Returns false for `( -- )` or anything else.
20/// The typechecker is responsible for rejecting other shapes; this just
21/// reads the declared effect.
22fn main_returns_int_effect(word: &WordDef) -> bool {
23    let Some(effect) = &word.effect else {
24        return false;
25    };
26    // Inputs must be empty (or just a row var) — main takes no inputs
27    // Outputs must be exactly one Int on top of the row var
28    matches!(
29        &effect.outputs,
30        StackType::Cons { rest, top: Type::Int }
31            if matches!(**rest, StackType::Empty | StackType::RowVar(_))
32    )
33}
34
35impl CodeGen {
36    /// Generate LLVM IR for entire program
37    pub fn codegen_program(
38        &mut self,
39        program: &Program,
40        type_map: HashMap<usize, Type>,
41        statement_types: HashMap<(String, usize), Type>,
42    ) -> Result<String, CodeGenError> {
43        self.codegen_program_with_config(
44            program,
45            type_map,
46            statement_types,
47            &CompilerConfig::default(),
48        )
49    }
50
51    /// Generate LLVM IR for entire program with custom configuration.
52    ///
53    /// This allows external projects to extend the compiler with additional
54    /// builtins that will be declared and callable from Seq code.
55    pub fn codegen_program_with_config(
56        &mut self,
57        program: &Program,
58        type_map: HashMap<usize, Type>,
59        statement_types: HashMap<(String, usize), Type>,
60        config: &CompilerConfig,
61    ) -> Result<String, CodeGenError> {
62        self.prepare_program_state(program, type_map, statement_types, config)?;
63        self.generate_words_and_main(program)?;
64
65        let mut ir = String::new();
66        self.emit_ir_header(&mut ir)?;
67        self.emit_ir_type_and_globals(&mut ir)?;
68        emit_runtime_decls(&mut ir)?;
69        self.emit_external_builtins(&mut ir)?;
70        self.emit_quotation_functions(&mut ir)?;
71        ir.push_str(&self.output);
72        self.dbg_emit_module_metadata(&mut ir);
73        Ok(ir)
74    }
75
76    /// Generate LLVM IR for entire program with FFI support.
77    ///
78    /// This is the main entry point for compiling programs that use FFI.
79    pub fn codegen_program_with_ffi(
80        &mut self,
81        program: &Program,
82        type_map: HashMap<usize, Type>,
83        statement_types: HashMap<(String, usize), Type>,
84        config: &CompilerConfig,
85        ffi_bindings: &FfiBindings,
86    ) -> Result<String, CodeGenError> {
87        self.ffi_bindings = ffi_bindings.clone();
88        self.generate_ffi_wrappers()?;
89
90        self.prepare_program_state(program, type_map, statement_types, config)?;
91        self.generate_words_and_main(program)?;
92
93        let mut ir = String::new();
94        self.emit_ir_header(&mut ir)?;
95        self.emit_ir_type_and_globals(&mut ir)?;
96        emit_runtime_decls(&mut ir)?;
97        self.emit_ffi_c_declarations(&mut ir)?;
98        self.emit_external_builtins(&mut ir)?;
99        self.emit_ffi_wrappers_section(&mut ir)?;
100        self.emit_quotation_functions(&mut ir)?;
101        ir.push_str(&self.output);
102        self.dbg_emit_module_metadata(&mut ir);
103        Ok(ir)
104    }
105
106    // =========================================================================
107    // Shared program-generation helpers
108    // =========================================================================
109
110    /// Copy typechecker outputs and config-derived state onto the CodeGen,
111    /// and sanity-check the presence/shape of `main`.
112    fn prepare_program_state(
113        &mut self,
114        program: &Program,
115        type_map: HashMap<usize, Type>,
116        statement_types: HashMap<(String, usize), Type>,
117        config: &CompilerConfig,
118    ) -> Result<(), CodeGenError> {
119        self.type_map = type_map;
120        self.statement_types = statement_types;
121        // resolved_sugar is set separately via set_resolved_sugar()
122        self.unions = program.unions.clone();
123        self.external_builtins = config
124            .external_builtins
125            .iter()
126            .map(|b| (b.seq_name.clone(), b.symbol.clone()))
127            .collect();
128
129        self.instrument = config.instrument;
130        self.loop_opt_enabled = config.loop_opt;
131        self.loop_yield_cadence = config.loop_yield_cadence;
132        if self.instrument {
133            for (id, word) in program.words.iter().enumerate() {
134                self.word_instrument_ids.insert(word.name.clone(), id);
135            }
136        }
137
138        // Issue #355: detect `main ( -- Int )` so seq_main writes the top-of-
139        // stack int into the exit-code global before tearing down.
140        let main_word = program
141            .find_word("main")
142            .ok_or_else(|| CodeGenError::Logic("No main word defined".to_string()))?;
143        self.main_returns_int = main_returns_int_effect(main_word);
144
145        Ok(())
146    }
147
148    /// Generate code for every user-defined word, then emit `main`.
149    fn generate_words_and_main(&mut self, program: &Program) -> Result<(), CodeGenError> {
150        for word in &program.words {
151            self.codegen_word(word)?;
152        }
153        self.codegen_main()
154    }
155
156    /// Module ID and target triple — the first lines of the IR file.
157    fn emit_ir_header(&self, ir: &mut String) -> Result<(), CodeGenError> {
158        writeln!(ir, "; ModuleID = 'main'")?;
159        writeln!(ir, "target triple = \"{}\"", get_target_triple())?;
160        writeln!(ir)?;
161        Ok(())
162    }
163
164    /// Value type definition, string/symbol globals, and instrumentation
165    /// globals when `--instrument` is enabled.
166    fn emit_ir_type_and_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
167        self.emit_value_type_def(ir)?;
168        self.emit_string_and_symbol_globals(ir)?;
169        if self.instrument {
170            self.emit_instrumentation_globals(ir)?;
171        }
172        Ok(())
173    }
174
175    fn emit_external_builtins(&self, ir: &mut String) -> Result<(), CodeGenError> {
176        if self.external_builtins.is_empty() {
177            return Ok(());
178        }
179        writeln!(ir, "; External builtin declarations")?;
180        // All external builtins follow the standard stack convention: ptr -> ptr
181        for symbol in self.external_builtins.values() {
182            writeln!(ir, "declare ptr @{}(ptr)", symbol)?;
183        }
184        writeln!(ir)?;
185        Ok(())
186    }
187
188    fn emit_quotation_functions(&self, ir: &mut String) -> Result<(), CodeGenError> {
189        if self.quotation_functions.is_empty() {
190            return Ok(());
191        }
192        writeln!(ir, "; Quotation functions")?;
193        ir.push_str(&self.quotation_functions);
194        writeln!(ir)?;
195        Ok(())
196    }
197
198    fn emit_ffi_c_declarations(&self, ir: &mut String) -> Result<(), CodeGenError> {
199        if self.ffi_bindings.functions.is_empty() {
200            return Ok(());
201        }
202        writeln!(ir, "; FFI C function declarations")?;
203        writeln!(ir, "declare ptr @malloc(i64)")?;
204        writeln!(ir, "declare void @free(ptr)")?;
205        writeln!(ir, "declare i64 @strlen(ptr)")?;
206        writeln!(ir, "declare ptr @memcpy(ptr, ptr, i64)")?;
207        // FFI string helpers from runtime
208        writeln!(ir, "declare ptr @patch_seq_string_to_cstring(ptr, ptr)")?;
209        writeln!(ir, "declare ptr @patch_seq_cstring_to_string(ptr, ptr)")?;
210        for func in self.ffi_bindings.functions.values() {
211            let c_ret_type = ffi_return_type(&func.return_spec);
212            let c_args = ffi_c_args(&func.args);
213            writeln!(ir, "declare {} @{}({})", c_ret_type, func.c_name, c_args)?;
214        }
215        writeln!(ir)?;
216        Ok(())
217    }
218
219    fn emit_ffi_wrappers_section(&self, ir: &mut String) -> Result<(), CodeGenError> {
220        if self.ffi_wrapper_code.is_empty() {
221            return Ok(());
222        }
223        writeln!(ir, "; FFI wrapper functions")?;
224        ir.push_str(&self.ffi_wrapper_code);
225        writeln!(ir)?;
226        Ok(())
227    }
228
229    /// Emit instrumentation globals for `--instrument` mode:
230    /// - `@seq_word_counters`: array of i64 counters (one per word)
231    /// - `@seq_word_name_K`: per-word C string constants
232    /// - `@seq_word_names`: array of pointers to name strings
233    fn emit_instrumentation_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
234        let n = self.word_instrument_ids.len();
235        if n == 0 {
236            return Ok(());
237        }
238
239        writeln!(ir, "; Instrumentation globals (--instrument)")?;
240
241        writeln!(
242            ir,
243            "@seq_word_counters = global [{} x i64] zeroinitializer",
244            n
245        )?;
246
247        // Sort by id for deterministic output
248        let mut words: Vec<(usize, &str)> = self
249            .word_instrument_ids
250            .iter()
251            .map(|(name, &id)| (id, name.as_str()))
252            .collect();
253        words.sort_by_key(|&(id, _)| id);
254
255        for &(id, name) in &words {
256            let name_bytes = name.as_bytes();
257            let len = name_bytes.len() + 1; // +1 for null terminator
258            let escaped: String = name_bytes
259                .iter()
260                .map(|&b| format!("\\{:02X}", b))
261                .collect::<String>();
262            writeln!(
263                ir,
264                "@seq_word_name_{} = private constant [{} x i8] c\"{}\\00\"",
265                id, len, escaped
266            )?;
267        }
268
269        let ptrs: Vec<String> = words
270            .iter()
271            .map(|&(id, _name)| format!("ptr @seq_word_name_{}", id))
272            .collect();
273        writeln!(
274            ir,
275            "@seq_word_names = private constant [{} x ptr] [{}]",
276            n,
277            ptrs.join(", ")
278        )?;
279
280        writeln!(ir)?;
281        Ok(())
282    }
283}