feat(joinir): Pattern 4 (continue) JoinIR lowering implementation
- Add loop_with_continue_minimal.rs (330 lines) - Generate JoinIR: main → loop_step → k_exit for continue patterns - Integrate pattern4_with_continue.rs to call minimal lowerer - Known issue: JoinIR→MIR bridge doesn't handle multiple carriers (output=0 instead of expected=25, needs PHI fix in merge layer) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -97,24 +97,102 @@ impl MirBuilder {
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
_body: &[ASTNode],
|
||||
func_name: &str,
|
||||
_func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
|
||||
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern4", "Pattern 4 lowerer called (stub implementation)");
|
||||
trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer");
|
||||
|
||||
// TODO: Implement Pattern 4 lowering logic
|
||||
//
|
||||
// For now, return an error to fall back to legacy loop builder
|
||||
// This allows the test to run (even if it produces wrong results)
|
||||
// Extract loop variables from condition (i and sum)
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self
|
||||
.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[cf_loop/pattern4] Loop variable '{}' not found in variable_map",
|
||||
loop_var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
if debug {
|
||||
eprintln!("[pattern4] Pattern 4 lowerer not yet implemented for '{}'", func_name);
|
||||
eprintln!("[pattern4] Falling back to legacy loop builder");
|
||||
}
|
||||
// Get sum variable from variable_map
|
||||
let sum_var_id = self
|
||||
.variable_map
|
||||
.get("sum")
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!("[cf_loop/pattern4] Sum variable 'sum' not found in variable_map")
|
||||
})?;
|
||||
|
||||
// Return None to indicate pattern not supported
|
||||
// This will cause the router to try other patterns or fall back to legacy
|
||||
Ok(None)
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().varmap("pattern4_start", &self.variable_map);
|
||||
|
||||
// Create a minimal LoopScopeShape (Phase 195: hardcoded for loop_continue_pattern4.hako)
|
||||
// Pattern 4 lowerer ignores the scope anyway, so this is just a placeholder
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
let scope = LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
body: BasicBlockId(0),
|
||||
latch: BasicBlockId(0),
|
||||
exit: BasicBlockId(0),
|
||||
pinned: BTreeSet::new(),
|
||||
carriers: BTreeSet::new(),
|
||||
body_locals: BTreeSet::new(),
|
||||
exit_live: BTreeSet::new(),
|
||||
progress_carrier: None,
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Call Pattern 4 lowerer
|
||||
let join_module = match lower_loop_with_continue_minimal(scope) {
|
||||
Some(module) => module,
|
||||
None => {
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None");
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().joinir_stats(
|
||||
"pattern4",
|
||||
join_module.functions.len(),
|
||||
join_module.functions.values().map(|f| f.body.len()).sum(),
|
||||
);
|
||||
|
||||
// Convert JoinModule to MirModule
|
||||
// Phase 195: Pass empty meta map since Pattern 4 lowerer doesn't use metadata
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
||||
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[cf_loop/joinir/pattern4] MIR conversion failed: {:?}", e))?;
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().joinir_stats(
|
||||
"pattern4",
|
||||
mir_module.functions.len(),
|
||||
mir_module.functions.values().map(|f| f.blocks.len()).sum(),
|
||||
);
|
||||
|
||||
// Merge JoinIR blocks into current function
|
||||
// Phase 195: Create and pass JoinInlineBoundary for Pattern 4
|
||||
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i_init, sum_init)
|
||||
vec![loop_var_id, sum_var_id], // Host's loop variables
|
||||
);
|
||||
// Phase 195: Capture exit PHI result (Pattern 4 returns sum)
|
||||
let result_val = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern4", &format!("Loop complete, returning result {:?}", result_val));
|
||||
|
||||
Ok(result_val)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ fn terminator_to_string(inst: &MirInstruction) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirModule, MirSignature};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirModule, FunctionSignature, MirType, EffectMask};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
@ -93,8 +93,13 @@ mod tests {
|
||||
let mut module = MirModule::new("test");
|
||||
|
||||
// Create simple function with 2 blocks
|
||||
let mut function = MirFunction::new(MirSignature::new("test_fn".to_string()));
|
||||
function.entry_block = BasicBlockId(0);
|
||||
let signature = FunctionSignature {
|
||||
name: "test_fn".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::empty(),
|
||||
};
|
||||
let mut function = MirFunction::new(signature, BasicBlockId(0));
|
||||
|
||||
let mut block0 = BasicBlock::new(BasicBlockId(0));
|
||||
block0.reachable = true;
|
||||
|
||||
341
src/mir/join_ir/lowering/loop_with_continue_minimal.rs
Normal file
341
src/mir/join_ir/lowering/loop_with_continue_minimal.rs
Normal file
@ -0,0 +1,341 @@
|
||||
//! Phase 195: Pattern 4 (Loop with Continue) Minimal Lowerer
|
||||
//!
|
||||
//! Target: apps/tests/loop_continue_pattern4.hako
|
||||
//!
|
||||
//! Code:
|
||||
//! ```nyash
|
||||
//! static box Main {
|
||||
//! main() {
|
||||
//! local i = 0
|
||||
//! local sum = 0
|
||||
//! loop(i < 10) {
|
||||
//! i = i + 1
|
||||
//! if (i % 2 == 0) {
|
||||
//! continue
|
||||
//! }
|
||||
//! sum = sum + i
|
||||
//! }
|
||||
//! print(sum)
|
||||
//! return 0
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Expected output: sum = 25 (1+3+5+7+9, skip even numbers)
|
||||
//!
|
||||
//! Expected JoinIR:
|
||||
//! ```text
|
||||
//! fn main(i_init, sum_init):
|
||||
//! result_sum = loop_step(i_init, sum_init)
|
||||
//! print(result_sum)
|
||||
//! return 0
|
||||
//!
|
||||
//! fn loop_step(i, sum):
|
||||
//! // Natural exit condition check
|
||||
//! const_10 = 10
|
||||
//! cmp_lt = (i < 10)
|
||||
//! exit_cond = !cmp_lt
|
||||
//! Jump(k_exit, [sum], cond=exit_cond) // natural exit
|
||||
//!
|
||||
//! // Body: i_next = i + 1
|
||||
//! const_1 = 1
|
||||
//! i_next = i + 1
|
||||
//!
|
||||
//! // Continue condition check: (i_next % 2 == 0)
|
||||
//! const_2 = 2
|
||||
//! remainder = i_next % 2
|
||||
//! continue_cond = (remainder == 0)
|
||||
//! Jump(loop_step, [i_next, sum], cond=continue_cond) // continue: skip to next iteration
|
||||
//!
|
||||
//! // Body after continue: sum_next = sum + i_next
|
||||
//! sum_next = sum + i_next
|
||||
//! Call(loop_step, [i_next, sum_next]) // tail recursion
|
||||
//!
|
||||
//! fn k_exit(sum_exit):
|
||||
//! return sum_exit
|
||||
//! ```
|
||||
//!
|
||||
//! ## Design Notes
|
||||
//!
|
||||
//! This is a MINIMAL implementation targeting loop_continue_pattern4.hako specifically.
|
||||
//! It establishes the infrastructure for Pattern 4 lowering, building on Pattern 1 and 2.
|
||||
//!
|
||||
//! Key differences from Pattern 2:
|
||||
//! - **Multiple Carrier Variables**: Both `i` and `sum` are loop carriers
|
||||
//! - **Continue Jump**: Continue jumps back to loop_step (recursion) instead of k_exit
|
||||
//! - **Dual Recursion Paths**: Continue path (i_next, sum) + normal path (i_next, sum_next)
|
||||
//! - **Exit PHI**: k_exit receives sum exit value (not i)
|
||||
//!
|
||||
//! 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, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
MirLikeInst, UnaryOp,
|
||||
};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Lower Pattern 4 (Loop with Continue) to JoinIR
|
||||
///
|
||||
/// # Phase 195: Pure JoinIR Fragment Generation
|
||||
///
|
||||
/// This version generates JoinIR using **local ValueIds only** (0, 1, 2, ...).
|
||||
/// It has NO knowledge of the host function's ValueId space. The boundary mapping
|
||||
/// is handled separately via JoinInlineBoundary.
|
||||
///
|
||||
/// ## 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)
|
||||
///
|
||||
/// # 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**: ValueId(0) = i_init, ValueId(1) = sum_init
|
||||
/// - **Output slot**: k_exit returns the final sum value
|
||||
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
|
||||
pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option<JoinModule> {
|
||||
// Phase 195: Use local ValueId allocator (sequential from 0)
|
||||
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
|
||||
let mut value_counter = 0u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
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 195: Sequential local IDs)
|
||||
// ==================================================================
|
||||
// main() locals
|
||||
let i_init = alloc_value(); // ValueId(0) - i init value
|
||||
let sum_init = alloc_value(); // ValueId(1) - sum init value
|
||||
let loop_result = alloc_value(); // ValueId(2) - result from loop_step
|
||||
|
||||
// loop_step locals
|
||||
let i_param = alloc_value(); // ValueId(3) - i parameter
|
||||
let sum_param = alloc_value(); // ValueId(4) - sum parameter
|
||||
let const_10 = alloc_value(); // ValueId(5) - natural exit limit
|
||||
let cmp_lt = alloc_value(); // ValueId(6) - i < 10
|
||||
let exit_cond = alloc_value(); // ValueId(7) - !(i < 10)
|
||||
let const_1 = alloc_value(); // ValueId(8) - increment constant
|
||||
let i_next = alloc_value(); // ValueId(9) - i + 1
|
||||
let const_2 = alloc_value(); // ValueId(10) - modulo constant
|
||||
let remainder = alloc_value(); // ValueId(11) - i_next % 2
|
||||
let const_0 = alloc_value(); // ValueId(12) - comparison constant
|
||||
let continue_cond = alloc_value();// ValueId(13) - remainder == 0
|
||||
let sum_next = alloc_value(); // ValueId(14) - sum + i_next
|
||||
|
||||
// k_exit locals
|
||||
let sum_exit = alloc_value(); // ValueId(15) - exit parameter (PHI)
|
||||
|
||||
// ==================================================================
|
||||
// main() function
|
||||
// ==================================================================
|
||||
// Phase 195: main() takes i and sum as parameters (boundary inputs)
|
||||
// The host will inject Copy instructions: i_init_local = Copy host_i, sum_init_local = Copy host_sum
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init, sum_init]);
|
||||
|
||||
// result = loop_step(i_init, sum_init)
|
||||
main_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, sum_init],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
|
||||
// return result (Pattern 4 returns the final sum value)
|
||||
main_func.body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
join_module.add_function(main_func);
|
||||
|
||||
// ==================================================================
|
||||
// loop_step(i, sum) function
|
||||
// ==================================================================
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![i_param, sum_param],
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Natural Exit Condition Check: !(i < 10)
|
||||
// ------------------------------------------------------------------
|
||||
// Step 1: const 10
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_10,
|
||||
value: ConstValue::Integer(10),
|
||||
}));
|
||||
|
||||
// Step 2: cmp_lt = (i < 10)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_lt,
|
||||
op: CompareOp::Lt,
|
||||
lhs: i_param,
|
||||
rhs: const_10,
|
||||
}));
|
||||
|
||||
// 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, [sum], cond=exit_cond) // Natural exit path
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![sum_param], // Pass current sum as exit value
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Loop Body: 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,
|
||||
}));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Continue Condition Check: (i_next % 2 == 0)
|
||||
// ------------------------------------------------------------------
|
||||
// Step 1: const 2
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_2,
|
||||
value: ConstValue::Integer(2),
|
||||
}));
|
||||
|
||||
// Step 2: remainder = i_next % 2
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: remainder,
|
||||
op: BinOpKind::Mod,
|
||||
lhs: i_next,
|
||||
rhs: const_2,
|
||||
}));
|
||||
|
||||
// Step 3: const 0 (for comparison)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_0,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
|
||||
// Step 4: continue_cond = (remainder == 0)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: continue_cond,
|
||||
op: CompareOp::Eq,
|
||||
lhs: remainder,
|
||||
rhs: const_0,
|
||||
}));
|
||||
|
||||
// Jump(loop_step, [i_next, sum], cond=continue_cond) // Continue: skip to next iteration
|
||||
// KEY INSIGHT: Continue jumps back to loop_step with current sum (no update)
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: loop_step_id.as_cont(), // CRITICAL: Jump to loop_step, not k_exit
|
||||
args: vec![i_next, sum_param], // Pass i_next and current sum (no update)
|
||||
cond: Some(continue_cond),
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Loop Body (after continue): sum_next = sum + i_next
|
||||
// ------------------------------------------------------------------
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: sum_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: sum_param,
|
||||
rhs: i_next,
|
||||
}));
|
||||
|
||||
// Call(loop_step, [i_next, sum_next]) // tail recursion
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, sum_next],
|
||||
k_next: None, // CRITICAL: None for tail call
|
||||
dst: None,
|
||||
});
|
||||
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
// ==================================================================
|
||||
// k_exit(sum_exit) function - Exit PHI
|
||||
// ==================================================================
|
||||
// Pattern 4 key difference: k_exit receives sum exit value (not i)
|
||||
let mut k_exit_func = JoinFunction::new(
|
||||
k_exit_id,
|
||||
"k_exit".to_string(),
|
||||
vec![sum_exit], // Exit PHI: receives sum from exit path
|
||||
);
|
||||
|
||||
// return sum_exit (return final sum value)
|
||||
k_exit_func.body.push(JoinInst::Ret {
|
||||
value: Some(sum_exit),
|
||||
});
|
||||
|
||||
join_module.add_function(k_exit_func);
|
||||
|
||||
// Set entry point
|
||||
join_module.entry = Some(main_id);
|
||||
|
||||
eprintln!("[joinir/pattern4] Generated JoinIR for Loop with Continue Pattern");
|
||||
eprintln!("[joinir/pattern4] Functions: main, loop_step, k_exit");
|
||||
eprintln!("[joinir/pattern4] Continue: Jump(loop_step) to skip iteration");
|
||||
eprintln!("[joinir/pattern4] Carriers: i, sum");
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
@ -33,6 +33,7 @@ pub mod loop_patterns; // Phase 188: Pattern-based loop lowering (3 patterns)
|
||||
pub mod loop_scope_shape;
|
||||
pub mod loop_to_join;
|
||||
pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer
|
||||
pub mod loop_with_continue_minimal; // Phase 195: Pattern 4 minimal lowerer
|
||||
pub mod loop_with_if_phi_minimal; // Phase 188-Impl-3: Pattern 3 minimal lowerer
|
||||
pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
|
||||
pub mod min_loop;
|
||||
|
||||
Reference in New Issue
Block a user