diff --git a/apps/tests/phase246ex_atoi_mini.hako b/apps/tests/phase246ex_atoi_mini.hako new file mode 100644 index 00000000..486ddfc3 --- /dev/null +++ b/apps/tests/phase246ex_atoi_mini.hako @@ -0,0 +1,33 @@ +// Phase 246-EX: Minimal _atoi test +// Tests NumberAccumulation pattern: result = result * 10 + digit_pos + +static box Phase246ExAtoiMini { + main() { + local s = "42" + local len = 2 + local result = me.atoi(s, len) + + // Expected: 42 + if result == 42 { + return "PASS" + } else { + return "FAIL" + } + } + + method atoi(s, len) { + local result = 0 + local digits = "0123456789" + local i = 0 + + loop(i < len) { + local ch = s.substring(i, i + 1) + local digit_pos = digits.indexOf(ch) + if digit_pos < 0 { break } + result = result * 10 + digit_pos + i = i + 1 + } + + return result + } +} diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs index 9b263559..cb40fb71 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs @@ -125,35 +125,57 @@ impl ExitMetaCollector { bindings.push(binding); } else { // Phase 228-8: Check if this is a ConditionOnly carrier - use crate::mir::join_ir::lowering::carrier_info::CarrierRole; - let is_condition_only = if let Some(ci) = carrier_info { + // Phase 247-EX: Also check if this is a FromHost carrier (e.g., digit_value) + use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierInit}; + let carrier_meta = if let Some(ci) = carrier_info { ci.carriers.iter() - .any(|c| c.name == *carrier_name && c.role == CarrierRole::ConditionOnly) + .find(|c| c.name == *carrier_name) + .map(|c| (c.role, c.init)) } else { - false + None }; - if is_condition_only { - // Phase 228-8: Include ConditionOnly carrier in exit_bindings - // (needed for latch incoming, not for exit PHI) - let binding = LoopExitBinding { - carrier_name: carrier_name.clone(), - join_exit_value: *join_exit_value, - host_slot: ValueId(0), // Placeholder - not used for ConditionOnly - role: CarrierRole::ConditionOnly, - }; + match carrier_meta { + Some((CarrierRole::ConditionOnly, _)) => { + // Phase 228-8: Include ConditionOnly carrier in exit_bindings + // (needed for latch incoming, not for exit PHI) + let binding = LoopExitBinding { + carrier_name: carrier_name.clone(), + join_exit_value: *join_exit_value, + host_slot: ValueId(0), // Placeholder - not used for ConditionOnly + role: CarrierRole::ConditionOnly, + }; - eprintln!( - "[cf_loop/exit_line] Phase 228-8: Collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)", - carrier_name, join_exit_value - ); + eprintln!( + "[cf_loop/exit_line] Phase 228-8: Collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)", + carrier_name, join_exit_value + ); - bindings.push(binding); - } else { - eprintln!( - "[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map and not ConditionOnly (skip)", - carrier_name - ); + bindings.push(binding); + } + Some((CarrierRole::LoopState, CarrierInit::FromHost)) => { + // Phase 247-EX: Include FromHost carrier in exit_bindings + // (needed for latch incoming, not for exit PHI or variable_map) + let binding = LoopExitBinding { + carrier_name: carrier_name.clone(), + join_exit_value: *join_exit_value, + host_slot: ValueId(0), // Placeholder - not used for FromHost + role: CarrierRole::LoopState, + }; + + eprintln!( + "[cf_loop/exit_line] Phase 247-EX: Collected FromHost carrier '{}' JoinIR {:?} (not in variable_map)", + carrier_name, join_exit_value + ); + + bindings.push(binding); + } + _ => { + eprintln!( + "[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map and not ConditionOnly/FromHost (skip)", + carrier_name + ); + } } } } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 3fcad8eb..0f331302 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -503,10 +503,14 @@ impl MirBuilder { // // Phase 227: Keep ConditionOnly carriers even if they don't have updates // (they're used in loop/break conditions, not updated) + // Phase 247-EX: Keep FromHost carriers (e.g., digit_value) even if they don't have updates + // (they're initialized from loop body and used in update expressions) let original_carrier_count = carrier_info.carriers.len(); carrier_info.carriers.retain(|carrier| { - use crate::mir::join_ir::lowering::carrier_info::CarrierRole; - carrier_updates.contains_key(&carrier.name) || carrier.role == CarrierRole::ConditionOnly + use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierInit}; + carrier_updates.contains_key(&carrier.name) + || carrier.role == CarrierRole::ConditionOnly + || carrier.init == CarrierInit::FromHost // Phase 247-EX }); eprintln!( diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal.rs b/src/mir/join_ir/lowering/loop_with_break_minimal.rs index 2dd2a01a..dcb7bb04 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal.rs @@ -391,7 +391,9 @@ pub(crate) fn lower_loop_with_break_minimal( // Phase 227: ConditionOnly carriers don't have update expressions // They just pass through their current value unchanged - use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + // Phase 247-EX: FromHost carriers (e.g., digit_value) also passthrough + // They're initialized from loop body and used in update expressions but not updated themselves + use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierInit}; if carrier.role == CarrierRole::ConditionOnly { // ConditionOnly carrier: just pass through the current value // The carrier's ValueId from env is passed unchanged @@ -406,6 +408,23 @@ pub(crate) fn lower_loop_with_break_minimal( continue; } + // Phase 247-EX: FromHost carriers passthrough (no update expressions) + // FromHost carriers (e.g., digit_value) are initialized from loop body (indexOf result) + // and used in update expressions, but not updated themselves. + // They're already in env (added by Phase 176-5), so pass through from there. + if carrier.init == CarrierInit::FromHost && !carrier_updates.contains_key(carrier_name) { + // FromHost carrier without update: pass through current value from env + let current_value = env.get(carrier_name).ok_or_else(|| { + format!("FromHost carrier '{}' not found in env", carrier_name) + })?; + updated_carrier_values.push(current_value); + eprintln!( + "[loop/carrier_update] Phase 247-EX: FromHost carrier '{}' passthrough: {:?}", + carrier_name, current_value + ); + continue; + } + // Get the update expression for this carrier let update_expr = carrier_updates.get(carrier_name).ok_or_else(|| { format!(