Files
hakorune/src/mir/builder/control_flow/joinir/patterns/router.rs
tomoaki a2c5fd90fe feat(joinir): Phase 188.3 - Pattern6 (NestedLoopMinimal) 選択ロジック実装
## Phase 188.3 進捗: Phase 2 完了 (6/13 tasks)

### 実装完了 

**Phase 1: Fixture作成**
- apps/tests/phase1883_nested_minimal.hako 追加
  - Add/Compare のみ(乗算なし)
  - 期待 exit code: 9 (3×3 nested loops)
- 既存 lowering で fallback 動作確認

**Phase 2: 選択ロジック (SSOT)**
- LoopPatternContext に step_tree_max_loop_depth フィールド追加
- choose_pattern_kind() に Pattern6 選択ロジック実装:
  1. Cheap check (has_inner_loop)
  2. StepTree 構築 (max_loop_depth 取得)
  3. AST validation (is_pattern6_lowerable)
- pattern6_nested_minimal.rs モジュール作成 (stub)
- LOOP_PATTERNS に Pattern6 entry 追加
- **検証**: Pattern6 が正しく選択される 

### 設計原則 (確認済み)

1. **Fail-Fast**: Pattern6 選択後は Ok(None) で逃げない
2. **outer 変数 write-back 検出 → validation false** (Phase 188.4+)
3. **最小実装**: inner local だけ、Pattern1 モデル二重化
4. **cfg! 依存なし**: production で動作

### 検証結果

```
[choose_pattern_kind] has_inner_loop=true
[choose_pattern_kind] max_loop_depth=2
[choose_pattern_kind] is_pattern6_lowerable=true
 Pattern6 SELECTED!
```

Stub からの期待エラー:
```
[ERROR]  [Pattern6] Nested loop lowering not yet implemented
```

### 次: Phase 3 (Lowering 実装 - 推定4時間)

残りタスク:
- Phase 3-1: AST 抽出ヘルパー
- Phase 3-2: Validation ヘルパー
- Phase 3-3: Continuation 生成 (outer_step, inner_step, k_inner_exit)
- Phase 3-4: fixture が exit=9 を返すことを検証

### 変更ファイル

**新規**:
- apps/tests/phase1883_nested_minimal.hako
- src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs
- docs/development/current/main/phases/phase-188.{1,2,3}/README.md

**変更**:
- src/mir/builder/control_flow/joinir/routing.rs (Pattern6 選択)
- src/mir/builder/control_flow/joinir/patterns/router.rs (Context 拡張)
- src/mir/builder/control_flow/joinir/patterns/mod.rs (module 宣言)

🎯 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 05:45:12 +09:00

