feat(joinir): Phase 227 - CarrierRole separation (LoopState vs ConditionOnly)

- Add CarrierRole enum to distinguish state carriers from condition-only carriers
- ConditionOnly carriers (is_digit_pos) skip exit PHI but keep header PHI
- Update all test struct literals with role field
- 877/884 tests PASS (7 pre-existing failures unrelated)

Remaining: ValueId(0) undefined - header PHI initialization for ConditionOnly

🤖 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 20:07:30 +09:00
parent d28e54ba06
commit 478cc0012e
20 changed files with 163 additions and 6 deletions

View File

@ -89,10 +89,12 @@ 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;
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)
};
eprintln!(

View File

@ -134,6 +134,7 @@ mod tests {
carrier_name: "sum".to_string(),
join_exit_value: ValueId(18),
host_slot: ValueId(5),
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
};
let remapper = JoinIrIdRemapper::new();

View File

@ -607,8 +607,19 @@ pub(super) fn merge_and_rewrite(
//
// For each carrier, use the header PHI dst instead of
// the undefined exit binding value.
//
// Phase 227: Filter out ConditionOnly carriers from exit PHI
if let Some(b) = boundary {
for binding in &b.exit_bindings {
// Phase 227: Skip ConditionOnly carriers (they don't need exit PHI)
if binding.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly {
eprintln!(
"[DEBUG-177] Phase 227: Skipping ConditionOnly carrier '{}' from exit PHI",
binding.carrier_name
);
continue;
}
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
carrier_inputs.entry(binding.carrier_name.clone())
.or_insert_with(Vec::new)

View File

@ -82,6 +82,7 @@ impl LoopHeaderPhiBuilder {
phi_dst: loop_var_phi_dst,
entry_incoming: (entry_block, loop_var_init),
latch_incoming: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Loop var is always LoopState
},
);
// Phase 177-STRUCT-2: Record insertion order
@ -103,6 +104,7 @@ impl LoopHeaderPhiBuilder {
phi_dst,
entry_incoming: (entry_block, *init_value),
latch_incoming: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default to LoopState (will be updated later if needed)
},
);
// Phase 177-STRUCT-2: Record insertion order

View File

@ -11,6 +11,7 @@
//! - ExitLineReconnector: to update variable_map with final values
use crate::mir::{BasicBlockId, ValueId};
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
use std::collections::BTreeMap;
/// Information about loop header PHIs
@ -55,6 +56,9 @@ pub struct CarrierPhiEntry {
/// Latch edge: (from_block, updated_value) - set after instruction rewrite
pub latch_incoming: Option<(BasicBlockId, ValueId)>,
/// Phase 227: Role of this carrier (LoopState or ConditionOnly)
pub role: CarrierRole,
}
impl LoopHeaderPhiInfo {
@ -152,6 +156,7 @@ mod tests {
phi_dst: ValueId(100),
entry_incoming: (BasicBlockId(1), ValueId(5)),
latch_incoming: None,
role: CarrierRole::LoopState,
},
);

View File

@ -162,6 +162,7 @@ impl CommonPatternInitializer {
name: name.clone(),
host_id: ValueId(0), // Dummy
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default
})
} else {
None

View File

@ -142,6 +142,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -181,11 +182,13 @@ mod tests {
name: "printed".to_string(),
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
],
);
@ -228,6 +231,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -256,6 +260,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -284,6 +289,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -315,6 +321,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);

View File

@ -52,6 +52,7 @@ pub fn apply_exit_bindings_to_boundary(
carrier_name: carrier.name.clone(),
host_slot: post_loop_id,
join_exit_value: join_exit_id,
role: carrier.role, // Phase 227: Propagate role from CarrierInfo
});
join_outputs.push(join_exit_id);
@ -83,10 +84,12 @@ pub fn apply_exit_bindings_to_boundary(
///
/// LoopExitBinding for the loop variable
pub fn create_loop_var_exit_binding(carrier_info: &CarrierInfo) -> LoopExitBinding {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
LoopExitBinding {
carrier_name: carrier_info.loop_var_name.clone(),
join_exit_value: carrier_info.loop_var_id, // Loop var maps to itself
host_slot: carrier_info.loop_var_id,
role: CarrierRole::LoopState, // Phase 227: Loop var is always LoopState
}
}
@ -104,6 +107,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);

View File

@ -40,6 +40,7 @@ pub fn build_loop_exit_bindings(
carrier_name: carrier.name.clone(),
join_exit_value: join_exit_id,
host_slot: carrier.host_id,
role: carrier.role, // Phase 227: Propagate role from CarrierInfo
});
// Allocate new ValueId for post-loop carrier value
@ -90,6 +91,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -127,11 +129,13 @@ mod tests {
name: "printed".to_string(),
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
],
);

View File

@ -69,6 +69,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -88,11 +89,13 @@ mod tests {
name: "printed".to_string(),
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
],
);
@ -115,6 +118,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -135,6 +139,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);
@ -155,6 +160,7 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}],
);

View File

@ -475,9 +475,13 @@ impl MirBuilder {
// Issue: CommonPatternInitializer includes all variables in variable_map as carriers,
// but only variables with updates in the loop body are true carriers.
// Condition-only variables (like 'len', 's') should be excluded.
//
// Phase 227: Keep ConditionOnly carriers even if they don't have updates
// (they're used in loop/break conditions, not updated)
let original_carrier_count = carrier_info.carriers.len();
carrier_info.carriers.retain(|carrier| {
carrier_updates.contains_key(&carrier.name)
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
carrier_updates.contains_key(&carrier.name) || carrier.role == CarrierRole::ConditionOnly
});
eprintln!(

View File

@ -276,16 +276,19 @@ mod tests {
name: "i".to_string(),
host_id: ValueId(1),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(2),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "M".to_string(),
host_id: ValueId(3),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
],
trim_helper: None,

View File

@ -396,11 +396,13 @@ mod tests {
name: "sum".to_string(),
host_id: ValueId(10),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
CarrierVar {
name: "count".to_string(),
host_id: ValueId(11),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
},
],
trim_helper: None,