/home/noah/src/ruchy/src/middleend/mir/lower.rs
Line | Count | Source |
1 | | //! AST to MIR lowering |
2 | | |
3 | | use super::builder::MirBuilder; |
4 | | use super::types::{ |
5 | | BinOp, BlockId, Constant, Mutability, Operand, Place, Program, Rvalue, Type, UnOp, |
6 | | }; |
7 | | use crate::frontend::ast::{ |
8 | | BinaryOp as AstBinOp, Expr, ExprKind, Literal, Param, Type as AstType, UnaryOp as AstUnOp, |
9 | | }; |
10 | | use anyhow::{anyhow, Result}; |
11 | | use std::collections::HashMap; |
12 | | |
13 | | /// Context for lowering AST to MIR |
14 | | pub struct LoweringContext { |
15 | | /// MIR builder |
16 | | builder: MirBuilder, |
17 | | /// Type environment for expressions |
18 | | #[allow(dead_code)] |
19 | | type_env: HashMap<String, Type>, |
20 | | /// Current block being built |
21 | | current_block: Option<BlockId>, |
22 | | } |
23 | | |
24 | | impl LoweringContext { |
25 | | /// Create a new lowering context |
26 | | #[must_use] |
27 | 4 | pub fn new() -> Self { |
28 | 4 | Self { |
29 | 4 | builder: MirBuilder::new(), |
30 | 4 | type_env: HashMap::new(), |
31 | 4 | current_block: None, |
32 | 4 | } |
33 | 4 | } |
34 | | |
35 | | /// Lower an expression to MIR |
36 | | /// |
37 | | /// # Errors |
38 | | /// |
39 | | /// Returns an error if the expression cannot be lowered to MIR |
40 | | /// # Errors |
41 | | /// |
42 | | /// Returns an error if the operation fails |
43 | 4 | pub fn lower_expr(&mut self, expr: &Expr) -> Result<Program> { |
44 | 4 | match &expr.kind { |
45 | | ExprKind::Function { |
46 | 1 | name, |
47 | 1 | params, |
48 | 1 | return_type, |
49 | 1 | body, |
50 | | .. |
51 | 1 | } => self.lower_function(name, params, return_type.as_ref(), body), |
52 | | _ => { |
53 | | // For non-function expressions, create a main function |
54 | 3 | self.lower_main_expr(expr) |
55 | | } |
56 | | } |
57 | 4 | } |
58 | | |
59 | | /// Lower a function expression |
60 | 1 | fn lower_function( |
61 | 1 | &mut self, |
62 | 1 | name: &str, |
63 | 1 | params: &[Param], |
64 | 1 | return_type: Option<&AstType>, |
65 | 1 | body: &Expr, |
66 | 1 | ) -> Result<Program> { |
67 | 1 | let func_name = name.to_string(); |
68 | 1 | let ret_ty = return_type.map_or(Type::Unit, |t| self.ast_to_mir_type(t)); |
69 | | |
70 | 1 | self.builder.start_function(func_name.clone(), ret_ty); |
71 | | |
72 | | // Add parameters |
73 | 3 | for param2 in params { |
74 | 2 | let ty = self.ast_to_mir_type(¶m.ty); |
75 | 2 | self.builder.add_param(param.name(), ty); |
76 | 2 | } |
77 | | |
78 | | // Create entry block |
79 | 1 | let entry = self.builder.new_block(); |
80 | 1 | self.current_block = Some(entry); |
81 | | |
82 | | // Lower function body |
83 | 1 | let result = self.lower_expr_to_operand(body)?0 ; |
84 | | |
85 | | // Return the result |
86 | 1 | self.builder.return_(entry, Some(result)); |
87 | | |
88 | 1 | let function = self |
89 | 1 | .builder |
90 | 1 | .finish_function() |
91 | 1 | .ok_or_else(|| anyhow!("Failed to finish function"0 ))?0 ; |
92 | | |
93 | 1 | let mut functions = HashMap::new(); |
94 | 1 | functions.insert(func_name.clone(), function); |
95 | | |
96 | 1 | Ok(Program { |
97 | 1 | functions, |
98 | 1 | entry: func_name, |
99 | 1 | }) |
100 | 1 | } |
101 | | |
102 | | /// Lower a main expression (wrap in main function) |
103 | 3 | fn lower_main_expr(&mut self, expr: &Expr) -> Result<Program> { |
104 | 3 | self.builder.start_function("main".to_string(), Type::Unit); |
105 | | |
106 | 3 | let entry = self.builder.new_block(); |
107 | 3 | self.current_block = Some(entry); |
108 | | |
109 | | // Lower the expression |
110 | 3 | let _result = self.lower_expr_to_operand(expr)?0 ; |
111 | | |
112 | | // Main function returns unit |
113 | 3 | self.builder |
114 | 3 | .return_(entry, Some(Operand::Constant(Constant::Unit))); |
115 | | |
116 | 3 | let function = self |
117 | 3 | .builder |
118 | 3 | .finish_function() |
119 | 3 | .ok_or_else(|| anyhow!("Failed to finish main function"0 ))?0 ; |
120 | | |
121 | 3 | let mut functions = HashMap::new(); |
122 | 3 | functions.insert("main".to_string(), function); |
123 | | |
124 | 3 | Ok(Program { |
125 | 3 | functions, |
126 | 3 | entry: "main".to_string(), |
127 | 3 | }) |
128 | 3 | } |
129 | | |
130 | | /// Lower an expression to an operand |
131 | 14 | fn lower_expr_to_operand(&mut self, expr: &Expr) -> Result<Operand> { |
132 | 14 | let block = self |
133 | 14 | .current_block |
134 | 14 | .ok_or_else(|| anyhow!("No current block"0 ))?0 ; |
135 | | |
136 | 14 | match &expr.kind { |
137 | 6 | ExprKind::Literal(lit) => Ok(Operand::Constant(Self::lower_literal(lit))), |
138 | 2 | ExprKind::Identifier(name) => { |
139 | 2 | if let Some(local) = self.builder.get_local(name) { |
140 | 2 | Ok(Operand::Copy(Place::Local(local))) |
141 | | } else { |
142 | 0 | Err(anyhow!("Unbound variable: {}", name)) |
143 | | } |
144 | | } |
145 | 2 | ExprKind::Binary { op, left, right } => { |
146 | 2 | let left_op = self.lower_expr_to_operand(left)?0 ; |
147 | 2 | let right_op = self.lower_expr_to_operand(right)?0 ; |
148 | 2 | let mir_op = Self::lower_binary_op(*op); |
149 | | |
150 | | // Create a variable for the result |
151 | 2 | let result_ty = Self::infer_binary_result_type(*op); |
152 | 2 | let temp = self.builder.alloc_local(result_ty, false, None); |
153 | | |
154 | 2 | self.builder |
155 | 2 | .binary_op(block, temp, mir_op, left_op, right_op); |
156 | 2 | Ok(Operand::Move(Place::Local(temp))) |
157 | | } |
158 | 0 | ExprKind::Unary { op, operand } => { |
159 | 0 | let operand_mir = self.lower_expr_to_operand(operand)?; |
160 | 0 | let mir_op = Self::lower_unary_op(*op); |
161 | | |
162 | 0 | let result_ty = Self::infer_unary_result_type(*op); |
163 | 0 | let temp = self.builder.alloc_local(result_ty, false, None); |
164 | | |
165 | 0 | self.builder.unary_op(block, temp, mir_op, operand_mir); |
166 | 0 | Ok(Operand::Move(Place::Local(temp))) |
167 | | } |
168 | | ExprKind::Let { |
169 | 0 | name, value, body, .. |
170 | | } => { |
171 | | // Lower the value |
172 | 0 | let value_op = self.lower_expr_to_operand(value)?; |
173 | | |
174 | | // Create a local for the binding |
175 | 0 | let local_ty = Type::I32; // Default type inference |
176 | 0 | let local = self |
177 | 0 | .builder |
178 | 0 | .alloc_local(local_ty, false, Some(name.clone())); |
179 | | |
180 | | // Assign the value |
181 | 0 | self.builder |
182 | 0 | .assign(block, Place::Local(local), Rvalue::Use(value_op)); |
183 | | |
184 | | // Lower the body with the binding in scope |
185 | 0 | self.lower_expr_to_operand(body) |
186 | | } |
187 | | ExprKind::If { |
188 | 1 | condition, |
189 | 1 | then_branch, |
190 | 1 | else_branch, |
191 | | } => { |
192 | 1 | let cond_op = self.lower_expr_to_operand(condition)?0 ; |
193 | | |
194 | 1 | let then_block = self.builder.new_block(); |
195 | 1 | let else_block = self.builder.new_block(); |
196 | 1 | let merge_block = self.builder.new_block(); |
197 | | |
198 | | // Branch based on condition |
199 | 1 | self.builder.branch(block, cond_op, then_block, else_block); |
200 | | |
201 | | // Lower then branch |
202 | 1 | self.current_block = Some(then_block); |
203 | 1 | let then_result = self.lower_expr_to_operand(then_branch)?0 ; |
204 | 1 | self.builder.goto(then_block, merge_block); |
205 | | |
206 | | // Lower else branch |
207 | 1 | self.current_block = Some(else_block); |
208 | 1 | let _else_result = if let Some(else_expr) = else_branch { |
209 | 1 | self.lower_expr_to_operand(else_expr)?0 |
210 | | } else { |
211 | 0 | Operand::Constant(Constant::Unit) |
212 | | }; |
213 | 1 | self.builder.goto(else_block, merge_block); |
214 | | |
215 | | // Create a variable for the result |
216 | 1 | let result_ty = Type::I32; // Type inference would determine this |
217 | 1 | let result_temp = self.builder.alloc_local(result_ty, false, None); |
218 | | |
219 | | // In merge block, we'd need phi nodes, but for simplicity we'll just use the then result |
220 | 1 | self.current_block = Some(merge_block); |
221 | 1 | self.builder.assign( |
222 | 1 | merge_block, |
223 | 1 | Place::Local(result_temp), |
224 | 1 | Rvalue::Use(then_result), |
225 | | ); |
226 | | |
227 | 1 | Ok(Operand::Move(Place::Local(result_temp))) |
228 | | } |
229 | 0 | ExprKind::Call { func, args } => { |
230 | 0 | let func_op = self.lower_expr_to_operand(func)?; |
231 | 0 | let mut arg_ops = Vec::new(); |
232 | | |
233 | 0 | for arg in args { |
234 | 0 | arg_ops.push(self.lower_expr_to_operand(arg)?); |
235 | | } |
236 | | |
237 | | // Create a variable for the result |
238 | 0 | let result_ty = Type::I32; // Type inference would determine this |
239 | 0 | let result_temp = self.builder.alloc_local(result_ty, false, None); |
240 | | |
241 | | // Create call terminator |
242 | 0 | let next_block = self.builder.call(block, result_temp, func_op, arg_ops); |
243 | 0 | self.current_block = Some(next_block); |
244 | | |
245 | 0 | Ok(Operand::Move(Place::Local(result_temp))) |
246 | | } |
247 | 3 | ExprKind::Block(exprs) => { |
248 | 3 | if exprs.is_empty() { |
249 | 0 | Ok(Operand::Constant(Constant::Unit)) |
250 | | } else { |
251 | | // Lower all expressions, return the last one |
252 | 3 | let mut result = Operand::Constant(Constant::Unit); |
253 | 6 | for expr3 in exprs { |
254 | 3 | result = self.lower_expr_to_operand(expr)?0 ; |
255 | | } |
256 | 3 | Ok(result) |
257 | | } |
258 | | } |
259 | | _ => { |
260 | | // For unsupported expressions, return unit for now |
261 | 0 | Ok(Operand::Constant(Constant::Unit)) |
262 | | } |
263 | | } |
264 | 14 | } |
265 | | |
266 | | /// Lower a literal to a constant |
267 | 6 | fn lower_literal(lit: &Literal) -> Constant { |
268 | 6 | match lit { |
269 | 5 | Literal::Integer(i) => Constant::Int(i128::from(*i), Type::I32), |
270 | 0 | Literal::Float(f) => Constant::Float(*f, Type::F64), |
271 | 0 | Literal::String(s) => Constant::String(s.clone()), |
272 | 1 | Literal::Bool(b) => Constant::Bool(*b), |
273 | 0 | Literal::Char(c) => Constant::Char(*c), |
274 | 0 | Literal::Unit => Constant::Unit, |
275 | | } |
276 | 6 | } |
277 | | |
278 | | /// Lower binary operator |
279 | 2 | fn lower_binary_op(op: AstBinOp) -> BinOp { |
280 | 2 | match op { |
281 | 2 | AstBinOp::Add => BinOp::Add, |
282 | 0 | AstBinOp::Subtract => BinOp::Sub, |
283 | 0 | AstBinOp::Multiply => BinOp::Mul, |
284 | 0 | AstBinOp::Divide => BinOp::Div, |
285 | 0 | AstBinOp::Modulo => BinOp::Rem, |
286 | 0 | AstBinOp::Power => BinOp::Pow, |
287 | 0 | AstBinOp::Equal => BinOp::Eq, |
288 | 0 | AstBinOp::NotEqual => BinOp::Ne, |
289 | 0 | AstBinOp::Less => BinOp::Lt, |
290 | 0 | AstBinOp::LessEqual => BinOp::Le, |
291 | 0 | AstBinOp::Greater => BinOp::Gt, |
292 | 0 | AstBinOp::GreaterEqual => BinOp::Ge, |
293 | 0 | AstBinOp::And => BinOp::And, |
294 | 0 | AstBinOp::Or => BinOp::Or, |
295 | 0 | AstBinOp::NullCoalesce => BinOp::NullCoalesce, |
296 | 0 | AstBinOp::BitwiseAnd => BinOp::BitAnd, |
297 | 0 | AstBinOp::BitwiseOr => BinOp::BitOr, |
298 | 0 | AstBinOp::BitwiseXor => BinOp::BitXor, |
299 | 0 | AstBinOp::LeftShift => BinOp::Shl, |
300 | | } |
301 | 2 | } |
302 | | |
303 | | /// Lower unary operator |
304 | 0 | fn lower_unary_op(op: AstUnOp) -> UnOp { |
305 | 0 | match op { |
306 | 0 | AstUnOp::Negate => UnOp::Neg, |
307 | 0 | AstUnOp::Not => UnOp::Not, |
308 | 0 | AstUnOp::BitwiseNot => UnOp::BitNot, |
309 | 0 | AstUnOp::Reference => UnOp::Ref, |
310 | | } |
311 | 0 | } |
312 | | |
313 | | /// Convert AST Type to MIR Type |
314 | | #[allow(clippy::only_used_in_recursion)] |
315 | 3 | fn ast_to_mir_type(&self, ast_ty: &AstType) -> Type { |
316 | | use crate::frontend::ast::TypeKind; |
317 | 3 | match &ast_ty.kind { |
318 | 3 | TypeKind::Named(name) => match name.as_str() { |
319 | 3 | "bool" => Type::Bool0 , |
320 | 3 | "i8" => Type::I80 , |
321 | 3 | "i16" => Type::I160 , |
322 | 3 | "i32" => Type::I32, |
323 | 0 | "i64" => Type::I64, |
324 | 0 | "i128" => Type::I128, |
325 | 0 | "u8" => Type::U8, |
326 | 0 | "u16" => Type::U16, |
327 | 0 | "u32" => Type::U32, |
328 | 0 | "u64" => Type::U64, |
329 | 0 | "u128" => Type::U128, |
330 | 0 | "f32" => Type::F32, |
331 | 0 | "f64" => Type::F64, |
332 | 0 | "String" => Type::String, |
333 | 0 | "()" => Type::Unit, |
334 | 0 | _ => Type::UserType(name.clone()), |
335 | | }, |
336 | 0 | TypeKind::Generic { base, params } => { |
337 | 0 | match base.as_str() { |
338 | 0 | "Vec" if params.len() == 1 => { |
339 | 0 | Type::Vec(Box::new(self.ast_to_mir_type(¶ms[0]))) |
340 | | } |
341 | 0 | "Array" if params.len() == 1 => { |
342 | | // For simplicity, treat arrays as vectors for now |
343 | 0 | Type::Vec(Box::new(self.ast_to_mir_type(¶ms[0]))) |
344 | | } |
345 | 0 | _ => Type::UserType(base.clone()), |
346 | | } |
347 | | } |
348 | 0 | TypeKind::Optional(inner) => { |
349 | | // For simplicity, treat optionals as user types for now |
350 | 0 | Type::UserType(format!("Option<{:?}>", inner.kind)) |
351 | | } |
352 | 0 | TypeKind::Function { params, ret } => { |
353 | 0 | let param_types = params.iter().map(|p| self.ast_to_mir_type(p)).collect(); |
354 | 0 | Type::FnPtr(param_types, Box::new(self.ast_to_mir_type(ret))) |
355 | | } |
356 | 0 | TypeKind::List(inner) => Type::Vec(Box::new(self.ast_to_mir_type(inner))), |
357 | | TypeKind::DataFrame { .. } => { |
358 | | // Map DataFrames to a user type for now |
359 | 0 | Type::UserType("DataFrame".to_string()) |
360 | | } |
361 | | TypeKind::Series { .. } => { |
362 | | // Map Series to a user type for now |
363 | 0 | Type::UserType("Series".to_string()) |
364 | | } |
365 | 0 | TypeKind::Tuple(types) => { |
366 | 0 | let mir_types: Vec<_> = types.iter().map(|t| self.ast_to_mir_type(t)).collect(); |
367 | 0 | Type::Tuple(mir_types) |
368 | | } |
369 | 0 | TypeKind::Reference { inner, .. } => { |
370 | | // For MIR, treat references as the inner type for now |
371 | 0 | self.ast_to_mir_type(inner) |
372 | | } |
373 | | } |
374 | 3 | } |
375 | | |
376 | | /// Infer result type for binary operations |
377 | 2 | fn infer_binary_result_type(op: AstBinOp) -> Type { |
378 | 2 | match op { |
379 | | AstBinOp::Add |
380 | | | AstBinOp::Subtract |
381 | | | AstBinOp::Multiply |
382 | | | AstBinOp::Divide |
383 | | | AstBinOp::Modulo |
384 | | | AstBinOp::Power |
385 | | | AstBinOp::BitwiseAnd |
386 | | | AstBinOp::BitwiseOr |
387 | | | AstBinOp::BitwiseXor |
388 | 2 | | AstBinOp::LeftShift => Type::I32, |
389 | | AstBinOp::Equal |
390 | | | AstBinOp::NotEqual |
391 | | | AstBinOp::Less |
392 | | | AstBinOp::LessEqual |
393 | | | AstBinOp::Greater |
394 | | | AstBinOp::GreaterEqual |
395 | | | AstBinOp::And |
396 | 0 | | AstBinOp::Or => Type::Bool, |
397 | 0 | AstBinOp::NullCoalesce => Type::I32, // For now, assume Int (could be improved) |
398 | | } |
399 | 2 | } |
400 | | |
401 | | /// Infer result type for unary operations |
402 | 0 | fn infer_unary_result_type(op: AstUnOp) -> Type { |
403 | 0 | match op { |
404 | 0 | AstUnOp::Negate | AstUnOp::BitwiseNot => Type::I32, |
405 | 0 | AstUnOp::Not => Type::Bool, |
406 | 0 | AstUnOp::Reference => Type::Ref(Box::new(Type::I32), Mutability::Immutable), // Reference creates an immutable reference |
407 | | } |
408 | 0 | } |
409 | | } |
410 | | |
411 | | impl Default for LoweringContext { |
412 | 0 | fn default() -> Self { |
413 | 0 | Self::new() |
414 | 0 | } |
415 | | } |
416 | | |
417 | | #[cfg(test)] |
418 | | #[allow(clippy::unwrap_used, clippy::panic)] |
419 | | mod tests { |
420 | | use super::*; |
421 | | use crate::frontend::Parser; |
422 | | |
423 | | #[test] |
424 | 1 | fn test_lower_literal() -> Result<()> { |
425 | 1 | let mut parser = Parser::new("42"); |
426 | 1 | let ast = parser.parse()?0 ; |
427 | | |
428 | 1 | let mut ctx = LoweringContext::new(); |
429 | 1 | let program = ctx.lower_expr(&ast)?0 ; |
430 | | |
431 | 1 | assert_eq!(program.entry, "main"); |
432 | 1 | assert!(program.functions.contains_key("main")); |
433 | | |
434 | 1 | Ok(()) |
435 | 1 | } |
436 | | |
437 | | #[test] |
438 | 1 | fn test_lower_binary_expr() -> Result<()> { |
439 | 1 | let mut parser = Parser::new("1 + 2"); |
440 | 1 | let ast = parser.parse()?0 ; |
441 | | |
442 | 1 | let mut ctx = LoweringContext::new(); |
443 | 1 | let program = ctx.lower_expr(&ast)?0 ; |
444 | | |
445 | 1 | let main_func = &program.functions["main"]; |
446 | 1 | assert!(!main_func.blocks.is_empty()); |
447 | | |
448 | 1 | Ok(()) |
449 | 1 | } |
450 | | |
451 | | #[test] |
452 | 1 | fn test_lower_function() -> Result<()> { |
453 | 1 | let mut parser = Parser::new("fun add(x: i32, y: i32) -> i32 { x + y }"); |
454 | 1 | let ast = parser.parse()?0 ; |
455 | | |
456 | 1 | let mut ctx = LoweringContext::new(); |
457 | 1 | let program = ctx.lower_expr(&ast)?0 ; |
458 | | |
459 | 1 | assert!(program.functions.contains_key("add")); |
460 | 1 | let func = &program.functions["add"]; |
461 | 1 | assert_eq!(func.params.len(), 2); |
462 | | |
463 | 1 | Ok(()) |
464 | 1 | } |
465 | | |
466 | | #[test] |
467 | 1 | fn test_lower_if_expr() -> Result<()> { |
468 | 1 | let mut parser = Parser::new("if true { 1 } else { 2 }"); |
469 | 1 | let ast = parser.parse()?0 ; |
470 | | |
471 | 1 | let mut ctx = LoweringContext::new(); |
472 | 1 | let program = ctx.lower_expr(&ast)?0 ; |
473 | | |
474 | 1 | let main_func = &program.functions["main"]; |
475 | | // Should have multiple blocks for if/else |
476 | 1 | assert!(main_func.blocks.len() > 1); |
477 | | |
478 | 1 | Ok(()) |
479 | 1 | } |
480 | | } |