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        Ok(ir)
73    }
74
75    /// Generate LLVM IR for entire program with FFI support.
76    ///
77    /// This is the main entry point for compiling programs that use FFI.
78    pub fn codegen_program_with_ffi(
79        &mut self,
80        program: &Program,
81        type_map: HashMap<usize, Type>,
82        statement_types: HashMap<(String, usize), Type>,
83        config: &CompilerConfig,
84        ffi_bindings: &FfiBindings,
85    ) -> Result<String, CodeGenError> {
86        self.ffi_bindings = ffi_bindings.clone();
87        self.generate_ffi_wrappers()?;
88
89        self.prepare_program_state(program, type_map, statement_types, config)?;
90        self.generate_words_and_main(program)?;
91
92        let mut ir = String::new();
93        self.emit_ir_header(&mut ir)?;
94        self.emit_ir_type_and_globals(&mut ir)?;
95        emit_runtime_decls(&mut ir)?;
96        self.emit_ffi_c_declarations(&mut ir)?;
97        self.emit_external_builtins(&mut ir)?;
98        self.emit_ffi_wrappers_section(&mut ir)?;
99        self.emit_quotation_functions(&mut ir)?;
100        ir.push_str(&self.output);
101        Ok(ir)
102    }
103
104    // =========================================================================
105    // Shared program-generation helpers
106    // =========================================================================
107
108    /// Copy typechecker outputs and config-derived state onto the CodeGen,
109    /// and sanity-check the presence/shape of `main`.
110    fn prepare_program_state(
111        &mut self,
112        program: &Program,
113        type_map: HashMap<usize, Type>,
114        statement_types: HashMap<(String, usize), Type>,
115        config: &CompilerConfig,
116    ) -> Result<(), CodeGenError> {
117        self.type_map = type_map;
118        self.statement_types = statement_types;
119        // resolved_sugar is set separately via set_resolved_sugar()
120        self.unions = program.unions.clone();
121        self.external_builtins = config
122            .external_builtins
123            .iter()
124            .map(|b| (b.seq_name.clone(), b.symbol.clone()))
125            .collect();
126
127        self.instrument = config.instrument;
128        if self.instrument {
129            for (id, word) in program.words.iter().enumerate() {
130                self.word_instrument_ids.insert(word.name.clone(), id);
131            }
132        }
133
134        // Issue #355: detect `main ( -- Int )` so seq_main writes the top-of-
135        // stack int into the exit-code global before tearing down.
136        let main_word = program
137            .find_word("main")
138            .ok_or_else(|| CodeGenError::Logic("No main word defined".to_string()))?;
139        self.main_returns_int = main_returns_int_effect(main_word);
140
141        Ok(())
142    }
143
144    /// Generate code for every user-defined word, then emit `main`.
145    fn generate_words_and_main(&mut self, program: &Program) -> Result<(), CodeGenError> {
146        for word in &program.words {
147            self.codegen_word(word)?;
148        }
149        self.codegen_main()
150    }
151
152    /// Module ID and target triple — the first lines of the IR file.
153    fn emit_ir_header(&self, ir: &mut String) -> Result<(), CodeGenError> {
154        writeln!(ir, "; ModuleID = 'main'")?;
155        writeln!(ir, "target triple = \"{}\"", get_target_triple())?;
156        writeln!(ir)?;
157        Ok(())
158    }
159
160    /// Value type definition, string/symbol globals, and instrumentation
161    /// globals when `--instrument` is enabled.
162    fn emit_ir_type_and_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
163        self.emit_value_type_def(ir)?;
164        self.emit_string_and_symbol_globals(ir)?;
165        if self.instrument {
166            self.emit_instrumentation_globals(ir)?;
167        }
168        Ok(())
169    }
170
171    fn emit_external_builtins(&self, ir: &mut String) -> Result<(), CodeGenError> {
172        if self.external_builtins.is_empty() {
173            return Ok(());
174        }
175        writeln!(ir, "; External builtin declarations")?;
176        // All external builtins follow the standard stack convention: ptr -> ptr
177        for symbol in self.external_builtins.values() {
178            writeln!(ir, "declare ptr @{}(ptr)", symbol)?;
179        }
180        writeln!(ir)?;
181        Ok(())
182    }
183
184    fn emit_quotation_functions(&self, ir: &mut String) -> Result<(), CodeGenError> {
185        if self.quotation_functions.is_empty() {
186            return Ok(());
187        }
188        writeln!(ir, "; Quotation functions")?;
189        ir.push_str(&self.quotation_functions);
190        writeln!(ir)?;
191        Ok(())
192    }
193
194    fn emit_ffi_c_declarations(&self, ir: &mut String) -> Result<(), CodeGenError> {
195        if self.ffi_bindings.functions.is_empty() {
196            return Ok(());
197        }
198        writeln!(ir, "; FFI C function declarations")?;
199        writeln!(ir, "declare ptr @malloc(i64)")?;
200        writeln!(ir, "declare void @free(ptr)")?;
201        writeln!(ir, "declare i64 @strlen(ptr)")?;
202        writeln!(ir, "declare ptr @memcpy(ptr, ptr, i64)")?;
203        // FFI string helpers from runtime
204        writeln!(ir, "declare ptr @patch_seq_string_to_cstring(ptr, ptr)")?;
205        writeln!(ir, "declare ptr @patch_seq_cstring_to_string(ptr, ptr)")?;
206        for func in self.ffi_bindings.functions.values() {
207            let c_ret_type = ffi_return_type(&func.return_spec);
208            let c_args = ffi_c_args(&func.args);
209            writeln!(ir, "declare {} @{}({})", c_ret_type, func.c_name, c_args)?;
210        }
211        writeln!(ir)?;
212        Ok(())
213    }
214
215    fn emit_ffi_wrappers_section(&self, ir: &mut String) -> Result<(), CodeGenError> {
216        if self.ffi_wrapper_code.is_empty() {
217            return Ok(());
218        }
219        writeln!(ir, "; FFI wrapper functions")?;
220        ir.push_str(&self.ffi_wrapper_code);
221        writeln!(ir)?;
222        Ok(())
223    }
224
225    /// Emit instrumentation globals for `--instrument` mode:
226    /// - `@seq_word_counters`: array of i64 counters (one per word)
227    /// - `@seq_word_name_K`: per-word C string constants
228    /// - `@seq_word_names`: array of pointers to name strings
229    fn emit_instrumentation_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
230        let n = self.word_instrument_ids.len();
231        if n == 0 {
232            return Ok(());
233        }
234
235        writeln!(ir, "; Instrumentation globals (--instrument)")?;
236
237        writeln!(
238            ir,
239            "@seq_word_counters = global [{} x i64] zeroinitializer",
240            n
241        )?;
242
243        // Sort by id for deterministic output
244        let mut words: Vec<(usize, &str)> = self
245            .word_instrument_ids
246            .iter()
247            .map(|(name, &id)| (id, name.as_str()))
248            .collect();
249        words.sort_by_key(|&(id, _)| id);
250
251        for &(id, name) in &words {
252            let name_bytes = name.as_bytes();
253            let len = name_bytes.len() + 1; // +1 for null terminator
254            let escaped: String = name_bytes
255                .iter()
256                .map(|&b| format!("\\{:02X}", b))
257                .collect::<String>();
258            writeln!(
259                ir,
260                "@seq_word_name_{} = private constant [{} x i8] c\"{}\\00\"",
261                id, len, escaped
262            )?;
263        }
264
265        let ptrs: Vec<String> = words
266            .iter()
267            .map(|&(id, _name)| format!("ptr @seq_word_name_{}", id))
268            .collect();
269        writeln!(
270            ir,
271            "@seq_word_names = private constant [{} x ptr] [{}]",
272            n,
273            ptrs.join(", ")
274        )?;
275
276        writeln!(ir)?;
277        Ok(())
278    }
279}