feat(joinir): Phase 170-C-1 Carrier name heuristic for shape detection

Add detect_with_carrier_name() to improve CaseALoweringShape detection
accuracy by using carrier variable names as a heuristic.

Since LoopUpdateAnalyzer operates at AST level (not accessible from MIR),
use carrier naming conventions instead:

- Typical index names (i, e, idx, pos, start, end) → StringExamination
- Other names (result, items, defs) → ArrayAccumulation

This reduces Generic fallback cases for single-carrier loops.

Changes:
- case_a_lowering_shape.rs: Add detect_with_carrier_name(), is_typical_index_name()
- loop_to_join.rs: Extract progress_carrier name, call new detection function

Phase 170-C-2 can add MIR-based update pattern analysis for higher accuracy.

🤖 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-07 14:07:43 +09:00
parent c636b0b032
commit 367aa83240
2 changed files with 111 additions and 9 deletions

View File

@ -82,7 +82,7 @@ pub enum CaseALoweringShape {
}
impl CaseALoweringShape {
/// Detect lowering shape from LoopFeatures
/// Detect lowering shape from LoopFeatures (legacy, use detect_with_updates when possible)
///
/// # Phase 170-A Design Principle
///
@ -96,9 +96,8 @@ impl CaseALoweringShape {
/// - Multiple carriers (2+) → likely IterationWithAccumulation
/// - has_break/has_continue → affects Case-A eligibility
///
/// # Phase 170-B Future Work
/// - Analyze loop body AST for more precise classification
/// - Distinguish StringExamination vs ArrayAccumulation
/// # Phase 170-C Future Work
/// - Use detect_with_updates() for better single-carrier classification
///
/// # Arguments
/// * `features` - LoopFeatures (structure-based, name-agnostic)
@ -128,7 +127,7 @@ impl CaseALoweringShape {
}
1 => {
// Single carrier: could be StringExamination or ArrayAccumulation
// Further distinction requires analyzing loop body (Phase 170-B)
// Further distinction requires analyzing loop body (Phase 170-C)
// For now, return Generic to allow both paths to be tried
CaseALoweringShape::Generic
}
@ -140,6 +139,83 @@ impl CaseALoweringShape {
}
}
/// Phase 170-C-1: Carrier pattern を使った精度向上版
///
/// carrier_count == 1 の場合に carrier 名パターンで StringExamination vs ArrayAccumulation を区別
///
/// # Design Notes
///
/// 本来は LoopUpdateAnalyzer を使って AST レベルの更新式を解析する予定だったが、
/// loop_to_join.rs は MIR レベルで動作するため AST にアクセスできない。
///
/// そのため、Phase 170-C-1 では以下の簡易的なヒューリスティックを使用:
/// - progress carrier の名前が 'i', 'e', 'idx' などの典型的なインデックス名 → StringExamination
/// - それ以外 → ArrayAccumulation
///
/// # Future Work (Phase 170-C-2+)
///
/// - MIR 命令を解析して更新パターンを抽出BinOp の定数加算パターンなど)
/// - より正確な分類を実現
///
/// # Arguments
/// * `features` - LoopFeatures (structure-based)
/// * `carrier_count` - Number of carrier variables
/// * `has_progress_carrier` - Whether progress carrier exists
/// * `progress_carrier_name` - Name of progress carrier (if available)
///
/// # Returns
/// More precise CaseALoweringShape classification
pub fn detect_with_carrier_name(
features: &crate::mir::loop_pattern_detection::LoopFeatures,
carrier_count: usize,
has_progress_carrier: bool,
progress_carrier_name: Option<&str>,
) -> Self {
// Case-A requirement: must have a progress carrier
if !has_progress_carrier {
return CaseALoweringShape::NotCaseA;
}
// Case-A requirement: no complex control flow (continue)
if features.has_continue {
return CaseALoweringShape::NotCaseA;
}
match carrier_count {
0 => CaseALoweringShape::NotCaseA,
1 => {
// Phase 170-C-1: carrier 名パターンで StringExamination vs ArrayAccumulation を区別
if let Some(name) = progress_carrier_name {
if Self::is_typical_index_name(name) {
// 'i', 'e', 'idx' などの典型的なインデックス名 → StringExamination (skip/trim 系)
CaseALoweringShape::StringExamination
} else {
// それ以外 → ArrayAccumulation
CaseALoweringShape::ArrayAccumulation
}
} else {
// carrier 名が不明な場合は Generic従来通り
CaseALoweringShape::Generic
}
}
2.. => CaseALoweringShape::IterationWithAccumulation,
}
}
/// Typical index variable name detection
///
/// StringExamination パターンskip/trimで使われる典型的なインデックス名
/// - 'i', 'e', 'idx', 'pos', 'start', 'end'
///
/// ArrayAccumulation パターンは通常:
/// - より意味のある名前 ('result', 'items', 'defs' など)
fn is_typical_index_name(name: &str) -> bool {
matches!(
name,
"i" | "e" | "idx" | "index" | "pos" | "position" | "start" | "end"
)
}
/// Legacy wrapper: Detect from LoopScopeShape (deprecated, use detect_from_features)
///
/// Phase 170-A: Kept for backward compatibility during transition.

View File

@ -382,14 +382,40 @@ impl LoopToJoinLowerer {
//
// This enables gradual migration: existing name-based code continues to work,
// while new patterns can be added purely by structure detection.
#[allow(deprecated)]
let shape = CaseALoweringShape::detect(&scope);
// Phase 170-C-1: Use carrier name-based detection for improved accuracy
// Extract progress carrier name from scope
let progress_carrier_name = scope.progress_carrier.as_deref();
// Construct minimal LoopFeatures from scope (for now)
// TODO: Pass real LoopFeatures from LoopPatternDetection when available
let stub_features = crate::mir::loop_pattern_detection::LoopFeatures {
has_break: false, // Unknown at this level
has_continue: false, // Unknown at this level
has_if: false,
has_if_else_phi: false,
carrier_count: scope.carriers.len(),
break_count: 0,
continue_count: 0,
};
let has_progress_carrier = scope.progress_carrier.is_some();
let carrier_count = scope.carriers.len();
// Phase 170-C-1: Use new carrier name-based detection
let shape = CaseALoweringShape::detect_with_carrier_name(
&stub_features,
carrier_count,
has_progress_carrier,
progress_carrier_name,
);
if self.debug {
eprintln!(
"[LoopToJoinLowerer] Phase 170-A: shape={:?}, name={:?}",
"[LoopToJoinLowerer] Phase 170-C-1: shape={:?}, name={:?}, carrier={:?}",
shape.name(),
name
name,
progress_carrier_name
);
}