Coverage Report

Created: 2025-09-05 15: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
6
    pub fn new() -> Self {
25
6
        Self {
26
6
            current_function: None,
27
6
            next_local: 0,
28
6
            next_block: 0,
29
6
            local_map: HashMap::new(),
30
6
        }
31
6
    }
32
33
    /// Start building a new function
34
6
    pub fn start_function(&mut self, name: String, return_ty: Type) -> &mut Self {
35
6
        self.current_function = Some(Function {
36
6
            name,
37
6
            params: Vec::new(),
38
6
            return_ty,
39
6
            locals: Vec::new(),
40
6
            blocks: Vec::new(),
41
6
            entry_block: BlockId(0),
42
6
        });
43
6
        self.next_local = 0;
44
6
        self.next_block = 0;
45
6
        self.local_map.clear();
46
6
        self
47
6
    }
48
49
    /// Add a parameter to the current function
50
5
    pub fn add_param(&mut self, name: String, ty: Type) -> Local {
51
5
        let local = self.alloc_local(ty, true, Some(name.clone()));
52
5
        if let Some(ref mut func) = self.current_function {
53
5
            func.params.push(local);
54
5
        
}0
55
5
        self.local_map.insert(name, local);
56
5
        local
57
5
    }
58
59
    /// Allocate a new local variable
60
11
    pub fn alloc_local(&mut self, ty: Type, mutable: bool, name: Option<String>) -> Local {
61
11
        let id = Local(self.next_local);
62
11
        self.next_local += 1;
63
64
11
        let decl = LocalDecl {
65
11
            id,
66
11
            ty,
67
11
            mutable,
68
11
            name: name.clone(),
69
11
        };
70
71
11
        if let Some(ref mut func) = self.current_function {
72
11
            func.locals.push(decl);
73
11
        
}0
74
75
11
        if let Some(
n6
) = name {
76
6
            self.local_map.insert(n, id);
77
6
        
}5
78
79
11
        id
80
11
    }
81
82
    /// Get a local by name
83
2
    pub fn get_local(&self, name: &str) -> Option<Local> {
84
2
        self.local_map.get(name).copied()
85
2
    }
86
87
    /// Create a new basic block
88
12
    pub fn new_block(&mut self) -> BlockId {
89
12
        let id = BlockId(self.next_block);
90
12
        self.next_block += 1;
91
92
12
        if let Some(ref mut func) = self.current_function {
93
12
            func.blocks.push(BasicBlock {
94
12
                id,
95
12
                statements: Vec::new(),
96
12
                terminator: Terminator::Unreachable,
97
12
            });
98
12
        
}0
99
100
12
        id
101
12
    }
102
103
    /// Get a mutable reference to a block
104
19
    pub fn block_mut(&mut self, id: BlockId) -> Option<&mut BasicBlock> {
105
19
        self.current_function
106
19
            .as_mut()
107
19
            .and_then(|f| f.blocks.get_mut(id.0))
108
19
    }
109
110
    /// Add a statement to a block
111
7
    pub fn push_statement(&mut self, block: BlockId, stmt: Statement) {
112
7
        if let Some(bb) = self.block_mut(block) {
113
7
            bb.statements.push(stmt);
114
7
        
}0
115
7
    }
116
117
    /// Set the terminator for a block
118
12
    pub fn set_terminator(&mut self, block: BlockId, term: Terminator) {
119
12
        if let Some(bb) = self.block_mut(block) {
120
12
            bb.terminator = term;
121
12
        
}0
122
12
    }
123
124
    /// Finish building the current function
125
6
    pub fn finish_function(&mut self) -> Option<Function> {
126
6
        self.current_function.take()
127
6
    }
128
129
    /// Build an assignment statement
130
6
    pub fn assign(&mut self, block: BlockId, place: Place, rvalue: Rvalue) {
131
6
        self.push_statement(block, Statement::Assign(place, rvalue));
132
6
    }
133
134
    /// Build a storage live statement
135
1
    pub fn storage_live(&mut self, block: BlockId, local: Local) {
136
1
        self.push_statement(block, Statement::StorageLive(local));
137
1
    }
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
4
    pub fn goto(&mut self, block: BlockId, target: BlockId) {
146
4
        self.set_terminator(block, Terminator::Goto(target));
147
4
    }
148
149
    /// Build an if terminator
