/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 | | } |