## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/middleend/mir/builder.rs

    Line
    Count
    Source
    1
    //! MIR Builder - Provides a convenient API for constructing MIR
    2
    3
    use super::types::{
    4
        BasicBlock, BinOp, BlockId, CastKind, Constant, Function, Local, LocalDecl, Mutability,
    5
        Operand, Place, Rvalue, Statement, Terminator, Type, UnOp,
    6
    };
    7
    use std::collections::HashMap;
    8
    9
    /// Builder for constructing MIR programs
    10
    pub struct MirBuilder {
    11
        /// Current function being built
    12
        current_function: Option<Function>,
    13
        /// Next local variable ID
    14
        next_local: usize,
    15
        /// Next block ID
    16
        next_block: usize,
    17
        /// Mapping from names to locals
    18
        local_map: HashMap<String, Local>,
    19
    }
    20
    21
    impl MirBuilder {
    22
        /// Create a new MIR builder
    23
        #[must_use]
    24
    0
        pub fn new() -> Self {
    25
    0
            Self {
    26
    0
                current_function: None,
    27
    0
                next_local: 0,
    28
    0
                next_block: 0,
    29
    0
                local_map: HashMap::new(),
    30
    0
            }
    31
    0
        }
    32
    33
        /// Start building a new function
    34
    0
        pub fn start_function(&mut self, name: String, return_ty: Type) -> &mut Self {
    35
    0
            self.current_function = Some(Function {
    36
    0
                name,
    37
    0
                params: Vec::new(),
    38
    0
                return_ty,
    39
    0
                locals: Vec::new(),
    40
    0
                blocks: Vec::new(),
    41
    0
                entry_block: BlockId(0),
    42
    0
            });
    43
    0
            self.next_local = 0;
    44
    0
            self.next_block = 0;
    45
    0
            self.local_map.clear();
    46
    0
            self
    47
    0
        }
    48
    49
        /// Add a parameter to the current function
    50
    0
        pub fn add_param(&mut self, name: String, ty: Type) -> Local {
    51
    0
            let local = self.alloc_local(ty, true, Some(name.clone()));
    52
    0
            if let Some(ref mut func) = self.current_function {
    53
    0
                func.params.push(local);
    54
    0
            }
    55
    0
            self.local_map.insert(name, local);
    56
    0
            local
    57
    0
        }
    58
    59
        /// Allocate a new local variable
    60
    0
        pub fn alloc_local(&mut self, ty: Type, mutable: bool, name: Option<String>) -> Local {
    61
    0
            let id = Local(self.next_local);
    62
    0
            self.next_local += 1;
    63
    64
    0
            let decl = LocalDecl {
    65
    0
                id,
    66
    0
                ty,
    67
    0
                mutable,
    68
    0
                name: name.clone(),
    69
    0
            };
    70
    71
    0
            if let Some(ref mut func) = self.current_function {
    72
    0
                func.locals.push(decl);
    73
    0
            }
    74
    75
    0
            if let Some(n) = name {
    76
    0
                self.local_map.insert(n, id);
    77
    0
            }
    78
    79
    0
            id
    80
    0
        }
    81
    82
        /// Get a local by name
    83
    0
        pub fn get_local(&self, name: &str) -> Option<Local> {
    84
    0
            self.local_map.get(name).copied()
    85
    0
        }
    86
    87
        /// Create a new basic block
    88
    0
        pub fn new_block(&mut self) -> BlockId {
    89
    0
            let id = BlockId(self.next_block);
    90
    0
            self.next_block += 1;
    91
    92
    0
            if let Some(ref mut func) = self.current_function {
    93
    0
                func.blocks.push(BasicBlock {
    94
    0
                    id,
    95
    0
                    statements: Vec::new(),
    96
    0
                    terminator: Terminator::Unreachable,
    97
    0
                });
    98
    0
            }
    99
    100
    0
            id
    101
    0
        }
    102
    103
        /// Get a mutable reference to a block
    104
    0
        pub fn block_mut(&mut self, id: BlockId) -> Option<&mut BasicBlock> {
    105
    0
            self.current_function
    106
    0
                .as_mut()
    107
    0
                .and_then(|f| f.blocks.get_mut(id.0))
    108
    0
        }
    109
    110
        /// Add a statement to a block
    111
    0
        pub fn push_statement(&mut self, block: BlockId, stmt: Statement) {
    112
    0
            if let Some(bb) = self.block_mut(block) {
    113
    0
                bb.statements.push(stmt);
    114
    0
            }
    115
    0
        }
    116
    117
        /// Set the terminator for a block
    118
    0
        pub fn set_terminator(&mut self, block: BlockId, term: Terminator) {
    119
    0
            if let Some(bb) = self.block_mut(block) {
    120
    0
                bb.terminator = term;
    121
    0
            }
    122
    0
        }
    123
    124
        /// Finish building the current function
    125
    0
        pub fn finish_function(&mut self) -> Option<Function> {
    126
    0
            self.current_function.take()
    127
    0
        }
    128
    129
        /// Build an assignment statement
    130
    0
        pub fn assign(&mut self, block: BlockId, place: Place, rvalue: Rvalue) {
    131
    0
            self.push_statement(block, Statement::Assign(place, rvalue));
    132
    0
        }
    133
    134
        /// Build a storage live statement
    135
    0
        pub fn storage_live(&mut self, block: BlockId, local: Local) {
    136
    0
            self.push_statement(block, Statement::StorageLive(local));
    137
    0
        }
    138
    139
        /// Build a storage dead statement
    140
    0
        pub fn storage_dead(&mut self, block: BlockId, local: Local) {
    141
    0
            self.push_statement(block, Statement::StorageDead(local));
    142
    0
        }
    143
    144
        /// Build a goto terminator
    145
    0
        pub fn goto(&mut self, block: BlockId, target: BlockId) {
    146
    0
            self.set_terminator(block, Terminator::Goto(target));
    147
    0
        }
    148
    149
        /// Build an if terminator
    150
    0
        pub fn branch(
    151
    0
            &mut self,
    152
    0
            block: BlockId,
    153
    0
            cond: Operand,
    154
    0
            then_block: BlockId,
    155
    0
            else_block: BlockId,
    156
    0
        ) {
    157
    0
            self.set_terminator(
    158
    0
                block,
    159
    0
                Terminator::If {
    160
    0
                    condition: cond,
    161
    0
                    then_block,
    162
    0
                    else_block,
    163
    0
                },
    164
            );
    165
    0
        }
    166
    167
        /// Build a return terminator
    168
    0
        pub fn return_(&mut self, block: BlockId, value: Option<Operand>) {
    169
    0
            self.set_terminator(block, Terminator::Return(value));
    170
    0
        }
    171
    172
        /// Build a call terminator
    173
    0
        pub fn call_term(
    174
    0
            &mut self,
    175
    0
            block: BlockId,
    176
    0
            func: Operand,
    177
    0
            args: Vec<Operand>,
    178
    0
            dest: Option<(Place, BlockId)>,
    179
    0
        ) {
    180
    0
            self.set_terminator(
    181
    0
                block,
    182
    0
                Terminator::Call {
    183
    0
                    func,
    184
    0
                    args,
    185
    0
                    destination: dest,
    186
    0
                },
    187
            );
    188
    0
        }
    189
    190
        /// Build a switch terminator
    191
    0
        pub fn switch(
    192
    0
            &mut self,
    193
    0
            block: BlockId,
    194
    0
            discriminant: Operand,
    195
    0
            targets: Vec<(Constant, BlockId)>,
    196
    0
            default: Option<BlockId>,
    197
    0
        ) {
    198
    0
            self.set_terminator(
    199
    0
                block,
    200
    0
                Terminator::Switch {
    201
    0
                    discriminant,
    202
    0
                    targets,
    203
    0
                    default,
    204
    0
                },
    205
            );
    206
    0
        }
    207
    }
    208
    209
    /// Helper functions for creating common patterns
    210
    impl MirBuilder {
    211
        /// Create a binary operation and assign to a local
    212
    0
        pub fn binary_op(
    213
    0
            &mut self,
    214
    0
            block: BlockId,
    215
    0
            dest: Local,
    216
    0
            op: BinOp,
    217
    0
            left: Operand,
    218
    0
            right: Operand,
    219
    0
        ) {
    220
    0
            let rvalue = Rvalue::BinaryOp(op, left, right);
    221
    0
            self.assign(block, Place::Local(dest), rvalue);
    222
    0
        }
    223
    224
        /// Create a unary operation and assign to a local
    225
    0
        pub fn unary_op(&mut self, block: BlockId, dest: Local, op: UnOp, operand: Operand) {
    226
    0
            let rvalue = Rvalue::UnaryOp(op, operand);
    227
    0
            self.assign(block, Place::Local(dest), rvalue);
    228
    0
        }
    229
    230
        /// Create a function call and assign result to a local
    231
    0
        pub fn call(
    232
    0
            &mut self,
    233
    0
            block: BlockId,
    234
    0
            dest: Local,
    235
    0
            func: Operand,
    236
    0
            args: Vec<Operand>,
    237
    0
        ) -> BlockId {
    238
    0
            let next_block = self.new_block();
    239
    0
            self.call_term(block, func, args, Some((Place::Local(dest), next_block)));
    240
    0
            next_block
    241
    0
        }
    242
    243
        /// Create a cast and assign to a local
    244
    0
        pub fn cast(
    245
    0
            &mut self,
    246
    0
            block: BlockId,
    247
    0
            dest: Local,
    248
    0
            kind: CastKind,
    249
    0
            operand: Operand,
    250
    0
            target_ty: Type,
    251
    0
        ) {
    252
    0
            let rvalue = Rvalue::Cast(kind, operand, target_ty);
    253
    0
            self.assign(block, Place::Local(dest), rvalue);
    254
    0
        }
    255
    256
        /// Create a reference and assign to a local
    257
    0
        pub fn ref_(&mut self, block: BlockId, dest: Local, mutability: Mutability, place: Place) {
    258
    0
            let rvalue = Rvalue::Ref(mutability, place);
    259
    0
            self.assign(block, Place::Local(dest), rvalue);
    260
    0
        }
    261
    262
        /// Move a value from one place to another
    263
    0
        pub fn move_(&mut self, block: BlockId, dest: Place, source: Place) {
    264
    0
            let rvalue = Rvalue::Use(Operand::Move(source));
    265
    0
            self.assign(block, dest, rvalue);
    266
    0
        }
    267
    268
        /// Copy a value from one place to another
    269
    0
        pub fn copy(&mut self, block: BlockId, dest: Place, source: Place) {
    270
    0
            let rvalue = Rvalue::Use(Operand::Copy(source));
    271
    0
            self.assign(block, dest, rvalue);
    272
    0
        }
    273
    274
        /// Assign a constant to a place
    275
    0
        pub fn const_(&mut self, block: BlockId, dest: Place, constant: Constant) {
    276
    0
            let rvalue = Rvalue::Use(Operand::Constant(constant));
    277
    0
            self.assign(block, dest, rvalue);
    278
    0
        }
    279
    }
    280
    281
    impl Default for MirBuilder {
    282
    0
        fn default() -> Self {
    283
    0
            Self::new()
    284
    0
        }
    285
    }
    286
    287
    #[cfg(test)]
    288
    #[allow(clippy::unwrap_used)]
    289
    mod tests {
    290
        use super::*;
    291
    292
        #[test]
    293
        fn test_build_simple_function() {
    294
            let mut builder = MirBuilder::new();
    295
    296
            // Build: fn add(a: i32, b: i32) -> i32 { a + b }
    297
            builder.start_function("add".to_string(), Type::I32);
    298
    299
            let a = builder.add_param("a".to_string(), Type::I32);
    300
            let b = builder.add_param("b".to_string(), Type::I32);
    301
    302
            let entry = builder.new_block();
    303
            let result = builder.alloc_local(Type::I32, false, Some("result".to_string()));
    304
    305
            builder.storage_live(entry, result);
    306
            builder.binary_op(
    307
                entry,
    308
                result,
    309
                BinOp::Add,
    310
                Operand::Copy(Place::Local(a)),
    311
                Operand::Copy(Place::Local(b)),
    312
            );
    313
            builder.return_(entry, Some(Operand::Move(Place::Local(result))));
    314
    315
            let func = builder.finish_function().unwrap();
    316
            assert_eq!(func.name, "add");
    317
            assert_eq!(func.params.len(), 2);
    318
            assert_eq!(func.blocks.len(), 1);
    319
        }
    320
    321
        #[test]
    322
        fn test_build_if_else() {
    323
            let mut builder = MirBuilder::new();
    324
    325
            // Build: fn abs(x: i32) -> i32 { if x < 0 { -x } else { x } }
    326
            builder.start_function("abs".to_string(), Type::I32);
    327
    328
            let x = builder.add_param("x".to_string(), Type::I32);
    329
    330
            let entry = builder.new_block();
    331
            let then_block = builder.new_block();
    332
            let else_block = builder.new_block();
    333
            let merge_block = builder.new_block();
    334
    335
            // Check if x < 0
    336
            let cond = builder.alloc_local(Type::Bool, false, None);
    337
            builder.binary_op(
    338
                entry,
    339
                cond,
    340
                BinOp::Lt,
    341
                Operand::Copy(Place::Local(x)),
    342
                Operand::Constant(Constant::Int(0, Type::I32)),
    343
            );
    344
            builder.branch(
    345
                entry,
    346
                Operand::Copy(Place::Local(cond)),
    347
                then_block,
    348
                else_block,
    349
            );
    350
    351
            // Then branch: -x
    352
            let neg_x = builder.alloc_local(Type::I32, false, None);
    353
            builder.unary_op(then_block, neg_x, UnOp::Neg, Operand::Copy(Place::Local(x)));
    354
            builder.goto(then_block, merge_block);
    355
    356
            // Else branch: x
    357
            builder.goto(else_block, merge_block);
    358
    359
            // Merge and return
    360
            builder.return_(merge_block, Some(Operand::Copy(Place::Local(x))));
    361
    362
            let func = builder.finish_function().unwrap();
    363
            assert_eq!(func.blocks.len(), 4);
    364
        }
    365
    }