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:
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user