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}