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        // Store type map for use during code generation
63        self.type_map = type_map;
64        self.statement_types = statement_types;
65        // resolved_sugar is set separately via set_resolved_sugar()
66
67        // Store union definitions for pattern matching
68        self.unions = program.unions.clone();
69
70        // Build external builtins map from config
71        self.external_builtins = config
72            .external_builtins
73            .iter()
74            .map(|b| (b.seq_name.clone(), b.symbol.clone()))
75            .collect();
76
77        // Flow instrumentation config
78        self.instrument = config.instrument;
79        if self.instrument {
80            for (id, word) in program.words.iter().enumerate() {
81                self.word_instrument_ids.insert(word.name.clone(), id);
82            }
83        }
84
85        // Verify we have a main word and detect its return shape (Issue #355)
86        let main_word = program
87            .find_word("main")
88            .ok_or_else(|| CodeGenError::Logic("No main word defined".to_string()))?;
89        self.main_returns_int = main_returns_int_effect(main_word);
90
91        // Generate all user-defined words
92        for word in &program.words {
93            self.codegen_word(word)?;
94        }
95
96        // Generate main function
97        self.codegen_main()?;
98
99        // Assemble final IR
100        let mut ir = String::new();
101
102        // Target and type declarations
103        writeln!(&mut ir, "; ModuleID = 'main'")?;
104        writeln!(&mut ir, "target triple = \"{}\"", get_target_triple())?;
105        writeln!(&mut ir)?;
106
107        // Value type definition (8-byte tagged pointer)
108        self.emit_value_type_def(&mut ir)?;
109
110        // String and symbol constants
111        self.emit_string_and_symbol_globals(&mut ir)?;
112
113        // Instrumentation globals (when --instrument)
114        if self.instrument {
115            self.emit_instrumentation_globals(&mut ir)?;
116        }
117
118        // Runtime function declarations
119        emit_runtime_decls(&mut ir)?;
120
121        // External builtin declarations (from config)
122        if !self.external_builtins.is_empty() {
123            writeln!(&mut ir, "; External builtin declarations")?;
124            for symbol in self.external_builtins.values() {
125                // All external builtins follow the standard stack convention: ptr -> ptr
126                writeln!(&mut ir, "declare ptr @{}(ptr)", symbol)?;
127            }
128            writeln!(&mut ir)?;
129        }
130
131        // Quotation functions (generated from quotation literals)
132        if !self.quotation_functions.is_empty() {
133            writeln!(&mut ir, "; Quotation functions")?;
134            ir.push_str(&self.quotation_functions);
135            writeln!(&mut ir)?;
136        }
137
138        // User-defined words and main
139        ir.push_str(&self.output);
140
141        Ok(ir)
142    }
143
144    /// Generate LLVM IR for entire program with FFI support
145    ///
146    /// This is the main entry point for compiling programs that use FFI.
147    pub fn codegen_program_with_ffi(
148        &mut self,
149        program: &Program,
150        type_map: HashMap<usize, Type>,
151        statement_types: HashMap<(String, usize), Type>,
152        config: &CompilerConfig,
153        ffi_bindings: &FfiBindings,
154    ) -> Result<String, CodeGenError> {
155        // Store FFI bindings
156        self.ffi_bindings = ffi_bindings.clone();
157
158        // Generate FFI wrapper functions
159        self.generate_ffi_wrappers()?;
160
161        // Store type map for use during code generation
162        self.type_map = type_map;
163        self.statement_types = statement_types;
164
165        // Store union definitions for pattern matching
166        self.unions = program.unions.clone();
167
168        // Build external builtins map from config
169        self.external_builtins = config
170            .external_builtins
171            .iter()
172            .map(|b| (b.seq_name.clone(), b.symbol.clone()))
173            .collect();
174
175        // Flow instrumentation config
176        self.instrument = config.instrument;
177        if self.instrument {
178            for (id, word) in program.words.iter().enumerate() {
179                self.word_instrument_ids.insert(word.name.clone(), id);
180            }
181        }
182
183        // Verify we have a main word and detect its return shape (Issue #355)
184        let main_word = program
185            .find_word("main")
186            .ok_or_else(|| CodeGenError::Logic("No main word defined".to_string()))?;
187        self.main_returns_int = main_returns_int_effect(main_word);
188
189        // Generate all user-defined words
190        for word in &program.words {
191            self.codegen_word(word)?;
192        }
193
194        // Generate main function
195        self.codegen_main()?;
196
197        // Assemble final IR
198        let mut ir = String::new();
199
200        // Target and type declarations
201        writeln!(&mut ir, "; ModuleID = 'main'")?;
202        writeln!(&mut ir, "target triple = \"{}\"", get_target_triple())?;
203        writeln!(&mut ir)?;
204
205        // Value type definition (8-byte tagged pointer)
206        self.emit_value_type_def(&mut ir)?;
207
208        // String and symbol constants
209        self.emit_string_and_symbol_globals(&mut ir)?;
210
211        // Instrumentation globals (when --instrument)
212        if self.instrument {
213            self.emit_instrumentation_globals(&mut ir)?;
214        }
215
216        // Runtime function declarations (same as codegen_program_with_config)
217        self.emit_runtime_declarations(&mut ir)?;
218
219        // FFI C function declarations
220        if !self.ffi_bindings.functions.is_empty() {
221            writeln!(&mut ir, "; FFI C function declarations")?;
222            writeln!(&mut ir, "declare ptr @malloc(i64)")?;
223            writeln!(&mut ir, "declare void @free(ptr)")?;
224            writeln!(&mut ir, "declare i64 @strlen(ptr)")?;
225            writeln!(&mut ir, "declare ptr @memcpy(ptr, ptr, i64)")?;
226            // Declare FFI string helpers from runtime
227            writeln!(
228                &mut ir,
229                "declare ptr @patch_seq_string_to_cstring(ptr, ptr)"
230            )?;
231            writeln!(
232                &mut ir,
233                "declare ptr @patch_seq_cstring_to_string(ptr, ptr)"
234            )?;
235            for func in self.ffi_bindings.functions.values() {
236                let c_ret_type = ffi_return_type(&func.return_spec);
237                let c_args = ffi_c_args(&func.args);
238                writeln!(
239                    &mut ir,
240                    "declare {} @{}({})",
241                    c_ret_type, func.c_name, c_args
242                )?;
243            }
244            writeln!(&mut ir)?;
245        }
246
247        // External builtin declarations (from config)
248        if !self.external_builtins.is_empty() {
249            writeln!(&mut ir, "; External builtin declarations")?;
250            for symbol in self.external_builtins.values() {
251                writeln!(&mut ir, "declare ptr @{}(ptr)", symbol)?;
252            }
253            writeln!(&mut ir)?;
254        }
255
256        // FFI wrapper functions
257        if !self.ffi_wrapper_code.is_empty() {
258            writeln!(&mut ir, "; FFI wrapper functions")?;
259            ir.push_str(&self.ffi_wrapper_code);
260            writeln!(&mut ir)?;
261        }
262
263        // Quotation functions
264        if !self.quotation_functions.is_empty() {
265            writeln!(&mut ir, "; Quotation functions")?;
266            ir.push_str(&self.quotation_functions);
267            writeln!(&mut ir)?;
268        }
269
270        // User-defined words and main
271        ir.push_str(&self.output);
272
273        Ok(ir)
274    }
275
276    /// Emit runtime function declarations
277    pub(super) fn emit_runtime_declarations(&self, ir: &mut String) -> Result<(), CodeGenError> {
278        emit_runtime_decls(ir)
279    }
280
281    /// Emit instrumentation globals for --instrument mode
282    ///
283    /// Generates:
284    /// - @seq_word_counters: array of i64 counters (one per word)
285    /// - @seq_word_name_K: per-word C string constants
286    /// - @seq_word_names: array of pointers to name strings
287    fn emit_instrumentation_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
288        let n = self.word_instrument_ids.len();
289        if n == 0 {
290            return Ok(());
291        }
292
293        writeln!(ir, "; Instrumentation globals (--instrument)")?;
294
295        // Counter array: [N x i64] zeroinitializer
296        writeln!(
297            ir,
298            "@seq_word_counters = global [{} x i64] zeroinitializer",
299            n
300        )?;
301
302        // Build sorted list of (id, name) for deterministic output
303        let mut words: Vec<(usize, &str)> = self
304            .word_instrument_ids
305            .iter()
306            .map(|(name, &id)| (id, name.as_str()))
307            .collect();
308        words.sort_by_key(|&(id, _)| id);
309
310        // Per-word name string constants
311        for &(id, name) in &words {
312            let name_bytes = name.as_bytes();
313            let len = name_bytes.len() + 1; // +1 for null terminator
314            let escaped: String = name_bytes
315                .iter()
316                .map(|&b| format!("\\{:02X}", b))
317                .collect::<String>();
318            writeln!(
319                ir,
320                "@seq_word_name_{} = private constant [{} x i8] c\"{}\\00\"",
321                id, len, escaped
322            )?;
323        }
324
325        // Name pointer table
326        let ptrs: Vec<String> = words
327            .iter()
328            .map(|&(id, _name)| format!("ptr @seq_word_name_{}", id))
329            .collect();
330        writeln!(
331            ir,
332            "@seq_word_names = private constant [{} x ptr] [{}]",
333            n,
334            ptrs.join(", ")
335        )?;
336
337        writeln!(ir)?;
338        Ok(())
339    }
340}