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:
@ -18,6 +18,42 @@
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
/// Phase 227: CarrierRole - Distinguishes loop state carriers from condition-only carriers
|
||||
///
|
||||
/// When LoopBodyLocal variables are promoted to carriers, we need to know whether
|
||||
/// they carry loop state (need exit PHI) or are only used in conditions (no exit PHI).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // LoopState carrier: sum needs exit PHI (value persists after loop)
|
||||
/// loop(i < n) {
|
||||
/// sum = sum + i; // sum updated in loop body
|
||||
/// }
|
||||
/// print(sum); // sum used after loop
|
||||
///
|
||||
/// // ConditionOnly carrier: is_digit_pos does NOT need exit PHI
|
||||
/// loop(p < s.length()) {
|
||||
/// local digit_pos = digits.indexOf(s.substring(p, p+1));
|
||||
/// if digit_pos < 0 { break; } // Only used in condition
|
||||
/// num_str = num_str + ch;
|
||||
/// p = p + 1;
|
||||
/// }
|
||||
/// // digit_pos not used after loop
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CarrierRole {
|
||||
/// Value needed after loop (sum, result, count, p, num_str)
|
||||
/// - Participates in header PHI (loop iteration)
|
||||
/// - Participates in exit PHI (final value after loop)
|
||||
LoopState,
|
||||
|
||||
/// Only used for loop condition (is_digit_pos, is_whitespace)
|
||||
/// - Participates in header PHI (loop iteration)
|
||||
/// - Does NOT participate in exit PHI (not needed after loop)
|
||||
ConditionOnly,
|
||||
}
|
||||
|
||||
/// Phase 224-D: Alias for promoted LoopBodyLocal in condition expressions
|
||||
///
|
||||
/// When a LoopBodyLocal variable is promoted to a carrier, the original variable
|
||||
@ -44,7 +80,7 @@ pub struct ConditionAlias {
|
||||
/// Information about a single carrier variable
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CarrierVar {
|
||||
/// Variable name (e.g., "sum", "printed")
|
||||
/// Variable name (e.g., "sum", "printed", "is_digit_pos")
|
||||
pub name: String,
|
||||
/// Host ValueId for this variable (MIR側)
|
||||
pub host_id: ValueId,
|
||||
@ -56,6 +92,36 @@ pub struct CarrierVar {
|
||||
/// - `Some(vid)`: Header PHI生成後にセットされる
|
||||
/// - `None`: まだPHI生成前、または該当なし
|
||||
pub join_id: Option<ValueId>,
|
||||
/// Phase 227: Role of this carrier (LoopState or ConditionOnly)
|
||||
///
|
||||
/// - `LoopState`: Value needed after loop (participates in exit PHI)
|
||||
/// - `ConditionOnly`: Only used for loop condition (no exit PHI)
|
||||
pub role: CarrierRole,
|
||||
}
|
||||
|
||||
impl CarrierVar {
|
||||
/// Create a new CarrierVar with default LoopState role
|
||||
///
|
||||
/// This is the primary constructor for CarrierVar. Use this instead of
|
||||
/// struct literal syntax to ensure role defaults to LoopState.
|
||||
pub fn new(name: String, host_id: ValueId) -> Self {
|
||||
Self {
|
||||
name,
|
||||
host_id,
|
||||
join_id: None,
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a CarrierVar with explicit role
|
||||
pub fn with_role(name: String, host_id: ValueId, role: CarrierRole) -> Self {
|
||||
Self {
|
||||
name,
|
||||
host_id,
|
||||
join_id: None,
|
||||
role,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete carrier information for a loop
|
||||
@ -130,6 +196,7 @@ impl CarrierInfo {
|
||||
name: name.clone(),
|
||||
host_id: id,
|
||||
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation
|
||||
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -189,6 +256,7 @@ impl CarrierInfo {
|
||||
name,
|
||||
host_id,
|
||||
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation
|
||||
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
|
||||
});
|
||||
}
|
||||
|
||||
@ -507,6 +575,7 @@ mod tests {
|
||||
name: name.to_string(),
|
||||
host_id: ValueId(id),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -421,6 +421,7 @@ mod tests {
|
||||
name: name.to_string(),
|
||||
host_id: ValueId(host_id),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use super::carrier_info::CarrierRole;
|
||||
|
||||
/// Explicit binding between JoinIR exit value and host variable
|
||||
///
|
||||
@ -68,13 +69,13 @@ use crate::mir::ValueId;
|
||||
///
|
||||
/// ```text
|
||||
/// vec![
|
||||
/// LoopExitBinding { carrier_name: "sum", join_exit_value: ValueId(18), host_slot: ValueId(5) },
|
||||
/// LoopExitBinding { carrier_name: "count", join_exit_value: ValueId(19), host_slot: ValueId(6) },
|
||||
/// LoopExitBinding { carrier_name: "sum", join_exit_value: ValueId(18), host_slot: ValueId(5), role: LoopState },
|
||||
/// LoopExitBinding { carrier_name: "count", join_exit_value: ValueId(19), host_slot: ValueId(6), role: LoopState },
|
||||
/// ]
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoopExitBinding {
|
||||
/// Carrier variable name (e.g., "sum", "count")
|
||||
/// Carrier variable name (e.g., "sum", "count", "is_digit_pos")
|
||||
///
|
||||
/// This is the variable name in the host's variable_map that should
|
||||
/// receive the exit value.
|
||||
@ -91,6 +92,13 @@ pub struct LoopExitBinding {
|
||||
/// This is the host function's ValueId for the variable that should be
|
||||
/// updated with the exit PHI result.
|
||||
pub host_slot: ValueId,
|
||||
|
||||
/// Phase 227: Role of this carrier (LoopState or ConditionOnly)
|
||||
///
|
||||
/// Determines whether this carrier should participate in exit PHI:
|
||||
/// - LoopState: Needs exit PHI (value used after loop)
|
||||
/// - ConditionOnly: No exit PHI (only used in loop condition)
|
||||
pub role: CarrierRole,
|
||||
}
|
||||
|
||||
/// Boundary information for inlining a JoinIR fragment into a host function
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
use crate::mir::ValueId;
|
||||
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use super::condition_to_joinir::ConditionBinding;
|
||||
use super::carrier_info::CarrierRole;
|
||||
|
||||
/// Role of a parameter in JoinIR lowering (Phase 200-A)
|
||||
///
|
||||
@ -299,6 +300,7 @@ mod tests {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(18),
|
||||
host_slot: ValueId(5),
|
||||
role: CarrierRole::LoopState,
|
||||
};
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
@ -346,6 +348,7 @@ mod tests {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(18),
|
||||
host_slot: ValueId(101),
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
])
|
||||
.with_loop_var_name(Some("i".to_string()))
|
||||
@ -372,11 +375,13 @@ mod tests {
|
||||
carrier_name: "i".to_string(),
|
||||
join_exit_value: ValueId(11),
|
||||
host_slot: ValueId(100),
|
||||
role: CarrierRole::LoopState,
|
||||
},
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(20),
|
||||
host_slot: ValueId(101),
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
])
|
||||
.with_loop_var_name(Some("i".to_string()))
|
||||
|
||||
@ -298,6 +298,7 @@ mod tests {
|
||||
name: "count".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None, // Phase 177-STRUCT-1
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
@ -355,6 +356,7 @@ mod tests {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
@ -413,6 +415,7 @@ mod tests {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
@ -471,6 +474,7 @@ mod tests {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
|
||||
@ -377,6 +377,23 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
|
||||
let carrier_name = &carrier.name;
|
||||
|
||||
// Phase 227: ConditionOnly carriers don't have update expressions
|
||||
// They just pass through their current value unchanged
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
if carrier.role == CarrierRole::ConditionOnly {
|
||||
// ConditionOnly carrier: just pass through the current value
|
||||
// The carrier's ValueId from env is passed unchanged
|
||||
let current_value = env.get(carrier_name).ok_or_else(|| {
|
||||
format!("ConditionOnly carrier '{}' not found in env", carrier_name)
|
||||
})?;
|
||||
updated_carrier_values.push(current_value);
|
||||
eprintln!(
|
||||
"[loop/carrier_update] Phase 227: ConditionOnly carrier '{}' passthrough: {:?}",
|
||||
carrier_name, current_value
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the update expression for this carrier
|
||||
let update_expr = carrier_updates.get(carrier_name).ok_or_else(|| {
|
||||
format!(
|
||||
|
||||
Reference in New Issue
Block a user