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        if self.instrument {
131            for (id, word) in program.words.iter().enumerate() {
132                self.word_instrument_ids.insert(word.name.clone(), id);
133            }
134        }
135
136        // Issue #355: detect `main ( -- Int )` so seq_main writes the top-of-
137        // stack int into the exit-code global before tearing down.
138        let main_word = program
139            .find_word("main")
140            .ok_or_else(|| CodeGenError::Logic("No main word defined".to_string()))?;
141        self.main_returns_int = main_returns_int_effect(main_word);
142
143        Ok(())
144    }
145
146    /// Generate code for every user-defined word, then emit `main`.
147    fn generate_words_and_main(&mut self, program: &Program) -> Result<(), CodeGenError> {
148        for word in &program.words {
149            self.codegen_word(word)?;
150        }
151        self.codegen_main()
152    }
153
154    /// Module ID and target triple — the first lines of the IR file.
155    fn emit_ir_header(&self, ir: &mut String) -> Result<(), CodeGenError> {
156        writeln!(ir, "; ModuleID = 'main'")?;
157        writeln!(ir, "target triple = \"{}\"", get_target_triple())?;
158        writeln!(ir)?;
159        Ok(())
160    }
161
162    /// Value type definition, string/symbol globals, and instrumentation
163    /// globals when `--instrument` is enabled.
164    fn emit_ir_type_and_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
165        self.emit_value_type_def(ir)?;
166        self.emit_string_and_symbol_globals(ir)?;
167        if self.instrument {
168            self.emit_instrumentation_globals(ir)?;
169        }
170        Ok(())
171    }
172
173    fn emit_external_builtins(&self, ir: &mut String) -> Result<(), CodeGenError> {
174        if self.external_builtins.is_empty() {
175            return Ok(());
176        }
177        writeln!(ir, "; External builtin declarations")?;
178        // All external builtins follow the standard stack convention: ptr -> ptr
179        for symbol in self.external_builtins.values() {
180            writeln!(ir, "declare ptr @{}(ptr)", symbol)?;
181        }
182        writeln!(ir)?;
183        Ok(())
184    }
185
186    fn emit_quotation_functions(&self, ir: &mut String) -> Result<(), CodeGenError> {
187        if self.quotation_functions.is_empty() {
188            return Ok(());
189        }
190        writeln!(ir, "; Quotation functions")?;
191        ir.push_str(&self.quotation_functions);
192        writeln!(ir)?;
193        Ok(())
194    }
195
196    fn emit_ffi_c_declarations(&self, ir: &mut String) -> Result<(), CodeGenError> {
197        if self.ffi_bindings.functions.is_empty() {
198            return Ok(());
199        }
200        writeln!(ir, "; FFI C function declarations")?;
201        writeln!(ir, "declare ptr @malloc(i64)")?;
202        writeln!(ir, "declare void @free(ptr)")?;
203        writeln!(ir, "declare i64 @strlen(ptr)")?;
204        writeln!(ir, "declare ptr @memcpy(ptr, ptr, i64)")?;
205        // FFI string helpers from runtime
206        writeln!(ir, "declare ptr @patch_seq_string_to_cstring(ptr, ptr)")?;
207        writeln!(ir, "declare ptr @patch_seq_cstring_to_string(ptr, ptr)")?;
208        for func in self.ffi_bindings.functions.values() {
209            let c_ret_type = ffi_return_type(&func.return_spec);
210            let c_args = ffi_c_args(&func.args);
211            writeln!(ir, "declare {} @{}({})", c_ret_type, func.c_name, c_args)?;
212        }
213        writeln!(ir)?;
214        Ok(())
215    }
216
217    fn emit_ffi_wrappers_section(&self, ir: &mut String) -> Result<(), CodeGenError> {
218        if self.ffi_wrapper_code.is_empty() {
219            return Ok(());
220        }
221        writeln!(ir, "; FFI wrapper functions")?;
222        ir.push_str(&self.ffi_wrapper_code);
223        writeln!(ir)?;
224        Ok(())
225    }
226
227    /// Emit instrumentation globals for `--instrument` mode:
228    /// - `@seq_word_counters`: array of i64 counters (one per word)
229    /// - `@seq_word_name_K`: per-word C string constants
230    /// - `@seq_word_names`: array of pointers to name strings
231    fn emit_instrumentation_globals(&self, ir: &mut String) -> Result<(), CodeGenError> {
232        let n = self.word_instrument_ids.len();
233        if n == 0 {
234            return Ok(());
235        }
236
237        writeln!(ir, "; Instrumentation globals (--instrument)")?;
238
239        writeln!(
240            ir,
241            "@seq_word_counters = global [{} x i64] zeroinitializer",
242            n
243        )?;
244
245        // Sort by id for deterministic output
246        let mut words: Vec<(usize, &str)> = self
247            .word_instrument_ids
248            .iter()
249            .map(|(name, &id)| (id, name.as_str()))
250            .collect();
251        words.sort_by_key(|&(id, _)| id);
252
253        for &(id, name) in &words {
254            let name_bytes = name.as_bytes();
255            let len = name_bytes.len() + 1; // +1 for null terminator
256            let escaped: String = name_bytes
257                .iter()
258                .map(|&b| format!("\\{:02X}", b))
259                .collect::<String>();
260            writeln!(
261                ir,
262                "@seq_word_name_{} = private constant [{} x i8] c\"{}\\00\"",
263                id, len, escaped
264            )?;
265        }
266
267        let ptrs: Vec<String> = words
268            .iter()
269            .map(|&(id, _name)| format!("ptr @seq_word_name_{}", id))
270            .collect();
271        writeln!(
272            ir,
273            "@seq_word_names = private constant [{} x ptr] [{}]",
274            n,
275            ptrs.join(", ")
276        )?;
277
278        writeln!(ir)?;
279        Ok(())
280    }
281}