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::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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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, ...)
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user