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:
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -546,8 +546,9 @@ impl MirBuilder {
|
||||
let exit_meta = &fragment_meta.exit_meta;
|
||||
|
||||
// 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;
|
||||
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
|
||||
// JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...)
|
||||
|
||||
@ -154,7 +154,8 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
// 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
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
@ -297,9 +298,11 @@ impl MirBuilder {
|
||||
// Phase 213: Use ExitMetaCollector for dynamic exit binding generation
|
||||
// Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta
|
||||
// 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(
|
||||
self,
|
||||
exit_meta,
|
||||
None, // Phase 228-8: No carrier_info in legacy path
|
||||
debug,
|
||||
);
|
||||
|
||||
|
||||
@ -330,10 +330,12 @@ impl MirBuilder {
|
||||
|
||||
// Phase 179 Task 2: Use ExitMetaCollector for unified exit binding generation
|
||||
// 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;
|
||||
let exit_bindings = ExitMetaCollector::collect(
|
||||
self,
|
||||
&exit_meta,
|
||||
Some(&carrier_info), // Phase 228-8: Include ConditionOnly carriers
|
||||
debug,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user