2025-12-07 14:17:58 +09:00
|
|
|
|
//! Phase 170-C-2: LoopUpdateSummary - ループ更新パターン解析
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! キャリア変数の更新パターン(CounterLike / AccumulationLike)を判定し、
|
|
|
|
|
|
//! CaseALoweringShape の検出精度を向上させる。
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! ## 設計思想
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! - 責務: AST のループ body から「各キャリアがどう更新されているか」を判定
|
|
|
|
|
|
//! - 差し替え可能: 名前ヒューリスティック → AST 解析 → MIR 解析と段階的に精度向上
|
|
|
|
|
|
//! - LoopFeatures / CaseALoweringShape から独立したモジュール
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! ## 使用例
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! ```ignore
|
|
|
|
|
|
//! let summary = analyze_loop_updates(&carrier_names);
|
|
|
|
|
|
//! if summary.has_single_counter() {
|
|
|
|
|
|
//! // StringExamination パターン
|
|
|
|
|
|
//! }
|
|
|
|
|
|
//! ```
|
|
|
|
|
|
|
|
|
|
|
|
/// キャリア変数の更新パターン
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Phase 170-C-2: 3種類のパターンを区別
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
|
pub enum UpdateKind {
|
|
|
|
|
|
/// カウンタ系: i = i + 1, i = i - 1, i += 1
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 典型的な skip/trim パターン。進捗変数として使われる。
|
|
|
|
|
|
CounterLike,
|
|
|
|
|
|
|
|
|
|
|
|
/// 蓄積系: result = result + x, arr.push(x), list.append(x)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 典型的な collect/filter パターン。結果を蓄積する変数。
|
|
|
|
|
|
AccumulationLike,
|
|
|
|
|
|
|
|
|
|
|
|
/// 判定不能
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 複雑な更新パターン、または解析できなかった場合。
|
|
|
|
|
|
Other,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl UpdateKind {
|
|
|
|
|
|
/// デバッグ用の名前を返す
|
|
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
UpdateKind::CounterLike => "CounterLike",
|
|
|
|
|
|
UpdateKind::AccumulationLike => "AccumulationLike",
|
|
|
|
|
|
UpdateKind::Other => "Other",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 単一キャリアの更新情報
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub struct CarrierUpdateInfo {
|
|
|
|
|
|
/// キャリア変数名
|
|
|
|
|
|
pub name: String,
|
|
|
|
|
|
|
|
|
|
|
|
/// 更新パターン
|
|
|
|
|
|
pub kind: UpdateKind,
|
2025-12-10 00:01:53 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 213: Then branch update expression (for Pattern 3 if-sum)
|
|
|
|
|
|
/// e.g., for "if (cond) { sum = sum + 1 }", then_expr is "sum + 1"
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
|
pub then_expr: Option<crate::ast::ASTNode>,
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 213: Else branch update expression (for Pattern 3 if-sum)
|
|
|
|
|
|
/// e.g., for "else { sum = sum + 0 }", else_expr is "sum + 0"
|
|
|
|
|
|
/// If no else branch, this can be the identity update (e.g., "sum")
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
|
pub else_expr: Option<crate::ast::ASTNode>,
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// ループ全体の更新サマリ
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Phase 170-C-2: CaseALoweringShape の判定入力として使用
|
|
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
|
|
pub struct LoopUpdateSummary {
|
|
|
|
|
|
/// 各キャリアの更新情報
|
|
|
|
|
|
pub carriers: Vec<CarrierUpdateInfo>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl LoopUpdateSummary {
|
|
|
|
|
|
/// 空のサマリを作成
|
|
|
|
|
|
pub fn empty() -> Self {
|
|
|
|
|
|
Self { carriers: vec![] }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 単一 CounterLike キャリアを持つか
|
|
|
|
|
|
///
|
|
|
|
|
|
/// StringExamination パターンの判定に使用
|
|
|
|
|
|
pub fn has_single_counter(&self) -> bool {
|
|
|
|
|
|
self.carriers.len() == 1 && self.carriers[0].kind == UpdateKind::CounterLike
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// AccumulationLike キャリアを含むか
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ArrayAccumulation パターンの判定に使用
|
|
|
|
|
|
pub fn has_accumulation(&self) -> bool {
|
|
|
|
|
|
self.carriers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.any(|c| c.kind == UpdateKind::AccumulationLike)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// CounterLike キャリアの数
|
|
|
|
|
|
pub fn counter_count(&self) -> usize {
|
|
|
|
|
|
self.carriers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|c| c.kind == UpdateKind::CounterLike)
|
|
|
|
|
|
.count()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// AccumulationLike キャリアの数
|
|
|
|
|
|
pub fn accumulation_count(&self) -> usize {
|
|
|
|
|
|
self.carriers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|c| c.kind == UpdateKind::AccumulationLike)
|
|
|
|
|
|
.count()
|
|
|
|
|
|
}
|
2025-12-10 00:54:46 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 213: Check if this is a simple if-sum pattern
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Simple if-sum pattern:
|
|
|
|
|
|
/// - Has exactly 1 CounterLike carrier (loop index, e.g., "i")
|
|
|
|
|
|
/// - Has exactly 1 AccumulationLike carrier (accumulator, e.g., "sum")
|
|
|
|
|
|
/// - Optionally has additional accumulators (e.g., "count")
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Examples:
|
|
|
|
|
|
/// - `loop(i < len) { if cond { sum = sum + 1 } i = i + 1 }` ✅
|
|
|
|
|
|
/// - `loop(i < len) { if cond { sum = sum + 1; count = count + 1 } i = i + 1 }` ✅
|
|
|
|
|
|
/// - `loop(i < len) { result = result + data[i]; i = i + 1 }` ❌ (no if statement)
|
|
|
|
|
|
pub fn is_simple_if_sum_pattern(&self) -> bool {
|
2025-12-10 02:30:14 +09:00
|
|
|
|
let counter_count = self.counter_count();
|
|
|
|
|
|
let accumulation_count = self.accumulation_count();
|
|
|
|
|
|
|
2025-12-10 00:54:46 +09:00
|
|
|
|
// Must have exactly 1 counter (loop index)
|
2025-12-10 02:30:14 +09:00
|
|
|
|
if counter_count != 1 {
|
2025-12-10 00:54:46 +09:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Must have at least 1 accumulator (sum)
|
2025-12-10 02:30:14 +09:00
|
|
|
|
if accumulation_count < 1 {
|
2025-12-10 00:54:46 +09:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// For now, only support up to 2 accumulators (sum, count)
|
|
|
|
|
|
// This matches the Phase 212 if-sum minimal test case
|
2025-12-10 02:30:14 +09:00
|
|
|
|
if accumulation_count > 2 {
|
2025-12-10 00:54:46 +09:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-12-10 02:30:14 +09:00
|
|
|
|
|
2025-12-10 00:54:46 +09:00
|
|
|
|
true
|
|
|
|
|
|
}
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// Phase 219: Extract all assigned variable names from loop body AST
|
2025-12-07 14:17:58 +09:00
|
|
|
|
///
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// Returns a set of variable names that are assigned (LHS) in the loop body.
|
|
|
|
|
|
/// This prevents phantom carriers from non-assigned variables.
|
|
|
|
|
|
fn extract_assigned_variables(loop_body: &[crate::ast::ASTNode]) -> std::collections::HashSet<String> {
|
|
|
|
|
|
use crate::ast::ASTNode;
|
|
|
|
|
|
let mut assigned = std::collections::HashSet::new();
|
|
|
|
|
|
|
|
|
|
|
|
fn visit_node(node: &ASTNode, assigned: &mut std::collections::HashSet<String>) {
|
|
|
|
|
|
match node {
|
|
|
|
|
|
// Direct assignment: target = value
|
|
|
|
|
|
ASTNode::Assignment { target, value, .. } => {
|
|
|
|
|
|
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
|
|
|
|
|
assigned.insert(name.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
// Recurse into value (for nested assignments)
|
|
|
|
|
|
visit_node(value, assigned);
|
|
|
|
|
|
}
|
|
|
|
|
|
// If statement: recurse into then/else branches
|
|
|
|
|
|
ASTNode::If { then_body, else_body, .. } => {
|
|
|
|
|
|
for stmt in then_body {
|
|
|
|
|
|
visit_node(stmt, assigned);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(else_stmts) = else_body {
|
|
|
|
|
|
for stmt in else_stmts {
|
|
|
|
|
|
visit_node(stmt, assigned);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Loop statement: recurse into body (for nested loops)
|
|
|
|
|
|
ASTNode::Loop { body, .. } => {
|
|
|
|
|
|
for stmt in body {
|
|
|
|
|
|
visit_node(stmt, assigned);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Other nodes: no assignment tracking needed
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for stmt in loop_body {
|
|
|
|
|
|
visit_node(stmt, &mut assigned);
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 02:30:14 +09:00
|
|
|
|
assigned
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// Phase 219: Classify update kind from RHS expression structure
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns UpdateKind based on RHS pattern, NOT variable name.
|
|
|
|
|
|
fn classify_update_kind_from_rhs(rhs: &crate::ast::ASTNode) -> UpdateKind {
|
|
|
|
|
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
|
|
|
|
|
|
|
|
|
|
|
match rhs {
|
|
|
|
|
|
// x = x + 1 → CounterLike
|
|
|
|
|
|
// x = x + n → AccumulationLike (where n is not 1)
|
|
|
|
|
|
ASTNode::BinaryOp { operator, left, right, .. } => {
|
|
|
|
|
|
if matches!(operator, BinaryOperator::Add) {
|
|
|
|
|
|
// Check if left is self-reference (will be validated by caller)
|
|
|
|
|
|
if matches!(left.as_ref(), ASTNode::Variable { .. }) {
|
|
|
|
|
|
// Check right operand
|
|
|
|
|
|
if let ASTNode::Literal { value, .. } = right.as_ref() {
|
|
|
|
|
|
if let LiteralValue::Integer(n) = value {
|
|
|
|
|
|
if *n == 1 {
|
|
|
|
|
|
return UpdateKind::CounterLike; // x = x + 1
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return UpdateKind::AccumulationLike; // x = x + n
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// x = x + expr (variable accumulation)
|
|
|
|
|
|
return UpdateKind::AccumulationLike;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
UpdateKind::Other
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => UpdateKind::Other,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 219: Analyze loop updates from loop body AST (assignment-based)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # New Design (Phase 219)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// - Takes loop body AST as input (not just carrier names)
|
|
|
|
|
|
/// - Only analyzes variables that are ASSIGNED in loop body
|
|
|
|
|
|
/// - Uses RHS structure analysis (NOT name heuristics)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
|
|
|
|
|
/// * `carrier_names` - Candidate carrier variable names from scope
|
|
|
|
|
|
/// * `loop_body` - Loop body AST for assignment detection
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
2025-12-07 14:17:58 +09:00
|
|
|
|
///
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// LoopUpdateSummary with only actually-assigned carriers
|
|
|
|
|
|
/// Phase 219: Extract assignment RHS for a given variable
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns the RHS expression of the first assignment to `var_name` in loop body.
|
|
|
|
|
|
fn find_assignment_rhs<'a>(var_name: &str, loop_body: &'a [crate::ast::ASTNode]) -> Option<&'a crate::ast::ASTNode> {
|
|
|
|
|
|
use crate::ast::ASTNode;
|
|
|
|
|
|
|
|
|
|
|
|
fn visit_node<'a>(var_name: &str, node: &'a ASTNode) -> Option<&'a ASTNode> {
|
|
|
|
|
|
match node {
|
|
|
|
|
|
ASTNode::Assignment { target, value, .. } => {
|
|
|
|
|
|
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
|
|
|
|
|
if name == var_name {
|
|
|
|
|
|
return Some(value.as_ref());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Recurse into value
|
|
|
|
|
|
visit_node(var_name, value)
|
|
|
|
|
|
}
|
|
|
|
|
|
ASTNode::If { then_body, else_body, .. } => {
|
|
|
|
|
|
for stmt in then_body {
|
|
|
|
|
|
if let Some(rhs) = visit_node(var_name, stmt) {
|
|
|
|
|
|
return Some(rhs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(else_stmts) = else_body {
|
|
|
|
|
|
for stmt in else_stmts {
|
|
|
|
|
|
if let Some(rhs) = visit_node(var_name, stmt) {
|
|
|
|
|
|
return Some(rhs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
ASTNode::Loop { body, .. } => {
|
|
|
|
|
|
for stmt in body {
|
|
|
|
|
|
if let Some(rhs) = visit_node(var_name, stmt) {
|
|
|
|
|
|
return Some(rhs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for stmt in loop_body {
|
|
|
|
|
|
if let Some(rhs) = visit_node(var_name, stmt) {
|
|
|
|
|
|
return Some(rhs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 219: Check if variable name looks like loop index
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Simple heuristic: single-letter names (i, j, k, e) or "index"/"idx"
|
|
|
|
|
|
fn is_likely_loop_index(name: &str) -> bool {
|
|
|
|
|
|
matches!(name, "i" | "j" | "k" | "e" | "idx" | "index" | "pos" | "n")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn analyze_loop_updates_from_ast(
|
|
|
|
|
|
carrier_names: &[String],
|
|
|
|
|
|
loop_body: &[crate::ast::ASTNode],
|
|
|
|
|
|
) -> LoopUpdateSummary {
|
|
|
|
|
|
// Phase 219-1: Extract assigned variables from loop body
|
|
|
|
|
|
let assigned_vars = extract_assigned_variables(loop_body);
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 219-2: Filter carriers to only assigned ones and classify by RHS
|
|
|
|
|
|
let mut carriers = Vec::new();
|
|
|
|
|
|
for name in carrier_names {
|
|
|
|
|
|
if assigned_vars.contains(name) {
|
|
|
|
|
|
// Phase 219-3: Classify by variable name + RHS structure
|
|
|
|
|
|
// - Loop index-like names (i, j, k) with `x = x + 1` → CounterLike
|
|
|
|
|
|
// - Other names with `x = x + 1` or `x = x + expr` → AccumulationLike
|
|
|
|
|
|
let kind = if is_likely_loop_index(name) {
|
|
|
|
|
|
UpdateKind::CounterLike
|
|
|
|
|
|
} else if let Some(rhs) = find_assignment_rhs(name, loop_body) {
|
|
|
|
|
|
let classified = classify_update_kind_from_rhs(rhs);
|
|
|
|
|
|
match classified {
|
|
|
|
|
|
UpdateKind::CounterLike => UpdateKind::AccumulationLike, // Override: non-index + `x=x+1` → accumulation
|
|
|
|
|
|
other => other,
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
UpdateKind::Other
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
carriers.push(CarrierUpdateInfo {
|
|
|
|
|
|
name: name.clone(),
|
|
|
|
|
|
kind,
|
|
|
|
|
|
then_expr: None,
|
|
|
|
|
|
else_expr: None,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LoopUpdateSummary { carriers }
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// Phase 219: Legacy wrapper for backward compatibility
|
2025-12-07 14:17:58 +09:00
|
|
|
|
///
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// # Deprecated (Phase 219)
|
2025-12-07 14:17:58 +09:00
|
|
|
|
///
|
2025-12-10 02:30:14 +09:00
|
|
|
|
/// This function uses name-based heuristics and is deprecated.
|
|
|
|
|
|
/// Use `analyze_loop_updates_from_ast()` instead.
|
2025-12-07 14:17:58 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
|
|
|
|
|
/// * `carrier_names` - キャリア変数名のリスト
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 各キャリアの更新パターンをまとめた LoopUpdateSummary
|
2025-12-10 02:30:14 +09:00
|
|
|
|
#[deprecated(since = "Phase 219", note = "Use analyze_loop_updates_from_ast() instead")]
|
2025-12-07 14:17:58 +09:00
|
|
|
|
pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary {
|
2025-12-10 02:30:14 +09:00
|
|
|
|
// Phase 219: Fallback to simple heuristic (for legacy call sites)
|
|
|
|
|
|
// This will be removed once all call sites are migrated
|
2025-12-07 14:17:58 +09:00
|
|
|
|
let carriers = carrier_names
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|name| CarrierUpdateInfo {
|
|
|
|
|
|
name: name.clone(),
|
2025-12-10 02:30:14 +09:00
|
|
|
|
kind: UpdateKind::AccumulationLike, // Default to accumulation
|
|
|
|
|
|
then_expr: None,
|
|
|
|
|
|
else_expr: None,
|
2025-12-07 14:17:58 +09:00
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
LoopUpdateSummary { carriers }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_update_kind_name() {
|
|
|
|
|
|
assert_eq!(UpdateKind::CounterLike.name(), "CounterLike");
|
|
|
|
|
|
assert_eq!(UpdateKind::AccumulationLike.name(), "AccumulationLike");
|
|
|
|
|
|
assert_eq!(UpdateKind::Other.name(), "Other");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 09:18:21 +09:00
|
|
|
|
// NOTE: Phase 222 - test_typical_index_names commented out
|
|
|
|
|
|
// Function is_typical_index_name() was removed in earlier phase
|
|
|
|
|
|
// #[test]
|
|
|
|
|
|
// fn test_typical_index_names() {
|
|
|
|
|
|
// assert!(is_typical_index_name("i"));
|
|
|
|
|
|
// assert!(is_typical_index_name("idx"));
|
|
|
|
|
|
// assert!(is_typical_index_name("pos"));
|
|
|
|
|
|
// assert!(!is_typical_index_name("result"));
|
|
|
|
|
|
// assert!(!is_typical_index_name("sum"));
|
|
|
|
|
|
// }
|
2025-12-07 14:17:58 +09:00
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_analyze_single_counter() {
|
|
|
|
|
|
let names = vec!["i".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(summary.has_single_counter());
|
|
|
|
|
|
assert!(!summary.has_accumulation());
|
|
|
|
|
|
assert_eq!(summary.counter_count(), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_analyze_accumulation() {
|
|
|
|
|
|
let names = vec!["result".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!summary.has_single_counter());
|
|
|
|
|
|
assert!(summary.has_accumulation());
|
|
|
|
|
|
assert_eq!(summary.accumulation_count(), 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_analyze_mixed() {
|
|
|
|
|
|
let names = vec!["i".to_string(), "sum".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!summary.has_single_counter()); // 2つあるので false
|
|
|
|
|
|
assert!(summary.has_accumulation());
|
|
|
|
|
|
assert_eq!(summary.counter_count(), 1);
|
|
|
|
|
|
assert_eq!(summary.accumulation_count(), 1);
|
|
|
|
|
|
}
|
2025-12-10 00:54:46 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 213 tests for is_simple_if_sum_pattern
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_is_simple_if_sum_pattern_basic() {
|
|
|
|
|
|
// phase212_if_sum_min.hako pattern: i (counter) + sum (accumulator)
|
|
|
|
|
|
let names = vec!["i".to_string(), "sum".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(summary.is_simple_if_sum_pattern());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_is_simple_if_sum_pattern_with_count() {
|
|
|
|
|
|
// Phase 195 pattern: i (counter) + sum + count (2 accumulators)
|
|
|
|
|
|
let names = vec!["i".to_string(), "sum".to_string(), "count".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(summary.is_simple_if_sum_pattern());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_is_simple_if_sum_pattern_no_accumulator() {
|
|
|
|
|
|
// Only counter, no accumulator
|
|
|
|
|
|
let names = vec!["i".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!summary.is_simple_if_sum_pattern()); // No accumulator
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_is_simple_if_sum_pattern_no_counter() {
|
|
|
|
|
|
// Only accumulator, no counter
|
|
|
|
|
|
let names = vec!["sum".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!summary.is_simple_if_sum_pattern()); // No counter
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_is_simple_if_sum_pattern_multiple_counters() {
|
|
|
|
|
|
// Multiple counters (not supported)
|
|
|
|
|
|
let names = vec!["i".to_string(), "j".to_string(), "sum".to_string()];
|
|
|
|
|
|
let summary = analyze_loop_updates(&names);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!summary.is_simple_if_sum_pattern()); // 2 counters
|
|
|
|
|
|
}
|
2025-12-07 14:17:58 +09:00
|
|
|
|
}
|