feat(joinir): Structural pattern detection + Pattern 4 scaffold

- Add LoopFeatures struct for structure-based detection (no name deps)
- Add LoopPatternKind enum and classify() function
- Pattern 3: has_if_else_phi && !has_break && !has_continue
- Pattern 4: has_continue == true (detection only, lowering TODO)
- Unify router to use extract_features()/classify() instead of legacy
- Remove AST dependency, use LoopForm/LoopScope only
- Add Pattern 4 test file (loop_continue_pattern4.hako)
- Pattern 3 test passes (sum=9)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-06 00:10:27 +09:00
parent 255517ed58
commit a21501286e
7 changed files with 657 additions and 32 deletions

View File

@ -2,17 +2,183 @@
//!
//! Phase 188 Task 188-4: Pattern detection helpers for JoinIR loop lowering.
//!
//! This module provides detection functions for 3 loop patterns:
//! This module provides detection functions for 4 loop patterns:
//! - Pattern 1: Simple While Loop (foundational)
//! - Pattern 2: Loop with Conditional Break (early exit)
//! - Pattern 3: Loop with If-Else PHI (variable mutation)
//! - Pattern 4: Loop with Continue (skip iteration)
//!
//! These functions are "thin boxes" that take LoopForm and return bool.
//! No side effects, pure detection logic.
//! Phase 194+: Structure-based detection using LoopFeatures.
//! Patterns are classified based on CFG structure, not function names.
//!
//! # Architecture
//!
//! ```
//! LoopForm → extract_features() → LoopFeatures → classify() → LoopPatternKind
//! ```
//!
//! Reference: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/design.md
use crate::mir::loop_form::LoopForm;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
// ============================================================================
// Pattern Classification System (Phase 194+)
// ============================================================================
/// Loop pattern classification based on structure.
///
/// This enum represents the 4 main loop patterns we support.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopPatternKind {
/// Pattern 1: Simple While Loop
/// - No break, no continue
/// - Single backedge
Pattern1SimpleWhile,
/// Pattern 2: Loop with Conditional Break
/// - Has break statement(s)
/// - No continue statements
Pattern2Break,
/// Pattern 3: Loop with If-Else PHI
/// - Has if-else statement with PHI
/// - No break, no continue
/// - Multiple carrier variables
Pattern3IfPhi,
/// Pattern 4: Loop with Continue
/// - Has continue statement(s)
/// - No break statements (for simplicity)
Pattern4Continue,
/// Pattern not recognized
Unknown,
}
/// Feature vector extracted from loop structure.
///
/// This structure captures all relevant properties needed for pattern classification.
/// It is name-agnostic and purely structure-based.
#[derive(Debug, Clone)]
pub struct LoopFeatures {
/// Has break statement(s)?
pub has_break: bool,
/// Has continue statement(s)?
pub has_continue: bool,
/// Has if statement(s) in body?
pub has_if: bool,
/// Has if-else statement with PHI nodes?
/// (detected via multiple carriers or specific CFG patterns)
pub has_if_else_phi: bool,
/// Number of carrier variables (loop variables that are updated)
pub carrier_count: usize,
/// Number of break targets
pub break_count: usize,
/// Number of continue targets
pub continue_count: usize,
}
/// Extract features from LoopForm for pattern classification.
///
/// This function is the entry point for structure-based pattern detection.
/// It analyzes the CFG structure without relying on variable names.
///
/// # Arguments
/// * `loop_form` - The loop structure to analyze
/// * `scope` - Optional LoopScopeShape for carrier analysis
///
/// # Returns
/// * `LoopFeatures` - Feature vector for pattern classification
pub fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeShape>) -> LoopFeatures {
// Phase 194: Basic feature extraction from LoopForm
let has_break = !loop_form.break_targets.is_empty();
let has_continue = !loop_form.continue_targets.is_empty();
let break_count = loop_form.break_targets.len();
let continue_count = loop_form.continue_targets.len();
// Phase 194+: Extract carrier_count from LoopScopeShape if available
let carrier_count = scope.map(|s| s.carriers.len()).unwrap_or(0);
// Pattern 3 heuristic: has_if_else_phi if carrier_count > 1
// This is a conservative heuristic - multiple carriers typically
// indicate if-else statements with PHI nodes.
let has_if_else_phi = carrier_count > 1;
// TODO: Implement has_if detection via CFG analysis
// For now, we infer it from carrier_count > 1 (Pattern 3 heuristic)
let has_if = has_if_else_phi;
LoopFeatures {
has_break,
has_continue,
has_if,
has_if_else_phi,
carrier_count,
break_count,
continue_count,
}
}
/// Classify loop pattern based on feature vector.
///
/// This function implements the pattern classification logic using
/// structure-based rules. It does NOT depend on function names or
/// variable names like "sum".
///
/// # Pattern Classification Rules
///
/// 1. **Pattern 4 (Continue)**: `has_continue == true`
/// - Priority: Check first (most specific)
///
/// 2. **Pattern 3 (If-Else PHI)**: `has_if_else_phi && !has_break && !has_continue`
/// - Multiple carriers indicate if-else with PHI
///
/// 3. **Pattern 2 (Break)**: `has_break && !has_continue`
/// - Has break but no continue
///
/// 4. **Pattern 1 (Simple While)**: `!has_break && !has_continue && !has_if_else_phi`
/// - No control flow alterations
///
/// # Arguments
/// * `features` - Feature vector from extract_features()
///
/// # Returns
/// * `LoopPatternKind` - Classified pattern
pub fn classify(features: &LoopFeatures) -> LoopPatternKind {
// Pattern 4: Continue (highest priority)
if features.has_continue {
return LoopPatternKind::Pattern4Continue;
}
// Pattern 3: If-Else PHI (check before Pattern 1)
if features.has_if_else_phi && !features.has_break && !features.has_continue {
return LoopPatternKind::Pattern3IfPhi;
}
// Pattern 2: Break
if features.has_break && !features.has_continue {
return LoopPatternKind::Pattern2Break;
}
// Pattern 1: Simple While
if !features.has_break && !features.has_continue && !features.has_if_else_phi {
return LoopPatternKind::Pattern1SimpleWhile;
}
// Unknown pattern
LoopPatternKind::Unknown
}
// ============================================================================
// Legacy Detection Functions (Phase 188)
// ============================================================================
// ============================================================================
// Pattern 1: Simple While Loop
@ -200,6 +366,71 @@ pub fn is_loop_with_conditional_phi_pattern(loop_form: &LoopForm) -> bool {
true
}
// ============================================================================
// Pattern 4: Loop with Continue
// ============================================================================
/// Detect Pattern 4: Loop with Continue
///
/// Returns true ONLY if:
/// - Loop has continue statement(s)
/// - Continue is typically in an if statement
/// - NO break statements (for simplicity)
/// - Loop has multiple carrier variables
///
/// # Arguments
/// * `loop_form` - The loop structure to analyze
///
/// # Returns
/// * `true` if the loop matches Pattern 4 (Continue), `false` otherwise
///
/// # Reference
/// See design.md § Pattern 4 → LoopScopeShape Recognition
///
/// # Example
/// ```rust,ignore
/// let loop_form = /* ... */;
/// if is_loop_with_continue_pattern(&loop_form) {
/// // Lower to loop with continue pattern
/// }
/// ```
pub fn is_loop_with_continue_pattern(loop_form: &LoopForm) -> bool {
// Pattern 4 Recognition Criteria:
// 1. continue_targets: NON-EMPTY (at least 1 continue)
// 2. break_targets: EMPTY (for simplicity in Pattern 4)
// 3. At least ONE continue target
//
// Phase 188-Impl-4: Minimal implementation
// Advanced checks (nested loops, if-statement structure) are deferred to
// lowering phase where we can fail gracefully if needed.
// Check 1: continue_targets is NON-EMPTY (has at least 1 continue)
if loop_form.continue_targets.is_empty() {
return false;
}
// Check 2: At least ONE continue target (pattern assumes single continue for now)
if loop_form.continue_targets.len() < 1 {
return false;
}
// Check 3: No break statements (for simplicity in Pattern 4)
if !loop_form.break_targets.is_empty() {
return false;
}
// Pattern 4 matched
// The LoopForm structure guarantees:
// - Valid loop structure
// - At least one continue target
// - No breaks
//
// Advanced checks (continue is in if-statement, etc.) are deferred to
// lowering phase for graceful failure.
true
}
// ============================================================================
// Helper Functions (Future Use)
// ============================================================================
@ -344,4 +575,29 @@ mod tests {
// Step 2: Call is_loop_with_conditional_phi_pattern()
// Step 3: Assert returns false
}
// ========================================================================
// Pattern 4: Loop with Continue Tests
// ========================================================================
#[test]
#[ignore] // TODO: Implement test after detection logic is complete
fn test_pattern4_continue_detection() {
// TODO: Add unit test for continue pattern detection
// Step 1: Create mock LoopForm with:
// - Non-empty continue_targets (at least 1)
// - Empty break_targets
// - If statement with continue
// Step 2: Call is_loop_with_continue_pattern()
// Step 3: Assert returns true
}
#[test]
#[ignore] // TODO: Implement test after detection logic is complete
fn test_pattern4_rejects_no_continue() {
// TODO: Add test that rejects loop without continue
// Step 1: Create mock LoopForm with empty continue_targets
// Step 2: Call is_loop_with_continue_pattern()
// Step 3: Assert returns false
}
}