feat(joinir): Phase 228-8 - ConditionOnly carrier latch incoming support

- ExitMetaCollector includes ConditionOnly carriers in exit_bindings (for latch)
- ExitLineReconnector skips ConditionOnly in variable_map updates
- Pattern 2/3/4 pass carrier_info to ExitMetaCollector
- Resolves "has no latch incoming set" error

Role separation:
- Header PHI: entry + latch for both LoopState and ConditionOnly
- Exit PHI: LoopState only (ConditionOnly excluded)
- variable_map: LoopState only (ConditionOnly skipped)

🤖 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-10 21:30:03 +09:00
parent 192620f842
commit 717a50ceeb
5 changed files with 86 additions and 10 deletions

View File

@ -8,6 +8,7 @@
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder
/// ExitMetaCollector: A Box that builds exit_bindings from ExitMeta
///
@ -58,9 +59,16 @@ impl ExitMetaCollector {
/// 2. Create LoopExitBinding with carrier_name, join_exit_value, host_slot
/// 3. Collect into Vec<LoopExitBinding>
///
/// # Phase 228-8: ConditionOnly carrier handling
///
/// ConditionOnly carriers are included in exit_bindings even if they're not
/// in variable_map, because they need latch incoming values for header PHI.
/// The host_slot is set to ValueId(0) as a placeholder since ConditionOnly
/// carriers don't participate in exit PHI.
///
/// # Skipped carriers
///
/// Carriers not found in variable_map are silently skipped (filter_map behavior).
/// Carriers not found in variable_map AND not in carrier_info are silently skipped.
/// This is intentional: some carriers may not be relevant to the current pattern.
///
/// # Logging
@ -69,6 +77,7 @@ impl ExitMetaCollector {
pub fn collect(
builder: &MirBuilder,
exit_meta: &ExitMeta,
carrier_info: Option<&crate::mir::join_ir::lowering::carrier_info::CarrierInfo>, // Phase 228-8: Added carrier_info
debug: bool,
) -> Vec<LoopExitBinding> {
let mut bindings = Vec::new();
@ -90,24 +99,62 @@ impl ExitMetaCollector {
// Look up host slot from variable_map
if let Some(&host_slot) = builder.variable_map.get(carrier_name) {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
// Phase 228-8: Look up role from carrier_info if available
let role = if let Some(ci) = carrier_info {
ci.carriers.iter()
.find(|c| c.name == *carrier_name)
.map(|c| c.role)
.unwrap_or(CarrierRole::LoopState)
} else {
CarrierRole::LoopState
};
let binding = LoopExitBinding {
carrier_name: carrier_name.clone(),
join_exit_value: *join_exit_value,
host_slot,
role: CarrierRole::LoopState, // Phase 227: Default to LoopState (caller should update if needed)
role,
};
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}",
carrier_name, join_exit_value, host_slot
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}, role={:?}",
carrier_name, join_exit_value, host_slot, role
);
bindings.push(binding);
} else {
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map (skip)",
carrier_name
);
// 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 {
ci.carriers.iter()
.any(|c| c.name == *carrier_name && c.role == CarrierRole::ConditionOnly)
} else {
false
};
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,
};
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
);
}
}
}

View File

@ -104,6 +104,18 @@ impl ExitLineReconnector {
// Process each exit binding
for binding in &boundary.exit_bindings {
// Phase 228-8: Skip ConditionOnly carriers (no variable_map update needed)
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
if binding.role == CarrierRole::ConditionOnly {
if debug {
eprintln!(
"[cf_loop/joinir/exit_line] Phase 228-8: Skipping ConditionOnly carrier '{}' (no variable_map update)",
binding.carrier_name
);
}
continue;
}
// Phase 33-13: Look up the PHI dst for this carrier
let phi_dst = carrier_phis.get(&binding.carrier_name);
@ -173,7 +185,18 @@ impl ExitLineReconnector {
carrier_phis: &BTreeMap<String, ValueId>,
variable_map: &BTreeMap<String, ValueId>,
) {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
for binding in &boundary.exit_bindings {
// Phase 228-8: Skip ConditionOnly carriers (not in variable_map by design)
if binding.role == CarrierRole::ConditionOnly {
eprintln!(
"[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_map)",
binding.carrier_name
);
continue;
}
// Contract 1: carrier_phis must contain this carrier
let phi_dst = carrier_phis.get(&binding.carrier_name);
if phi_dst.is_none() {