fix(joinir): Phase 196 Select double-remap bug in instruction_rewriter

Root cause: PHI inputs were being remapped twice in instruction_rewriter.rs
- Line 304: remap_instruction() already remapped JoinIR → Host ValueIds
- Line 328: remap_value() attempted to remap again → undefined ValueIds

Fix: Only remap block IDs, use already-remapped ValueIds as-is

Test results:
- phase195_sum_count.hako → 93  (multi-carrier P3)
- loop_if_phi.hako → sum=9  (single-carrier P3)
- loop_min_while.hako → 0,1,2  (Pattern 1)
- joinir_min_loop.hako → RC:0  (Pattern 2)
- No [joinir/freeze], no regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-09 14:45:04 +09:00
parent fa8727c2f8
commit 996925ebaf
11 changed files with 1207 additions and 90 deletions

View File

@ -320,13 +320,15 @@ pub(super) fn merge_and_rewrite(
type_hint: None,
} => MirInstruction::Phi {
dst,
// Phase 172: Fix P0 - Remap BOTH block ID AND value ID for PHI incoming
// Phase 196: Fix Select expansion PHI - ValueIds are ALREADY remapped by remap_instruction()
// We only need to remap block IDs here (not ValueIds!)
inputs: inputs
.iter()
.map(|(bb, val)| {
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
let remapped_val = remapper.remap_value(*val);
(remapped_bb, remapped_val)
// Phase 196 FIX: Don't double-remap values!
// remapper.remap_instruction() already remapped *val
(remapped_bb, *val)
})
.collect(),
type_hint: None,

View File

@ -5,12 +5,12 @@ use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
use super::super::trace;
/// Phase 179-A: Expected ValueId for k_exit parameter (sum_final) in Pattern 3
/// This corresponds to the exit PHI input in the JoinIR lowering for loop_with_if_phi_minimal
/// Phase 179-A / Phase 195: Expected ValueIds for k_exit parameters in Pattern 3
/// These correspond to the exit PHI inputs in the JoinIR lowering for loop_with_if_phi_minimal
///
/// # TODO (Phase 179 Task 2): Convert to ExitMeta-based exit binding generation
///
/// **Current State**: Hardcoded ValueId(18) - fragile and non-reusable
/// **Current State**: Hardcoded ValueIds - fragile and non-reusable
///
/// **Why it's hardcoded**:
/// - Pattern 3's lowerer (`lower_loop_with_if_phi_pattern`) returns `Option<JoinModule>`
@ -19,12 +19,15 @@ use super::super::trace;
///
/// **Migration Path** (when Pattern 3 lowerer is updated):
/// 1. Change `lower_loop_with_if_phi_pattern` to return `(JoinModule, JoinFragmentMeta)`
/// 2. Remove this constant
/// 2. Remove these constants
/// 3. Use ExitMeta loop (like Pattern 4 lines 350-378) to generate exit_bindings dynamically
/// 4. See: pattern4_with_continue.rs lines 350-378 for reference implementation
///
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded value
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(18);
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded values
///
/// Phase 195: Multi-carrier support - now includes both sum_final and count_final
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24); // Phase 195: Updated from ValueId(18)
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25); // Phase 195: New count carrier
/// Phase 194: Detection function for Pattern 3
///
@ -80,16 +83,20 @@ impl MirBuilder {
PatternVariant::Pattern3,
)?;
// Phase 179-B: Extract sum_var_id from context
// Pattern 3 specifically needs the "sum" carrier
let sum_var_id = ctx.carrier_info.carriers.iter()
// Phase 195: Extract carrier var_ids dynamically based on what exists
// This maintains backward compatibility with single-carrier (sum only) and multi-carrier (sum+count) tests
let sum_carrier = ctx.carrier_info.carriers.iter()
.find(|c| c.name == "sum")
.ok_or_else(|| {
format!(
"[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map"
)
})?
.host_id;
})?;
let sum_var_id = sum_carrier.host_id;
let count_carrier_opt = ctx.carrier_info.carriers.iter()
.find(|c| c.name == "count");
let has_count = count_carrier_opt.is_some();
// Phase 195: Use unified trace
trace::trace().varmap("pattern3_start", &self.variable_map);
@ -104,26 +111,57 @@ impl MirBuilder {
}
};
// Phase 179-B: Create boundary from context
// Phase 195: Create boundary from context (multi-carrier support with backward compatibility)
// Phase 201: Use JoinInlineBoundaryBuilder for clean construction
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
self.trace_varmap("pattern3_before_merge");
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(
vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i, sum init)
vec![ctx.loop_var_id, sum_var_id], // Host's loop variables
// Phase 195: Build inputs and exit_bindings dynamically based on available carriers
let (join_inputs, host_inputs, exit_bindings) = if has_count {
// Multi-carrier: i, sum, count
let count_var_id = count_carrier_opt.unwrap().host_id;
(
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters
vec![ctx.loop_var_id, sum_var_id, count_var_id], // Host's loop variables
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
host_slot: sum_var_id,
},
LoopExitBinding {
carrier_name: "count".to_string(),
join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ValueId(25)
host_slot: count_var_id,
}
]
)
.with_exit_bindings(vec![
// Phase 33-16: Only include non-loop-variable carriers in exit_bindings
// The loop variable is handled separately via boundary.loop_var_name
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // k_exit's parameter (sum_final)
host_slot: sum_var_id, // variable_map["sum"]
}
])
} else {
// Single-carrier (backward compatibility): i, sum only
// Phase 195: JoinIR lowerer now always generates 3 parameters (i, sum, count)
// For backward compat, we create a dummy count variable that will be discarded
use crate::mir::builder::emission::constant;
let dummy_count_id = constant::emit_void(self); // Use void as dummy value
(
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters (i, sum, count)
vec![ctx.loop_var_id, sum_var_id, dummy_count_id], // Host's loop variables (count is dummy)
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
host_slot: sum_var_id,
}
// Don't bind count in single-carrier mode - it's just discarded
]
)
};
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs)
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(ctx.loop_var_name.clone())) // Phase 33-16: Enable header PHI generation for SSA correctness
.build();

