//! Phase 188-Impl-1: Pattern 1 (Simple While Loop) Minimal Lowerer //! //! Target: apps/tests/loop_min_while.hako //! //! Code: //! ```nyash //! static box Main { //! main() { //! local i = 0 //! loop(i < 3) { //! print(i) //! i = i + 1 //! } //! return 0 //! } //! } //! ``` //! //! Expected JoinIR: //! ```text //! fn main(): //! i_init = 0 //! result = loop_step(i_init) //! return 0 //! //! fn loop_step(i): //! exit_cond = !(i < 3) //! Jump(k_exit, [], cond=exit_cond) // early return if i >= 3 //! print(i) // body //! i_next = i + 1 // increment //! Call(loop_step, [i_next]) // tail recursion //! //! fn k_exit(): //! return 0 //! ``` //! //! ## Design Notes //! //! This is a MINIMAL implementation targeting loop_min_while.hako specifically. //! It establishes the infrastructure for Pattern 1 lowering, which will be //! generalized in future phases. //! //! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later. 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, }; /// Lower Pattern 1 (Simple While Loop) to JoinIR /// /// # Phase 188-Impl-3: Pure JoinIR Fragment Generation /// # Phase 202-A: JoinValueSpace Integration /// /// This version generates JoinIR using **JoinValueSpace** for unified ValueId allocation. /// It uses the Local region (1000+) to avoid collision with Param region (100-999). /// /// ## Design Philosophy /// /// - **Box A**: JoinIR Frontend (doesn't know about host ValueIds) /// - **Box B**: This function - converts to JoinIR with local IDs /// - **Box C**: JoinInlineBoundary - stores boundary info /// - **Box D**: merge_joinir_mir_blocks - injects Copy instructions /// /// This clean separation ensures JoinIR lowerers are: /// - Pure transformers (no side effects) /// - Reusable (same lowerer works in any context) /// - Testable (can test JoinIR independently) /// /// # 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 slot**: ValueId(0) in loop_step function represents the loop variable /// - **Caller responsibility**: Create JoinInlineBoundary to map ValueId(0) to host's loop var pub(crate) fn lower_simple_while_minimal( _scope: LoopScopeShape, join_value_space: &mut JoinValueSpace, ) -> Option { // Phase 202-A: Use JoinValueSpace for Local region allocation (1000+) // This ensures no collision with Param region (100-999) used by ConditionEnv/CarrierInfo let mut alloc_value = || join_value_space.alloc_local(); let mut join_module = JoinModule::new(); // ================================================================== // Function IDs allocation // ================================================================== let main_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); let k_exit_id = JoinFuncId::new(2); // ================================================================== // ValueId allocation (Phase 188-Impl-3: Sequential local IDs) // ================================================================== // main() locals let i_init = alloc_value(); // ValueId(0) - loop init value let loop_result = alloc_value(); // ValueId(1) - result from loop_step let const_0_main = alloc_value(); // ValueId(2) - return value // loop_step locals let i_param = alloc_value(); // ValueId(3) - parameter let const_3 = alloc_value(); // ValueId(4) - comparison constant let cmp_lt = alloc_value(); // ValueId(5) - i < 3 let exit_cond = alloc_value(); // ValueId(6) - !(i < 3) let const_1 = alloc_value(); // ValueId(7) - increment constant let i_next = alloc_value(); // ValueId(8) - i + 1 // k_exit locals let const_0_exit = alloc_value(); // ValueId(9) - exit return value // ================================================================== // main() function // ================================================================== // Phase 188-Impl-3: main() takes i as a parameter (boundary input) // The host will inject a Copy instruction: i_init_local = Copy host_i let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init]); // result = loop_step(i_init) main_func.body.push(JoinInst::Call { func: loop_step_id, args: vec![i_init], k_next: None, dst: Some(loop_result), }); // return 0 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) function // ================================================================== let mut loop_step_func = JoinFunction::new( loop_step_id, "loop_step".to_string(), vec![i_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_lt = (i < 3) loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Compare { dst: cmp_lt, op: CompareOp::Lt, lhs: i_param, rhs: const_3, })); // Step 3: exit_cond = !cmp_lt loop_step_func .body .push(JoinInst::Compute(MirLikeInst::UnaryOp { dst: exit_cond, op: UnaryOp::Not, operand: cmp_lt, })); // Jump(k_exit, [], cond=exit_cond) loop_step_func.body.push(JoinInst::Jump { cont: k_exit_id.as_cont(), args: vec![], cond: Some(exit_cond), }); // print(i) // Phase 188-Impl-1-E: Use Print instruction loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Print { value: i_param, })); // i_next = i + 1 // Step 1: const 1 loop_step_func .body .push(JoinInst::Compute(MirLikeInst::Const { dst: const_1, value: ConstValue::Integer(1), })); // Step 2: i_next = i + 1 loop_step_func .body .push(JoinInst::Compute(MirLikeInst::BinOp { dst: i_next, op: BinOpKind::Add, lhs: i_param, rhs: const_1, })); // Call(loop_step, [i_next]) // tail recursion loop_step_func.body.push(JoinInst::Call { func: loop_step_id, args: vec![i_next], k_next: None, // CRITICAL: None for tail call dst: None, }); join_module.add_function(loop_step_func); // ================================================================== // k_exit() function // ================================================================== let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![]); // return 0 (Pattern 1 has no exit values) // Phase 188-Impl-3: Use pre-allocated const_0_exit (ValueId(9)) k_exit_func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: const_0_exit, value: ConstValue::Integer(0), })); k_exit_func.body.push(JoinInst::Ret { value: Some(const_0_exit), }); join_module.add_function(k_exit_func); // Set entry point join_module.entry = Some(main_id); eprintln!("[joinir/pattern1] Generated JoinIR for Simple While Pattern"); eprintln!("[joinir/pattern1] Functions: main, loop_step, k_exit"); Some(join_module) }