//! Phase 188.3 P1: Pattern 6 (Nested Loop Minimal) JoinIR Lowerer //! //! Target: apps/tests/phase1883_nested_minimal.hako //! //! Code: //! ```nyash //! static box Main { //! main() { //! local sum = 0 //! local i = 0 //! loop(i < 3) { //! local j = 0 //! loop(j < 3) { //! sum = sum + 1 //! j = j + 1 //! } //! i = i + 1 //! } //! return sum //! } //! } //! ``` //! //! Expected JoinIR (4 functions): //! ```text //! fn main(i0, sum0): //! Call(loop_step, [i0, sum0]) //! Ret 0 //! //! fn loop_step(i, sum): // outer loop //! exit_cond = !(i < 3) //! Jump(k_exit, [sum], cond=exit_cond) //! j0 = 0 //! Call(inner_step, [j0, i, sum]) //! //! fn inner_step(j, i_outer, sum): // inner loop (tail recursion) //! exit_cond = !(j < 3) //! Jump(k_inner_exit, [i_outer, sum], cond=exit_cond) //! sum_next = sum + 1 //! j_next = j + 1 //! Call(inner_step, [j_next, i_outer, sum_next]) //! //! fn k_inner_exit(i, sum): // outer continuation (after inner loop) //! i_next = i + 1 //! Call(loop_step, [i_next, sum]) //! //! fn k_exit(sum): //! Ret sum //! ``` //! //! ## Design Notes (Phase 188.3) //! //! - **4-function model**: main, loop_step (outer), inner_step, k_inner_exit, k_exit //! - **Carrier design**: sum is passed as argument (not global) //! - **Merge control**: continuation_func_ids includes k_exit, k_inner_exit, inner_step //! - **Pattern1-based**: Both outer and inner are Pattern1 (no break/continue) use crate::mir::join_ir::lowering::canonical_names as cn; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, UnaryOp, }; /// Canonical names for Pattern 6 continuations pub const INNER_STEP: &str = "inner_step"; pub const K_INNER_EXIT: &str = "k_inner_exit"; /// Lower Pattern 6 (Nested Loop Minimal) to JoinIR /// /// # Phase 188.3 P1: Minimal nested loop implementation /// /// This version generates JoinIR using **JoinValueSpace** for unified ValueId allocation. /// It uses the Local region (1000+) to avoid collision with Param region (100-999). /// /// ## 4-Function Model /// /// - **main(i0, sum0)**: Entry point, calls loop_step /// - **loop_step(i, sum)**: Outer loop header, initializes & calls inner loop /// - **inner_step(j, i_outer, sum)**: Inner loop body (tail recursion) /// - **k_inner_exit(i, sum)**: Outer continuation after inner loop exits /// - **k_exit(sum)**: Final exit continuation /// /// # Arguments /// /// * `_scope` - LoopScopeShape (reserved for future generic implementation) /// * `join_value_space` - Unified ValueId allocator (Phase 202-A) /// /// # Returns /// /// * `Some(JoinModule)` - Successfully lowered to JoinIR /// * `None` - Pattern not matched (fallback to other lowerers) /// /// # Boundary Contract /// /// This function returns a JoinModule with: /// - **Input slots**: main() params (i0, sum0) for outer loop variables /// - **Caller responsibility**: Create JoinInlineBoundary to map param ValueIds to host variables pub(crate) fn lower_nested_loop_minimal( _scope: LoopScopeShape, join_value_space: &mut JoinValueSpace, ) -> Option { let mut join_module = JoinModule::new(); // ================================================================== // Function IDs allocation // ================================================================== let main_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); let inner_step_id = JoinFuncId::new(2); let k_inner_exit_id = JoinFuncId::new(3); let k_exit_id = JoinFuncId::new(4); // ================================================================== // ValueId allocation - main() function // ================================================================== let i_main_param = join_value_space.alloc_param(); // i0 (outer loop var) let sum_main_param = join_value_space.alloc_param(); // sum0 (carrier) let loop_result = join_value_space.alloc_local(); // result from loop_step let const_0_main = join_value_space.alloc_local(); // return value // ================================================================== // ValueId allocation - loop_step (outer loop) // ================================================================== let i_step_param = join_value_space.alloc_param(); // outer loop var let sum_step_param = join_value_space.alloc_param(); // sum carrier let const_3 = join_value_space.alloc_local(); // outer limit (3) let cmp_i_lt = join_value_space.alloc_local(); // i < 3 let exit_cond_outer = join_value_space.alloc_local(); // !(i < 3) let j0 = join_value_space.alloc_local(); // inner loop init (0) // ================================================================== // ValueId allocation - inner_step (inner loop) // ================================================================== let j_inner_param = join_value_space.alloc_param(); // inner loop var let i_inner_param = join_value_space.alloc_param(); // outer var (read-only) let sum_inner_param = join_value_space.alloc_param(); // sum carrier let const_3_inner = join_value_space.alloc_local(); // inner limit (3) let cmp_j_lt = join_value_space.alloc_local(); // j < 3 let exit_cond_inner = join_value_space.alloc_local(); // !(j < 3) let const_1_sum = join_value_space.alloc_local(); // increment (1) let sum_next = join_value_space.alloc_local(); // sum + 1 let const_1_j = join_value_space.alloc_local(); // increment (1) let j_next = join_value_space.alloc_local(); // j + 1 // ================================================================== // ValueId allocation - k_inner_exit (outer continuation) // ================================================================== let i_kexit_param = join_value_space.alloc_param(); // outer var let sum_kexit_param = join_value_space.alloc_param(); // sum carrier let const_1_i = join_value_space.alloc_local(); // increment (1) let i_next = join_value_space.alloc_local(); // i + 1 // ================================================================== // ValueId allocation - k_exit (final exit) // ================================================================== let sum_exit_param = join_value_space.alloc_param(); // sum for return // ================================================================== // main() function // ================================================================== let mut main_func = JoinFunction::new(main_id, cn::MAIN.to_string(), vec![i_main_param, sum_main_param]); // result = loop_step(i_main_param, sum_main_param) main_func.body.push(JoinInst::Call { func: loop_step_id, args: vec![i_main_param, sum_main_param], k_next: None, dst: Some(loop_result), }); // return 0 (statement position) main_func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: const_0_main, value: ConstValue::Integer(0), })); main_func.body.push(JoinInst::Ret { value: Some(const_0_main), }); join_module.add_function(main_func); // ================================================================== // loop_step(i, sum) - outer loop header // ================================================================== let mut loop_step_func = JoinFunction::new( loop_step_id, cn::LOOP_STEP.to_string(), vec![i_step_param, sum_step_param], ); // exit_cond = !(i < 3) // Step 1: const 3 loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_3, value: ConstValue::Integer(3), })); // Step 2: cmp_i_lt = (i < 3) loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Compare { dst: cmp_i_lt, op: CompareOp::Lt, lhs: i_step_param, rhs: const_3, })); // Step 3: exit_cond_outer = !cmp_i_lt loop_step_func .body .push(JoinInst::Compute(MirLikeInst::UnaryOp { dst: exit_cond_outer, op: UnaryOp::Not, operand: cmp_i_lt, })); // Jump(k_exit, [sum], cond=exit_cond_outer) loop_step_func.body.push(JoinInst::Jump { cont: k_exit_id.as_cont(), args: vec![sum_step_param], cond: Some(exit_cond_outer), }); // j0 = 0 (inner loop initialization) loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: j0, value: ConstValue::Integer(0), })); // Call(inner_step, [j0, i, sum]) - no continuation (tail call) loop_step_func.body.push(JoinInst::Call { func: inner_step_id, args: vec![j0, i_step_param, sum_step_param], k_next: None, dst: None, }); join_module.add_function(loop_step_func); // ================================================================== // inner_step(j, i_outer, sum) - inner loop (tail recursion) // ================================================================== let mut inner_step_func = JoinFunction::new( inner_step_id, INNER_STEP.to_string(), vec![j_inner_param, i_inner_param, sum_inner_param], ); // exit_cond = !(j < 3) // Step 1: const 3 inner_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_3_inner, value: ConstValue::Integer(3), })); // Step 2: cmp_j_lt = (j < 3) inner_step_func .body .push(JoinInst::Compute(MirLikeInst::Compare { dst: cmp_j_lt, op: CompareOp::Lt, lhs: j_inner_param, rhs: const_3_inner, })); // Step 3: exit_cond_inner = !cmp_j_lt inner_step_func .body .push(JoinInst::Compute(MirLikeInst::UnaryOp { dst: exit_cond_inner, op: UnaryOp::Not, operand: cmp_j_lt, })); // Jump(k_inner_exit, [i_outer, sum], cond=exit_cond_inner) inner_step_func.body.push(JoinInst::Jump { cont: k_inner_exit_id.as_cont(), args: vec![i_inner_param, sum_inner_param], cond: Some(exit_cond_inner), }); // sum_next = sum + 1 // Step 1: const 1 inner_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_1_sum, value: ConstValue::Integer(1), })); // Step 2: sum_next = sum + 1 inner_step_func .body .push(JoinInst::Compute(MirLikeInst::BinOp { dst: sum_next, op: BinOpKind::Add, lhs: sum_inner_param, rhs: const_1_sum, })); // j_next = j + 1 // Step 1: const 1 inner_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_1_j, value: ConstValue::Integer(1), })); // Step 2: j_next = j + 1 inner_step_func .body .push(JoinInst::Compute(MirLikeInst::BinOp { dst: j_next, op: BinOpKind::Add, lhs: j_inner_param, rhs: const_1_j, })); // Call(inner_step, [j_next, i_outer, sum_next]) - tail recursion inner_step_func.body.push(JoinInst::Call { func: inner_step_id, args: vec![j_next, i_inner_param, sum_next], k_next: None, dst: None, }); join_module.add_function(inner_step_func); // ================================================================== // k_inner_exit(i, sum) - outer continuation (after inner loop) // ================================================================== let mut k_inner_exit_func = JoinFunction::new( k_inner_exit_id, K_INNER_EXIT.to_string(), vec![i_kexit_param, sum_kexit_param], ); // i_next = i + 1 // Step 1: const 1 k_inner_exit_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_1_i, value: ConstValue::Integer(1), })); // Step 2: i_next = i + 1 k_inner_exit_func .body .push(JoinInst::Compute(MirLikeInst::BinOp { dst: i_next, op: BinOpKind::Add, lhs: i_kexit_param, rhs: const_1_i, })); // Call(loop_step, [i_next, sum]) - tail call to outer loop k_inner_exit_func.body.push(JoinInst::Call { func: loop_step_id, args: vec![i_next, sum_kexit_param], k_next: None, dst: None, }); join_module.add_function(k_inner_exit_func); // ================================================================== // k_exit(sum) - final exit continuation // ================================================================== let mut k_exit_func = JoinFunction::new(k_exit_id, cn::K_EXIT.to_string(), vec![sum_exit_param]); // return sum k_exit_func.body.push(JoinInst::Ret { value: Some(sum_exit_param), }); join_module.add_function(k_exit_func); // Set entry point join_module.entry = Some(main_id); eprintln!("[joinir/pattern6] Generated JoinIR for Nested Loop Minimal Pattern"); eprintln!("[joinir/pattern6] Functions: main, loop_step, inner_step, k_inner_exit, k_exit"); Some(join_module) }