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 // 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;
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)
}; };
eprintln!( eprintln!(

View File

@ -134,6 +134,7 @@ mod tests {
carrier_name: "sum".to_string(), carrier_name: "sum".to_string(),
join_exit_value: ValueId(18), join_exit_value: ValueId(18),
host_slot: ValueId(5), host_slot: ValueId(5),
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}; };
let remapper = JoinIrIdRemapper::new(); 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 // For each carrier, use the header PHI dst instead of
// the undefined exit binding value. // the undefined exit binding value.
//
// Phase 227: Filter out ConditionOnly carriers from exit PHI
if let Some(b) = boundary { if let Some(b) = boundary {
for binding in &b.exit_bindings { 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) { if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
carrier_inputs.entry(binding.carrier_name.clone()) carrier_inputs.entry(binding.carrier_name.clone())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)

View File

@ -82,6 +82,7 @@ impl LoopHeaderPhiBuilder {
phi_dst: loop_var_phi_dst, phi_dst: loop_var_phi_dst,
entry_incoming: (entry_block, loop_var_init), entry_incoming: (entry_block, loop_var_init),
latch_incoming: None, 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 // Phase 177-STRUCT-2: Record insertion order
@ -103,6 +104,7 @@ impl LoopHeaderPhiBuilder {
phi_dst, phi_dst,
entry_incoming: (entry_block, *init_value), entry_incoming: (entry_block, *init_value),
latch_incoming: None, 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 // Phase 177-STRUCT-2: Record insertion order

View File

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

View File

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

View File

@ -142,6 +142,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -181,11 +182,13 @@ mod tests {
name: "printed".to_string(), name: "printed".to_string(),
host_id: ValueId(11), host_id: ValueId(11),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}, },
CarrierVar { CarrierVar {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}, },
], ],
); );
@ -228,6 +231,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -256,6 +260,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -284,6 +289,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -315,6 +321,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, 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(), carrier_name: carrier.name.clone(),
host_slot: post_loop_id, host_slot: post_loop_id,
join_exit_value: join_exit_id, join_exit_value: join_exit_id,
role: carrier.role, // Phase 227: Propagate role from CarrierInfo
}); });
join_outputs.push(join_exit_id); join_outputs.push(join_exit_id);
@ -83,10 +84,12 @@ pub fn apply_exit_bindings_to_boundary(
/// ///
/// LoopExitBinding for the loop variable /// LoopExitBinding for the loop variable
pub fn create_loop_var_exit_binding(carrier_info: &CarrierInfo) -> LoopExitBinding { pub fn create_loop_var_exit_binding(carrier_info: &CarrierInfo) -> LoopExitBinding {
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
LoopExitBinding { LoopExitBinding {
carrier_name: carrier_info.loop_var_name.clone(), carrier_name: carrier_info.loop_var_name.clone(),
join_exit_value: carrier_info.loop_var_id, // Loop var maps to itself join_exit_value: carrier_info.loop_var_id, // Loop var maps to itself
host_slot: carrier_info.loop_var_id, 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(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, 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(), carrier_name: carrier.name.clone(),
join_exit_value: join_exit_id, join_exit_value: join_exit_id,
host_slot: carrier.host_id, host_slot: carrier.host_id,
role: carrier.role, // Phase 227: Propagate role from CarrierInfo
}); });
// Allocate new ValueId for post-loop carrier value // Allocate new ValueId for post-loop carrier value
@ -90,6 +91,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -127,11 +129,13 @@ mod tests {
name: "printed".to_string(), name: "printed".to_string(),
host_id: ValueId(11), host_id: ValueId(11),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}, },
CarrierVar { CarrierVar {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, 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(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -88,11 +89,13 @@ mod tests {
name: "printed".to_string(), name: "printed".to_string(),
host_id: ValueId(11), host_id: ValueId(11),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}, },
CarrierVar { CarrierVar {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}, },
], ],
); );
@ -115,6 +118,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -135,6 +139,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}], }],
); );
@ -155,6 +160,7 @@ mod tests {
name: "sum".to_string(), name: "sum".to_string(),
host_id: ValueId(10), host_id: ValueId(10),
join_id: None, 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, // Issue: CommonPatternInitializer includes all variables in variable_map as carriers,
// but only variables with updates in the loop body are true carriers. // but only variables with updates in the loop body are true carriers.
// Condition-only variables (like 'len', 's') should be excluded. // 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(); let original_carrier_count = carrier_info.carriers.len();
carrier_info.carriers.retain(|carrier| { 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!( eprintln!(

View File

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

View File

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

View File

@ -18,6 +18,42 @@
use crate::mir::ValueId; use crate::mir::ValueId;
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism 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 /// Phase 224-D: Alias for promoted LoopBodyLocal in condition expressions
/// ///
/// When a LoopBodyLocal variable is promoted to a carrier, the original variable /// 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 /// Information about a single carrier variable
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CarrierVar { pub struct CarrierVar {
/// Variable name (e.g., "sum", "printed") /// Variable name (e.g., "sum", "printed", "is_digit_pos")
pub name: String, pub name: String,
/// Host ValueId for this variable (MIR側) /// Host ValueId for this variable (MIR側)
pub host_id: ValueId, pub host_id: ValueId,
@ -56,6 +92,36 @@ pub struct CarrierVar {
/// - `Some(vid)`: Header PHI生成後にセットされる /// - `Some(vid)`: Header PHI生成後にセットされる
/// - `None`: まだPHI生成前、または該当なし /// - `None`: まだPHI生成前、または該当なし
pub join_id: Option<ValueId>, 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 /// Complete carrier information for a loop
@ -130,6 +196,7 @@ impl CarrierInfo {
name: name.clone(), name: name.clone(),
host_id: id, host_id: id,
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
}) })
.collect(); .collect();
@ -189,6 +256,7 @@ impl CarrierInfo {
name, name,
host_id, host_id,
join_id: None, // Phase 177-STRUCT-1: Set by header PHI generation 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(), name: name.to_string(),
host_id: ValueId(id), host_id: ValueId(id),
join_id: None, // Phase 177-STRUCT-1 join_id: None, // Phase 177-STRUCT-1
role: CarrierRole::LoopState, // Phase 227: Default to LoopState
} }
} }

View File

@ -421,6 +421,7 @@ mod tests {
name: name.to_string(), name: name.to_string(),
host_id: ValueId(host_id), host_id: ValueId(host_id),
join_id: None, // Phase 177-STRUCT-1 join_id: None, // Phase 177-STRUCT-1
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
} }
} }

