diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index d39ef45d..a00ae711 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -163,7 +163,7 @@ Pattern numbers (1-9+) became architectural decision points: - `seq(a, b)`: Sequential composition (Normal wiring) - `if_(header, cond, t, e, join)`: Conditional composition (Branch wiring) - `loop_(loop_id, header, after, body)`: Loop composition (Break/Continue wiring) -- `cleanup(body, cleanup)`: Cleanup composition (planned, Phase 280+) +- `cleanup(main, cleanup_frag, normal_target, ret_target)`: Cleanup composition (Normal/Return wiring; Phase 281) **Reference**: `docs/development/current/main/design/edgecfg-fragments.md` (Active SSOT) @@ -183,36 +183,36 @@ Both routes converge on the same Frag composition SSOT: - Plan route: Handles complex scan patterns (Pattern6/7) via DomainPlan → CorePlan → Frag - Both routes use same Frag composition API for CFG lowering (no duplication) -### Pattern Absorption Status (Phase 280) +### Pattern Absorption Status (Phase 281) | Pattern | Structure | Status | Absorption Target | |---------|-----------|--------|-------------------| -| **Pattern6** | ScanWithInit (forward scan) | Plan-based (Phase 273) | **Phase 280 C: Frag refactor target** | -| **Pattern7** | SplitScan (conditional split) | Plan-based (Phase 273) | **Phase 280 C: Frag refactor target** | -| Pattern8 | BoolPredicateScan (is_integer) | JoinIR-based | Phase 281+ (migration planned) | +| **Pattern6** | ScanWithInit (early-exit) | ✅ compose-based (Phase 281) | absorbed (hand-roll removed) | +| **Pattern7** | SplitScan (then/else join) | ✅ compose-based (Phase 281) | absorbed (hand-roll removed) | +| Pattern8 | BoolPredicateScan (is_integer) | JoinIR-based | Phase 283+ (migration planned) | | Pattern9 | AccumConstLoop (bridge) | JoinIR-based (Phase 271) | 撤去条件 defined (minimal loop SSOT) | | Pattern1-5 | Legacy (SimpleWhile, Break, IfPhi, Continue, InfiniteEarlyExit) | JoinIR-based | Test/error stubs (not absorbed) | -**Phase 280 Focus**: Pattern6/7 preparation (documentation of hand-rolled locations, defer refactor to Phase 281) +**Phase 281 Result**: Pattern6/7 absorbed (compose SSOT), hand-rolled Frag eliminated **Absorption criteria** (Pattern6/7 → Frag composition): 1. Hand-rolled Frag construction identified (function name + 識別コメント) 2. TODO comments documenting future compose_* migration path -3. Behavior-preserving refactor deferred to Phase 281 (Phase 280 = SSOT positioning only) +3. Behavior-preserving refactor completed in Phase 281 ### Absorption Timeline -**Phase 280 (Current)**: SSOT positioning + 導線固定 +**Phase 280 (Complete)**: SSOT positioning + 導線固定 - A: Documentation (edgecfg-fragments.md → Active SSOT) - B: API solidification (compose.rs contract verification) - C: Pattern preparation (Pattern6/7 hand-rolled locations documented) - **Goal**: Establish Frag composition as THE absorption destination - **Non-Goal**: Full Pattern6/7 migration (deferred to Phase 281) -**Phase 281 (Planned)**: Full Pattern6/7 absorption -- Replace hand-rolled Frag construction with compose_* calls -- Behavior-preserving verification (smoke tests) -- Goal: Pattern6/7 use compose::if_/loop_ exclusively +**Phase 281 (Complete)**: Full Pattern6/7 absorption +- Pattern7: body cond_match → `compose::if_()` +- Pattern6: early-exit → `compose::cleanup()`(Normal/Return wiring) +- Behavior-preserving verification (VM/LLVM smokes) **Phase 282 (Planned)**: Router shrinkage - Pattern numbers → test labels only diff --git a/docs/development/current/main/phases/phase-282/README.md b/docs/development/current/main/phases/phase-282/README.md index 39e8c087..c2964116 100644 --- a/docs/development/current/main/phases/phase-282/README.md +++ b/docs/development/current/main/phases/phase-282/README.md @@ -58,8 +58,9 @@ CFG構築は以下に収束させる: - ❌ 禁止: CFG 分岐(`if pattern == 6 then ...`)、アーキテクチャ SSOT(Frag composition が SSOT) **Detection 戦略**(簡潔版、詳細は P2+ で補完): -- **ExtractionBased**(Pattern6,7,8): extract() 成功 → match(SSOT 単一) -- **StructureBased**(Pattern1-5,9): ctx.pattern_kind チェック(legacy、2 つの SSOT) +- **ExtractionBased**(Pattern6/7/8/9): extract() 成功 → match(SSOT 単一) + - 注: Pattern8/9 は JoinIR table 経由でも、can_lower 内部で extract を使って判定している(pattern_kind 依存ではない) +- **StructureBased**(主に Pattern1-5): ctx.pattern_kind などの事前分類を使う(legacy、SSOT が二重になりやすい) **SSOT 参照**: - Frag composition: `src/mir/builder/control_flow/edgecfg/api/compose.rs` @@ -108,7 +109,7 @@ extract_scan_with_init_plan() → Ok(None) for unsupported cases 5. **Emission**: emit_frag()(terminator SSOT) **JoinIR table の責務**(Phase 194+ table-driven): -1. **Detection**: can_lower()(structure-based、ctx.pattern_kind) +1. **Detection**: can_lower()(pattern により structure-based / extraction-based が混在) 2. **Extraction**: cf_loop 抽出 3. **収束先**: Frag composition → emit_frag(内部パイプライン詳細は最小化) @@ -126,4 +127,3 @@ extract_scan_with_init_plan() → Ok(None) for unsupported cases - router の責務が docs で SSOT 化されている - router の変更が「extractor配線」のみになっている(CFG構築の詳細を持たない) - 既存の VM/LLVM smokes に退行がない - diff --git a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs index 74f2f593..fa2e7bf3 100644 --- a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs +++ b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs @@ -198,10 +198,22 @@ fn detect_if_in_body(body: &[ASTNode]) -> bool { /// Effect: Loops with conditional assignment fall through to Pattern1. /// /// Phase 264 P1: TODO - Implement accurate if-sum signature detection. -fn detect_if_else_phi_in_body(_body: &[ASTNode]) -> bool { - // Phase 264 P0: Conservative - always return false - // This prevents simple conditional assignments from being classified as Pattern3IfPhi - false +fn detect_if_else_phi_in_body(body: &[ASTNode]) -> bool { + // Phase 282 P5: Proper if-else PHI detection (re-enabled with ExtractionBased safety) + // + // This function provides initial classification for Pattern3IfPhi. + // The actual validation is done by extractors::pattern3::extract_loop_with_if_phi_parts() + // which performs deep checks (PHI assignments, no control flow, etc.) + // + // Here we just check: Does the loop body contain an if-else statement? + // This allows Pattern3 to be attempted, and extraction will validate. + + for stmt in body { + if matches!(stmt, ASTNode::If { else_body: Some(_), .. }) { + return true; // Found if-else + } + } + false // No if-else found } /// Count carrier variables (variables assigned in loop body) diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs index b59b9779..c101335e 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs @@ -14,11 +14,13 @@ //! //! - Pattern1: ✅ Migrated (Phase 282 P3) //! - Pattern2: ✅ Migrated (Phase 282 P4) -//! - Pattern3-5: ⏸ Pending (future phases) +//! - Pattern3: ✅ Migrated (Phase 282 P5) +//! - Pattern4-5: ⏸ Pending (future phases) //! - Pattern6-7: ✅ Plan-based (Phase 273, different path) //! - Pattern8-9: ✅ Already ExtractionBased (Phase 259/270) pub(crate) mod pattern1; pub(crate) mod pattern2; // Phase 282 P4: Pattern2 extraction +pub(crate) mod pattern3; // Phase 282 P5: Pattern3 extraction -// Future: pattern3, pattern4, pattern5 +// Future: pattern4, pattern5 diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs index 8c919fe4..8714d404 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs @@ -55,7 +55,9 @@ pub(crate) fn extract_simple_while_parts( } /// Validate condition: 比較演算 (左辺が変数) -fn validate_condition_structure(condition: &ASTNode) -> Option { +/// +/// Exported for reuse by Pattern3 (Phase 282 P5) +pub(crate) fn validate_condition_structure(condition: &ASTNode) -> Option { match condition { ASTNode::BinaryOp { operator, left, .. } => { // 比較演算子チェック diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs new file mode 100644 index 00000000..0816b79c --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs @@ -0,0 +1,509 @@ +//! Phase 282 P5: Pattern3 (Loop with If-Else PHI) Extraction + +use crate::ast::{ASTNode, BinaryOperator}; + +#[derive(Debug, Clone)] +pub(crate) struct Pattern3Parts { + pub loop_var: String, // Loop variable name (e.g., "i") + pub merged_var: String, // Primary PHI carrier (e.g., "sum") + pub carrier_count: usize, // Validation: 1-2 accumulators + // Note: has_else (always true), phi_like_merge (implicit) omitted + // AST reused from ctx - no duplication +} + +/// Extract Pattern3 (Loop with If-Else PHI) parts +/// +/// # Detection Criteria +/// +/// 1. **Condition**: 比較演算 (left=variable) +/// 2. **Body**: At least one if-else statement (else REQUIRED) +/// 3. **Assignments**: Both branches assign to same variable(s) +/// 4. **Control Flow**: NO break/continue/nested-if (return → Ok(None)) +/// +/// # Four-Phase Validation +/// +/// **Phase 1**: Validate condition structure (reuse Pattern1) +/// **Phase 2**: Find if-else statement (else branch REQUIRED) +/// **Phase 3**: Validate PHI assignments (intersection of then/else) +/// **Phase 4**: Validate NO control flow +/// +/// # Fail-Fast Rules +/// +/// - `Ok(Some(parts))`: Pattern3 confirmed +/// - `Ok(None)`: Not Pattern3 (structural mismatch) +/// - `Err(msg)`: Logic bug (malformed AST) +pub(crate) fn extract_loop_with_if_phi_parts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + // Phase 1: Validate condition (reuse Pattern1) + use super::pattern1::validate_condition_structure; + let loop_var = match validate_condition_structure(condition) { + Some(var) => var, + None => return Ok(None), + }; + + // Phase 2: Find if-else statement + let if_stmt = match find_if_else_statement(body) { + Some(stmt) => stmt, + None => return Ok(None), // No if-else → Not Pattern3 + }; + + // Phase 3: Validate PHI assignments + let merged_vars = match extract_phi_assignments(if_stmt) { + Some(vars) if !vars.is_empty() => vars, + _ => return Ok(None), // No matching assignments → Not Pattern3 + }; + + // Phase 4a: Check for return (early Ok(None) - let other patterns try) + if has_return_statement(body) { + return Ok(None); // Has return → delegate to other patterns + } + + // Phase 4b: Validate NO control flow (break/continue/nested-if only) + if has_control_flow_statement(body) { + return Ok(None); // Has break/continue/nested-if → Not Pattern3 + } + + // Extract primary carrier (first merged var) + let merged_var = merged_vars[0].clone(); + let carrier_count = merged_vars.len(); + + Ok(Some(Pattern3Parts { + loop_var, + merged_var, + carrier_count, + })) +} + +/// Find if-else statement in loop body +/// +/// Returns first if statement with else branch. +/// Allows multiple if statements - just finds the first if-else. +fn find_if_else_statement(body: &[ASTNode]) -> Option<&ASTNode> { + for stmt in body { + if matches!(stmt, ASTNode::If { else_body: Some(_), .. }) { + return Some(stmt); // Found first if-else + } + } + None // No if-else found +} + +/// Extract variables assigned in BOTH then and else branches +fn extract_phi_assignments(if_stmt: &ASTNode) -> Option> { + let (then_body, else_body) = match if_stmt { + ASTNode::If { + then_body, + else_body: Some(else_body), + .. + } => (then_body, else_body), + _ => return None, + }; + + let then_assignments = extract_assignment_targets(then_body); + let else_assignments = extract_assignment_targets(else_body); + + // Find intersection + let mut merged_vars = Vec::new(); + for var in &then_assignments { + if else_assignments.contains(var) { + merged_vars.push(var.clone()); + } + } + + if merged_vars.is_empty() { + return None; + } + + // Use first occurrence (AST order) - deterministic and meaningful SSOT + // Don't sort alphabetically - preserve natural appearance order + Some(merged_vars) +} + +fn extract_assignment_targets(body: &[ASTNode]) -> Vec { + let mut targets = Vec::new(); + for stmt in body { + if let ASTNode::Assignment { target, .. } = stmt { + if let ASTNode::Variable { name, .. } = target.as_ref() { + targets.push(name.clone()); + } + } + } + targets +} + +/// Check for return statements (Pattern3 version) +/// +/// Return → Ok(None) to let other patterns try. +/// This is checked separately before control flow validation. +fn has_return_statement(body: &[ASTNode]) -> bool { + for stmt in body { + if has_return_recursive(stmt) { + return true; + } + } + false +} + +fn has_return_recursive(node: &ASTNode) -> bool { + match node { + ASTNode::Return { .. } => true, + ASTNode::If { + then_body, + else_body, + .. + } => { + then_body.iter().any(has_return_recursive) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_return_recursive)) + } + _ => false, + } +} + +/// Check for control flow (Pattern3 version) +/// +/// Pattern3 allows ONE if-else (that's the PHI pattern). +/// Reject: break, continue, NESTED if only (return checked separately). +fn has_control_flow_statement(body: &[ASTNode]) -> bool { + for stmt in body { + if has_control_flow_recursive_p3(stmt) { + return true; + } + } + false +} + +fn has_control_flow_recursive_p3(node: &ASTNode) -> bool { + match node { + ASTNode::Break { .. } | ASTNode::Continue { .. } => true, + // Return removed - checked separately + ASTNode::If { + then_body, + else_body, + .. + } => { + // Check for NESTED if (reject) + let has_nested_then = then_body.iter().any(|n| matches!(n, ASTNode::If { .. })); + let has_nested_else = else_body + .as_ref() + .map_or(false, |b| b.iter().any(|n| matches!(n, ASTNode::If { .. }))); + + if has_nested_then || has_nested_else { + return true; + } + + // Check control flow INSIDE branches + then_body.iter().any(has_control_flow_recursive_p3) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_control_flow_recursive_p3)) + } + + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{LiteralValue, Span}; + + #[test] + fn test_extract_if_phi_success() { + // loop(i < 3) { if (i > 0) { sum = sum + 1 } else { sum = sum + 0 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ + ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, + ]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + let parts = result.unwrap(); + assert!(parts.is_some()); + let parts = parts.unwrap(); + assert_eq!(parts.loop_var, "i"); + assert_eq!(parts.merged_var, "sum"); + assert_eq!(parts.carrier_count, 1); + } + + #[test] + fn test_extract_no_else_returns_none() { + // loop(i < 3) { if (i > 0) { sum = sum + 1 } i = i + 1 } // No else + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: None, // ← No else + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // No else → Not Pattern3 + } + + #[test] + fn test_extract_different_vars_returns_none() { + // loop(i < 3) { if (i > 0) { sum = 1 } else { count = 1 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), // then: sum + span: Span::unknown(), + }), + value: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "count".to_string(), // else: count (different!) + span: Span::unknown(), + }), + value: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Different vars → Not Pattern3 + } + + #[test] + fn test_extract_with_break_returns_none() { + // loop(i < 3) { if (i > 0) { sum = sum + 1; break } else { sum = sum + 0 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ + ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, + ASTNode::Break { + span: Span::unknown(), + }, // ← break + ], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has break → Not Pattern3 + } +} diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs index 45445fb7..65587f4b 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs @@ -19,24 +19,79 @@ use crate::mir::ValueId; /// Phase 194: Detection function for Pattern 3 /// -/// Phase 192: Updated to structure-based detection +/// Phase 282 P5: Updated to ExtractionBased detection with safety valve /// /// Pattern 3 matches: -/// - Pattern kind is Pattern3IfPhi (has if-else with PHI, no break/continue) -/// -/// NOTE: Priority is now handled by pattern classification, not router order +/// - Pattern kind is Pattern3IfPhi (safety valve, O(1) early rejection) +/// - Extraction validates: if-else PHI + NO break/continue/nested-if (return → Ok(None)) pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use crate::mir::loop_pattern_detection::LoopPatternKind; - ctx.pattern_kind == LoopPatternKind::Pattern3IfPhi + + // Step 1: Early rejection guard (safety valve, O(1)) + if ctx.pattern_kind != LoopPatternKind::Pattern3IfPhi { + if ctx.debug { + trace::trace().debug( + "pattern3/can_lower", + &format!("reject: pattern_kind={:?}", ctx.pattern_kind), + ); + } + return false; + } + + // Step 2: ExtractionBased validation (SSOT, deep check) + use super::extractors::pattern3::extract_loop_with_if_phi_parts; + + match extract_loop_with_if_phi_parts(ctx.condition, ctx.body) { + Ok(Some(_)) => { + if ctx.debug { + trace::trace().debug("pattern3/can_lower", "accept: extractable (Phase 282 P5)"); + } + true + } + Ok(None) => { + if ctx.debug { + trace::trace().debug( + "pattern3/can_lower", + "reject: not Pattern3 (no if-else PHI or has control flow)", + ); + } + false + } + Err(e) => { + if ctx.debug { + trace::trace().debug("pattern3/can_lower", &format!("error: {}", e)); + } + false + } + } } /// Phase 194: Lowering function for Pattern 3 /// +/// Phase 282 P5: Re-extracts for SSOT (no caching from can_lower) +/// /// Wrapper around cf_loop_pattern3_with_if_phi to match router signature pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { + use super::extractors::pattern3::extract_loop_with_if_phi_parts; + + // Re-extract (SSOT principle - no caching from can_lower) + let parts = extract_loop_with_if_phi_parts(ctx.condition, ctx.body)? + .ok_or_else(|| "[pattern3] Not a loop with if-phi pattern in lower()".to_string())?; + + if ctx.debug { + trace::trace().debug( + "pattern3/lower", + &format!( + "loop_var={}, merged_var={}, carriers={} (Phase 282 P5)", + parts.loop_var, parts.merged_var, parts.carrier_count + ), + ); + } + + // Call existing orchestrator implementation (completely unchanged) builder.cf_loop_pattern3_with_if_phi(ctx.condition, ctx.body, ctx.func_name, ctx.debug) }