|
|
|
|
@ -56,31 +56,35 @@
|
|
|
|
|
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
|
|
|
|
|
|
|
|
|
|
use crate::ast::ASTNode;
|
|
|
|
|
mod header_break_lowering;
|
|
|
|
|
mod boundary_builder;
|
|
|
|
|
mod header_break_lowering;
|
|
|
|
|
mod step_schedule;
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|
|
|
|
|
|
|
|
|
|
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, JoinFragmentMeta};
|
|
|
|
|
use crate::mir::join_ir::lowering::carrier_update_emitter::{emit_carrier_update, emit_carrier_update_with_env};
|
|
|
|
|
use crate::mir::join_ir::lowering::carrier_update_emitter::{
|
|
|
|
|
emit_carrier_update, emit_carrier_update_with_env,
|
|
|
|
|
};
|
|
|
|
|
use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv;
|
|
|
|
|
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
|
|
|
|
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
|
|
|
|
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
|
|
|
|
use crate::mir::join_ir::{
|
|
|
|
|
BinOpKind, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
|
|
|
|
MirLikeInst, UnaryOp,
|
|
|
|
|
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
|
|
|
|
UnaryOp,
|
|
|
|
|
};
|
|
|
|
|
use crate::mir::loop_pattern_detection::error_messages::{
|
|
|
|
|
extract_body_local_names, format_unsupported_condition_error,
|
|
|
|
|
};
|
|
|
|
|
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
|
|
|
|
use crate::mir::loop_pattern_detection::error_messages::{
|
|
|
|
|
format_unsupported_condition_error, extract_body_local_names,
|
|
|
|
|
};
|
|
|
|
|
use crate::mir::ValueId;
|
|
|
|
|
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
|
|
|
|
use header_break_lowering::{lower_break_condition, lower_header_condition};
|
|
|
|
|
use boundary_builder::build_fragment_meta;
|
|
|
|
|
use header_break_lowering::{lower_break_condition, lower_header_condition};
|
|
|
|
|
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
|
|
|
|
use step_schedule::{Pattern2Step, Pattern2StepSchedule};
|
|
|
|
|
|
|
|
|
|
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
|
|
|
|
|
///
|
|
|
|
|
@ -154,11 +158,8 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
// Phase 170-D-impl-3: Validate that conditions only use supported variable scopes
|
|
|
|
|
// LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables
|
|
|
|
|
let loop_var_name = &carrier_info.loop_var_name; // Phase 176-3: Extract from CarrierInfo
|
|
|
|
|
let loop_cond_scope = LoopConditionScopeBox::analyze(
|
|
|
|
|
loop_var_name,
|
|
|
|
|
&[condition, break_condition],
|
|
|
|
|
Some(&_scope),
|
|
|
|
|
);
|
|
|
|
|
let loop_cond_scope =
|
|
|
|
|
LoopConditionScopeBox::analyze(loop_var_name, &[condition, break_condition], Some(&_scope));
|
|
|
|
|
|
|
|
|
|
if loop_cond_scope.has_loop_body_local() {
|
|
|
|
|
// Phase 224: Filter out promoted variables from body-local check
|
|
|
|
|
@ -176,7 +177,10 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
unpromoted_locals.len(),
|
|
|
|
|
unpromoted_locals
|
|
|
|
|
);
|
|
|
|
|
return Err(format_unsupported_condition_error("pattern2", &unpromoted_locals));
|
|
|
|
|
return Err(format_unsupported_condition_error(
|
|
|
|
|
"pattern2",
|
|
|
|
|
&unpromoted_locals,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
@ -214,31 +218,41 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 176-3: Generating JoinIR for {} carriers: {:?}",
|
|
|
|
|
carrier_count,
|
|
|
|
|
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
|
|
|
|
|
carrier_info
|
|
|
|
|
.carriers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|c| &c.name)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Phase 201: main() parameters use Local region (entry point slots)
|
|
|
|
|
// These don't need to match ConditionEnv - they're just input slots for main
|
|
|
|
|
let i_init = alloc_value(); // e.g., ValueId(1000)
|
|
|
|
|
let i_init = alloc_value(); // e.g., ValueId(1000)
|
|
|
|
|
let mut carrier_init_ids: Vec<ValueId> = Vec::new();
|
|
|
|
|
for _ in 0..carrier_count {
|
|
|
|
|
carrier_init_ids.push(alloc_value());
|
|
|
|
|
}
|
|
|
|
|
let loop_result = alloc_value(); // result from loop_step
|
|
|
|
|
let loop_result = alloc_value(); // result from loop_step
|
|
|
|
|
|
|
|
|
|
// Phase 201: loop_step() parameters MUST match ConditionEnv's ValueIds!
|
|
|
|
|
// This is critical because condition lowering uses ConditionEnv to resolve variables.
|
|
|
|
|
// If loop_step.params[0] != env.get(loop_var_name), the condition will reference wrong ValueIds.
|
|
|
|
|
let i_param = env.get(loop_var_name).ok_or_else(||
|
|
|
|
|
format!("Phase 201: ConditionEnv missing loop variable '{}' - required for loop_step param", loop_var_name)
|
|
|
|
|
)?;
|
|
|
|
|
let i_param = env.get(loop_var_name).ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Phase 201: ConditionEnv missing loop variable '{}' - required for loop_step param",
|
|
|
|
|
loop_var_name
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// Phase 201: Carrier params for loop_step - use CarrierInfo's join_id
|
|
|
|
|
let mut carrier_param_ids: Vec<ValueId> = Vec::new();
|
|
|
|
|
for carrier in &carrier_info.carriers {
|
|
|
|
|
let carrier_join_id = carrier.join_id.ok_or_else(||
|
|
|
|
|
format!("Phase 201: CarrierInfo missing join_id for carrier '{}'", carrier.name)
|
|
|
|
|
)?;
|
|
|
|
|
let carrier_join_id = carrier.join_id.ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Phase 201: CarrierInfo missing join_id for carrier '{}'",
|
|
|
|
|
carrier.name
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
carrier_param_ids.push(carrier_join_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -258,10 +272,10 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
// After condition lowering, allocate remaining ValueIds
|
|
|
|
|
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
|
|
|
|
|
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
|
|
|
|
|
|
|
|
|
|
// Phase 170-B / Phase 236-EX / Phase 244: Lower break condition
|
|
|
|
|
let (break_cond_value, mut break_cond_instructions) = lower_break_condition(
|
|
|
|
|
let (break_cond_value, break_cond_instructions) = lower_break_condition(
|
|
|
|
|
break_condition,
|
|
|
|
|
env,
|
|
|
|
|
carrier_info,
|
|
|
|
|
@ -270,11 +284,11 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
&mut alloc_value,
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
let _const_1 = alloc_value(); // Increment constant
|
|
|
|
|
let _i_next = alloc_value(); // i + 1
|
|
|
|
|
let _const_1 = alloc_value(); // Increment constant
|
|
|
|
|
let _i_next = alloc_value(); // i + 1
|
|
|
|
|
|
|
|
|
|
// k_exit locals
|
|
|
|
|
let i_exit = alloc_value(); // Exit parameter (PHI)
|
|
|
|
|
let i_exit = alloc_value(); // Exit parameter (PHI)
|
|
|
|
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
// main() function
|
|
|
|
|
@ -310,76 +324,169 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
// Phase 176-3: Multi-carrier support - loop_step includes all carrier parameters
|
|
|
|
|
let mut loop_params = vec![i_param];
|
|
|
|
|
loop_params.extend(carrier_param_ids.iter().copied());
|
|
|
|
|
let mut loop_step_func = JoinFunction::new(
|
|
|
|
|
loop_step_id,
|
|
|
|
|
"loop_step".to_string(),
|
|
|
|
|
loop_params,
|
|
|
|
|
let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params);
|
|
|
|
|
|
|
|
|
|
// Decide evaluation order (header/body-init/break/updates/tail) up-front.
|
|
|
|
|
let schedule =
|
|
|
|
|
Pattern2StepSchedule::for_pattern2(body_local_env.as_ref().map(|env| &**env), carrier_info);
|
|
|
|
|
let schedule_desc: Vec<&str> = schedule
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|step| match step {
|
|
|
|
|
Pattern2Step::HeaderAndNaturalExit => "header+exit",
|
|
|
|
|
Pattern2Step::BodyLocalInit => "body-init",
|
|
|
|
|
Pattern2Step::BreakCondition => "break",
|
|
|
|
|
Pattern2Step::CarrierUpdates => "updates",
|
|
|
|
|
Pattern2Step::TailCall => "tail",
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[pattern2/schedule] Selected Pattern2 step schedule: {:?} ({})",
|
|
|
|
|
schedule_desc,
|
|
|
|
|
schedule.reason()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Collect fragments per step; append them according to the schedule below.
|
|
|
|
|
let mut header_block: Vec<JoinInst> = Vec::new();
|
|
|
|
|
let mut body_init_block: Vec<JoinInst> = Vec::new();
|
|
|
|
|
let mut break_block: Vec<JoinInst> = Vec::new();
|
|
|
|
|
let mut carrier_update_block: Vec<JoinInst> = Vec::new();
|
|
|
|
|
let mut tail_block: Vec<JoinInst> = Vec::new();
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Natural Exit Condition Check (Phase 169: from AST)
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Insert all condition evaluation instructions
|
|
|
|
|
loop_step_func.body.append(&mut cond_instructions);
|
|
|
|
|
header_block.append(&mut cond_instructions);
|
|
|
|
|
|
|
|
|
|
// Negate the condition for exit check: exit_cond = !cond_value
|
|
|
|
|
loop_step_func
|
|
|
|
|
.body
|
|
|
|
|
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
|
|
|
|
dst: exit_cond,
|
|
|
|
|
op: UnaryOp::Not,
|
|
|
|
|
operand: cond_value,
|
|
|
|
|
}));
|
|
|
|
|
header_block.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
|
|
|
|
dst: exit_cond,
|
|
|
|
|
op: UnaryOp::Not,
|
|
|
|
|
operand: cond_value,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Phase 176-3: Multi-carrier support - Jump includes all carrier values
|
|
|
|
|
// Jump(k_exit, [i, carrier1, carrier2, ...], cond=exit_cond) // Natural exit path
|
|
|
|
|
let mut natural_exit_args = vec![i_param];
|
|
|
|
|
natural_exit_args.extend(carrier_param_ids.iter().copied());
|
|
|
|
|
loop_step_func.body.push(JoinInst::Jump {
|
|
|
|
|
header_block.push(JoinInst::Jump {
|
|
|
|
|
cont: k_exit_id.as_cont(),
|
|
|
|
|
args: natural_exit_args,
|
|
|
|
|
cond: Some(exit_cond),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Phase 191: Body-Local Variable Initialization
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Phase 246-EX: CRITICAL FIX - Move body-local init BEFORE break condition check
|
|
|
|
|
//
|
|
|
|
|
// Why: Break conditions may depend on body-local variables (e.g., digit_pos < 0).
|
|
|
|
|
// If we check the break condition before computing digit_pos, we're checking against
|
|
|
|
|
// the previous iteration's value (or initial value), causing incorrect early exits.
|
|
|
|
|
//
|
|
|
|
|
// Evaluation order:
|
|
|
|
|
// 1. Natural exit condition (i < len) - uses loop params only
|
|
|
|
|
// 2. Body-local init (digit_pos = digits.indexOf(ch)) - compute fresh values
|
|
|
|
|
// 3. Break condition (digit_pos < 0) - uses fresh body-local values
|
|
|
|
|
// 4. Carrier updates (result = result * 10 + digit_pos) - uses body-local values
|
|
|
|
|
//
|
|
|
|
|
// Lower body-local variable initialization expressions to JoinIR
|
|
|
|
|
// This must happen BEFORE break condition AND carrier updates since both may reference body-locals
|
|
|
|
|
if let Some(ref mut body_env) = body_local_env {
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_body_local_init::LoopBodyLocalInitLowerer;
|
|
|
|
|
|
|
|
|
|
// Create a mutable reference to the instruction buffer
|
|
|
|
|
let mut init_lowerer =
|
|
|
|
|
LoopBodyLocalInitLowerer::new(env, &mut body_init_block, Box::new(&mut alloc_value));
|
|
|
|
|
|
|
|
|
|
init_lowerer.lower_inits_for_loop(body_ast, body_env)?;
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 191/246-EX: Lowered {} body-local init expressions (scheduled block before break)",
|
|
|
|
|
body_env.len()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Phase 170-B: Break Condition Check (delegated to condition_to_joinir)
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Insert all break condition evaluation instructions
|
|
|
|
|
loop_step_func.body.append(&mut break_cond_instructions);
|
|
|
|
|
// Phase 246-EX: Rewrite break condition instructions to use fresh body-local values
|
|
|
|
|
//
|
|
|
|
|
// Problem: Break condition was normalized (e.g., "digit_pos < 0" → "!is_digit_pos")
|
|
|
|
|
// and lowered before body-local init. It references the carrier param which has stale values.
|
|
|
|
|
//
|
|
|
|
|
// Solution: Replace references to promoted carriers with fresh body-local computations.
|
|
|
|
|
// For "!is_digit_pos", we replace "is_digit_pos" with a fresh comparison of "digit_pos >= 0".
|
|
|
|
|
for inst in break_cond_instructions.into_iter() {
|
|
|
|
|
if let JoinInst::Compute(MirLikeInst::UnaryOp {
|
|
|
|
|
op: UnaryOp::Not,
|
|
|
|
|
operand,
|
|
|
|
|
dst,
|
|
|
|
|
}) = inst
|
|
|
|
|
{
|
|
|
|
|
let mut operand_value = operand;
|
|
|
|
|
// Check if operand is a promoted carrier (e.g., is_digit_pos)
|
|
|
|
|
for carrier in &carrier_info.carriers {
|
|
|
|
|
if carrier.join_id == Some(operand_value) {
|
|
|
|
|
if let Some(stripped) = carrier.name.strip_prefix("is_") {
|
|
|
|
|
// Phase 246-EX: "is_digit_pos" → "digit_pos" (no additional suffix needed)
|
|
|
|
|
let source_name = stripped.to_string();
|
|
|
|
|
if let Some(src_val) = body_local_env
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|env| env.get(&source_name))
|
|
|
|
|
{
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 246-EX: Rewriting break condition - replacing carrier '{}' ({:?}) with fresh body-local '{}' ({:?})",
|
|
|
|
|
carrier.name, operand_value, source_name, src_val
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Emit fresh comparison: is_digit_pos = (digit_pos >= 0)
|
|
|
|
|
let zero = alloc_value();
|
|
|
|
|
break_block.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: zero,
|
|
|
|
|
value: ConstValue::Integer(0),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
let fresh_bool = alloc_value();
|
|
|
|
|
break_block.push(JoinInst::Compute(MirLikeInst::Compare {
|
|
|
|
|
dst: fresh_bool,
|
|
|
|
|
op: CompareOp::Ge,
|
|
|
|
|
lhs: src_val,
|
|
|
|
|
rhs: zero,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Update the UnaryOp to use the fresh boolean
|
|
|
|
|
operand_value = fresh_bool;
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 246-EX: Break condition now uses fresh value {:?} instead of stale carrier param {:?}",
|
|
|
|
|
fresh_bool, carrier.join_id
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break_block.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
|
|
|
|
dst,
|
|
|
|
|
op: UnaryOp::Not,
|
|
|
|
|
operand: operand_value,
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
break_block.push(inst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 176-3: Multi-carrier support - Jump includes all carrier values
|
|
|
|
|
// Jump(k_exit, [i, carrier1, carrier2, ...], cond=break_cond) // Break exit path
|
|
|
|
|
let mut break_exit_args = vec![i_param];
|
|
|
|
|
break_exit_args.extend(carrier_param_ids.iter().copied());
|
|
|
|
|
loop_step_func.body.push(JoinInst::Jump {
|
|
|
|
|
break_block.push(JoinInst::Jump {
|
|
|
|
|
cont: k_exit_id.as_cont(),
|
|
|
|
|
args: break_exit_args,
|
|
|
|
|
cond: Some(break_cond_value), // Phase 170-B: Use lowered condition
|
|
|
|
|
cond: Some(break_cond_value), // Phase 170-B: Use lowered condition
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Phase 191: Body-Local Variable Initialization
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Lower body-local variable initialization expressions to JoinIR
|
|
|
|
|
// This must happen BEFORE carrier updates since carrier updates may reference body-locals
|
|
|
|
|
if let Some(ref mut body_env) = body_local_env {
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_body_local_init::LoopBodyLocalInitLowerer;
|
|
|
|
|
|
|
|
|
|
// Create a mutable reference to the instruction buffer
|
|
|
|
|
let mut init_lowerer = LoopBodyLocalInitLowerer::new(
|
|
|
|
|
env,
|
|
|
|
|
&mut loop_step_func.body,
|
|
|
|
|
Box::new(&mut alloc_value),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
init_lowerer.lower_inits_for_loop(body_ast, body_env)?;
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 191: Lowered {} body-local init expressions",
|
|
|
|
|
body_env.len()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
// Loop Body: Compute updated values for all carriers
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
|
@ -389,13 +496,67 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
|
|
|
|
|
let carrier_name = &carrier.name;
|
|
|
|
|
|
|
|
|
|
// Phase 247-EX: Loop-local derived carriers (e.g., digit_value) take the body-local
|
|
|
|
|
// computed value (digit_pos) as their update source each iteration.
|
|
|
|
|
if carrier.init == CarrierInit::LoopLocalZero {
|
|
|
|
|
if let Some(stripped) = carrier_name.strip_suffix("_value") {
|
|
|
|
|
let source_name = format!("{}_pos", stripped);
|
|
|
|
|
if let Some(src_val) = body_local_env
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|env| env.get(&source_name))
|
|
|
|
|
{
|
|
|
|
|
updated_carrier_values.push(src_val);
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[loop/carrier_update] Phase 247-EX: Loop-local carrier '{}' updated from body-local '{}' → {:?}",
|
|
|
|
|
carrier_name, source_name, src_val
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[loop/carrier_update] Phase 247-EX WARNING: loop-local carrier '{}' could not find body-local source '{}'",
|
|
|
|
|
carrier_name, source_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 227: ConditionOnly carriers don't have update expressions
|
|
|
|
|
// They just pass through their current value unchanged
|
|
|
|
|
// Phase 247-EX: FromHost carriers (e.g., digit_value) also passthrough
|
|
|
|
|
// Phase 247-EX: FromHost carriers (e.g., digit_value when truly from host) 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};
|
|
|
|
|
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
|
|
|
|
if carrier.role == CarrierRole::ConditionOnly {
|
|
|
|
|
// ConditionOnly carrier: just pass through the current value
|
|
|
|
|
// Phase 247-EX: If this is a promoted digit_pos boolean carrier, derive from body-local digit_pos
|
|
|
|
|
if let Some(stripped) = carrier_name.strip_prefix("is_") {
|
|
|
|
|
let source_name = format!("{}_pos", stripped);
|
|
|
|
|
if let Some(src_val) = body_local_env
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|env| env.get(&source_name))
|
|
|
|
|
{
|
|
|
|
|
let zero = alloc_value();
|
|
|
|
|
carrier_update_block.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: zero,
|
|
|
|
|
value: ConstValue::Integer(0),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
let cmp = alloc_value();
|
|
|
|
|
carrier_update_block.push(JoinInst::Compute(MirLikeInst::Compare {
|
|
|
|
|
dst: cmp,
|
|
|
|
|
op: CompareOp::Ge,
|
|
|
|
|
lhs: src_val,
|
|
|
|
|
rhs: zero,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
updated_carrier_values.push(cmp);
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[loop/carrier_update] Phase 247-EX: ConditionOnly carrier '{}' derived from body-local '{}' → {:?}",
|
|
|
|
|
carrier_name, source_name, cmp
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ConditionOnly carrier fallback: just pass through the current value
|
|
|
|
|
// The carrier's ValueId from env is passed unchanged
|
|
|
|
|
let current_value = env.get(carrier_name).ok_or_else(|| {
|
|
|
|
|
format!("ConditionOnly carrier '{}' not found in env", carrier_name)
|
|
|
|
|
@ -414,9 +575,9 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
// 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)
|
|
|
|
|
})?;
|
|
|
|
|
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: {:?}",
|
|
|
|
|
@ -443,7 +604,7 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
update_expr,
|
|
|
|
|
&mut alloc_value,
|
|
|
|
|
&update_env,
|
|
|
|
|
&mut loop_step_func.body,
|
|
|
|
|
&mut carrier_update_block,
|
|
|
|
|
)?
|
|
|
|
|
} else {
|
|
|
|
|
// Backward compatibility: use ConditionEnv directly
|
|
|
|
|
@ -452,7 +613,7 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
update_expr,
|
|
|
|
|
&mut alloc_value,
|
|
|
|
|
env,
|
|
|
|
|
&mut loop_step_func.body,
|
|
|
|
|
&mut carrier_update_block,
|
|
|
|
|
)?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -468,22 +629,18 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
// Call(loop_step, [i_next, carrier1_next, carrier2_next, ...]) // tail recursion
|
|
|
|
|
// Note: We need to emit i_next = i + 1 first for the loop variable
|
|
|
|
|
let const_1 = alloc_value();
|
|
|
|
|
loop_step_func
|
|
|
|
|
.body
|
|
|
|
|
.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: const_1,
|
|
|
|
|
value: ConstValue::Integer(1),
|
|
|
|
|
}));
|
|
|
|
|
tail_block.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: const_1,
|
|
|
|
|
value: ConstValue::Integer(1),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
let i_next = alloc_value();
|
|
|
|
|
loop_step_func
|
|
|
|
|
.body
|
|
|
|
|
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
|
|
|
|
dst: i_next,
|
|
|
|
|
op: BinOpKind::Add,
|
|
|
|
|
lhs: i_param,
|
|
|
|
|
rhs: const_1,
|
|
|
|
|
}));
|
|
|
|
|
tail_block.push(JoinInst::Compute(MirLikeInst::BinOp {
|
|
|
|
|
dst: i_next,
|
|
|
|
|
op: BinOpKind::Add,
|
|
|
|
|
lhs: i_param,
|
|
|
|
|
rhs: const_1,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
let mut tail_call_args = vec![i_next];
|
|
|
|
|
tail_call_args.extend(updated_carrier_values.iter().copied());
|
|
|
|
|
@ -494,13 +651,24 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
updated_carrier_values
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
loop_step_func.body.push(JoinInst::Call {
|
|
|
|
|
tail_block.push(JoinInst::Call {
|
|
|
|
|
func: loop_step_id,
|
|
|
|
|
args: tail_call_args,
|
|
|
|
|
k_next: None, // CRITICAL: None for tail call
|
|
|
|
|
k_next: None, // CRITICAL: None for tail call
|
|
|
|
|
dst: None,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Apply scheduled order to assemble the loop_step body.
|
|
|
|
|
for step in schedule.iter() {
|
|
|
|
|
match step {
|
|
|
|
|
Pattern2Step::HeaderAndNaturalExit => loop_step_func.body.append(&mut header_block),
|
|
|
|
|
Pattern2Step::BodyLocalInit => loop_step_func.body.append(&mut body_init_block),
|
|
|
|
|
Pattern2Step::BreakCondition => loop_step_func.body.append(&mut break_block),
|
|
|
|
|
Pattern2Step::CarrierUpdates => loop_step_func.body.append(&mut carrier_update_block),
|
|
|
|
|
Pattern2Step::TailCall => loop_step_func.body.append(&mut tail_block),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
join_module.add_function(loop_step_func);
|
|
|
|
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
@ -515,12 +683,27 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
carrier_exit_ids.push(alloc_value());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let debug_dump = crate::config::env::joinir_debug_level() > 0;
|
|
|
|
|
if debug_dump {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] k_exit param layout: i_exit={:?}, carrier_exit_ids={:?}",
|
|
|
|
|
i_exit, carrier_exit_ids
|
|
|
|
|
);
|
|
|
|
|
for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
|
|
|
|
|
let exit_id = carrier_exit_ids.get(idx).copied().unwrap_or(ValueId(0));
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] carrier '{}' exit → {:?}",
|
|
|
|
|
carrier.name, exit_id
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut exit_params = vec![i_exit];
|
|
|
|
|
exit_params.extend(carrier_exit_ids.iter().copied());
|
|
|
|
|
let mut k_exit_func = JoinFunction::new(
|
|
|
|
|
k_exit_id,
|
|
|
|
|
"k_exit".to_string(),
|
|
|
|
|
exit_params, // Exit PHI: receives (i, carrier1, carrier2, ...) from both exit paths
|
|
|
|
|
exit_params, // Exit PHI: receives (i, carrier1, carrier2, ...) from both exit paths
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// return i_exit (return final loop variable value)
|
|
|
|
|
@ -539,8 +722,7 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|
|
|
|
eprintln!("[joinir/pattern2] Break condition from AST (delegated to condition_to_joinir)");
|
|
|
|
|
eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break");
|
|
|
|
|
|
|
|
|
|
let fragment_meta =
|
|
|
|
|
build_fragment_meta(carrier_info, loop_var_name, i_exit, &carrier_exit_ids);
|
|
|
|
|
let fragment_meta = build_fragment_meta(carrier_info, loop_var_name, i_exit, &carrier_exit_ids);
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[joinir/pattern2] Phase 33-14/176-3: JoinFragmentMeta {{ expr_result: {:?}, carriers: {} }}",
|
|
|
|
|
|