View File

@ -43,6 +43,7 @@
//! ``` //! ```
use crate::mir::ValueId; use crate::mir::ValueId;
use super::carrier_info::CarrierRole;
/// Explicit binding between JoinIR exit value and host variable /// Explicit binding between JoinIR exit value and host variable
/// ///
@ -68,13 +69,13 @@ use crate::mir::ValueId;
/// ///
/// ```text /// ```text
/// vec![ /// vec![
/// LoopExitBinding { carrier_name: "sum", join_exit_value: ValueId(18), host_slot: ValueId(5) }, /// 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) }, /// LoopExitBinding { carrier_name: "count", join_exit_value: ValueId(19), host_slot: ValueId(6), role: LoopState },
/// ] /// ]
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LoopExitBinding { 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 /// This is the variable name in the host's variable_map that should
/// receive the exit value. /// receive the exit value.
@ -91,6 +92,13 @@ pub struct LoopExitBinding {
/// This is the host function's ValueId for the variable that should be /// This is the host function's ValueId for the variable that should be
/// updated with the exit PHI result. /// updated with the exit PHI result.
pub host_slot: ValueId, 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 /// Boundary information for inlining a JoinIR fragment into a host function

View File

@ -27,6 +27,7 @@
use crate::mir::ValueId; use crate::mir::ValueId;
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
use super::condition_to_joinir::ConditionBinding; use super::condition_to_joinir::ConditionBinding;
use super::carrier_info::CarrierRole;
/// Role of a parameter in JoinIR lowering (Phase 200-A) /// Role of a parameter in JoinIR lowering (Phase 200-A)
/// ///
@ -299,6 +300,7 @@ mod tests {
carrier_name: "sum".to_string(), carrier_name: "sum".to_string(),
join_exit_value: ValueId(18), join_exit_value: ValueId(18),
host_slot: ValueId(5), host_slot: ValueId(5),
role: CarrierRole::LoopState,
}; };
let boundary = JoinInlineBoundaryBuilder::new() let boundary = JoinInlineBoundaryBuilder::new()
@ -346,6 +348,7 @@ mod tests {
carrier_name: "sum".to_string(), carrier_name: "sum".to_string(),
join_exit_value: ValueId(18), join_exit_value: ValueId(18),
host_slot: ValueId(101), host_slot: ValueId(101),
role: CarrierRole::LoopState,
} }
]) ])
.with_loop_var_name(Some("i".to_string())) .with_loop_var_name(Some("i".to_string()))
@ -372,11 +375,13 @@ mod tests {
carrier_name: "i".to_string(), carrier_name: "i".to_string(),
join_exit_value: ValueId(11), join_exit_value: ValueId(11),
host_slot: ValueId(100), host_slot: ValueId(100),
role: CarrierRole::LoopState,
}, },
LoopExitBinding { LoopExitBinding {
carrier_name: "sum".to_string(), carrier_name: "sum".to_string(),
join_exit_value: ValueId(20), join_exit_value: ValueId(20),
host_slot: ValueId(101), host_slot: ValueId(101),
role: CarrierRole::LoopState,
} }
]) ])
.with_loop_var_name(Some("i".to_string())) .with_loop_var_name(Some("i".to_string()))

