//! 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::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, UnaryOp, }; use crate::mir::ValueId; /// Context passed from the host function to the Pattern 1 lowerer pub struct Pattern1Context { /// The loop variable ValueId from the host function (e.g., ValueId(6) for `i`) pub loop_var: ValueId, /// ValueId allocator function pub value_allocator: Box ValueId>, } impl Pattern1Context { /// Create a standalone context with hardcoded ValueIds (for backward compatibility) pub fn standalone() -> Self { let mut counter = 1000u32; Self { loop_var: ValueId(counter), value_allocator: Box::new(move || { counter += 1; ValueId(counter) }), } } } /// Lower Pattern 1 (Simple While Loop) to JoinIR /// /// This is a minimal implementation for loop_min_while.hako. /// It generates JoinIR that integrates with the host function's variable bindings. /// /// # Phase 188-Impl-2: Host Variable Integration /// /// This version accepts the host's loop variable ValueId and allocates fresh IDs /// for intermediate values. This ensures the generated JoinIR connects properly /// to the host function's variable bindings. /// /// If called without a context (from legacy code), it uses standalone mode with /// hardcoded ValueIds for backward compatibility. /// /// # Arguments /// /// * `_scope` - LoopScopeShape (reserved for future generic implementation) /// * `ctx` - Pattern1Context containing host variable bindings (or None for standalone) /// /// # Returns /// /// * `Some(JoinModule)` - Successfully lowered to JoinIR /// * `None` - Pattern not matched (fallback to other lowerers) pub fn lower_simple_while_minimal( _scope: LoopScopeShape, ctx: Option, ) -> Option { let mut ctx = ctx.unwrap_or_else(Pattern1Context::standalone); // Phase 188-Impl-1: Hardcoded JoinIR for loop_min_while.hako // This establishes the infrastructure. Generic implementation in Phase 188-Impl-2+. 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-2: Use host variable + allocator) // ================================================================== // Host's loop variable (e.g., ValueId(6) for `i`) let i_init = ctx.loop_var; // Allocate fresh IDs for local values let loop_result = (ctx.value_allocator)(); let const_0_main = (ctx.value_allocator)(); // loop_step locals let i_param = (ctx.value_allocator)(); let const_3 = (ctx.value_allocator)(); let cmp_lt = (ctx.value_allocator)(); let exit_cond = (ctx.value_allocator)(); let const_1 = (ctx.value_allocator)(); let i_next = (ctx.value_allocator)(); // ================================================================== // main() function // ================================================================== let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![]); // Phase 188-Impl-2: Skip i_init = 0 (host already initialized the variable) // The host's ValueId (i_init) is already bound to 0 in the host function // result = loop_step(i_init) ← Use host's i directly 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) let const_0_exit = ValueId(3000); 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) }