508 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Pattern Router - Table-driven dispatch for loop patterns
//!
//! Phase 194: Replace if/else chain with table-driven routing
//!
//! # Architecture
//!
//! - Each pattern registers a detect function and a lower function
//! - Patterns are tried in priority order (lower = tried first)
//! - First matching pattern wins
//! - Feature extraction delegated to ast_feature_extractor module
//!
//! # Adding New Patterns
//!
//! 1. Create a new module in `patterns/` (e.g., `pattern4_your_name.rs`)
//! 2. Implement `pub fn can_lower(ctx: &LoopPatternContext) -> bool`
//! 3. Implement `pub fn lower(builder: &mut MirBuilder, ctx: &LoopPatternContext) -> Result<Option<ValueId>, String>`
//! 4. Add entry to `LOOP_PATTERNS` table below
//!
//! That's it! No need to modify routing logic.
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind};
// Phase 273 P1: Import Plan components (DomainPlan → Normalizer → Verifier → Lowerer)
use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer;
use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer;
use crate::mir::builder::control_flow::plan::verifier::PlanVerifier;
/// AST Feature Extractor (declared in mod.rs as pub module, import from parent)
use super::ast_feature_extractor as ast_features;
/// Phase 92 P0-2: Import LoopSkeleton for Option A
use crate::mir::loop_canonicalizer::LoopSkeleton;
/// Context passed to pattern detect/lower functions
pub(crate) struct LoopPatternContext<'a> {
/// Loop condition AST node
pub condition: &'a ASTNode,
/// Loop body statements
pub body: &'a [ASTNode],
/// Current function name (for routing)
pub func_name: &'a str,
/// Debug logging enabled
pub debug: bool,
/// Has continue statement(s) in body? (Phase 194+)
#[allow(dead_code)]
pub has_continue: bool,
/// Has break statement(s) in body? (Phase 194+)
#[allow(dead_code)]
pub has_break: bool,
/// Phase 192: Loop features extracted from AST
#[allow(dead_code)]
pub features: LoopFeatures,
/// Phase 192: Pattern classification based on features
pub pattern_kind: LoopPatternKind,
/// Phase 200-C: Optional function body AST for capture analysis
/// None if not available, Some(&[ASTNode]) if function body is accessible
pub fn_body: Option<&'a [ASTNode]>,
/// Phase 92 P0-2: Optional LoopSkeleton from canonicalizer
/// This provides ConditionalStep information for Pattern2 lowering.
/// None if canonicalizer hasn't run yet (backward compatibility).
/// SSOT Principle: Avoid re-detecting ConditionalStep in lowering phase.
#[allow(dead_code)]
pub skeleton: Option<&'a LoopSkeleton>,
/// Phase 188.3: Cached StepTree max_loop_depth for Pattern6
/// None if not computed, Some(depth) if Pattern6 candidate
/// Avoids re-building StepTree in lowering phase
pub step_tree_max_loop_depth: Option<u32>,
}
impl<'a> LoopPatternContext<'a> {
/// Create new context from routing parameters
///
/// Automatically detects continue/break statements in body
/// Extracts features and classifies pattern from AST
/// Detects infinite loop condition
/// Uses choose_pattern_kind() SSOT entry point
pub(crate) fn new(
condition: &'a ASTNode,
body: &'a [ASTNode],
func_name: &'a str,
debug: bool,
) -> Self {
// Use AST Feature Extractor for break/continue detection
let has_continue = ast_features::detect_continue_in_body(body);
let has_break = ast_features::detect_break_in_body(body);
// Extract features (includes infinite loop detection)
let features = ast_features::extract_features(condition, body, has_continue, has_break);
// Phase 137-6-S1: Use SSOT pattern selection entry point
use crate::mir::builder::control_flow::joinir::routing::choose_pattern_kind;
let pattern_kind = choose_pattern_kind(condition, body);
Self {
condition,
body,
func_name,
debug,
has_continue,
has_break,
features,
pattern_kind,
fn_body: None, // Phase 200-C: Default to None
skeleton: None, // Phase 92 P0-2: Default to None
step_tree_max_loop_depth: None, // Phase 188.3: Default to None
}
}
/// Phase 200-C: Create context with fn_body for capture analysis
pub(crate) fn with_fn_body(
condition: &'a ASTNode,
body: &'a [ASTNode],
func_name: &'a str,
debug: bool,
fn_body: &'a [ASTNode],
) -> Self {
let mut ctx = Self::new(condition, body, func_name, debug);
ctx.fn_body = Some(fn_body);
ctx
}
/// Phase 92 P0-2: Set skeleton (for canonicalizer integration)
#[allow(dead_code)]
pub(crate) fn with_skeleton(mut self, skeleton: &'a LoopSkeleton) -> Self {
self.skeleton = Some(skeleton);
self
}
}
/// Phase 286 P2.2: Common helper for Plan line lowering
///
/// Extracts the common 3-line pattern used by Pattern6/7/4/1:
/// 1. Normalize DomainPlan → CorePlan
/// 2. Verify CorePlan invariants (fail-fast)
/// 3. Lower CorePlan → MIR
///
/// This eliminates repetition across 4 pattern routing blocks.
fn lower_via_plan(
builder: &mut MirBuilder,
domain_plan: crate::mir::builder::control_flow::plan::DomainPlan,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
PlanVerifier::verify(&core_plan)?;
PlanLowerer::lower(builder, core_plan, ctx)
}
/// Phase 287: Plan extractor function type for routing table
///
/// Extractors have two signatures:
/// - Simple: (condition, body) - Pattern1/4/9
/// - WithFnBody: (condition, body, fn_body) - Pattern6/7
type SimplePlanExtractor = fn(&ASTNode, &[ASTNode]) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>;
/// Phase 287: Plan extractor table entry
struct PlanExtractorEntry {
/// Pattern name for debug logging
name: &'static str,
/// Extractor function
extractor: PlanExtractorVariant,
}
/// Phase 287: Extractor function variant (handles different signatures)
enum PlanExtractorVariant {
/// Simple extractor: (condition, body)
Simple(SimplePlanExtractor),
/// Extractor with fn_body: (condition, body, fn_body) - Pattern6
WithFnBody(fn(&ASTNode, &[ASTNode], Option<&[ASTNode]>) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
/// Extractor with post_loop_code: (condition, body, post_loop_code) - Pattern7
///
/// NOTE (Phase 286): Currently always called with &[] for post_loop_code.
/// This variant is kept for future extension (post-loop segment analysis).
/// The _post_loop_code parameter in Pattern7 extractor is intentionally unused.
WithPostLoop(fn(&ASTNode, &[ASTNode], &[ASTNode]) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
}
/// Phase 287: Plan extractor routing table (SSOT)
///
/// 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)
static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[
PlanExtractorEntry {
name: "Pattern6_ScanWithInit (Phase 273)",
extractor: PlanExtractorVariant::WithFnBody(super::pattern6_scan_with_init::extract_scan_with_init_plan),
},
PlanExtractorEntry {
name: "Pattern7_SplitScan (Phase 273)",
extractor: PlanExtractorVariant::WithPostLoop(super::pattern7_split_scan::extract_split_scan_plan),
},
PlanExtractorEntry {
name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern5::extract_pattern5_plan),
},
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),
},
PlanExtractorEntry {
name: "Pattern9_AccumConstLoop (Phase 286 P2.3)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern9::extract_pattern9_plan),
},
PlanExtractorEntry {
name: "Pattern2_Break (Phase 286 P3.1)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern2::extract_pattern2_plan),
},
PlanExtractorEntry {
name: "Pattern1_SimpleWhile (Phase 286 P2.1)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern1::extract_pattern1_plan),
},
];
/// Phase 287: Try all plan extractors in priority order
///
/// Returns Ok(Some(value_id)) if any extractor succeeds
/// Returns Ok(None) if no extractor matches
/// Returns Err if extraction or lowering fails
fn try_plan_extractors(
builder: &mut MirBuilder,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
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) => {
extractor(ctx.condition, ctx.body)?
}
PlanExtractorVariant::WithFnBody(extractor) => {
// Pattern6: needs fn_body for capture analysis
extractor(ctx.condition, ctx.body, ctx.fn_body)?
}
PlanExtractorVariant::WithPostLoop(extractor) => {
// Pattern7: uses empty slice for post_loop_code (3rd param)
extractor(ctx.condition, ctx.body, &[])?
}
};
// If extraction succeeded, check if we can lower it
if let Some(domain_plan) = plan_opt {
// Phase 286 P3: Pattern8 static box filtering
// Plan extractors are pure (no builder access), so we filter here
// Static box loops with `me.method()` should be handled by ReceiverNormalizeBox, not Pattern8
if entry.name.contains("Pattern8") && builder.comp_ctx.current_static_box.is_some() {
if ctx.debug {
trace::trace().debug(
"route/plan",
&format!("{} extracted but rejected: static box context (fallback to legacy)", entry.name),
);
}
// Skip this pattern, try next extractor
continue;
}
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);
// Phase 286 P2.6.1: Fail-Fast 統一 - extract 成功 → normalize/lower 失敗は即 Err
// Pattern3 stub fallback 撤去normalizer 実装済み)
return lower_via_plan(builder, domain_plan, ctx);
} else {
// Extraction returned None - try next extractor
if ctx.debug {
let debug_msg = format!("{} extraction returned None, trying next pattern", entry.name);
trace::trace().debug("route", &debug_msg);
}
}
}
// No extractor matched
Ok(None)
}
/// Phase 272 P0.2 Refactoring: can_lower() strategy classification
///
/// Clarifies the two main detection strategies used across patterns:
///
/// ## ExtractionBased (SSOT Approach)
/// - Used by: Pattern6, Pattern7
/// - Strategy: Try pattern extraction, if successful → match
/// - Pros: Single source of truth (extract function defines pattern)
/// - Cons: Extraction can be expensive (but amortized over lowering)
///
/// ## StructureBased (Feature Classification)
/// - Used by: Pattern1, Pattern2, Pattern3, Pattern4, Pattern5, Pattern8, Pattern9
/// - Strategy: Check pattern_kind (from LoopPatternContext), plus optional structural checks
/// - Pros: Fast classification, reuses centralized feature detection
/// - Cons: Two sources of truth (classify + structural checks)
///
/// ## Rationale for Dual Strategy:
/// - Pattern6/7: Complex extraction logic (variable step, carrier tracking)
/// → ExtractionBased avoids duplication between detect and extract
/// - Other patterns: Simple structural features (break/continue/if-phi)
/// → StructureBased leverages centralized LoopFeatures classification
///
/// This documentation prevents bugs like Phase 272 P0.2's Pattern7 issue
/// (pattern_kind check was too restrictive, extraction-based approach fixed it).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] // Documentation purpose - not enforced in code yet
pub(crate) enum CanLowerStrategy {
/// Extraction-based detection: Try extract(), success → match
/// Used by Pattern6, Pattern7
ExtractionBased,
/// Structure-based detection: Check pattern_kind from LoopPatternContext
/// Used by Pattern1, Pattern2, Pattern3, Pattern4, Pattern5, Pattern8, Pattern9
StructureBased,
}
/// Entry in the loop pattern router table.
/// Each pattern registers a detect function and a lower function.
pub(crate) struct LoopPatternEntry {
/// Human-readable pattern name for debugging
pub(crate) name: &'static str,
/// Detection function: returns true if this pattern matches
pub(crate) detect: fn(&MirBuilder, &LoopPatternContext) -> bool,
/// Lowering function: performs the actual JoinIR generation
pub(crate) lower: fn(&mut MirBuilder, &LoopPatternContext) -> Result<Option<ValueId>, String>,
}
/// Static table of all registered loop patterns.
///
/// **IMPORTANT**: Patterns are tried in array order (SSOT).
/// Array order defines priority - earlier entries are tried first.
/// Pattern4 → Pattern8 → Pattern9 → Pattern3 → Pattern1 → Pattern2
///
/// # Current Patterns (Structure-based detection, established Phase 131-11+)
///
/// Pattern detection strategies (updated Phase 286):
/// - Pattern6/7: ExtractionBased (Plan line, Phase 273+)
/// - Pattern5/8/9: ExtractionBased (extraction functions, Plan line Phase 286+)
/// - Pattern1-4: StructureBased (LoopFeatures classification, legacy)
///
/// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako)
/// - Detection: pattern_kind == Pattern4Continue
/// - Structure: has_continue && !has_break
///
/// - Pattern 3: Loop with If-Else PHI (loop_if_phi.hako)
/// - Detection: pattern_kind == Pattern3IfPhi
/// - Structure: has_if_else_phi && !has_break && !has_continue
///
/// - Pattern 1: Simple While Loop (loop_min_while.hako)
/// - Detection: pattern_kind == Pattern1SimpleWhile
/// - Structure: !has_break && !has_continue && !has_if_else_phi
///
/// - Pattern 2: Loop with Conditional Break (joinir_min_loop.hako)
/// - Detection: pattern_kind == Pattern2Break
/// - Structure: has_break && !has_continue
///
/// Note: func_name is now only used for debug logging, not pattern detection
/// Phase 286: Pattern5 removed (migrated to Plan-based routing)
pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
LoopPatternEntry {
name: "Pattern6_NestedLoopMinimal", // Phase 188.3: 1-level nested loop
detect: super::pattern6_nested_minimal::can_lower,
lower: super::pattern6_nested_minimal::lower,
},
LoopPatternEntry {
name: "Pattern4_WithContinue",
detect: super::pattern4_with_continue::can_lower,
lower: super::pattern4_with_continue::lower,
},
// Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing)
// Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer
// Phase 273 P2: Pattern7 entry removed (migrated to Plan-based routing)
// Pattern7_SplitScan now handled via extract_split_scan_plan() + PlanLowerer
LoopPatternEntry {
name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
detect: super::pattern8_scan_bool_predicate::can_lower,
lower: super::pattern8_scan_bool_predicate::lower,
},
LoopPatternEntry {
name: "Pattern9_AccumConstLoop", // Phase 270 P1: accumulator const loop (橋渡しパターン, before P1)
detect: super::pattern9_accum_const_loop::can_lower,
lower: super::pattern9_accum_const_loop::lower,
},
LoopPatternEntry {
name: "Pattern3_WithIfPhi",
detect: super::pattern3_with_if_phi::can_lower,
lower: super::pattern3_with_if_phi::lower,
},
LoopPatternEntry {
name: "Pattern1_Minimal",
detect: super::pattern1_minimal::can_lower,
lower: super::pattern1_minimal::lower,
},
LoopPatternEntry {
name: "Pattern2_WithBreak",
detect: super::pattern2_with_break::can_lower,
lower: super::pattern2_with_break::lower,
},
];
/// Try all registered patterns in priority order.
///
/// Returns Ok(Some(value_id)) if a pattern matched and lowered successfully.
/// Returns Ok(None) if no pattern matched.
/// Returns Err if a pattern matched but lowering failed.
///
/// # Router Architecture (Structure-based routing established Phase 183)
///
/// This router uses multiple detection strategies:
/// - Plan-based (Pattern6/7): extract_*_plan() → DomainPlan (Phase 273+ SSOT)
/// - Extraction-based (Pattern8/9): extract_*() functions (already implemented)
/// - Structure-based (Pattern1-5): ctx.pattern_kind classification (legacy)
///
/// # Plan Line SSOT for Pattern6/7 (Phase 273+)
///
/// This function implements the following routing strategy:
/// 1. Try Plan-based Pattern6 (extract_scan_with_init_plan) → DomainPlan
/// 2. Try Plan-based Pattern7 (extract_split_scan_plan) → DomainPlan
/// 3. Fall through to legacy Pattern1-5 table for other patterns
///
/// The Plan line (Extractor → Normalizer → Verifier → Lowerer) is the
/// current operational SSOT for Pattern6/7. Legacy patterns (1-5) use
/// the traditional LoopPatternContext-based routing.
///
/// Plan-based architecture (Phase 273 P1-P3):
/// - extract_*_plan() → DomainPlan (pure extraction, no builder)
/// - PlanNormalizer::normalize() → CorePlan (pattern knowledge expansion, SSOT)
/// - PlanVerifier::verify() → fail-fast validation
/// - PlanLowerer::lower() → MIR emission (pattern-agnostic, emit_frag SSOT)
///
/// SSOT Entry Points:
/// - Pattern6: src/mir/builder/control_flow/plan/normalizer.rs (ScanWithInit normalization)
/// - Pattern7: src/mir/builder/control_flow/plan/normalizer.rs (SplitScan normalization)
/// - Pattern1-5: src/mir/builder/control_flow/joinir/patterns/pattern*.rs (direct lowering)
pub(crate) fn route_loop_pattern(
builder: &mut MirBuilder,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
use super::super::trace;
// Phase 287: Try all Plan extractors in priority order (Pattern6/7/4/9/1)
// This replaces ~100 lines of repetitive extraction blocks
if let Some(value_id) = try_plan_extractors(builder, ctx)? {
return Ok(Some(value_id));
}
// Phase 183: Route based on pre-classified pattern kind
// Pattern kind was already determined by ctx.pattern_kind in LoopPatternContext::new()
// This eliminates duplicate detection logic across routers.
// Find matching pattern entry based on pattern_kind
// Phase 273 P0.1: Pattern6 skip logic removed (entry no longer in LOOP_PATTERNS)
for entry in LOOP_PATTERNS {
if (entry.detect)(builder, ctx) {
let log_msg = format!("route=joinir strategy=extract pattern={} (Phase 194+)", entry.name);
trace::trace().pattern("route", &log_msg, true);
return (entry.lower)(builder, ctx);
}
}
// No pattern matched - return None (caller will handle error)
if ctx.debug {
trace::trace().debug(
"route",
&format!(
"route=none (no pattern matched) func='{}' pattern_kind={:?} (exhausted: plan+joinir)",
ctx.func_name, ctx.pattern_kind
),
);
}
Ok(None)
}