diff --git a/src/mir/builder/control_flow/joinir/patterns/escape_pattern_recognizer.rs b/src/mir/builder/control_flow/joinir/patterns/escape_pattern_recognizer.rs index c709dad9..2b06e98d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/escape_pattern_recognizer.rs +++ b/src/mir/builder/control_flow/joinir/patterns/escape_pattern_recognizer.rs @@ -20,6 +20,9 @@ pub struct EscapeSkipPatternInfo { pub quote_char: char, pub escape_char: char, pub body_stmts: Vec, + /// Phase 92 P0-3: The condition expression for conditional increment + /// e.g., `ch == '\\'` for escape sequence handling + pub escape_cond: Box, } /// Detect escape sequence handling pattern in loop body @@ -57,8 +60,9 @@ pub fn detect_escape_skip_pattern(body: &[ASTNode]) -> Option Option Option { None } -/// Extract both escape_delta and normal_delta from if statement +/// Extract escape_delta, normal_delta, and condition from if statement /// /// Handles both: /// - if ch == escape_char { i = i + 2 } else { i = i + 1 } /// - if ch == escape_char { i = i + 2 } (followed by separate increment) -fn extract_delta_pair_from_if(body: &[ASTNode], idx: usize) -> Option<(String, i64, i64)> { +/// +/// Phase 92 P0-3: Now returns the condition expression for JoinIR Select generation +fn extract_delta_pair_from_if(body: &[ASTNode], idx: usize) -> Option<(String, i64, i64, Box)> { if idx >= body.len() { return None; } if let ASTNode::If { + condition, then_body, else_body, .. @@ -204,7 +212,8 @@ fn extract_delta_pair_from_if(body: &[ASTNode], idx: usize) -> Option<(String, i found_delta? }; - Some((counter_name, escape_delta, normal_delta)) + // Phase 92 P0-3: Return condition along with deltas + Some((counter_name, escape_delta, normal_delta, condition.clone())) } else { None } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 165e68b4..57e64658 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -717,6 +717,7 @@ pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternCo /// /// Wrapper around cf_loop_pattern2_with_break to match router signature /// Phase 200-C: Pass fn_body to cf_loop_pattern2_with_break +/// Phase 92 P0-3: Pass skeleton for ConditionalStep support pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, @@ -727,6 +728,7 @@ pub(crate) fn lower( ctx.func_name, ctx.debug, ctx.fn_body, + ctx.skeleton, // Phase 92 P0-3: Pass skeleton for ConditionalStep ) } @@ -735,6 +737,7 @@ impl MirBuilder { /// /// **Refactored**: Now uses PatternPipelineContext for unified preprocessing /// **Phase 200-C**: Added fn_body parameter for capture analysis + /// **Phase 92 P0-3**: Added skeleton parameter for ConditionalStep support /// /// # Pipeline (Phase 179-B) /// 1. Build preprocessing context → PatternPipelineContext @@ -754,10 +757,12 @@ impl MirBuilder { debug: bool, ) -> Result, String> { // Phase 200-C: Delegate to impl function with fn_body=None for backward compatibility - self.cf_loop_pattern2_with_break_impl(condition, _body, _func_name, debug, None) + // Phase 92 P0-3: skeleton=None for backward compatibility + self.cf_loop_pattern2_with_break_impl(condition, _body, _func_name, debug, None, None) } /// Phase 200-C: Pattern 2 implementation with optional fn_body for capture analysis + /// Phase 92 P0-3: Added skeleton parameter for ConditionalStep support fn cf_loop_pattern2_with_break_impl( &mut self, condition: &ASTNode, @@ -765,6 +770,7 @@ impl MirBuilder { _func_name: &str, debug: bool, fn_body: Option<&[ASTNode]>, + skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>, ) -> Result, String> { use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal; @@ -869,6 +875,7 @@ impl MirBuilder { analysis_body, // Phase 191/192: Pass normalized body AST for init lowering Some(&mut inputs.body_local_env), // Phase 191: Pass mutable body-local environment &mut inputs.join_value_space, // Phase 201: Unified ValueId allocation (Local region) + skeleton, // Phase 92 P0-3: Pass skeleton for ConditionalStep support ) { Ok((module, meta)) => (module, meta), Err(e) => { diff --git a/src/mir/join_ir/lowering/carrier_update_emitter.rs b/src/mir/join_ir/lowering/carrier_update_emitter.rs index f2ea3dd3..8d3a4ef4 100644 --- a/src/mir/join_ir/lowering/carrier_update_emitter.rs +++ b/src/mir/join_ir/lowering/carrier_update_emitter.rs @@ -362,6 +362,101 @@ pub fn emit_carrier_update( } } +// ============================================================================ +// Phase 92 P0-3: ConditionalStep Support +// ============================================================================ + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir; +use crate::mir::join_ir::VarId; +use crate::mir::MirType; + +/// Emit JoinIR instructions for conditional step update (Phase 92 P0-3) +/// +/// Handles the P5b escape sequence pattern where carrier update depends on a condition: +/// ```text +/// if escape_cond { carrier = carrier + then_delta } +/// else { carrier = carrier + else_delta } +/// ``` +/// +/// This generates: +/// 1. Lower condition expression to get cond_id +/// 2. Compute then_result = carrier + then_delta +/// 3. Compute else_result = carrier + else_delta +/// 4. JoinInst::Select { dst: carrier_new, cond: cond_id, then_val: then_result, else_val: else_result } +/// +/// # Arguments +/// +/// * `carrier` - Carrier variable information (name, ValueId) +/// * `cond_ast` - AST node for the condition expression (e.g., `ch == '\\'`) +/// * `then_delta` - Delta to add when condition is true +/// * `else_delta` - Delta to add when condition is false +/// * `alloc_value` - ValueId allocator closure +/// * `env` - ConditionEnv for variable resolution +/// * `instructions` - Output vector to append instructions to +/// +/// # Returns +/// +/// ValueId of the computed update result (the dst of Select) +pub fn emit_conditional_step_update( + carrier: &CarrierVar, + cond_ast: &ASTNode, + then_delta: i64, + else_delta: i64, + alloc_value: &mut dyn FnMut() -> ValueId, + env: &ConditionEnv, + instructions: &mut Vec, +) -> Result { + // Step 1: Lower the condition expression + let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env)?; + instructions.extend(cond_insts); + + // Step 2: Get carrier parameter ValueId from env + let carrier_param = env + .get(&carrier.name) + .ok_or_else(|| format!("Carrier '{}' not found in ConditionEnv", carrier.name))?; + + // Step 3: Compute then_result = carrier + then_delta + let then_const_id = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::Const { + dst: then_const_id, + value: ConstValue::Integer(then_delta), + })); + let then_result = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: then_result, + op: BinOpKind::Add, + lhs: carrier_param, + rhs: then_const_id, + })); + + // Step 4: Compute else_result = carrier + else_delta + let else_const_id = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::Const { + dst: else_const_id, + value: ConstValue::Integer(else_delta), + })); + let else_result = alloc_value(); + instructions.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: else_result, + op: BinOpKind::Add, + lhs: carrier_param, + rhs: else_const_id, + })); + + // Step 5: Emit Select instruction + let carrier_new: VarId = alloc_value(); + instructions.push(JoinInst::Select { + dst: carrier_new, + cond: cond_id, + then_val: then_result, + else_val: else_result, + type_hint: Some(MirType::Integer), // Carrier is always Integer + }); + + Ok(carrier_new) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal.rs b/src/mir/join_ir/lowering/loop_with_break_minimal.rs index 7cd6c644..ee09fdb6 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal.rs @@ -63,9 +63,10 @@ mod tests; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, JoinFragmentMeta}; use crate::mir::join_ir::lowering::carrier_update_emitter::{ - emit_carrier_update, emit_carrier_update_with_env, + emit_carrier_update, emit_carrier_update_with_env, emit_conditional_step_update, }; use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv; +use crate::mir::loop_canonicalizer::UpdateKind; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; @@ -145,6 +146,7 @@ use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for deter /// * `body_ast` - Phase 191: Loop body AST for body-local init lowering /// * `body_local_env` - Phase 185-2: Optional mutable body-local variable environment for init expressions /// * `join_value_space` - Phase 201: Unified JoinIR ValueId allocator (Local region: 1000+) +/// * `skeleton` - Phase 92 P0-3: Optional LoopSkeleton for ConditionalStep support pub(crate) fn lower_loop_with_break_minimal( _scope: LoopScopeShape, condition: &ASTNode, @@ -155,6 +157,7 @@ pub(crate) fn lower_loop_with_break_minimal( body_ast: &[ASTNode], mut body_local_env: Option<&mut LoopBodyLocalEnv>, join_value_space: &mut JoinValueSpace, + skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>, ) -> Result<(JoinModule, JoinFragmentMeta), String> { // Phase 170-D-impl-3: Validate that conditions only use supported variable scopes // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables @@ -585,6 +588,34 @@ pub(crate) fn lower_loop_with_break_minimal( continue; } + // Phase 92 P0-3: Check if skeleton has ConditionalStep for this carrier + if let Some(skel) = skeleton { + if let Some(carrier_slot) = skel.carriers.iter().find(|c| c.name == *carrier_name) { + if let UpdateKind::ConditionalStep { cond, then_delta, else_delta } = &carrier_slot.update_kind { + // Use emit_conditional_step_update instead of emit_carrier_update + eprintln!( + "[joinir/pattern2] Phase 92 P0-3: ConditionalStep detected for carrier '{}': then={}, else={}", + carrier_name, then_delta, else_delta + ); + let updated_value = emit_conditional_step_update( + carrier, + &*cond, + *then_delta, + *else_delta, + &mut alloc_value, + env, + &mut carrier_update_block, + )?; + updated_carrier_values.push(updated_value); + eprintln!( + "[joinir/pattern2] Phase 92 P0-3: ConditionalStep carrier '{}' updated → {:?}", + carrier_name, updated_value + ); + continue; // Skip normal carrier update + } + } + } + // Get the update expression for this carrier let update_expr = carrier_updates.get(carrier_name).ok_or_else(|| { format!( diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs index 9a96d01a..e7b2367e 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs @@ -144,6 +144,7 @@ fn test_pattern2_header_condition_via_exprlowerer() { &[], None, &mut join_value_space, + None, // Phase 92 P0-3: skeleton=None for backward compatibility ); assert!(result.is_ok(), "ExprLowerer header path should succeed"); diff --git a/src/mir/join_ir/normalized/fixtures.rs b/src/mir/join_ir/normalized/fixtures.rs index baea86d9..da19433f 100644 --- a/src/mir/join_ir/normalized/fixtures.rs +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -132,6 +132,7 @@ pub fn build_pattern2_minimal_structured() -> JoinModule { &[], None, &mut join_value_space, + None, // Phase 92 P0-3: skeleton=None for backward compatibility ) .expect("pattern2 minimal lowering should succeed"); diff --git a/src/mir/loop_canonicalizer/canonicalizer.rs b/src/mir/loop_canonicalizer/canonicalizer.rs index bf86c386..dce6392c 100644 --- a/src/mir/loop_canonicalizer/canonicalizer.rs +++ b/src/mir/loop_canonicalizer/canonicalizer.rs @@ -314,7 +314,8 @@ pub fn canonicalize_loop_expr( // Chosen: Pattern2Break (same as skip_whitespace, but with richer Skeleton) // Notes: Added for parity/observability, lowering deferred to Phase 92 - if let Some((counter_name, normal_delta, escape_delta, _quote_char, _escape_char, body_stmts)) = + // Phase 92 P0-3: Now also extracts escape_cond for JoinIR Select generation + if let Some((counter_name, normal_delta, escape_delta, _quote_char, _escape_char, body_stmts, escape_cond)) = try_extract_escape_skip_pattern(body) { // Build skeleton for escape skip pattern (P5b) @@ -334,12 +335,14 @@ pub fn canonicalize_loop_expr( // Step 3: Update step with ConditionalStep (escape_delta vs normal_delta) // Pattern: normal i = i + 1, escape i = i + escape_delta (e.g., +2) - // Represented as UpdateKind::ConditionalStep with both deltas + // Represented as UpdateKind::ConditionalStep with both deltas and condition + // Phase 92 P0-3: Now includes escape_cond for JoinIR Select generation skeleton.steps.push(SkeletonStep::Update { carrier_name: counter_name.clone(), update_kind: UpdateKind::ConditionalStep { - then_delta: escape_delta, // Escape branch: +2 or other - else_delta: normal_delta, // Normal branch: +1 + cond: escape_cond.clone(), // Phase 92 P0-3: Condition for Select + then_delta: escape_delta, // Escape branch: +2 or other + else_delta: normal_delta, // Normal branch: +1 }, }); @@ -348,6 +351,7 @@ pub fn canonicalize_loop_expr( name: counter_name, role: CarrierRole::Counter, update_kind: UpdateKind::ConditionalStep { + cond: escape_cond, // Phase 92 P0-3: Condition for Select then_delta: escape_delta, else_delta: normal_delta, }, @@ -1787,8 +1791,10 @@ mod tests { assert_eq!(carrier.role, CarrierRole::Counter, "Carrier should be a Counter"); // Verify ConditionalStep with escape_delta=2, normal_delta=1 + // Phase 92 P0-3: ConditionalStep now includes cond match &carrier.update_kind { UpdateKind::ConditionalStep { + cond: _, // Phase 92 P0-3: Condition for Select (don't check exact AST) then_delta, else_delta, } => { diff --git a/src/mir/loop_canonicalizer/pattern_recognizer.rs b/src/mir/loop_canonicalizer/pattern_recognizer.rs index ef67f954..fea2bce3 100644 --- a/src/mir/loop_canonicalizer/pattern_recognizer.rs +++ b/src/mir/loop_canonicalizer/pattern_recognizer.rs @@ -310,16 +310,21 @@ mod tests { /// } /// ``` /// -/// Returns (counter_name, normal_delta, escape_delta, quote_char, escape_char, body_stmts) +/// Returns (counter_name, normal_delta, escape_delta, quote_char, escape_char, body_stmts, escape_cond) /// if pattern matches. /// /// # Phase 91 P5b: Escape Sequence Pattern Detection /// /// This function delegates to `ast_feature_extractor::detect_escape_skip_pattern` /// for SSOT implementation. +/// +/// # Phase 92 P0-3: Added escape_cond +/// +/// The escape_cond is the condition expression for the conditional increment +/// (e.g., `ch == '\\'`). This is needed for JoinIR Select generation. pub fn try_extract_escape_skip_pattern( body: &[ASTNode], -) -> Option<(String, i64, i64, char, char, Vec)> { +) -> Option<(String, i64, i64, char, char, Vec, Box)> { ast_detect_escape(body).map(|info| { ( info.counter_name, @@ -328,6 +333,7 @@ pub fn try_extract_escape_skip_pattern( info.quote_char, info.escape_char, info.body_stmts, + info.escape_cond, // Phase 92 P0-3: Condition for JoinIR Select ) }) } diff --git a/src/mir/loop_canonicalizer/skeleton_types.rs b/src/mir/loop_canonicalizer/skeleton_types.rs index 8cd32f77..dce4bdb5 100644 --- a/src/mir/loop_canonicalizer/skeleton_types.rs +++ b/src/mir/loop_canonicalizer/skeleton_types.rs @@ -106,7 +106,15 @@ pub enum UpdateKind { /// ``` /// /// Phase 91 P5b: Used for escape sequence handling and similar conditional increments - ConditionalStep { then_delta: i64, else_delta: i64 }, + /// Phase 92 P0-3: Added condition expression for JoinIR Select generation + ConditionalStep { + /// The condition expression (e.g., `ch == '\\'`) + cond: Box, + /// Delta for then branch (when condition is true) + then_delta: i64, + /// Delta for else branch (when condition is false) + else_delta: i64, + }, /// Conditional update with AST expressions (`if cond { x = a } else { x = b }`) Conditional {