Skip to main content

shape_vm/mir/
lowering.rs

1//! MIR lowering: AST → MIR.
2//!
3//! Converts Shape AST function bodies into MIR basic blocks.
4//! This is the bridge between parsing and borrow analysis.
5
6use super::types::*;
7use shape_ast::ast::{self, Expr, Span, Spanned, Statement};
8
9/// Builder for constructing a MIR function from AST.
10pub struct MirBuilder {
11    /// Name of the function being built.
12    name: String,
13    /// Completed basic blocks.
14    blocks: Vec<BasicBlock>,
15    /// Statements for the current (in-progress) basic block.
16    current_stmts: Vec<MirStatement>,
17    /// ID of the current basic block.
18    current_block: BasicBlockId,
19    /// Next block ID to allocate.
20    next_block_id: u32,
21    /// Next local slot to allocate.
22    next_local: u16,
23    /// Next program point.
24    next_point: u32,
25    /// Next loan ID.
26    next_loan: u32,
27    /// Local variable name → slot mapping.
28    locals: Vec<(String, SlotId, LocalTypeInfo)>,
29    /// Parameter slots.
30    param_slots: Vec<SlotId>,
31    /// Function span.
32    span: Span,
33}
34
35impl MirBuilder {
36    pub fn new(name: String, span: Span) -> Self {
37        MirBuilder {
38            name,
39            blocks: Vec::new(),
40            current_stmts: Vec::new(),
41            current_block: BasicBlockId(0),
42            next_block_id: 1,
43            next_local: 0,
44            next_point: 0,
45            next_loan: 0,
46            locals: Vec::new(),
47            param_slots: Vec::new(),
48            span,
49        }
50    }
51
52    /// Allocate a new local variable slot.
53    pub fn alloc_local(&mut self, name: String, type_info: LocalTypeInfo) -> SlotId {
54        let slot = SlotId(self.next_local);
55        self.next_local += 1;
56        self.locals.push((name, slot, type_info));
57        slot
58    }
59
60    /// Register a parameter slot.
61    pub fn add_param(&mut self, name: String, type_info: LocalTypeInfo) -> SlotId {
62        let slot = self.alloc_local(name, type_info);
63        self.param_slots.push(slot);
64        slot
65    }
66
67    /// Allocate a new program point.
68    pub fn next_point(&mut self) -> Point {
69        let p = Point(self.next_point);
70        self.next_point += 1;
71        p
72    }
73
74    /// Allocate a new loan ID.
75    pub fn next_loan(&mut self) -> LoanId {
76        let l = LoanId(self.next_loan);
77        self.next_loan += 1;
78        l
79    }
80
81    /// Create a new basic block and return its ID.
82    pub fn new_block(&mut self) -> BasicBlockId {
83        let id = BasicBlockId(self.next_block_id);
84        self.next_block_id += 1;
85        id
86    }
87
88    /// Push a statement into the current block.
89    pub fn push_stmt(&mut self, kind: StatementKind, span: Span) {
90        let point = self.next_point();
91        self.current_stmts.push(MirStatement { kind, span, point });
92    }
93
94    /// Finish the current block with a terminator and switch to a new block.
95    pub fn finish_block(&mut self, terminator_kind: TerminatorKind, span: Span) {
96        let block = BasicBlock {
97            id: self.current_block,
98            statements: std::mem::take(&mut self.current_stmts),
99            terminator: Terminator {
100                kind: terminator_kind,
101                span,
102            },
103        };
104        self.blocks.push(block);
105    }
106
107    /// Start building a new block (after finishing the previous one).
108    pub fn start_block(&mut self, id: BasicBlockId) {
109        self.current_block = id;
110        self.current_stmts.clear();
111    }
112
113    /// Finalize and produce the MIR function.
114    pub fn build(self) -> MirFunction {
115        let local_types = self.locals.iter().map(|(_, _, t)| t.clone()).collect();
116        MirFunction {
117            name: self.name,
118            blocks: self.blocks,
119            num_locals: self.next_local,
120            param_slots: self.param_slots,
121            local_types,
122            span: self.span,
123        }
124    }
125}
126
127/// Lower a function body (list of statements) into MIR.
128pub fn lower_function(
129    name: &str,
130    params: &[ast::FunctionParameter],
131    body: &[Statement],
132    span: Span,
133) -> MirFunction {
134    let mut builder = MirBuilder::new(name.to_string(), span);
135
136    // Register parameters
137    for param in params {
138        let param_name = param.simple_name().unwrap_or("_").to_string();
139        let type_info = if param.is_reference {
140            LocalTypeInfo::NonCopy // references are always tracked
141        } else {
142            LocalTypeInfo::Unknown // will be resolved during analysis
143        };
144        builder.add_param(param_name, type_info);
145    }
146
147    // Create the exit block
148    let exit_block = builder.new_block();
149
150    // Lower body statements
151    lower_statements(&mut builder, body, exit_block);
152
153    // If current block hasn't been finished (no explicit return), emit goto exit
154    if builder.current_stmts.len() > 0 || builder.blocks.len() == 0 {
155        builder.finish_block(TerminatorKind::Goto(exit_block), span);
156    }
157
158    // Create exit block with Return terminator
159    builder.start_block(exit_block);
160    builder.finish_block(TerminatorKind::Return, span);
161
162    builder.build()
163}
164
165/// Lower a slice of statements into the current block.
166fn lower_statements(builder: &mut MirBuilder, stmts: &[Statement], exit_block: BasicBlockId) {
167    for stmt in stmts {
168        lower_statement(builder, stmt, exit_block);
169    }
170}
171
172/// Lower a single statement.
173fn lower_statement(builder: &mut MirBuilder, stmt: &Statement, exit_block: BasicBlockId) {
174    match stmt {
175        Statement::VariableDecl(decl, span) => {
176            lower_var_decl(builder, decl, *span);
177        }
178        Statement::Assignment(assign, span) => {
179            lower_assignment(builder, assign, *span);
180        }
181        Statement::Return(value, span) => {
182            if let Some(expr) = value {
183                let result_slot = lower_expr_to_temp(builder, expr);
184                builder.push_stmt(
185                    StatementKind::Assign(
186                        Place::Local(SlotId(0)), // return slot convention
187                        Rvalue::Use(Operand::Move(Place::Local(result_slot))),
188                    ),
189                    *span,
190                );
191            }
192            builder.finish_block(TerminatorKind::Return, *span);
193            // Start a new unreachable block for subsequent dead code
194            let dead_block = builder.new_block();
195            builder.start_block(dead_block);
196        }
197        Statement::Expression(expr, span) => {
198            // Expression statement — evaluate for side effects
199            let _slot = lower_expr_to_temp(builder, expr);
200            let _ = span; // span captured in sub-lowering
201        }
202        Statement::If(if_stmt, span) => {
203            lower_if(builder, if_stmt, *span, exit_block);
204        }
205        Statement::While(while_loop, span) => {
206            lower_while(
207                builder,
208                &while_loop.condition,
209                &while_loop.body,
210                *span,
211                exit_block,
212            );
213        }
214        Statement::For(for_loop, span) => {
215            lower_for_loop(builder, for_loop, *span, exit_block);
216        }
217        _ => {
218            // Other statement types: emit a Nop for now.
219            // Will be expanded as more AST constructs get MIR support.
220            let span = stmt.span().unwrap_or(Span::DUMMY);
221            builder.push_stmt(StatementKind::Nop, span);
222        }
223    }
224}
225
226/// Lower a variable declaration.
227fn lower_var_decl(builder: &mut MirBuilder, decl: &ast::VariableDecl, span: Span) {
228    let name = decl.pattern.as_identifier().unwrap_or("_").to_string();
229    let type_info = LocalTypeInfo::Unknown; // resolved during analysis
230    let slot = builder.alloc_local(name, type_info);
231
232    if let Some(init_expr) = &decl.value {
233        let init_slot = lower_expr_to_temp(builder, init_expr);
234        // Determine operand based on ownership modifier
235        let operand = match decl.ownership {
236            ast::OwnershipModifier::Move => Operand::Move(Place::Local(init_slot)),
237            ast::OwnershipModifier::Clone => Operand::Copy(Place::Local(init_slot)),
238            ast::OwnershipModifier::Inferred => {
239                // For `var`: decision deferred to liveness analysis
240                // For `let`: default to Move
241                Operand::Move(Place::Local(init_slot))
242            }
243        };
244        let rvalue = match decl.ownership {
245            ast::OwnershipModifier::Clone => Rvalue::Clone(operand),
246            _ => Rvalue::Use(operand),
247        };
248        builder.push_stmt(StatementKind::Assign(Place::Local(slot), rvalue), span);
249    }
250}
251
252/// Lower an assignment statement.
253fn lower_assignment(builder: &mut MirBuilder, assign: &ast::Assignment, span: Span) {
254    let value_slot = lower_expr_to_temp(builder, &assign.value);
255    // Simplified: assume LHS is a simple identifier for now
256    // Full place resolution will be added for field/index assignments
257    builder.push_stmt(
258        StatementKind::Assign(
259            Place::Local(SlotId(0)), // placeholder - real resolution TBD
260            Rvalue::Use(Operand::Move(Place::Local(value_slot))),
261        ),
262        span,
263    );
264}
265
266/// Lower an expression and return the temp slot it was placed in.
267/// This is a simplified version — full expression lowering will be more complex.
268fn lower_expr_to_temp(builder: &mut MirBuilder, expr: &Expr) -> SlotId {
269    let span = expr.span();
270    let temp = builder.alloc_local("_tmp".to_string(), LocalTypeInfo::Unknown);
271
272    match expr {
273        Expr::Literal(_, _) => {
274            builder.push_stmt(
275                StatementKind::Assign(
276                    Place::Local(temp),
277                    Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
278                ),
279                span,
280            );
281        }
282        Expr::Identifier(_, _) => {
283            // Reference to a local — would resolve to actual slot
284            builder.push_stmt(
285                StatementKind::Assign(
286                    Place::Local(temp),
287                    Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
288                ),
289                span,
290            );
291        }
292        Expr::Reference {
293            expr: inner,
294            is_mutable,
295            span: ref_span,
296        } => {
297            let inner_slot = lower_expr_to_temp(builder, inner);
298            let kind = if *is_mutable {
299                BorrowKind::Exclusive
300            } else {
301                BorrowKind::Shared
302            };
303            builder.push_stmt(
304                StatementKind::Assign(
305                    Place::Local(temp),
306                    Rvalue::Borrow(kind, Place::Local(inner_slot)),
307                ),
308                *ref_span,
309            );
310        }
311        Expr::BinaryOp { left, right, .. } => {
312            let l = lower_expr_to_temp(builder, left);
313            let r = lower_expr_to_temp(builder, right);
314            builder.push_stmt(
315                StatementKind::Assign(
316                    Place::Local(temp),
317                    Rvalue::BinaryOp(
318                        BinOp::Add, // simplified — real op from AST
319                        Operand::Copy(Place::Local(l)),
320                        Operand::Copy(Place::Local(r)),
321                    ),
322                ),
323                span,
324            );
325        }
326        Expr::FunctionCall { args, .. } => {
327            // Lower function calls — simplified for now
328            let arg_ops: Vec<Operand> = args
329                .iter()
330                .map(|a| {
331                    let s = lower_expr_to_temp(builder, a);
332                    Operand::Move(Place::Local(s))
333                })
334                .collect();
335            builder.push_stmt(
336                StatementKind::Assign(Place::Local(temp), Rvalue::Aggregate(arg_ops)),
337                span,
338            );
339        }
340        _ => {
341            // Fallback: emit a Nop + assign from constant
342            builder.push_stmt(
343                StatementKind::Assign(
344                    Place::Local(temp),
345                    Rvalue::Use(Operand::Constant(MirConstant::None)),
346                ),
347                span,
348            );
349        }
350    }
351
352    temp
353}
354
355/// Lower an if statement.
356fn lower_if(
357    builder: &mut MirBuilder,
358    if_stmt: &ast::IfStatement,
359    span: Span,
360    exit_block: BasicBlockId,
361) {
362    let cond_slot = lower_expr_to_temp(builder, &if_stmt.condition);
363
364    let then_block = builder.new_block();
365    let else_block = builder.new_block();
366    let merge_block = builder.new_block();
367
368    builder.finish_block(
369        TerminatorKind::SwitchBool {
370            operand: Operand::Copy(Place::Local(cond_slot)),
371            true_bb: then_block,
372            false_bb: if if_stmt.else_body.is_some() {
373                else_block
374            } else {
375                merge_block
376            },
377        },
378        span,
379    );
380
381    // Then branch
382    builder.start_block(then_block);
383    lower_statements(builder, &if_stmt.then_body, exit_block);
384    builder.finish_block(TerminatorKind::Goto(merge_block), span);
385
386    // Else branch
387    if let Some(else_body) = &if_stmt.else_body {
388        builder.start_block(else_block);
389        lower_statements(builder, else_body, exit_block);
390        builder.finish_block(TerminatorKind::Goto(merge_block), span);
391    }
392
393    // Continue in merge block
394    builder.start_block(merge_block);
395}
396
397/// Lower a while loop.
398fn lower_while(
399    builder: &mut MirBuilder,
400    cond: &Expr,
401    body: &[Statement],
402    span: Span,
403    exit_block: BasicBlockId,
404) {
405    let header = builder.new_block();
406    let body_block = builder.new_block();
407    let after = builder.new_block();
408
409    builder.finish_block(TerminatorKind::Goto(header), span);
410
411    // Loop header: evaluate condition
412    builder.start_block(header);
413    let cond_slot = lower_expr_to_temp(builder, cond);
414    builder.finish_block(
415        TerminatorKind::SwitchBool {
416            operand: Operand::Copy(Place::Local(cond_slot)),
417            true_bb: body_block,
418            false_bb: after,
419        },
420        span,
421    );
422
423    // Loop body
424    builder.start_block(body_block);
425    lower_statements(builder, body, exit_block);
426    builder.finish_block(TerminatorKind::Goto(header), span);
427
428    // After loop
429    builder.start_block(after);
430}
431
432/// Lower a for loop (simplified — treats as while with iterator).
433fn lower_for_loop(
434    builder: &mut MirBuilder,
435    for_loop: &ast::ForLoop,
436    span: Span,
437    exit_block: BasicBlockId,
438) {
439    // Extract the iterable expression
440    let iter_expr = match &for_loop.init {
441        ast::ForInit::ForIn { iter, .. } => iter,
442        ast::ForInit::ForC { condition, .. } => condition,
443    };
444
445    let _iter_slot = lower_expr_to_temp(builder, iter_expr);
446    let header = builder.new_block();
447    let body_block = builder.new_block();
448    let after = builder.new_block();
449
450    builder.finish_block(TerminatorKind::Goto(header), span);
451
452    builder.start_block(header);
453    builder.finish_block(
454        TerminatorKind::SwitchBool {
455            operand: Operand::Constant(MirConstant::Bool(true)),
456            true_bb: body_block,
457            false_bb: after,
458        },
459        span,
460    );
461
462    builder.start_block(body_block);
463    lower_statements(builder, &for_loop.body, exit_block);
464    builder.finish_block(TerminatorKind::Goto(header), span);
465
466    builder.start_block(after);
467}
468
469// Helper to get span from Statement
470trait StatementSpan {
471    fn span(&self) -> Option<Span>;
472}
473
474impl StatementSpan for Statement {
475    fn span(&self) -> Option<Span> {
476        match self {
477            Statement::VariableDecl(_, span) => Some(*span),
478            Statement::Assignment(_, span) => Some(*span),
479            Statement::Expression(_, span) => Some(*span),
480            Statement::Return(_, span) => Some(*span),
481            Statement::If(_, span) => Some(*span),
482            Statement::While(_, span) => Some(*span),
483            Statement::For(_, span) => Some(*span),
484            _ => None,
485        }
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492    use crate::mir::cfg::ControlFlowGraph;
493    use crate::mir::liveness;
494    use shape_ast::ast::{self, DestructurePattern, OwnershipModifier, VarKind};
495
496    fn span() -> Span {
497        Span { start: 0, end: 1 }
498    }
499
500    #[test]
501    fn test_lower_empty_function() {
502        let mir = lower_function("empty", &[], &[], span());
503        assert_eq!(mir.name, "empty");
504        assert!(mir.blocks.len() >= 2); // entry + exit
505        assert_eq!(mir.num_locals, 0);
506    }
507
508    #[test]
509    fn test_lower_simple_var_decl() {
510        let body = vec![Statement::VariableDecl(
511            ast::VariableDecl {
512                kind: VarKind::Let,
513                is_mut: false,
514                pattern: DestructurePattern::Identifier("x".to_string(), span()),
515                type_annotation: None,
516                value: Some(Expr::Literal(ast::Literal::Int(42), span())),
517                ownership: OwnershipModifier::Inferred,
518            },
519            span(),
520        )];
521        let mir = lower_function("test", &[], &body, span());
522        assert!(mir.num_locals >= 1); // at least x + temp
523        // Should have at least 2 blocks (entry + exit)
524        assert!(mir.blocks.len() >= 2);
525    }
526
527    #[test]
528    fn test_lower_with_liveness() {
529        // let x = 1; let y = x; (x live after first stmt, dead after second)
530        let body = vec![
531            Statement::VariableDecl(
532                ast::VariableDecl {
533                    kind: VarKind::Let,
534                    is_mut: false,
535                    pattern: DestructurePattern::Identifier("x".to_string(), span()),
536                    type_annotation: None,
537                    value: Some(Expr::Literal(ast::Literal::Int(1), span())),
538                    ownership: OwnershipModifier::Inferred,
539                },
540                span(),
541            ),
542            Statement::VariableDecl(
543                ast::VariableDecl {
544                    kind: VarKind::Let,
545                    is_mut: false,
546                    pattern: DestructurePattern::Identifier("y".to_string(), span()),
547                    type_annotation: None,
548                    value: Some(Expr::Identifier("x".to_string(), span())),
549                    ownership: OwnershipModifier::Inferred,
550                },
551                span(),
552            ),
553        ];
554        let mir = lower_function("test", &[], &body, span());
555        let cfg = ControlFlowGraph::build(&mir);
556        let _liveness = liveness::compute_liveness(&mir, &cfg);
557        // The MIR lowers and liveness computes without panic
558    }
559}