View File

@ -298,6 +298,7 @@ mod tests {
name: "count".to_string(), name: "count".to_string(),
host_id: crate::mir::ValueId(0), host_id: crate::mir::ValueId(0),
join_id: None, // Phase 177-STRUCT-1 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); let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -355,6 +356,7 @@ mod tests {
name: "result".to_string(), name: "result".to_string(),
host_id: crate::mir::ValueId(0), host_id: crate::mir::ValueId(0),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}]; }];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -413,6 +415,7 @@ mod tests {
name: "result".to_string(), name: "result".to_string(),
host_id: crate::mir::ValueId(0), host_id: crate::mir::ValueId(0),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}]; }];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
@ -471,6 +474,7 @@ mod tests {
name: "result".to_string(), name: "result".to_string(),
host_id: crate::mir::ValueId(0), host_id: crate::mir::ValueId(0),
join_id: None, join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
}]; }];
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers); let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);

View File

@ -377,6 +377,23 @@ pub(crate) fn lower_loop_with_break_minimal(
for (idx, carrier) in carrier_info.carriers.iter().enumerate() { for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
let carrier_name = &carrier.name; 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 // Get the update expression for this carrier
let update_expr = carrier_updates.get(carrier_name).ok_or_else(|| { let update_expr = carrier_updates.get(carrier_name).ok_or_else(|| {
format!( format!(

View File

@ -183,11 +183,12 @@ impl DigitPosPromoter {
// For DigitPos pattern, we add a NEW carrier (not replace loop_var) // For DigitPos pattern, we add a NEW carrier (not replace loop_var)
let carrier_name = format!("is_{}", var_in_cond); let carrier_name = format!("is_{}", var_in_cond);
use crate::mir::join_ir::lowering::carrier_info::CarrierVar; use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole};
let promoted_carrier = CarrierVar { let promoted_carrier = CarrierVar {
name: carrier_name.clone(), name: carrier_name.clone(),
host_id: ValueId(0), // Placeholder (will be remapped) host_id: ValueId(0), // Placeholder (will be remapped)
join_id: None, // Will be allocated later join_id: None, // Will be allocated later
role: CarrierRole::ConditionOnly, // Phase 227: DigitPos is condition-only
}; };
// Create CarrierInfo with a dummy loop_var_name (will be ignored during merge) // Create CarrierInfo with a dummy loop_var_name (will be ignored during merge)