150
2
    pub fn branch(
151
2
        &mut self,
152
2
        block: BlockId,
153
2
        cond: Operand,
154
2
        then_block: BlockId,
155
2
        else_block: BlockId,
156
2
    ) {
157
2
        self.set_terminator(
158
2
            block,
159
2
            Terminator::If {
160
2
                condition: cond,
161
2
                then_block,
162
2
                else_block,
163
2
            },
164
        );
165
2
    }
166
167
    /// Build a return terminator
168
6
    pub fn return_(&mut self, block: BlockId, value: Option<Operand>) {
169
6
        self.set_terminator(block, Terminator::Return(value));
170
6
    }
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
4
    pub fn binary_op(
213
4
        &mut self,
214
4
        block: BlockId,
215
4
        dest: Local,
216
4
        op: BinOp,
217
4
        left: Operand,
218
4
        right: Operand,
219
4
    ) {
220
4
        let rvalue = Rvalue::BinaryOp(op, left, right);
221
4
        self.assign(block, Place::Local(dest), rvalue);
222
4
    }
223
224
    /// Create a unary operation and assign to a local
225
1
    pub fn unary_op(&mut self, block: BlockId, dest: Local, op: UnOp, operand: Operand) {
226
1
        let rvalue = Rvalue::UnaryOp(op, operand);
227
1
        self.assign(block, Place::Local(dest), rvalue);
228
1
    }
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
1
    fn test_build_simple_function() {
294
1
        let mut builder = MirBuilder::new();
295
296
        // Build: fn add(a: i32, b: i32) -> i32 { a + b }
297
1
        builder.start_function("add".to_string(), Type::I32);
298
299
1
        let a = builder.add_param("a".to_string(), Type::I32);
300
1
        let b = builder.add_param("b".to_string(), Type::I32);
301
302
1
        let entry = builder.new_block();
303
1
        let result = builder.alloc_local(Type::I32, false, Some("result".to_string()));
304
305
1
        builder.storage_live(entry, result);
306
1
        builder.binary_op(
307
1
            entry,
308
1
            result,
309
1
            BinOp::Add,
310
1
            Operand::Copy(Place::Local(a)),
311
1
            Operand::Copy(Place::Local(b)),
312
        );
313
1
        builder.return_(entry, Some(Operand::Move(Place::Local(result))));
314
315
1
        let func = builder.finish_function().unwrap();
316
1
        assert_eq!(func.name, "add");
317
1
        assert_eq!(func.params.len(), 2);
318
1
        assert_eq!(func.blocks.len(), 1);
319
1
    }
320
321
    #[test]
322
1
    fn test_build_if_else() {
323
1
        let mut builder = MirBuilder::new();
324
325
        // Build: fn abs(x: i32) -> i32 { if x < 0 { -x } else { x } }
326
1
        builder.start_function("abs".to_string(), Type::I32);
327
328
1
        let x = builder.add_param("x".to_string(), Type::I32);
329
330
1
        let entry = builder.new_block();
331
1
        let then_block = builder.new_block();
332
1
        let else_block = builder.new_block();
333
1
        let merge_block = builder.new_block();
334
335
        // Check if x < 0
336
1
        let cond = builder.alloc_local(Type::Bool, false, None);
337
1
        builder.binary_op(
338
1
            entry,
339
1
            cond,
340
1
            BinOp::Lt,
341
1
            Operand::Copy(Place::Local(x)),
342
1
            Operand::Constant(Constant::Int(0, Type::I32)),
343
        );
344
1
        builder.branch(
345
1
            entry,
346
1
            Operand::Copy(Place::Local(cond)),
347
1
            then_block,
348
1
            else_block,
349
        );
350
351
        // Then branch: -x
352
1
        let neg_x = builder.alloc_local(Type::I32, false, None);
353
1
        builder.unary_op(then_block, neg_x, UnOp::Neg, Operand::Copy(Place::Local(x)));
354
1
        builder.goto(then_block, merge_block);
355
356
        // Else branch: x
357
1
        builder.goto(else_block, merge_block);
358
359
        // Merge and return
360
1
        builder.return_(merge_block, Some(Operand::Copy(Place::Local(x))));
361
362
1
        let func = builder.finish_function().unwrap();
363
1
        assert_eq!(func.blocks.len(), 4);
364
1
    }
365
}