diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index 5c2d2787..df0f4fb8 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -654,6 +654,19 @@ pub(super) fn merge_and_rewrite( "[DEBUG-177] Phase 246-EX: exit_phi_inputs from jump_args[0]: ({:?}, {:?})", new_block_id, loop_var_exit ); + + // Phase 246-EX-P5: Also add loop_var to carrier_inputs if it's a named variable + // (Pattern 5 case: counter is both loop_var AND a carrier) + if let Some(ref loop_var_name) = b.loop_var_name { + carrier_inputs + .entry(loop_var_name.clone()) + .or_insert_with(Vec::new) + .push((new_block_id, loop_var_exit)); + eprintln!( + "[DEBUG-177] Phase 246-EX-P5: Added loop_var '{}' to carrier_inputs: ({:?}, {:?})", + loop_var_name, new_block_id, loop_var_exit + ); + } } // Phase 246-EX-FIX: jump_args are in carrier_info.carriers order, not exit_bindings order! diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs b/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs index 4180d1e6..4ca6bd1d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs @@ -31,12 +31,83 @@ //! - Lowering logic: TODO (will fail with explicit error) use super::super::trace; +use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::loop_pattern_detection::LoopPatternKind; use crate::mir::ValueId; use super::router::LoopPatternContext; +/// Phase 131-11-D: Enhanced shape guard with real count + position constraints +/// +/// This function validates the loop structure for Pattern 5 with strict requirements: +/// - Exactly 1 break inside `if counter == N { break }` +/// - Exactly 1 continue at the end of the loop body (not nested) +/// - No nested loops +/// - No side effects beyond counter increment and break/continue +fn count_breaks_and_continues(body: &[ASTNode]) -> (usize, usize, bool) { + let mut break_count = 0; + let mut continue_count = 0; + let mut has_nested_loop = false; + + fn scan_node(node: &ASTNode, break_count: &mut usize, continue_count: &mut usize, has_nested_loop: &mut bool, depth: usize) { + match node { + ASTNode::Break { .. } => { + *break_count += 1; + } + ASTNode::Continue { .. } => { + *continue_count += 1; + } + ASTNode::Loop { .. } if depth > 0 => { + *has_nested_loop = true; + } + ASTNode::If { then_body, else_body, .. } => { + for stmt in then_body { + scan_node(stmt, break_count, continue_count, has_nested_loop, depth + 1); + } + if let Some(else_body) = else_body { + for stmt in else_body { + scan_node(stmt, break_count, continue_count, has_nested_loop, depth + 1); + } + } + } + _ => {} + } + } + + for stmt in body { + scan_node(stmt, &mut break_count, &mut continue_count, &mut has_nested_loop, 0); + } + + (break_count, continue_count, has_nested_loop) +} + +/// Phase 131-11-D: Validate continue position (must be at loop body end) +fn validate_continue_position(body: &[ASTNode]) -> bool { + if body.is_empty() { + return false; + } + + // Continue must be the last statement + matches!(body.last(), Some(ASTNode::Continue { .. })) +} + +/// Phase 131-11-D: Validate break is in simple if pattern +fn validate_break_pattern(body: &[ASTNode]) -> bool { + for stmt in body { + if let ASTNode::If { then_body, else_body, .. } = stmt { + // Check then branch for simple break + if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) { + // Ensure no else-break pattern + if else_body.is_none() { + return true; + } + } + } + } + false +} + /// Phase 131-11: Pattern detection for InfiniteEarlyExit /// /// This function checks if the loop matches Pattern 5 characteristics. @@ -66,35 +137,62 @@ pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool return false; } - // Step 3: Shape guard - exactly 1 break - if ctx.features.break_count != 1 { + // Phase 131-11-D: Enhanced real count validation + let (real_break_count, real_continue_count, has_nested_loop) = count_breaks_and_continues(ctx.body); + + // Step 3: Shape guard - exactly 1 break (real count) + if real_break_count != 1 { if debug { trace::trace().debug( "pattern5/detect", &format!( - "Break count mismatch: expected 1, got {}", - ctx.features.break_count + "Real break count mismatch: expected 1, got {}", + real_break_count ), ); } return false; } - // Step 4: Shape guard - exactly 1 continue - if ctx.features.continue_count != 1 { + // Step 4: Shape guard - exactly 1 continue (real count) + if real_continue_count != 1 { if debug { trace::trace().debug( "pattern5/detect", &format!( - "Continue count mismatch: expected 1, got {}", - ctx.features.continue_count + "Real continue count mismatch: expected 1, got {}", + real_continue_count ), ); } return false; } - // Step 5: Shape guard - exactly 1 carrier (counter-like) + // Step 5: No nested loops + if has_nested_loop { + if debug { + trace::trace().debug("pattern5/detect", "Nested loops not supported"); + } + return false; + } + + // Step 6: Validate continue position (must be at end) + if !validate_continue_position(ctx.body) { + if debug { + trace::trace().debug("pattern5/detect", "Continue must be at loop body end"); + } + return false; + } + + // Step 7: Validate break pattern (must be in simple if) + if !validate_break_pattern(ctx.body) { + if debug { + trace::trace().debug("pattern5/detect", "Break must be in simple if pattern"); + } + return false; + } + + // Step 8: Shape guard - exactly 1 carrier (counter-like) // Phase 131-11-C: Start with minimal carrier requirement if ctx.features.carrier_count != 1 { if debug { @@ -114,10 +212,10 @@ pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool trace::trace().debug( "pattern5/detect", &format!( - "Pattern 5 detected: infinite={}, break={}, continue={}, carriers={}", + "Pattern 5 detected: infinite={}, real_break={}, real_continue={}, carriers={}", ctx.features.is_infinite_loop, - ctx.features.break_count, - ctx.features.continue_count, + real_break_count, + real_continue_count, ctx.features.carrier_count ), ); @@ -126,14 +224,78 @@ pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool true } +/// Phase 131-11-D: Extract counter variable name from break condition +/// +/// Looks for pattern: `if counter == N { break }` +/// Returns the counter variable name +fn extract_counter_name(body: &[ASTNode]) -> Result { + use crate::ast::BinaryOperator; + + for stmt in body { + if let ASTNode::If { condition, then_body, .. } = stmt { + // Check if then_body contains just break + if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) { + // Extract counter from condition + if let ASTNode::BinaryOp { operator: BinaryOperator::Equal, left, .. } = condition.as_ref() { + if let ASTNode::Variable { name, .. } = left.as_ref() { + return Ok(name.clone()); + } + } + } + } + } + + Err("Could not extract counter variable from break condition".to_string()) +} + +/// Phase 131-11-D: Extract limit constant from break condition +/// +/// Looks for pattern: `if counter == LIMIT { break }` +/// Returns the LIMIT value +fn extract_limit_value(body: &[ASTNode]) -> Result { + use crate::ast::{BinaryOperator, LiteralValue}; + + for stmt in body { + if let ASTNode::If { condition, then_body, .. } = stmt { + // Check if then_body contains just break + if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) { + // Extract limit from condition + if let ASTNode::BinaryOp { operator: BinaryOperator::Equal, right, .. } = condition.as_ref() { + if let ASTNode::Literal { value: LiteralValue::Integer(limit), .. } = right.as_ref() { + return Ok(*limit); + } + } + } + } + } + + Err("Could not extract limit constant from break condition".to_string()) +} + /// Phase 131-11: Lower InfiniteEarlyExit pattern to JoinIR /// /// # Implementation Status /// -/// Phase 131-11-C: Minimal skeleton - returns Fail-Fast error -/// Phase 131-11-D: TODO - implement actual lowering logic +/// Phase 131-11-D: Full implementation with JoinIR generation +/// +/// # JoinIR Structure (post-increment pattern) +/// +/// ```text +/// fn main(counter_init): +/// result = loop_step(counter_init) +/// return Void +/// +/// fn loop_step(counter): +/// counter_next = counter + 1 +/// break_cond = (counter_next == LIMIT) +/// Jump(k_exit, [counter_next], cond=break_cond) +/// Call(loop_step, [counter_next]) // tail call +/// +/// fn k_exit(counter_exit): +/// return Void +/// ``` pub(crate) fn lower( - _builder: &mut MirBuilder, + builder: &mut MirBuilder, ctx: &LoopPatternContext, ) -> Result, String> { let debug = ctx.debug; @@ -141,15 +303,222 @@ pub(crate) fn lower( if debug { trace::trace().debug( "pattern5/lower", - "Phase 131-11-C: Pattern 5 lowering not yet implemented", + "Phase 131-11-D: Starting Pattern 5 lowering", ); } - // Phase 131-11-C: Fail-Fast with explicit error message - Err(format!( - "Pattern 5 (InfiniteEarlyExit) lowering not yet implemented (Phase 131-11-C skeleton)\n\ - Detected: loop(true) with {} break(s), {} continue(s), {} carrier(s)\n\ - Next step: Implement lowering logic in Phase 131-11-D", - ctx.features.break_count, ctx.features.continue_count, ctx.features.carrier_count - )) + // Step 1: Extract counter variable name + let counter_name = extract_counter_name(ctx.body)?; + if debug { + trace::trace().debug( + "pattern5/lower", + &format!("Extracted counter variable: '{}'", counter_name), + ); + } + + // Step 2: Get counter ValueId from variable_map + let counter_id = builder.variable_map.get(&counter_name).copied().ok_or_else(|| { + format!("Counter variable '{}' not found in variable_map", counter_name) + })?; + + if debug { + trace::trace().debug( + "pattern5/lower", + &format!("Counter ValueId: {:?}", counter_id), + ); + } + + // Step 3: Extract limit value + let limit = extract_limit_value(ctx.body)?; + if debug { + trace::trace().debug( + "pattern5/lower", + &format!("Extracted limit value: {}", limit), + ); + } + + // Step 4: Generate JoinIR + use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; + use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + }; + + let mut join_value_space = JoinValueSpace::new(); + let mut alloc_value = || join_value_space.alloc_local(); + + let mut join_module = JoinModule::new(); + + // Function IDs + let main_id = JoinFuncId::new(0); + let loop_step_id = JoinFuncId::new(1); + let k_exit_id = JoinFuncId::new(2); + + // ValueId allocation + // main() locals + let counter_init = alloc_value(); // ValueId(1000) - initial counter + let loop_result = alloc_value(); // ValueId(1001) - result from loop_step + + // loop_step locals + let counter_param = alloc_value(); // ValueId(1002) - parameter + let const_1 = alloc_value(); // ValueId(1003) - increment constant + let counter_next = alloc_value(); // ValueId(1004) - counter + 1 + let const_limit = alloc_value(); // ValueId(1005) - limit constant + let break_cond = alloc_value(); // ValueId(1006) - counter_next == LIMIT + + // k_exit locals + let counter_exit = alloc_value(); // ValueId(1007) - exit parameter + + // ================================================================== + // main() function + // ================================================================== + let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![counter_init]); + + // result = loop_step(counter_init) + main_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![counter_init], + k_next: None, + dst: Some(loop_result), + }); + + // return 0 (Pattern 5 doesn't produce a value, but we return 0 by convention) + let const_0 = alloc_value(); + main_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_0, + value: ConstValue::Integer(0), + })); + + main_func.body.push(JoinInst::Ret { + value: Some(const_0), + }); + + join_module.add_function(main_func); + + // ================================================================== + // loop_step(counter) function - post-increment pattern + // ================================================================== + let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![counter_param]); + + // counter_next = counter + 1 + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: counter_next, + op: BinOpKind::Add, + lhs: counter_param, + rhs: const_1, + })); + + // break_cond = (counter_next == LIMIT) + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_limit, + value: ConstValue::Integer(limit), + })); + + loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: break_cond, + op: CompareOp::Eq, + lhs: counter_next, + rhs: const_limit, + })); + + // Jump(k_exit, [counter_next], cond=break_cond) + loop_step_func.body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![counter_next], + cond: Some(break_cond), + }); + + // Call(loop_step, [counter_next]) - tail recursion + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![counter_next], + k_next: None, // tail call + dst: None, + }); + + join_module.add_function(loop_step_func); + + // ================================================================== + // k_exit(counter_exit) function + // ================================================================== + let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![counter_exit]); + + // Return counter_exit (the final counter value) instead of const 0 + k_exit_func.body.push(JoinInst::Ret { + value: Some(counter_exit), + }); + + join_module.add_function(k_exit_func); + + // Set entry point + join_module.entry = Some(main_id); + + if debug { + trace::trace().debug( + "pattern5/lower", + "Generated JoinIR: main, loop_step, k_exit", + ); + } + + // Step 5: Create CarrierInfo for the counter (Phase 131-11-D) + // Note: counter is the loop variable, NOT a separate carrier + use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; + + let carrier_info = CarrierInfo { + loop_var_name: counter_name.clone(), + loop_var_id: counter_id, + carriers: vec![], // No separate carriers - counter is the loop variable itself + trim_helper: None, + promoted_loopbodylocals: Vec::new(), + #[cfg(feature = "normalized_dev")] + promoted_bindings: std::collections::BTreeMap::new(), + }; + + // Step 6: Create ExitMeta with counter as exit value + // Phase 131-11-D: The counter flows through jump_args and must be registered in exit_values + let exit_meta = ExitMeta { + exit_values: vec![(counter_name.clone(), counter_exit)], + }; + + // Step 7: Build boundary and merge + use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector; + use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; + + let exit_bindings = ExitMetaCollector::collect(builder, &exit_meta, Some(&carrier_info), debug); + + let boundary = JoinInlineBoundaryBuilder::new() + .with_inputs( + vec![counter_init], // JoinIR main() parameter + vec![counter_id], // Host counter value + ) + .with_exit_bindings(exit_bindings) + .with_loop_var_name(Some(counter_name.clone())) // Phase 131-11-D: For LoopHeaderPhiBuilder + .with_carrier_info(carrier_info) // Phase 131-11-D: For exit line reconnection + .build(); + + // Step 7: Execute conversion pipeline + use super::conversion_pipeline::JoinIRConversionPipeline; + let _ = JoinIRConversionPipeline::execute( + builder, + join_module, + Some(&boundary), + "pattern5", + debug, + )?; + + // Step 8: Return Void (loops don't produce values) + let void_val = crate::mir::builder::emission::constant::emit_void(builder); + + if debug { + trace::trace().debug( + "pattern5/lower", + &format!("Pattern 5 lowering complete, returning Void {:?}", void_val), + ); + } + + Ok(Some(void_val)) }