feat(joinir): Phase 172-3 ExitMeta unified return from Pattern2 lowerer

Implement loop exit contract boxification for JoinIR Pattern2:

- lower_loop_with_break_minimal now returns (JoinModule, ExitMeta)
- ExitMeta contains k_exit parameter ValueId for carrier variables
- Pattern2 caller builds exit_bindings from ExitMeta
- merge/mod.rs adds exit_bindings join_exit_values to used_values for remap
- reconnect_boundary uses remapped exit values for variable_map updates

This completes Phases 172-3 through 172-5 of the Loop Exit Contract
boxification plan, enabling proper loop variable propagation after exit.

Test: joinir_min_loop.hako passes (RC: 0)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 02:03:55 +09:00
parent 0dde30ceb8
commit 41c50e4780
3 changed files with 69 additions and 19 deletions

View File

@ -88,6 +88,17 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
}
used_values.insert(binding.join_value);
}
// Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping
for binding in &boundary.exit_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values",
binding.carrier_name, binding.join_exit_value
);
}
used_values.insert(binding.join_exit_value);
}
}
// Phase 3: Remap ValueIds

View File

@ -141,9 +141,10 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(),
};
// Phase 169 / Phase 171-fix: Call Pattern 2 lowerer with ConditionEnv
let join_module = match lower_loop_with_break_minimal(scope, condition, &env) {
Ok(module) => module,
// Phase 169 / Phase 171-fix / Phase 172-3: Call Pattern 2 lowerer with ConditionEnv
// Now returns (JoinModule, ExitMeta) for proper exit_bindings construction
let (join_module, exit_meta) = match lower_loop_with_break_minimal(scope, condition, &env, &loop_var_name) {
Ok((module, meta)) => (module, meta),
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
@ -174,20 +175,43 @@ impl MirBuilder {
);
// Merge JoinIR blocks into current function
// Phase 171-fix: Create boundary with ConditionBindings
let boundary = if condition_bindings.is_empty() {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
// Phase 172-3/4: Create boundary with exit_bindings from ExitMeta
//
// The loop variable's exit value needs to be reflected back to variable_map.
// ExitMeta provides the correct JoinIR-local ValueId for k_exit parameter.
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
// Phase 172-3: Build exit bindings from ExitMeta (provides actual k_exit parameter ValueId)
let exit_bindings: Vec<LoopExitBinding> = exit_meta.exit_values
.iter()
.filter_map(|(carrier_name, join_exit_value)| {
// Look up host slot from variable_map
let host_slot = self.variable_map.get(carrier_name).copied()?;
Some(LoopExitBinding {
carrier_name: carrier_name.clone(),
join_exit_value: *join_exit_value,
host_slot,
})
})
.collect();
// Phase 172-3: Build boundary with both condition_bindings and exit_bindings
let mut boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
)
} else {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_condition_bindings(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
condition_bindings, // Phase 171-fix: ConditionBindings with JoinIR ValueIds
)
};
// Phase 189: Discard exit PHI result (Pattern 2 returns void)
);
boundary.condition_bindings = condition_bindings;
boundary.exit_bindings = exit_bindings.clone();
// Phase 172-3 Debug: Log exit bindings from ExitMeta
for binding in &exit_bindings {
eprintln!(
"[cf_loop/pattern2] Phase 172-3: exit_binding '{}' JoinIR {:?} → HOST {:?}",
binding.carrier_name, binding.join_exit_value, binding.host_slot
);
}
// Phase 189: Capture exit PHI result (now used for reconnect)
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
// Phase 188-Impl-2: Return Void (loops don't produce values)

View File

@ -56,6 +56,7 @@
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{
@ -106,11 +107,21 @@ use crate::mir::ValueId;
///
/// The caller must build a ConditionEnv that maps variable names to JoinIR-local ValueIds.
/// This ensures JoinIR never accesses HOST ValueIds directly.
///
/// # Phase 172-3: ExitMeta Return
///
/// Returns `(JoinModule, ExitMeta)` where ExitMeta contains the JoinIR-local ValueId
/// of the k_exit parameter. This allows the caller to build proper exit_bindings.
///
/// # Arguments
///
/// * `loop_var_name` - Name of the loop variable (for ExitMeta construction)
pub fn lower_loop_with_break_minimal(
_scope: LoopScopeShape,
condition: &ASTNode,
env: &ConditionEnv,
) -> Result<JoinModule, String> {
loop_var_name: &str,
) -> Result<(JoinModule, ExitMeta), String> {
// Phase 188-Impl-2: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32;
@ -294,5 +305,9 @@ pub fn lower_loop_with_break_minimal(
eprintln!("[joinir/pattern2] Condition from AST (not hardcoded)");
eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break");
Ok(join_module)
// Phase 172-3: Build ExitMeta with k_exit parameter's ValueId
let exit_meta = ExitMeta::single(loop_var_name.to_string(), i_exit);
eprintln!("[joinir/pattern2] Phase 172-3: ExitMeta {{ {}{:?} }}", loop_var_name, i_exit);
Ok((join_module, exit_meta))
}