diff --git a/docs/development/current/main/phases/phase-286/README.md b/docs/development/current/main/phases/phase-286/README.md index ca930062..bf66acd1 100644 --- a/docs/development/current/main/phases/phase-286/README.md +++ b/docs/development/current/main/phases/phase-286/README.md @@ -1,6 +1,6 @@ # Phase 286: JoinIR Line Absorption(JoinIR→CorePlan/Frag 収束) -Status: In Progress (P0, P1, P2, P2.1, P2.2, P2.3, P3, 286C-2 COMPLETE) +Status: In Progress (P0, P1, P2, P2.1, P2.2, P2.3, P3, 286C-2 COMPLETE, P2.6 IN PROGRESS) ## Goal @@ -249,6 +249,41 @@ Phase 286 では JoinIR line を “第2の lowerer” として放置せず、* - Regression test: quick smoke 0 failed - Debug log: `route=plan strategy=extract pattern=Pattern8_BoolPredicateScan` 確認 +### P2.6 (Pattern3 Plan化 PoC + Pattern1 退行修正) 🚧 IN PROGRESS (2025-12-26) + +**背景**: +- **退行バグ発見**: `apps/tests/phase118_pattern3_if_sum_min.hako` が FAIL (期待 12、実際 10) +- **原因**: Pattern1 Plan が Pattern3 fixture を誤ってマッチ + - Pattern1 extractor の `has_control_flow_statement()` が if/else をチェックしていない + - pattern_kind ガードもなく、Pattern1 Plan が Pattern3 にマッチしていた + +**実装内容**: + +**Step 0: Pattern1 退行修正(最優先)** ✅ COMPLETE +- **0.1 Router guard**: `router.rs` の `try_plan_extractors()` で Pattern1 Plan は `ctx.pattern_kind == Pattern1SimpleWhile` のみマッチ +- **0.2 has_if_statement 追加**: `common_helpers.rs` に再帰的 if 検出ヘルパー追加 +- **0.3 Pattern1 extractor 強化**: `has_if_statement()` による if-else 拒否を追加(防御in深さ) +- **検証**: phase118 PASS (出力 12)、legacy Pattern3 ルートが正しく動作 + +**Step 1: Pattern3 Plan line 実装** (予定) +- **DomainPlan 追加**: `Pattern3IfPhiPlan { loop_var, carrier_var, condition, if_condition, then_update, else_update, loop_increment }` +- **Extractor**: `extract_pattern3_plan()` - 既存 `extract_loop_with_if_phi_parts()` を活用 +- **Normalizer**: CFG構造 `preheader → header(PHI: i, sum) → body → then/else → merge(PHI: sum) → step → header → after` +- **Router**: PLAN_EXTRACTORS に Pattern3 追加(Pattern1 より前、Pattern4 より後) + +**成果物** (予定): +- `src/mir/builder/control_flow/joinir/patterns/router.rs` (変更: Pattern1 guard ✅) +- `src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs` (変更: has_if_statement ✅) +- `src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs` (変更: if 拒否 ✅) +- `src/mir/builder/control_flow/plan/mod.rs` (変更: Pattern3IfPhiPlan + DomainPlan variant) +- `src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs` (変更: extract_pattern3_plan) +- `src/mir/builder/control_flow/plan/normalizer.rs` (変更: normalize_pattern3_if_phi) + +**成功基準**: +- Regression fix: `phase118_pattern3_if_sum_vm` PASS (出力 12) ✅ +- Pattern3 Plan line: `route=plan pattern=Pattern3_IfPhi` 確認 +- Full regression: `tools/smokes/v2/run.sh --profile quick` 0 failed + ## Acceptance(P0) - 2本の lowering が "設計として" どこで 1 本に収束するかが明文化されている diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs index dab65d88..61b0e5ed 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs @@ -144,6 +144,62 @@ pub(crate) fn has_control_flow_statement(body: &[ASTNode]) -> bool { counts.break_count > 0 || counts.continue_count > 0 } +/// Phase 286 P2.6: Check if body has ANY if statement (recursive) +/// +/// This is a supplementary helper for Pattern1 extraction to prevent +/// Pattern1 from incorrectly matching Pattern3 fixtures (if-else-phi). +/// +/// # Returns +/// - true if any if statement found in body (recursively) +/// - false otherwise +pub(crate) fn has_if_statement(body: &[ASTNode]) -> bool { + for node in body { + match node { + ASTNode::If { .. } => return true, + ASTNode::ScopeBox { body, .. } => { + if has_if_statement(body) { + return true; + } + } + ASTNode::Loop { body, .. } => { + if has_if_statement(body) { + return true; + } + } + _ => {} + } + } + false +} + +/// Phase 286 P2.6: Check if body has ANY if-else statement (recursive) +/// +/// This is more specific than has_if_statement - it only detects if statements +/// with else branches, which are Pattern3 territory (if-phi merge). +/// +/// # Returns +/// - true if any if-else statement found in body (recursively) +/// - false otherwise +pub(crate) fn has_if_else_statement(body: &[ASTNode]) -> bool { + for node in body { + match node { + ASTNode::If { else_body: Some(_), .. } => return true, + ASTNode::ScopeBox { body, .. } => { + if has_if_else_statement(body) { + return true; + } + } + ASTNode::Loop { body, .. } => { + if has_if_else_statement(body) { + return true; + } + } + _ => {} + } + } + false +} + /// ============================================================ /// Group 3: Condition Validation (比較演算検証) /// ============================================================ 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 80ef9361..37337952 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs @@ -48,6 +48,13 @@ pub(crate) fn extract_simple_while_parts( return Ok(None); } + // Phase 286 P2.6: Reject if-else statements (Pattern3 territory) + // Pattern1 allows simple if without else, but not if-else (which is Pattern3) + if super::common_helpers::has_if_else_statement(body) { + // Has if-else statement → Pattern3 (if-phi merge) + return Ok(None); + } + // Phase 3: Validate step pattern (単純増減のみ) if !has_simple_step_pattern(body, &loop_var) { // Step が複雑 or 存在しない → Not Pattern1 diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs index a3451fad..d7fa9569 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs @@ -507,3 +507,97 @@ mod tests { assert!(result.unwrap().is_none()); // Has break → Not Pattern3 } } + +// ============================================================================ +// Phase 286 P2.6: Pattern3 → Plan/Frag SSOT extractor +// ============================================================================ + +/// Phase 286 P2.6: Extract Pattern3 (Loop with If-Phi) Plan +/// +/// Leverages existing `extract_loop_with_if_phi_parts()` for validation, +/// then extracts detailed AST components for Plan normalization. +/// +/// # Returns +/// - Ok(Some(DomainPlan::Pattern3IfPhi)) if Pattern3 match confirmed +/// - Ok(None) if not Pattern3 (structural mismatch) +/// - Err if malformed AST (rare) +pub(crate) fn extract_pattern3_plan( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern3IfPhiPlan}; + + // Step 1: Validate via existing extractor + let parts = extract_loop_with_if_phi_parts(condition, body)?; + if parts.is_none() { + return Ok(None); // Not Pattern3 → fallback + } + let parts = parts.unwrap(); + + // Step 2: Extract if-else statement (validated to exist) + let if_stmt = find_if_else_statement(body) + .ok_or_else(|| "Pattern3: if-else statement not found after validation".to_string())?; + + // Step 3: Extract if condition and branch updates + let (if_condition, then_update, else_update) = match if_stmt { + ASTNode::If { + condition: if_cond, + then_body, + else_body: Some(else_body), + .. + } => { + // Extract carrier update from then branch + let then_update = extract_single_update(then_body, &parts.merged_var)?; + + // Extract carrier update from else branch + let else_update = extract_single_update(else_body, &parts.merged_var)?; + + (if_cond.as_ref().clone(), then_update, else_update) + } + _ => { + return Err("Pattern3: if-else structure mismatch after validation".to_string()); + } + }; + + // Step 4: Extract loop increment + let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? { + Some(inc) => inc, + None => { + return Err(format!( + "Pattern3: loop increment not found for variable '{}'", + parts.loop_var + )); + } + }; + + Ok(Some(DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan { + loop_var: parts.loop_var, + carrier_var: parts.merged_var, + condition: condition.clone(), + if_condition, + then_update, + else_update, + loop_increment, + }))) +} + +/// Extract update expression from a branch body +/// +/// Expects a single assignment: `carrier = ` +/// Returns the RHS expression AST +fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result { + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + if let ASTNode::Variable { name, .. } = target.as_ref() { + if name == carrier_var { + return Ok(value.as_ref().clone()); + } + } + } + } + + Err(format!( + "Pattern3: carrier update not found for variable '{}' in branch", + carrier_var + )) +} diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index f697bb42..9d527f44 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -190,6 +190,7 @@ enum PlanExtractorVariant { /// Order is important: more specific patterns first /// - Pattern6/7: Need fn_body for capture analysis /// - Pattern8: Bool predicate scan (early-exit return, more specific) +/// - Pattern3: If-phi merge (carrier with conditional update, more specific than Pattern4) /// - Pattern4: Continue loop (more specific than Pattern1) /// - Pattern9: Accum const loop (2 carriers, more specific than Pattern1) /// - Pattern1: Simple while (fallback) @@ -206,6 +207,10 @@ static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[ name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan), }, + PlanExtractorEntry { + name: "Pattern3_IfPhi (Phase 286 P2.6)", + extractor: PlanExtractorVariant::Simple(super::extractors::pattern3::extract_pattern3_plan), + }, PlanExtractorEntry { name: "Pattern4_Continue (Phase 286 P2)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern4::extract_pattern4_plan), @@ -232,6 +237,16 @@ fn try_plan_extractors( use super::super::trace; for entry in PLAN_EXTRACTORS { + // Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast) + // Pattern1 should only match Pattern1SimpleWhile pattern_kind + // This prevents Pattern1 from incorrectly matching Pattern3 fixtures + if entry.name.contains("Pattern1") { + use crate::mir::loop_pattern_detection::LoopPatternKind; + if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile { + continue; + } + } + // Try extraction based on variant let plan_opt = match &entry.extractor { PlanExtractorVariant::Simple(extractor) => { @@ -266,6 +281,23 @@ fn try_plan_extractors( let log_msg = format!("route=plan strategy=extract pattern={}", entry.name); trace::trace().pattern("route", &log_msg, true); + // Phase 286 P2.6: Pattern3 PoC exception - allow fallback to legacy + // Pattern3 extractor validates structure, but normalizer is stub + if entry.name.contains("Pattern3") { + match lower_via_plan(builder, domain_plan, ctx) { + Ok(value_id) => return Ok(value_id), + Err(_) => { + if ctx.debug { + trace::trace().debug( + "route/plan", + "Pattern3 Plan normalization stub - fallback to legacy Pattern3", + ); + } + continue; // Try next extractor (will eventually reach legacy Pattern3) + } + } + } + // Phase 286 P2.4.1: Fail-Fast - extract 成功 → normalize/lower 失敗は即 Err // 構造的 Fail-Fast: 文字列判定なし、extract が Some なら fallback 禁止 return lower_via_plan(builder, domain_plan, ctx); diff --git a/src/mir/builder/control_flow/plan/mod.rs b/src/mir/builder/control_flow/plan/mod.rs index 05a2ea88..2151797e 100644 --- a/src/mir/builder/control_flow/plan/mod.rs +++ b/src/mir/builder/control_flow/plan/mod.rs @@ -53,6 +53,8 @@ pub(in crate::mir::builder) enum DomainPlan { Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan), /// Pattern8: Boolean Predicate Scan (Phase 286 P2.4) Pattern8BoolPredicateScan(Pattern8BoolPredicateScanPlan), + /// Pattern3: Loop with If-Phi (Phase 286 P2.6) + Pattern3IfPhi(Pattern3IfPhiPlan), } /// Phase 273 P0: Scan direction for forward/reverse scan @@ -176,6 +178,53 @@ pub(in crate::mir::builder) struct Pattern8BoolPredicateScanPlan { pub step_lit: i64, } +/// Phase 286 P2.6: Extracted structure for Pattern3 (Loop with If-Phi) +/// +/// This structure contains all the information needed to lower an if-phi merge loop. +/// Pattern3 is a loop with conditional carrier update via if-else branching. +/// +/// # Structure +/// ```text +/// loop(i < N) { +/// if (condition) { +/// carrier = then_update +/// } else { +/// carrier = else_update +/// } +/// i = i + step +/// } +/// ``` +/// +/// # CFG Layout +/// ```text +/// preheader → header(PHI: i, carrier) → body(if_condition) +/// ↓ ↓ +/// after then | else +/// ↓ ↓ +/// merge(PHI: carrier) +/// ↓ +/// step(i_next) +/// ↓ +/// back-edge to header +/// ``` +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern3IfPhiPlan { + /// Loop variable name (e.g., "i") + pub loop_var: String, + /// Carrier variable name (e.g., "sum") + pub carrier_var: String, + /// Loop condition AST (e.g., `i < 3`) + pub condition: ASTNode, + /// If condition AST (e.g., `i > 0`) + pub if_condition: ASTNode, + /// Then branch update AST (e.g., `sum + 1`) + pub then_update: ASTNode, + /// Else branch update AST (e.g., `sum + 0`) + pub else_update: ASTNode, + /// Loop increment expression AST (e.g., `i + 1`) + pub loop_increment: ASTNode, +} + // ============================================================================ // CorePlan (固定語彙 - 構造ノードのみ) // ============================================================================ diff --git a/src/mir/builder/control_flow/plan/normalizer.rs b/src/mir/builder/control_flow/plan/normalizer.rs index f53b9b75..63ab72ad 100644 --- a/src/mir/builder/control_flow/plan/normalizer.rs +++ b/src/mir/builder/control_flow/plan/normalizer.rs @@ -14,7 +14,7 @@ use super::{ CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, ScanWithInitPlan, SplitScanPlan, Pattern1SimpleWhilePlan, Pattern9AccumConstLoopPlan, - Pattern8BoolPredicateScanPlan, + Pattern8BoolPredicateScanPlan, Pattern3IfPhiPlan, }; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::MirBuilder; @@ -44,6 +44,7 @@ impl PlanNormalizer { DomainPlan::Pattern1SimpleWhile(parts) => Self::normalize_pattern1_simple_while(builder, parts, ctx), DomainPlan::Pattern9AccumConstLoop(parts) => Self::normalize_pattern9_accum_const_loop(builder, parts, ctx), DomainPlan::Pattern8BoolPredicateScan(parts) => Self::normalize_pattern8_bool_predicate_scan(builder, parts, ctx), + DomainPlan::Pattern3IfPhi(parts) => Self::normalize_pattern3_if_phi(builder, parts, ctx), } } @@ -2044,4 +2045,35 @@ impl PlanNormalizer { Ok(CorePlan::Loop(loop_plan)) } + + /// Phase 286 P2.6: Pattern3 (Loop with If-Phi) → CorePlan 変換 + /// + /// # Status: PoC Stub + /// + /// This is a minimal stub implementation for Phase 286 P2.6 PoC. + /// The extractor works and will be called, but this normalizer + /// returns an error to fallback to the legacy Pattern3 implementation. + /// + /// # CFG Structure (Design - not yet implemented) + /// ```text + /// preheader → header(PHI: i, carrier) → body(if_condition) + /// ↓ ↓ + /// after then | else + /// ↓ ↓ + /// merge(PHI: carrier) + /// ↓ + /// step(i_next) + /// ↓ + /// back-edge to header + /// ``` + fn normalize_pattern3_if_phi( + _builder: &mut MirBuilder, + _parts: Pattern3IfPhiPlan, + _ctx: &LoopPatternContext, + ) -> Result { + // Phase 286 P2.6 PoC: Stub implementation + // Extractor validates Pattern3 structure, but normalization is deferred + // Fallback to legacy Pattern3 implementation via Ok(None) in router + Err("Pattern3 Plan normalization not yet implemented (PoC stub - fallback to legacy)".to_string()) + } }