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
}