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::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::ExitMeta; use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; 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 /// 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 /// 2. Create LoopExitBinding with carrier_name, join_exit_value, host_slot
/// 3. Collect into Vec<LoopExitBinding> /// 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 /// # 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. /// This is intentional: some carriers may not be relevant to the current pattern.
/// ///
/// # Logging /// # Logging
@ -69,6 +77,7 @@ impl ExitMetaCollector {
pub fn collect( pub fn collect(
builder: &MirBuilder, builder: &MirBuilder,
exit_meta: &ExitMeta, exit_meta: &ExitMeta,
carrier_info: Option<&crate::mir::join_ir::lowering::carrier_info::CarrierInfo>, // Phase 228-8: Added carrier_info
debug: bool, debug: bool,
) -> Vec<LoopExitBinding> { ) -> Vec<LoopExitBinding> {
let mut bindings = Vec::new(); let mut bindings = Vec::new();
@ -90,24 +99,62 @@ impl ExitMetaCollector {
// Look up host slot from variable_map // Look up host slot from variable_map
if let Some(&host_slot) = builder.variable_map.get(carrier_name) { if let Some(&host_slot) = builder.variable_map.get(carrier_name) {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole; 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 { let binding = LoopExitBinding {
carrier_name: carrier_name.clone(), carrier_name: carrier_name.clone(),
join_exit_value: *join_exit_value, join_exit_value: *join_exit_value,
host_slot, host_slot,
role: CarrierRole::LoopState, // Phase 227: Default to LoopState (caller should update if needed) role,
}; };
eprintln!( eprintln!(
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}", "[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}, role={:?}",
carrier_name, join_exit_value, host_slot carrier_name, join_exit_value, host_slot, role
); );
bindings.push(binding); bindings.push(binding);
} else { } else {
eprintln!( // Phase 228-8: Check if this is a ConditionOnly carrier
"[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map (skip)", use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
carrier_name 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 // Process each exit binding
for binding in &boundary.exit_bindings { 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 // Phase 33-13: Look up the PHI dst for this carrier
let phi_dst = carrier_phis.get(&binding.carrier_name); let phi_dst = carrier_phis.get(&binding.carrier_name);
@ -173,7 +185,18 @@ impl ExitLineReconnector {
carrier_phis: &BTreeMap<String, ValueId>, carrier_phis: &BTreeMap<String, ValueId>,
variable_map: &BTreeMap<String, ValueId>, variable_map: &BTreeMap<String, ValueId>,
) { ) {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
for binding in &boundary.exit_bindings { 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 // Contract 1: carrier_phis must contain this carrier
let phi_dst = carrier_phis.get(&binding.carrier_name); let phi_dst = carrier_phis.get(&binding.carrier_name);
if phi_dst.is_none() { if phi_dst.is_none() {

View File

@ -546,8 +546,9 @@ impl MirBuilder {
let exit_meta = &fragment_meta.exit_meta; let exit_meta = &fragment_meta.exit_meta;
// Phase 33-10: Collect exit bindings from ExitMeta using Box // Phase 33-10: Collect exit bindings from ExitMeta using Box
// Phase 228-8: Pass carrier_info to include ConditionOnly carriers
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector; use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, debug); let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, Some(&carrier_info), debug);
// Phase 176-3: Build input mappings for all carriers // Phase 176-3: Build input mappings for all carriers
// JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...) // JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...)

View File

@ -154,7 +154,8 @@ impl MirBuilder {
} }
// Build exit bindings using ExitMetaCollector // Build exit bindings using ExitMetaCollector
let exit_bindings = ExitMetaCollector::collect(self, exit_meta, debug); // Phase 228-8: Pass carrier_info to include ConditionOnly carriers
let exit_bindings = ExitMetaCollector::collect(self, exit_meta, Some(&ctx.carrier_info), debug);
// Build boundary with carrier inputs // Build boundary with carrier inputs
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
@ -297,9 +298,11 @@ impl MirBuilder {
// Phase 213: Use ExitMetaCollector for dynamic exit binding generation // Phase 213: Use ExitMetaCollector for dynamic exit binding generation
// Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta // Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta
// have corresponding variable_map entries. No additional validation needed here. // have corresponding variable_map entries. No additional validation needed here.
// Phase 228-8: Pass None for carrier_info (legacy path doesn't have ConditionOnly carriers)
let exit_bindings = ExitMetaCollector::collect( let exit_bindings = ExitMetaCollector::collect(
self, self,
exit_meta, exit_meta,
None, // Phase 228-8: No carrier_info in legacy path
debug, debug,
); );

View File

@ -330,10 +330,12 @@ impl MirBuilder {
// Phase 179 Task 2: Use ExitMetaCollector for unified exit binding generation // Phase 179 Task 2: Use ExitMetaCollector for unified exit binding generation
// Replaces manual loop (Phase 196 implementation) with modular Box approach // Replaces manual loop (Phase 196 implementation) with modular Box approach
// Phase 228-8: Pass carrier_info to include ConditionOnly carriers
use super::super::merge::exit_line::meta_collector::ExitMetaCollector; use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect( let exit_bindings = ExitMetaCollector::collect(
self, self,
&exit_meta, &exit_meta,
Some(&carrier_info), // Phase 228-8: Include ConditionOnly carriers
debug, debug,
); );