Files
hakorune/src/mir/builder/control_flow/joinir/patterns/router.rs
tomoaki 960241795d feat(mir/llvm): Phase 273 P0-P1 DomainPlan→CorePlan + LLVM arg fix
Phase 273 P0-P1: Two-layer plan architecture
- DomainPlan: Pattern-specific knowledge (ScanWithInit)
- CorePlan: Fixed vocabulary (Seq, Loop, If, Effect, Exit)
- ValueId references only (String expressions forbidden)
- Pipeline: Extractor→Normalizer→Verifier→Lowerer

New plan/ module:
- mod.rs: Type definitions, SSOT spec
- normalizer.rs: DomainPlan→CorePlan + ID allocation
- verifier.rs: V1-V6 invariant checks (fail-fast)
- lowerer.rs: CorePlan→MIR (pattern-agnostic)

LLVM fix (ChatGPT):
- function_lower.py: Fix argument reference bug
- Phase 258 index_of_string now PASS on LLVM backend

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 22:42:56 +09:00

355 lines
14 KiB
Rust

//! Pattern Router - Table-driven dispatch for loop patterns
//!
//! Phase 194: Replace if/else chain with table-driven routing
//! Phase 193: Modularized feature extraction using ast_feature_extractor module
//!
//! # 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;
/// Phase 193: Import AST Feature Extractor Box
/// (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>,
}
impl<'a> LoopPatternContext<'a> {
/// Create new context from routing parameters
///
/// Phase 194+: Automatically detects continue/break statements in body
/// Phase 192: Extract features and classify pattern from AST
/// Phase 193: Feature extraction delegated to ast_feature_extractor module
/// Phase 131-11: Detects infinite loop condition
/// Phase 137-6-S1: Use choose_pattern_kind() SSOT entry point
/// Phase 92 P0-2: Added skeleton parameter (None for backward compatibility)
pub(crate) fn new(
condition: &'a ASTNode,
body: &'a [ASTNode],
func_name: &'a str,
debug: bool,
) -> Self {
// Phase 193: Use AST Feature Extractor Box for break/continue detection
let has_continue = ast_features::detect_continue_in_body(body);
let has_break = ast_features::detect_break_in_body(body);
// Phase 193: Extract features using modularized extractor
// Phase 131-11: Pass condition for 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
}
}
/// 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 193: Feature extraction moved to ast_feature_extractor module
/// See: src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs
/// 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.
/// Pattern5 (most specific) → Pattern4 → Pattern3 → Pattern1 → Pattern2
///
/// # Current Patterns (Phase 131-11: Pattern 5 added)
///
/// All patterns now use structure-based detection via LoopFeatures and classify():
///
/// - Pattern 5: Infinite Loop with Early Exit (llvm_stage3_loop_only.hako) [Phase 131-11]
/// - Detection: pattern_kind == InfiniteEarlyExit
/// - Structure: is_infinite_loop && has_break && has_continue
///
/// - 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
pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
LoopPatternEntry {
name: "Pattern5_InfiniteEarlyExit",
detect: super::pattern5_infinite_early_exit::can_lower,
lower: super::pattern5_infinite_early_exit::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
LoopPatternEntry {
name: "Pattern7_SplitScan", // Phase 256 P0: split/tokenization with variable step (before P3)
detect: super::pattern7_split_scan::can_lower,
lower: super::pattern7_split_scan::lower,
},
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.
///
/// # Phase 183: Structure-based routing
///
/// This router uses the centralized pattern classification system:
/// - Pattern detection: `ctx.pattern_kind` (from `loop_pattern_detection::classify`)
/// - No redundant pattern detection in detect functions
/// - All patterns use structure-based classification
///
/// # Phase 273 P1: DomainPlan → Normalizer → Verifier → Lowerer
///
/// Pattern6 uses the new two-layer Plan architecture:
/// - extract_scan_with_init_plan() → DomainPlan (pure extraction)
/// - PlanNormalizer::normalize() → CorePlan (pattern knowledge expansion)
/// - PlanVerifier::verify() → fail-fast validation
/// - PlanLowerer::lower() → MIR emission (pattern-agnostic)
pub(crate) fn route_loop_pattern(
builder: &mut MirBuilder,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
use super::super::trace;
// Phase 273 P1: Try Plan-based Pattern6 first (before table iteration)
// Flow: Extract → Normalize → Verify → Lower
match super::pattern6_scan_with_init::extract_scan_with_init_plan(
ctx.condition,
ctx.body,
ctx.fn_body,
)? {
Some(domain_plan) => {
// DomainPlan extracted successfully
trace::trace().pattern("route", "Pattern6_ScanWithInit (DomainPlan)", true);
// Step 1: Normalize DomainPlan → CorePlan
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
// Step 2: Verify CorePlan invariants (fail-fast)
PlanVerifier::verify(&core_plan)?;
// Step 3: Lower CorePlan → MIR
return PlanLowerer::lower(builder, core_plan, ctx);
}
None => {
// Not Pattern6 - continue to other patterns
if ctx.debug {
trace::trace().debug(
"route",
"Pattern6 Plan extraction returned None, trying other patterns",
);
}
}
}
// 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) {
// Phase 195: Use unified trace for pattern matching
trace::trace().pattern("route", entry.name, true);
return (entry.lower)(builder, ctx);
}
}
// No pattern matched - return None (caller will handle error)
// Phase 187-2: Legacy LoopBuilder removed, all loops must use JoinIR
if ctx.debug {
trace::trace().debug(
"route",
&format!(
"No pattern matched for function '{}' (pattern_kind={:?})",
ctx.func_name, ctx.pattern_kind
),
);
}
Ok(None)
}