diff --git a/src/mir/builder/control_flow/plan/lowerer.rs b/src/mir/builder/control_flow/plan/lowerer.rs index 2e036ade..4743aeaf 100644 --- a/src/mir/builder/control_flow/plan/lowerer.rs +++ b/src/mir/builder/control_flow/plan/lowerer.rs @@ -17,7 +17,7 @@ //! - Legacy fallback has been removed (Fail-Fast on missing fields) //! - Pattern-specific emission functions (emit_scan_with_init_edgecfg) no longer used -use super::{CoreEffectPlan, CoreLoopPlan, CorePlan}; +use super::{CoreEffectPlan, CoreExitPlan, CoreIfPlan, CoreLoopPlan, CorePlan}; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::MirBuilder; use crate::mir::{MirInstruction, ValueId}; @@ -41,18 +41,12 @@ impl PlanLowerer { match plan { CorePlan::Seq(plans) => Self::lower_seq(builder, plans, ctx), CorePlan::Loop(loop_plan) => Self::lower_loop(builder, loop_plan, ctx), - CorePlan::If(_if_plan) => { - // P1: If is handled inline in Loop body - Err("[lowerer] Standalone CorePlan::If not yet supported".to_string()) - } + CorePlan::If(if_plan) => Self::lower_if(builder, if_plan, ctx), CorePlan::Effect(effect) => { Self::emit_effect(builder, &effect)?; Ok(None) } - CorePlan::Exit(_exit) => { - // P1: Exit is handled by edge CFG in Loop - Err("[lowerer] Standalone CorePlan::Exit not yet supported".to_string()) - } + CorePlan::Exit(exit) => Self::lower_exit(builder, exit), } } @@ -103,6 +97,74 @@ impl PlanLowerer { Self::lower_loop_generalized(builder, loop_plan, ctx) } + /// If: emit Branch and lower then/else plans (standalone) + fn lower_if( + builder: &mut MirBuilder, + if_plan: CoreIfPlan, + ctx: &LoopPatternContext, + ) -> Result, String> { + use crate::mir::builder::emission::branch::{emit_conditional, emit_jump}; + + let _pre_branch_bb = builder + .current_block + .ok_or_else(|| "[lowerer] No current block for CorePlan::If".to_string())?; + + let then_bb = builder.next_block_id(); + let else_bb = builder.next_block_id(); + let merge_bb = builder.next_block_id(); + + builder.ensure_block_exists(then_bb)?; + builder.ensure_block_exists(else_bb)?; + builder.ensure_block_exists(merge_bb)?; + + let mut condition_val = if_plan.condition; + crate::mir::builder::ssa::local::finalize_branch_cond(builder, &mut condition_val); + emit_conditional(builder, condition_val, then_bb, else_bb)?; + + // then + builder.start_new_block(then_bb)?; + for plan in if_plan.then_plans { + Self::lower(builder, plan, ctx)?; + } + let then_reaches_merge = !builder.is_current_block_terminated(); + if then_reaches_merge { + emit_jump(builder, merge_bb)?; + } + + // else + builder.start_new_block(else_bb)?; + if let Some(else_plans) = if_plan.else_plans { + for plan in else_plans { + Self::lower(builder, plan, ctx)?; + } + } + let else_reaches_merge = !builder.is_current_block_terminated(); + if else_reaches_merge { + emit_jump(builder, merge_bb)?; + } + + // merge (may be unreachable if both branches terminate) + builder.start_new_block(merge_bb)?; + Ok(None) + } + + /// Exit: emit Return (standalone); Break/Continue require loop context + fn lower_exit( + builder: &mut MirBuilder, + exit: CoreExitPlan, + ) -> Result, String> { + match exit { + CoreExitPlan::Return(opt_val) => { + builder.emit_instruction(MirInstruction::Return { value: opt_val })?; + Ok(opt_val) + } + CoreExitPlan::Break => Err("[lowerer] CorePlan::Exit::Break requires loop context".to_string()), + CoreExitPlan::Continue => { + Err("[lowerer] CorePlan::Exit::Continue requires loop context".to_string()) + } + } + } + /// Phase 273 P3: Generalized loop lowering (SSOT) fn lower_loop_generalized( builder: &mut MirBuilder, @@ -265,3 +327,89 @@ impl PlanLowerer { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{ASTNode, LiteralValue, Span}; + use crate::mir::{ConstValue, MirInstruction}; + + fn make_ctx<'a>(condition: &'a ASTNode, body: &'a [ASTNode]) -> LoopPatternContext<'a> { + LoopPatternContext::new(condition, body, "test_coreplan", false, false) + } + + #[test] + fn test_lower_exit_return_sets_terminator() { + let mut builder = MirBuilder::new(); + builder.enter_function_for_test("test_exit".to_string()); + + let ret_val = builder.alloc_value_for_test(); + builder + .emit_for_test(MirInstruction::Const { + dst: ret_val, + value: ConstValue::Integer(1), + }) + .expect("emit const"); + + let cond = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let body: Vec = vec![]; + let ctx = make_ctx(&cond, &body); + + let plan = CorePlan::Exit(CoreExitPlan::Return(Some(ret_val))); + let result = PlanLowerer::lower(&mut builder, plan, &ctx); + assert!(result.is_ok()); + + let entry = builder.current_block_for_test().expect("entry block"); + let func = builder.scope_ctx.current_function.as_ref().expect("function"); + let block = func.get_block(entry).expect("block"); + assert!( + matches!(block.terminator, Some(MirInstruction::Return { value: Some(v) }) if v == ret_val), + "expected Return terminator" + ); + } + + #[test] + fn test_lower_if_emits_branch() { + let mut builder = MirBuilder::new(); + builder.enter_function_for_test("test_if".to_string()); + + let entry = builder.current_block_for_test().expect("entry block"); + let cond_val = builder.alloc_value_for_test(); + builder + .emit_for_test(MirInstruction::Const { + dst: cond_val, + value: ConstValue::Bool(true), + }) + .expect("emit const"); + + let then_val = builder.alloc_value_for_test(); + let if_plan = CoreIfPlan { + condition: cond_val, + then_plans: vec![CorePlan::Effect(CoreEffectPlan::Const { + dst: then_val, + value: ConstValue::Integer(2), + })], + else_plans: None, + }; + + let cond = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let body: Vec = vec![]; + let ctx = make_ctx(&cond, &body); + + let result = PlanLowerer::lower(&mut builder, CorePlan::If(if_plan), &ctx); + assert!(result.is_ok()); + + let func = builder.scope_ctx.current_function.as_ref().expect("function"); + let block = func.get_block(entry).expect("entry block"); + assert!( + matches!(block.terminator, Some(MirInstruction::Branch { .. })), + "expected Branch terminator" + ); + } +} diff --git a/src/mir/builder/control_flow/plan/verifier.rs b/src/mir/builder/control_flow/plan/verifier.rs index f41d464d..3377f544 100644 --- a/src/mir/builder/control_flow/plan/verifier.rs +++ b/src/mir/builder/control_flow/plan/verifier.rs @@ -17,6 +17,7 @@ //! - V8: Frag entry matches header_bb //! - V9: block_effects contains header_bb //! - V10: body_bb effects go in loop_plan.body (block_effects[body_bb] must be empty) +//! - V11: Exit must be last in Seq/If branch //! //! Phase 273 P3: V1 (Carrier completeness) removed with CoreCarrierInfo @@ -53,6 +54,8 @@ impl PlanVerifier { )); } + Self::verify_exit_position(plans, depth, "Seq")?; + for (i, plan) in plans.iter().enumerate() { Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| { format!("[Seq[{}]] {}", i, e) @@ -164,6 +167,20 @@ impl PlanVerifier { )); } + if let Some(else_plans) = &if_plan.else_plans { + if else_plans.is_empty() { + return Err(format!( + "[V5] If at depth {} has empty else_plans", + depth + )); + } + } + + Self::verify_exit_position(&if_plan.then_plans, depth, "If.then")?; + if let Some(else_plans) = &if_plan.else_plans { + Self::verify_exit_position(else_plans, depth, "If.else")?; + } + for (i, plan) in if_plan.then_plans.iter().enumerate() { Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| { format!("[If.then[{}]] {}", i, e) @@ -246,6 +263,21 @@ impl PlanVerifier { Ok(()) } + fn verify_exit_position(plans: &[CorePlan], depth: usize, scope: &str) -> Result<(), String> { + for (i, plan) in plans.iter().enumerate() { + if matches!(plan, CorePlan::Exit(_)) && i + 1 != plans.len() { + return Err(format!( + "[V11] Exit at depth {} in {} must be last (index {}, len {})", + depth, + scope, + i, + plans.len() + )); + } + } + Ok(()) + } + /// V6: Basic ValueId validity check /// /// Note: This is a basic check. Full validity would require builder context. @@ -291,6 +323,36 @@ mod tests { assert!(result.is_ok()); } + #[test] + fn test_verify_if_empty_else_fails() { + let if_plan = CoreIfPlan { + condition: ValueId(1), + then_plans: vec![CorePlan::Effect(CoreEffectPlan::Const { + dst: ValueId(2), + value: ConstValue::Integer(1), + })], + else_plans: Some(vec![]), + }; + let plan = CorePlan::If(if_plan); + let result = PlanVerifier::verify(&plan); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("[V5]")); + } + + #[test] + fn test_verify_exit_not_last_fails() { + let plan = CorePlan::Seq(vec![ + CorePlan::Exit(CoreExitPlan::Return(None)), + CorePlan::Effect(CoreEffectPlan::Const { + dst: ValueId(1), + value: ConstValue::Integer(0), + }), + ]); + let result = PlanVerifier::verify(&plan); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("[V11]")); + } + #[test] fn test_v10_body_bb_effects_in_block_effects_fails() { // V10: body_bb effects must be empty in block_effects