View File

@ -219,7 +219,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
/// * `Err(msg)` - Unsupported expression (Fail-Fast)
fn lower_init_expr(&mut self, expr: &ASTNode) -> Result<ValueId, String> {
match expr {
// Constant literal: 42, 0, 1 (use Literal with Integer value)
// Constant literal: 42, 0, 1, "string" (use Literal with value)
ASTNode::Literal { value, .. } => {
match value {
crate::ast::LiteralValue::Integer(i) => {
@ -234,8 +234,21 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
);
Ok(vid)
}
// Phase 193: String literal support (for method args like "0")
crate::ast::LiteralValue::String(s) => {
let vid = (self.alloc_value)();
self.instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::String(s.clone()),
}));
eprintln!(
"[loop_body_local_init] Const(\"{}\") → {:?}",
s, vid
);
Ok(vid)
}
_ => Err(format!(
"Unsupported literal type in init: {:?} (Phase 186 - only Integer supported)",
"Unsupported literal type in init: {:?} (Phase 193 - only Integer/String supported)",
value
)),
}
@ -283,11 +296,17 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
Ok(result)
}
// Fail-Fast for unsupported expressions (Phase 178 principle)
ASTNode::MethodCall { .. } => Err(
"Unsupported init expression: method call (Phase 186 limitation - int/arithmetic only)"
.to_string(),
),
// Phase 193: MethodCall support
ASTNode::MethodCall { object, method, arguments, .. } => {
Self::emit_method_call_init(
object,
method,
arguments,
self.cond_env,
self.instructions,
&mut self.alloc_value,
)
}
_ => Err(format!(
"Unsupported init expression: {:?} (Phase 186 limitation - only int/arithmetic supported)",
expr
@ -320,6 +339,205 @@ impl<'a> LoopBodyLocalInitLowerer<'a> {
)),
}
}
/// Phase 193: Emit a method call in body-local init expression
///
/// Lowers method calls like `digits.indexOf(ch)` to JoinIR BoxCall instruction.
///
/// # Supported Methods (Whitelist - Fail-Fast)
///
/// - `indexOf` (StringBox): Returns integer index
/// - `get` (ArrayBox): Returns element at index
///
/// # Arguments
///
/// * `receiver` - Object on which method is called (must be in ConditionEnv)
/// * `method` - Method name (must be in whitelist)
/// * `args` - Method arguments (only IntLiteral and Variable supported)
/// * `cond_env` - Condition environment for variable resolution
/// * `instructions` - Output buffer for JoinIR instructions
/// * `alloc` - ValueId allocator
///
/// # Returns
///
/// * `Ok(ValueId)` - JoinIR ValueId of method call result
/// * `Err(msg)` - Unsupported method or argument pattern
///
/// # Example
///
/// ```nyash
/// local digit = digits.indexOf(ch)
/// ```
///
/// Emits:
/// ```
/// %receiver = <resolve digits from ConditionEnv>
/// %arg0 = <resolve ch from ConditionEnv>
/// %result = BoxCall("StringBox", "indexOf", [%receiver, %arg0])
/// ```
fn emit_method_call_init(
receiver: &ASTNode,
method: &str,
args: &[ASTNode],
cond_env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<ValueId, String> {
// Method whitelist - Fail-Fast for unsupported methods
const SUPPORTED_INIT_METHODS: &[&str] = &["indexOf", "get", "toString"];
if !SUPPORTED_INIT_METHODS.contains(&method) {
return Err(format!(
"Method '{}' not supported in body-local init (Phase 193 limitation - only indexOf, get, toString supported)",
method
));
}
eprintln!("[loop_body_local_init] MethodCall: {}.{}(...)",
if let ASTNode::Variable { name, .. } = receiver { name } else { "?" },
method);
// 1. Resolve receiver (must be a Variable in ConditionEnv)
let receiver_id = match receiver {
ASTNode::Variable { name, .. } => {
cond_env.get(name).ok_or_else(|| {
format!(
"Method receiver '{}' not found in ConditionEnv (must be condition variable or loop parameter)",
name
)
})?
}
_ => {
return Err(
"Complex receiver not supported in init method call (Phase 193 - only simple variables)".to_string()
);
}
};
eprintln!("[loop_body_local_init] Receiver resolved → {:?}", receiver_id);
// 2. Lower arguments (recursively)
let arg_ids: Result<Vec<ValueId>, String> = args.iter()
.map(|arg| Self::lower_init_arg(arg, cond_env, instructions, alloc))
.collect();
let arg_ids = arg_ids?;
eprintln!("[loop_body_local_init] Args resolved → {:?}", arg_ids);
// 3. Determine box_name heuristically (Phase 193 simple heuristic)
// In real usage, type inference would provide this, but for init expressions
// we can infer from method name:
// - indexOf → StringBox
// - get → ArrayBox (generic, but we'll use "ArrayBox" as placeholder)
// - toString → IntegerBox (for loop counters)
let box_name = match method {
"indexOf" => "StringBox".to_string(),
"get" => "ArrayBox".to_string(),
"toString" => "IntegerBox".to_string(),
_ => unreachable!("Whitelist check should have caught this"),
};
// 4. Emit BoxCall instruction
let result_id = alloc();
// Build complete args: receiver + method args
let mut full_args = vec![receiver_id];
full_args.extend(arg_ids);
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(result_id),
box_name,
method: method.to_string(),
args: full_args,
}));
eprintln!(
"[loop_body_local_init] Emitted BoxCall → {:?}",
result_id
);
Ok(result_id)
}
/// Phase 193: Lower a method call argument
///
/// Supported argument types:
/// - IntLiteral: Constant integer
/// - Variable: Variable reference (resolved from ConditionEnv)
///
/// # Arguments
///
/// * `arg` - Argument AST node
/// * `cond_env` - Condition environment for variable resolution
/// * `instructions` - Output buffer for JoinIR instructions
/// * `alloc` - ValueId allocator
///
/// # Returns
///
/// * `Ok(ValueId)` - JoinIR ValueId of argument value
/// * `Err(msg)` - Unsupported argument type
fn lower_init_arg(
arg: &ASTNode,
cond_env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
alloc: &mut dyn FnMut() -> ValueId,
) -> Result<ValueId, String> {
match arg {
// Integer literal: emit Const instruction
ASTNode::Literal { value, .. } => {
match value {
crate::ast::LiteralValue::Integer(i) => {
let vid = alloc();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*i),
}));
eprintln!(
"[loop_body_local_init] Arg Const({}) → {:?}",
i, vid
);
Ok(vid)
}
crate::ast::LiteralValue::String(s) => {
let vid = alloc();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::String(s.clone()),
}));
eprintln!(
"[loop_body_local_init] Arg Const(\"{}\") → {:?}",
s, vid
);
Ok(vid)
}
_ => Err(format!(
"Unsupported literal type in method arg: {:?} (Phase 193 - only Integer/String supported)",
value
)),
}
}
// Variable reference: resolve from ConditionEnv
ASTNode::Variable { name, .. } => {
let vid = cond_env.get(name).ok_or_else(|| {
format!(
"Method arg variable '{}' not found in ConditionEnv",
name
)
})?;
eprintln!(
"[loop_body_local_init] Arg Variable({}) → {:?}",
name, vid
);
Ok(vid)
}
// Fail-Fast for complex expressions
_ => Err(format!(
"Complex method arguments not supported in init (Phase 193 - only int literals and variables)"
)),
}
}
}
#[cfg(test)]

