Skip to main content

lamina_ir/
module.rs

1//! # Module Representation
2//!
3//! Top-level structures for representing complete
4//! Lamina IR modules. A module is the highest level of organization and
5//! contains all functions, types, and global variables that make up a program.
6//!
7//! ## Module Structure
8//!
9//! A module consists of:
10//! - **Functions**: All function definitions in the module (the executable code)
11//! - **Type Declarations**: Named type definitions and aliases for code reuse
12//! - **Global Variables**: Module-level data that persists for the lifetime of the program
13//! - **Metadata**: Additional information about the module (version, dependencies, etc.)
14//!
15//! ## Module Organization
16//!
17//! ### Functions (`HashMap<&str, Function>`)
18//! The main code of the module, organized by name for fast lookup:
19//! - **Entry Points**: Functions that can be called from outside the module
20//! - **Internal Functions**: Helper functions used within the module
21//! - **Exported Functions**: Functions marked with `@export` for external use
22//!
23//! ### Types (`HashMap<&str, TypeDeclaration>`)
24//! User-defined types that can be referenced by functions:
25//! - **Struct Types**: Custom data structures with named fields
26//! - **Array Types**: Fixed-size sequences of elements
27//! - **Alias Types**: Shorter names for complex type expressions
28//!
29//! ### Globals (`HashMap<&str, GlobalDeclaration>`)
30//! Module-level data that persists for the lifetime of the program:
31//! - **Constants**: Immutable values known at compile time
32//! - **Static Data**: Mutable data with module lifetime
33//! - **External References**: References to data in other modules
34//!
35//! ## Module Lifecycle
36//!
37//! ### Construction Phase
38//! ```rust
39//! use lamina_ir::IRBuilder;
40//!
41//! let mut builder = IRBuilder::new();
42//! builder
43//!     .function("main", lamina_ir::Type::Void)
44//!     // ... add instructions
45//!     .ret_void();
46//! ```
47//!
48//! ### Build Phase
49//! ```rust
50//! # use lamina_ir::IRBuilder;
51//! let mut builder = IRBuilder::new();
52//! let module = builder.build(); // Convert to Module struct
53//! ```
54//!
55//! ### Code Generation Phase
56//! ```rust
57//! # use lamina_ir::IRBuilder;
58//!
59//! let mut builder = IRBuilder::new();
60//! let module = builder.build();
61//! let mut assembly: Vec<u8> = Vec::new();
62//! // Code generation would write assembly bytes to the vector
63//! ```
64//!
65//! ## Module Validation
66//!
67//! Modules are validated to ensure:
68//! - **Function Signatures**: All referenced functions exist and have compatible signatures
69//! - **Type Consistency**: All type references are valid and properly defined
70//! - **Global References**: All global variables are properly declared
71//! - **SSA Form**: All variables follow Single Static Assignment rules
72//! - **Control Flow**: All basic blocks have valid terminators
73//!
74//! ## Examples
75//!
76//! ### Simple Module
77//! ```rust
78//! use lamina_ir::{IRBuilder, Type, PrimitiveType};
79//! use lamina_ir::builder::i32;
80//!
81//! let mut builder = IRBuilder::new();
82//!
83//! builder
84//!     .function("main", Type::Void)
85//!     .print(i32(42))
86//!     .ret_void();
87//!
88//! let module = builder.build();
89//! assert!(module.functions.contains_key("main"));
90//! ```
91//!
92//! ### Module with Custom Types
93//! ```rust
94//! use lamina_ir::{IRBuilder, Type, PrimitiveType, StructField};
95//! use lamina_ir::builder::{var, i32};
96//!
97//! let mut builder = IRBuilder::new();
98//!
99//! builder
100//!     .function("use_custom_type", Type::Void)
101//!     // Allocate a struct with two i32 fields
102//!     .alloc_stack("point", Type::Struct(vec![
103//!         StructField { name: "x", ty: Type::Primitive(PrimitiveType::I32) },
104//!         StructField { name: "y", ty: Type::Primitive(PrimitiveType::I32) }
105//!     ]))
106//!     // Use the allocated struct
107//!     .ret_void();
108//!
109//! let module = builder.build();
110//! ```
111//!
112//! ### Multi-Function Module
113//! ```rust
114//! use lamina_ir::{IRBuilder, Type, PrimitiveType, BinaryOp};
115//! use lamina_ir::builder::{var, i32};
116//!
117//! let mut builder = IRBuilder::new();
118//!
119//! // Helper function
120//! builder
121//!     .function("double", Type::Primitive(PrimitiveType::I32))
122//!     .binary(BinaryOp::Mul, "result", PrimitiveType::I32, var("value"), i32(2))
123//!     .ret(Type::Primitive(PrimitiveType::I32), var("result"));
124//!
125//! // Main function
126//! builder
127//!     .function("main", Type::Void)
128//!     .call(Some("result"), "double", vec![i32(21)])
129//!     .print(var("result")) // Should print 42
130//!     .ret_void();
131//!
132//! let module = builder.build();
133//! ```
134
135use std::collections::HashMap; // Using HashMap for functions and types
136use std::fmt;
137
138use super::function::Function;
139use super::types::{Identifier, Type, Value};
140
141/// A named type declaration (e.g., `type @Vec2 = struct { ... }`).
142///
143/// Type declarations allow you to define custom types that can be referenced
144/// throughout the module. This is useful for creating reusable type definitions
145/// and making the IR more readable.
146///
147/// # Examples
148///
149/// ```rust
150/// use lamina_ir::{TypeDeclaration, Type, StructField, PrimitiveType, Value, Literal};
151///
152/// let point_type = TypeDeclaration {
153///     name: "Point",
154///     ty: Type::Struct(vec![
155///         StructField { name: "x", ty: Type::Primitive(PrimitiveType::I32) },
156///         StructField { name: "y", ty: Type::Primitive(PrimitiveType::I32) },
157///     ]),
158/// };
159/// ```
160#[derive(Debug, Clone, PartialEq, Eq, Hash)] // Eq might be too restrictive if Type contains f32 indirectly, but should be okay here.
161pub struct TypeDeclaration<'a> {
162    /// The type name (without the `@` prefix)
163    pub name: Identifier<'a>, // e.g., "@Vec2"
164    /// The type definition
165    pub ty: Type<'a>,
166}
167
168/// A global variable declaration (e.g., `global @message: [5 x i8] = "hello"`).
169///
170/// Global variables are module-level data that persists for the lifetime of the program.
171/// They can be initialized with constant values or left uninitialized (for external symbols).
172///
173/// # Examples
174///
175/// ```rust
176/// use lamina_ir::{GlobalDeclaration, Type, PrimitiveType, Value, Literal};
177///
178/// let message_global = GlobalDeclaration {
179///     name: "message",
180///     ty: Type::Array {
181///         element_type: Box::new(Type::Primitive(PrimitiveType::I8)),
182///         size: 13,
183///     },
184///     initializer: Some(Value::Constant(Literal::String("Hello, World!"))),
185/// };
186/// ```
187#[derive(Debug, Clone, PartialEq)] // PartialEq due to Value potentially holding f32
188pub struct GlobalDeclaration<'a> {
189    /// The global variable name (without the `@` prefix)
190    pub name: Identifier<'a>, // e.g., "@message"
191    /// The type of the global variable
192    pub ty: Type<'a>,
193    /// Optional initializer value (None for external symbols)
194    pub initializer: Option<Value<'a>>, // Globals might be uninitialized (extern)
195}
196
197/// A complete Lamina IR module (file).
198///
199/// A module is the top-level container for all IR code. It contains functions,
200/// type declarations, and global variables that together form a complete program.
201///
202/// # Structure
203///
204/// - **Functions**: All function definitions, keyed by name
205/// - **Type Declarations**: Named type definitions, keyed by name
206/// - **Global Variables**: Module-level data, keyed by name
207///
208/// # Examples
209///
210/// ```rust
211/// use lamina_ir::{IRBuilder, Type, PrimitiveType, BinaryOp};
212/// use lamina_ir::builder::{var, i32};
213///
214/// let mut builder = IRBuilder::new();
215/// builder
216///     .function("add", Type::Primitive(PrimitiveType::I32))
217///     .binary(BinaryOp::Add, "result", PrimitiveType::I32, i32(10), i32(20))
218///     .ret(Type::Primitive(PrimitiveType::I32), var("result"));
219///
220/// let module = builder.build();
221/// // module.functions contains the "add" function
222/// // module.type_declarations is empty
223/// // module.global_declarations is empty
224/// ```
225///
226/// ## Module Annotations
227///
228/// Modules can have attributes that affect how the entire compilation unit is treated.
229/// These annotations provide global control over optimization, linking, and code generation.
230///
231/// ### Available Module Annotations
232/// - **`@pic`**: Generate position-independent code
233/// - **`@pie`**: Generate position-independent executable
234/// - **`@optimize_speed`**: Optimize for execution speed
235/// - **`@optimize_size`**: Optimize for code size
236/// - **`@debug`**: Include debug information
237/// - **`@strip`**: Strip debug information and symbols
238/// - **`@target_triple`**: Specify target triple (e.g., "x86_64-linux-gnu")
239#[cfg(feature = "nightly")]
240#[derive(Debug, Clone, PartialEq)]
241pub enum ModuleAnnotation {
242    /// Generate position-independent code (PIC).
243    ///
244    /// PIC lets the code be loaded at any address in memory, which is
245    /// required for shared libraries and improves security through ASLR.
246    PositionIndependentCode,
247
248    /// Generate position-independent executable (PIE).
249    ///
250    /// PIE creates executables that can be loaded at random addresses,
251    /// providing additional security benefits.
252    PositionIndependentExecutable,
253
254    /// Optimize this module for execution speed.
255    ///
256    /// This may increase code size but should improve runtime performance.
257    OptimizeForSpeed,
258
259    /// Optimize this module for code size.
260    ///
261    /// This may reduce performance but will create smaller binaries.
262    OptimizeForSize,
263
264    /// Include debug information in the compiled output.
265    ///
266    /// Debug information improves debugging and profiling.
267    /// but increases the size of the final binary.
268    IncludeDebugInfo,
269
270    /// Strip debug information and symbols from the compiled output.
271    ///
272    /// This reduces binary size and removes potentially sensitive information
273    /// but makes debugging impossible.
274    StripSymbols,
275
276    /// Specify the target triple for this module.
277    ///
278    /// The target triple identifies the architecture, vendor, OS, and ABI
279    /// for which the code should be compiled (e.g., "x86_64-unknown-linux-gnu").
280    TargetTriple(String),
281}
282
283#[cfg(feature = "nightly")]
284impl fmt::Display for ModuleAnnotation {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        match self {
287            ModuleAnnotation::PositionIndependentCode => write!(f, "@pic"),
288            ModuleAnnotation::PositionIndependentExecutable => write!(f, "@pie"),
289            ModuleAnnotation::OptimizeForSpeed => write!(f, "@optimize_speed"),
290            ModuleAnnotation::OptimizeForSize => write!(f, "@optimize_size"),
291            ModuleAnnotation::IncludeDebugInfo => write!(f, "@debug"),
292            ModuleAnnotation::StripSymbols => write!(f, "@strip"),
293            ModuleAnnotation::TargetTriple(triple) => write!(f, "@target_triple({})", triple),
294        }
295    }
296}
297
298#[derive(Debug, Clone, PartialEq)]
299pub struct Module<'a> {
300    /// Named type declarations in this module
301    ///
302    /// Using a HashMap enables efficient lookup by type name. The order
303    /// of type declarations is not preserved, but this is typically not critical.
304    pub type_declarations: HashMap<Identifier<'a>, TypeDeclaration<'a>>,
305    /// Global variable declarations in this module
306    ///
307    /// Global variables are module-level data that persists for the lifetime
308    /// of the program. They can be initialized with constant values.
309    pub global_declarations: HashMap<Identifier<'a>, GlobalDeclaration<'a>>,
310    /// Function definitions in this module
311    ///
312    /// Functions contain the actual code of the module. They are organized
313    /// by name for fast lookup during function calls.
314    pub functions: HashMap<Identifier<'a>, Function<'a>>,
315
316    /// Module-level annotations that affect compilation.
317    ///
318    /// These annotations control global aspects of how the module is compiled,
319    /// such as optimization level, code generation options, and linking behavior.
320    #[cfg(feature = "nightly")]
321    pub annotations: Vec<ModuleAnnotation>,
322}
323
324impl<'a> Module<'a> {
325    // Basic constructor
326    pub fn new() -> Self {
327        #[cfg(feature = "nightly")]
328        let annotations = Vec::new();
329
330        Module {
331            type_declarations: HashMap::new(),
332            global_declarations: HashMap::new(),
333            functions: HashMap::new(),
334            #[cfg(feature = "nightly")]
335            annotations,
336        }
337    }
338
339    /// Validate all functions in the module.
340    ///
341    /// Runs `Function::validate()` on every function and aggregates all errors.
342    /// Returns `Ok(())` if no errors are found, or a list of all violations.
343    pub fn validate(&self) -> Result<(), Vec<String>> {
344        let mut all_errors: Vec<String> = Vec::new();
345        for func in self.functions.values() {
346            if let Err(errs) = func.validate() {
347                all_errors.extend(errs);
348            }
349        }
350        if all_errors.is_empty() {
351            Ok(())
352        } else {
353            Err(all_errors)
354        }
355    }
356}
357
358impl Default for Module<'_> {
359    fn default() -> Self {
360        Self::new()
361    }
362}
363
364// --- Display Implementations ---
365
366impl fmt::Display for TypeDeclaration<'_> {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(f, "type @{} = {}", self.name, self.ty)
369    }
370}
371
372impl fmt::Display for GlobalDeclaration<'_> {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        write!(f, "global @{}: {}", self.name, self.ty)?;
375        if let Some(init) = &self.initializer {
376            write!(f, " = {}", init)?;
377        }
378        Ok(())
379    }
380}
381
382impl fmt::Display for Module<'_> {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        // Print type declarations first
385        for decl in self.type_declarations.values() {
386            writeln!(f, "{}", decl)?;
387        }
388        if !self.type_declarations.is_empty() {
389            writeln!(f)?;
390        }
391
392        // Print global declarations
393        for decl in self.global_declarations.values() {
394            writeln!(f, "{}", decl)?;
395        }
396        if !self.global_declarations.is_empty() {
397            writeln!(f)?;
398        }
399
400        // Print functions
401        let mut function_names: Vec<_> = self.functions.keys().collect();
402        function_names.sort(); // Sort by name for consistent output order
403
404        for (i, name) in function_names.iter().enumerate() {
405            // Dereference name twice (&&str -> &str) to match the key type for lookup
406            if let Some(func) = self.functions.get(*name) {
407                write!(f, "{}", func)?;
408                if i < function_names.len() - 1 {
409                    writeln!(f)?;
410                }
411            }
412        }
413
414        Ok(())
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use crate::function::{BasicBlock, Function, FunctionParameter, FunctionSignature};
422    use crate::instruction::{BinaryOp, Instruction};
423    use crate::types::{Literal, PrimitiveType, Type, Value};
424    use std::collections::HashMap;
425
426    #[test]
427    fn test_display_type_declaration() {
428        let decl = TypeDeclaration {
429            name: "MyType",
430            ty: Type::Struct(vec![]), // Empty struct
431        };
432        assert_eq!(format!("{}", decl), "type @MyType = struct {  }");
433    }
434
435    #[test]
436    fn test_display_global_declaration() {
437        let decl1 = GlobalDeclaration {
438            name: "count",
439            ty: Type::Primitive(PrimitiveType::I32),
440            initializer: Some(Value::Constant(Literal::I32(0))),
441        };
442        assert_eq!(format!("{}", decl1), "global @count: i32 = 0");
443
444        let decl2 = GlobalDeclaration {
445            name: "external_data",
446            ty: Type::Array {
447                element_type: Box::new(Type::Primitive(PrimitiveType::I8)),
448                size: 256,
449            },
450            initializer: None,
451        };
452        assert_eq!(format!("{}", decl2), "global @external_data: [256 x i8]");
453    }
454
455    #[test]
456    fn test_display_module() {
457        let mut module = Module::new();
458
459        module.type_declarations.insert(
460            "Vec2",
461            TypeDeclaration {
462                name: "Vec2",
463                ty: Type::Struct(vec![
464                    crate::types::StructField {
465                        name: "x",
466                        ty: Type::Primitive(PrimitiveType::F32),
467                    },
468                    crate::types::StructField {
469                        name: "y",
470                        ty: Type::Primitive(PrimitiveType::F32),
471                    },
472                ]),
473            },
474        );
475
476        module.global_declarations.insert(
477            "PI",
478            GlobalDeclaration {
479                name: "PI",
480                ty: Type::Primitive(PrimitiveType::F32),
481                initializer: Some(Value::Constant(Literal::F32(std::f32::consts::PI))),
482            },
483        );
484
485        let func_sig = FunctionSignature {
486            params: vec![FunctionParameter {
487                name: "a",
488                ty: Type::Primitive(PrimitiveType::I32),
489                annotations: vec![],
490            }],
491            return_type: Type::Primitive(PrimitiveType::I32),
492        };
493        let mut func_blocks = HashMap::new();
494        func_blocks.insert(
495            "entry",
496            BasicBlock {
497                instructions: vec![
498                    Instruction::Binary {
499                        op: BinaryOp::Add,
500                        result: "res",
501                        ty: PrimitiveType::I32,
502                        lhs: Value::Variable("a"),
503                        rhs: Value::Constant(Literal::I32(1)),
504                    },
505                    Instruction::Ret {
506                        ty: Type::Primitive(PrimitiveType::I32),
507                        value: Some(Value::Variable("res")),
508                    },
509                ],
510            },
511        );
512        let func = Function {
513            name: "add_one",
514            signature: func_sig,
515            annotations: vec![],
516            basic_blocks: func_blocks,
517            entry_block: "entry",
518        };
519        module.functions.insert("add_one", func);
520
521        let expected_output = format!(
522            "type @Vec2 = struct {{ x: f32, y: f32 }}\n\nglobal @PI: f32 = {}\n\nfn @add_one(i32 %a) -> i32 {{\nentry:\n  %res = add.i32 %a, 1\n  ret.i32 %res\n}}\n",
523            std::f32::consts::PI
524        );
525
526        // Display sorts functions, but type/global maps still depend on insertion order.
527        assert_eq!(format!("{}", module), expected_output);
528    }
529
530    #[test]
531    fn test_display_empty_module() {
532        let module = Module::<'static>::new();
533        assert_eq!(format!("{}", module), "");
534    }
535}