View File

@ -134,32 +134,39 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
// main() locals
let i_init_val = alloc_value(); // ValueId(0) - i = 1
let sum_init_val = alloc_value(); // ValueId(1) - sum = 0
let loop_result = alloc_value(); // ValueId(2) - result from loop_step
let count_init_val = alloc_value(); // ValueId(2) - count = 0 (Phase 195: multi-carrier)
let loop_result = alloc_value(); // ValueId(3) - 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_5 = alloc_value(); // ValueId(5) - exit limit (5)
let cmp_le = alloc_value(); // ValueId(6) - i <= 5
let exit_cond = alloc_value(); // ValueId(7) - !(i <= 5)
let const_2 = alloc_value(); // ValueId(8) - modulo constant (2)
let mod_result = alloc_value(); // ValueId(9) - i % 2
let const_1_eq = alloc_value(); // ValueId(10) - equality constant (1)
let if_cond = alloc_value(); // ValueId(11) - (i % 2) == 1
let sum_then = alloc_value(); // ValueId(12) - sum + i (then branch)
let const_0 = alloc_value(); // ValueId(13) - else branch constant (0)
let sum_else = alloc_value(); // ValueId(14) - sum + 0 (else branch)
let sum_new = alloc_value(); // ValueId(15) - Select result
let const_1_inc = alloc_value(); // ValueId(16) - increment constant (1)
let i_next = alloc_value(); // ValueId(17) - i + 1
let i_param = alloc_value(); // ValueId(4) - i parameter
let sum_param = alloc_value(); // ValueId(5) - sum parameter
let count_param = alloc_value(); // ValueId(6) - count parameter (Phase 195: multi-carrier)
let const_5 = alloc_value(); // ValueId(7) - exit limit (5)
let cmp_le = alloc_value(); // ValueId(8) - i <= 5
let exit_cond = alloc_value(); // ValueId(9) - !(i <= 5)
let const_2 = alloc_value(); // ValueId(10) - modulo constant (2)
let mod_result = alloc_value(); // ValueId(11) - i % 2
let const_1_eq = alloc_value(); // ValueId(12) - equality constant (1)
let if_cond = alloc_value(); // ValueId(13) - (i % 2) == 1
let sum_then = alloc_value(); // ValueId(14) - sum + i (then branch)
let const_1_count = alloc_value(); // ValueId(15) - count increment constant (1) (Phase 195)
let count_then = alloc_value(); // ValueId(16) - count + 1 (then branch) (Phase 195)
let const_0 = alloc_value(); // ValueId(17) - else branch constant (0)
let sum_else = alloc_value(); // ValueId(18) - sum + 0 (else branch)
let count_else = alloc_value(); // ValueId(19) - count + 0 (else branch) (Phase 195)
let sum_new = alloc_value(); // ValueId(20) - Select result for sum
let count_new = alloc_value(); // ValueId(21) - Select result for count (Phase 195)
let const_1_inc = alloc_value(); // ValueId(22) - increment constant (1)
let i_next = alloc_value(); // ValueId(23) - i + 1
// k_exit locals
let sum_final = alloc_value(); // ValueId(18) - final sum parameter
let sum_final = alloc_value(); // ValueId(24) - final sum parameter
let count_final = alloc_value(); // ValueId(25) - final count parameter (Phase 195: multi-carrier)
// ==================================================================
// main() function
// ==================================================================
// Phase 188-Impl-3: main() initializes loop variables and calls loop_step
// Phase 195: main() initializes loop variables (i, sum, count) and calls loop_step
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![]);
// i_init = 1
@ -174,10 +181,16 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
value: ConstValue::Integer(0),
}));
// result = loop_step(i_init, sum_init)
// count_init = 0 (Phase 195: multi-carrier)
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: count_init_val,
value: ConstValue::Integer(0),
}));
// result = loop_step(i_init, sum_init, count_init) (Phase 195: 3 parameters)
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_init_val, sum_init_val],
args: vec![i_init_val, sum_init_val, count_init_val],
k_next: None,
dst: Some(loop_result),
});
@ -190,12 +203,13 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
join_module.add_function(main_func);
// ==================================================================
// loop_step(i, sum) function
// loop_step(i, sum, count) function
// ==================================================================
// Phase 195: Multi-carrier - now takes i, sum, count as parameters
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![i_param, sum_param], // Both carriers as parameters
vec![i_param, sum_param, count_param], // Phase 195: 3 carriers as parameters
);
// ------------------------------------------------------------------
@ -228,10 +242,10 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
operand: cmp_le,
}));
// Jump(k_exit, [sum], cond=exit_cond) // Natural exit path
// Jump(k_exit, [sum, count], cond=exit_cond) // Phase 195: Natural exit path with multi-carrier
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![sum_param], // Pass current sum as exit value
args: vec![sum_param, count_param], // Phase 195: Pass current sum and count as exit values
cond: Some(exit_cond),
});
@ -284,7 +298,25 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
rhs: i_param,
}));
// Step 6: const 0 (for else branch)
// Step 6: const 1 for count increment (Phase 195: multi-carrier)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1_count,
value: ConstValue::Integer(1),
}));
// Step 7: count_then = count + 1 (then branch) (Phase 195: multi-carrier)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: count_then,
op: BinOpKind::Add,
lhs: count_param,
rhs: const_1_count,
}));
// Step 8: const 0 (for else branch)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
@ -292,7 +324,7 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
value: ConstValue::Integer(0),
}));
// Step 7: sum_else = sum + 0 (else branch)
// Step 9: sum_else = sum + 0 (else branch)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
@ -302,7 +334,17 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
rhs: const_0,
}));
// Step 8: sum_new = Select(if_cond, sum_then, sum_else)
// Step 10: count_else = count + 0 (else branch) (Phase 195: multi-carrier)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: count_else,
op: BinOpKind::Add,
lhs: count_param,
rhs: const_0,
}));
// Step 11: sum_new = Select(if_cond, sum_then, sum_else)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Select {
@ -312,6 +354,16 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
else_val: sum_else,
}));
// Step 12: count_new = Select(if_cond, count_then, count_else) (Phase 195: multi-carrier)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Select {
dst: count_new,
cond: if_cond,
then_val: count_then,
else_val: count_else,
}));
// ------------------------------------------------------------------
// Update Counter: i_next = i + 1
// ------------------------------------------------------------------
@ -334,11 +386,12 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
}));
// ------------------------------------------------------------------
// Tail Recursion: Call(loop_step, [i_next, sum_new])
// Tail Recursion: Call(loop_step, [i_next, sum_new, count_new])
// ------------------------------------------------------------------
// Phase 195: Multi-carrier tail call with i, sum, count
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_next, sum_new], // BOTH updated carriers
args: vec![i_next, sum_new, count_new], // Phase 195: ALL 3 updated carriers
k_next: None, // CRITICAL: None for tail call
dst: None,
});
@ -346,16 +399,16 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
join_module.add_function(loop_step_func);
// ==================================================================
// k_exit(sum_final) function - Exit PHI
// k_exit(sum_final, count_final) function - Exit PHI
// ==================================================================
// Pattern 3 key difference: k_exit receives final sum value
// Phase 195: Multi-carrier k_exit receives final sum and count values
let mut k_exit_func = JoinFunction::new(
k_exit_id,
"k_exit".to_string(),
vec![sum_final], // Exit PHI: receives sum from exit path
vec![sum_final, count_final], // Phase 195: Exit PHI receives sum and count from exit path
);
// return sum_final (return accumulated sum)
// return sum_final (Pattern 3 convention: return first carrier value)
k_exit_func.body.push(JoinInst::Ret {
value: Some(sum_final),
});
@ -365,10 +418,12 @@ pub(crate) fn lower_loop_with_if_phi_pattern(_scope: LoopScopeShape) -> Option<J
// Set entry point
join_module.entry = Some(main_id);
eprintln!("[joinir/pattern3] Generated JoinIR for Loop with If-Else PHI");
eprintln!("[joinir/pattern3] Generated JoinIR for Loop with If-Else PHI (Phase 195: multi-carrier)");
eprintln!("[joinir/pattern3] Functions: main, loop_step, k_exit");
eprintln!("[joinir/pattern3] Carriers: i (counter), sum (accumulator)");
eprintln!("[joinir/pattern3] If-Else PHI in loop body: sum_new = (i % 2 == 1) ? sum+i : sum+0");
eprintln!("[joinir/pattern3] Carriers: i (counter), sum (accumulator), count (counter) [Phase 195]");
eprintln!("[joinir/pattern3] If-Else PHI in loop body:");
eprintln!("[joinir/pattern3] sum_new = (i % 2 == 1) ? sum+i : sum+0");
eprintln!("[joinir/pattern3] count_new = (i % 2 == 1) ? count+1 : count+0 [Phase 195]");
Some(join_module)
}