From f0536fa3302c7a179ab3272bd04c87612b53c7eb Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 10 Dec 2025 09:18:21 +0900 Subject: [PATCH] feat(joinir): Phase 222-2 ConditionPatternBox normalization implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 222: If Condition Normalization - Part 2 Goal: Support '0 < i', 'i > j' patterns in addition to 'i > 0' Changes: 1. condition_pattern.rs (+160 lines): - Added ConditionValue enum (Variable | Literal) - Added NormalizedCondition struct (left_var, op, right) - Added flip_compare_op() for operator reversal - Added binary_op_to_compare_op() converter - Added normalize_comparison() main normalization function - Extended analyze_condition_pattern() to accept 3 cases: * Phase 219: var CmpOp literal (e.g., i > 0) * Phase 222: literal CmpOp var (e.g., 0 < i) → normalized * Phase 222: var CmpOp var (e.g., i > j) - Added 9 unit tests (all passing) 2. loop_update_summary.rs (cleanup): - Commented out obsolete test_typical_index_names - Function is_typical_index_name() was removed in earlier phase Test results: - 7 normalization tests: PASS ✅ - 2 pattern analysis tests: PASS ✅ Next: Phase 222-3 - integrate normalization into is_if_sum_pattern() Status: Ready for integration --- .../main/phase222-if-cond-normalization.md | 237 +++++++++++++++ src/mir/join_ir/lowering/condition_pattern.rs | 274 ++++++++++++++++-- .../join_ir/lowering/loop_update_summary.rs | 18 +- 3 files changed, 504 insertions(+), 25 deletions(-) create mode 100644 docs/development/current/main/phase222-if-cond-normalization.md diff --git a/docs/development/current/main/phase222-if-cond-normalization.md b/docs/development/current/main/phase222-if-cond-normalization.md new file mode 100644 index 00000000..8fd4af97 --- /dev/null +++ b/docs/development/current/main/phase222-if-cond-normalization.md @@ -0,0 +1,237 @@ +# Phase 222: If Condition Normalization Design + +## 概要 + +Phase 221で発見した制約「if condition pattern: if-sum mode は `var CmpOp literal` のみ」を解消し、 +左辺変数・右辺変数の両方をサポートする条件正規化を実装する。 + +## 目標 + +以下のパターンを全て「simple condition」として扱えるようにする: + +1. **基本形(既にサポート済み)**: + - `i > 0`, `i < len`, `i == 5` (左辺=変数、右辺=リテラル) + +2. **左右反転(新規対応)**: + - `0 < i`, `len > i`, `5 == i` (左辺=リテラル、右辺=変数) + - → `i > 0`, `i < len`, `i == 5` に正規化 + +3. **変数同士(新規対応)**: + - `i > j`, `i < end`, `start == pos` (両辺=変数) + - → simple condition として扱う(ConditionEnv 経由で解決) + +4. **複雑条件(引き続き NG)**: + - `i % 2 == 1`, `i + 1 > len`, `f(x) == 0` 等 + - → legacy P3 経路へフォールバック + +## 設計方針 + +### 1. ConditionPatternBox の責務拡張 + +**現在の API**: +```rust +pub fn is_simple_comparison(cond: &ASTNode) -> bool +``` + +**拡張後の API**: +```rust +pub enum ConditionPattern { + SimpleComparison, // var CmpOp literal/var + Complex, // BinaryOp, MethodCall, etc. +} + +pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern + +pub fn is_simple_comparison(cond: &ASTNode) -> bool // 互換性維持 +``` + +**新規追加**: +```rust +/// Normalize condition to canonical form (var on left) +pub fn normalize_comparison(cond: &ASTNode) -> Option + +pub struct NormalizedCondition { + pub left_var: String, // 左辺変数名 + pub op: CompareOp, // 比較演算子 + pub right: ConditionValue, // 右辺(変数 or リテラル) +} + +pub enum ConditionValue { + Variable(String), + Literal(i64), +} +``` + +### 2. 正規化ルール + +#### Rule 1: 左右反転(literal on left → var on left) + +| Input | Normalized | 演算子変換 | +|-------|-----------|----------| +| `0 < i` | `i > 0` | `<` → `>` | +| `len > i` | `i < len` | `>` → `<` | +| `5 == i` | `i == 5` | `==` (不変) | +| `10 != i` | `i != 10` | `!=` (不変) | + +**実装箇所**: `ConditionPatternBox::normalize_comparison()` + +#### Rule 2: 変数同士は正規化不要 + +| Input | Normalized | 理由 | +|-------|-----------|-----| +| `i > j` | `i > j` | 既に canonical form | +| `i < end` | `i < end` | 既に canonical form | +| `j > i` | `j > i` | 左辺変数名の辞書順は気にしない | + +### 3. ConditionEnv との統合 + +**Phase 220-D で実装済み**: loop 条件で変数を ConditionEnv から解決する機能 + +```rust +// Phase 220-D: extract_loop_condition() の拡張版 +fn extract_loop_condition( + cond: &ASTNode, + alloc_value: &mut F, + cond_env: &ConditionEnv, +) -> Result<(String, CompareOp, ValueId, Vec), String> +``` + +**Phase 222 での拡張**: if 条件でも同様の処理を行う + +```rust +// 新規追加: extract_if_condition() +fn extract_if_condition( + cond: &ASTNode, + alloc_value: &mut F, + cond_env: &ConditionEnv, +) -> Result<(String, CompareOp, ConditionValue, Vec), String> +``` + +### 4. is_if_sum_pattern() の拡張 + +**現在の実装**(Phase 219-fix): +```rust +pub fn is_if_sum_pattern(&self) -> bool { + // 1. if statement 存在チェック + let if_stmt = self.extract_if_statement(); + if if_stmt.is_none() { return false; } + + // 2. Phase 219-fix: if 条件が simple comparison かチェック + if let Some(ASTNode::If { condition, .. }) = if_stmt { + use crate::mir::join_ir::lowering::condition_pattern::is_simple_comparison; + if !is_simple_comparison(condition) { + return false; // 複雑条件 → legacy mode + } + } + + // 3. Carrier structure check (既存ロジック) + // ... +} +``` + +**Phase 222 での拡張**: +```rust +pub fn is_if_sum_pattern(&self) -> bool { + // 1. if statement 存在チェック(既存) + let if_stmt = self.extract_if_statement(); + if if_stmt.is_none() { return false; } + + // 2. Phase 222: if 条件を正規化して simple comparison かチェック + if let Some(ASTNode::If { condition, .. }) = if_stmt { + use crate::mir::join_ir::lowering::condition_pattern::{ + analyze_condition_pattern, normalize_comparison + }; + + // (a) パターン判定 + let pattern = analyze_condition_pattern(condition); + if pattern != ConditionPattern::SimpleComparison { + return false; // 複雑条件 → legacy mode + } + + // (b) 正規化可能性チェック(optional: 詳細バリデーション) + if normalize_comparison(condition).is_none() { + return false; // 正規化失敗 → legacy mode + } + } + + // 3. Carrier structure check(既存ロジック) + // ... +} +``` + +## 実装戦略 + +### Phase 222-2: BoolExprLowerer/ConditionPatternBox 拡張 + +1. **ConditionPatternBox 拡張** (`condition_pattern.rs`): + - `normalize_comparison()` 関数追加 + - `NormalizedCondition` / `ConditionValue` 型追加 + - 左右反転ロジック実装(演算子マッピング) + +2. **BoolExprLowerer 統合** (`bool_expr_lowerer.rs`): + - 正規化後の条件を lowering する経路を追加 + - ConditionEnv で変数同士の比較を解決 + +3. **condition_to_joinir 統合** (`condition_to_joinir.rs`): + - loop 条件・if 条件の統一的な処理経路を確立 + +### Phase 222-3: if-sum 判定に統合 + +1. **PatternPipelineContext 更新** (`pattern_pipeline.rs`): + - `is_if_sum_pattern()` で正規化 API を使用 + +2. **if-sum lowerer 更新** (`loop_with_if_phi_if_sum.rs`): + - `extract_if_condition()` を正規化ベースに変更 + - ConditionValue::Variable / ConditionValue::Literal の両方をサポート + +### Phase 222-4: E2E & 回帰テスト + +1. **既存テスト確認**: + - phase212_if_sum_min.hako: RC=2 維持 + - loop_if_phi.hako: sum=9 維持(複雑条件 → legacy mode) + +2. **新規テスト作成**: + - phase222_if_cond_left_literal.hako: `if 0 < i { sum = sum + 1 }` + - phase222_if_cond_var_var.hako: `if i > j { sum = sum + 1 }` + +### Phase 222-5: ドキュメント更新 + +1. **joinir-architecture-overview.md**: + - Section 2.2 条件式ライン: ConditionPatternBox の正規化機能を追加 + - Section 4.3 JsonParser 実戦カバレッジ: Phase 222 成果を追記 + +2. **CURRENT_TASK.md**: + - Phase 222 サマリー追加(3行) + +## 期待される成果 + +1. **言語の自然性向上**: + - `if 0 < i`, `if i > j` のような自然な条件式が if-sum パターンで使える + +2. **制約の明確化**: + - 「simple condition」の定義が明確になる(正規化可能な比較式) + - 「complex condition」との境界が自明(BinaryOp, MethodCall 等) + +3. **コードの局所性**: + - 変更は ConditionPatternBox と if-sum lowerer のみ + - JoinIR の芯(P1-P5, ExitLine, PHI contract)は一切変更なし + +## 非目標(Non-Goals) + +1. **論理演算子のサポート**: + - `i > 0 && i < len` → Phase 223+ で対応予定 + - Phase 222 では単一比較式のみ + +2. **算術式のサポート**: + - `i + 1 > len`, `i * 2 < max` → Phase 223+ で対応予定 + - Phase 222 では変数・リテラルの直接比較のみ + +3. **MethodCall のサポート**: + - `f(x) > 0`, `s.length() < 10` → 別フェーズで対応 + - Phase 222 では変数のみ + +## 参照 + +- Phase 219-fix: ConditionPatternBox 初版実装 +- Phase 220-D: loop 条件変数サポート(ConditionEnv 統合) +- Phase 221: 制約整理(if condition pattern 制約を発見) diff --git a/src/mir/join_ir/lowering/condition_pattern.rs b/src/mir/join_ir/lowering/condition_pattern.rs index 84adfa67..da78d923 100644 --- a/src/mir/join_ir/lowering/condition_pattern.rs +++ b/src/mir/join_ir/lowering/condition_pattern.rs @@ -1,6 +1,7 @@ -//! ConditionPatternBox: if条件パターン判定 +//! ConditionPatternBox: if条件パターン判定と正規化 //! //! Phase 219 regression fix: if条件が「単純比較」かどうかを判定 +//! Phase 222: 左辺変数・右辺変数の両方をサポートする正規化を追加 //! //! ## 問題 //! @@ -8,20 +9,21 @@ //! `loop_if_phi.hako` のような複雑条件 (`i % 2 == 1`) を //! if-sumパターンと誤判定してしまう問題が発生。 //! +//! Phase 221で発見した制約:if条件が `0 < i` や `i > j` のような形式を拒否。 +//! //! ## 解決策 //! //! ConditionPatternBox を導入し、if条件が「単純比較」かどうかを判定する。 //! AST-based lowerer は単純比較のみ処理可能とし、複雑条件はlegacy modeへフォールバック。 //! -//! ## 単純比較の定義 +//! Phase 222: 左右反転(literal on left → var on left)と変数同士の比較をサポート。 //! -//! 以下のパターンのみ if-sum lowerer で処理可能: -//! - `var > literal` (e.g., `i > 0`) -//! - `var < literal` (e.g., `i < 10`) -//! - `var >= literal` -//! - `var <= literal` -//! - `var == literal` -//! - `var != literal` +//! ## 単純比較の定義(Phase 222拡張版) +//! +//! 以下のパターンを if-sum lowerer で処理可能: +//! - **Phase 219**: `var > literal` (e.g., `i > 0`) +//! - **Phase 222**: `literal < var` → `var > literal` に正規化 +//! - **Phase 222**: `var > var` (e.g., `i > j`) - 変数同士の比較 //! //! ## 複雑条件(legacy mode へフォールバック) //! @@ -30,7 +32,8 @@ //! - `method_call() > 0` (MethodCall) //! - その他 -use crate::ast::{ASTNode, BinaryOperator}; +use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use crate::mir::CompareOp; /// if条件のパターン種別 #[derive(Debug, Clone, PartialEq, Eq)] @@ -93,18 +96,29 @@ pub fn analyze_condition_pattern(cond: &ASTNode) -> ConditionPattern { return ConditionPattern::Complex; } - // Check if LHS is a simple variable + // Check LHS/RHS patterns let left_is_var = matches!(left.as_ref(), ASTNode::Variable { .. }); - - // Check if RHS is a literal + let left_is_literal = matches!(left.as_ref(), ASTNode::Literal { .. }); + let right_is_var = matches!(right.as_ref(), ASTNode::Variable { .. }); let right_is_literal = matches!(right.as_ref(), ASTNode::Literal { .. }); + // Phase 219: var CmpOp literal (e.g., i > 0) if left_is_var && right_is_literal { - ConditionPattern::SimpleComparison - } else { - // Complex LHS/RHS (e.g., i % 2 == 1, method_call() > 0) - ConditionPattern::Complex + return ConditionPattern::SimpleComparison; } + + // Phase 222: literal CmpOp var (e.g., 0 < i) + if left_is_literal && right_is_var { + return ConditionPattern::SimpleComparison; + } + + // Phase 222: var CmpOp var (e.g., i > j) + if left_is_var && right_is_var { + return ConditionPattern::SimpleComparison; + } + + // Complex LHS/RHS (e.g., i % 2 == 1, method_call() > 0) + ConditionPattern::Complex } // Any other node type → Complex _ => ConditionPattern::Complex, @@ -134,6 +148,144 @@ pub fn is_simple_comparison(cond: &ASTNode) -> bool { analyze_condition_pattern(cond) == ConditionPattern::SimpleComparison } +// ============================================================================ +// Phase 222: Condition Normalization +// ============================================================================ + +/// 条件式の右辺値(変数 or リテラル) +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConditionValue { + /// 変数 + Variable(String), + /// 整数リテラル + Literal(i64), +} + +/// 正規化された条件式 +/// +/// 常に左辺が変数の形に正規化される: +/// - `i > 0` → `i > 0` (そのまま) +/// - `0 < i` → `i > 0` (左右反転) +/// - `i > j` → `i > j` (変数同士、そのまま) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NormalizedCondition { + /// 左辺変数名 + pub left_var: String, + /// 比較演算子 + pub op: CompareOp, + /// 右辺(変数 or リテラル) + pub right: ConditionValue, +} + +/// 比較演算子を左右反転 +/// +/// 左辺がリテラル、右辺が変数の場合に使用。 +/// +/// # Examples +/// +/// ``` +/// // 0 < i → i > 0 +/// assert_eq!(flip_compare_op(CompareOp::Lt), CompareOp::Gt); +/// +/// // len > i → i < len +/// assert_eq!(flip_compare_op(CompareOp::Gt), CompareOp::Lt); +/// +/// // 5 == i → i == 5 (不変) +/// assert_eq!(flip_compare_op(CompareOp::Eq), CompareOp::Eq); +/// ``` +fn flip_compare_op(op: CompareOp) -> CompareOp { + match op { + CompareOp::Lt => CompareOp::Gt, // < → > + CompareOp::Gt => CompareOp::Lt, // > → < + CompareOp::Le => CompareOp::Ge, // <= → >= + CompareOp::Ge => CompareOp::Le, // >= → <= + CompareOp::Eq => CompareOp::Eq, // == → == (不変) + CompareOp::Ne => CompareOp::Ne, // != → != (不変) + } +} + +/// BinaryOperator を CompareOp に変換 +fn binary_op_to_compare_op(op: &BinaryOperator) -> Option { + match op { + BinaryOperator::Less => Some(CompareOp::Lt), + BinaryOperator::Greater => Some(CompareOp::Gt), + BinaryOperator::LessEqual => Some(CompareOp::Le), + BinaryOperator::GreaterEqual => Some(CompareOp::Ge), + BinaryOperator::Equal => Some(CompareOp::Eq), + BinaryOperator::NotEqual => Some(CompareOp::Ne), + _ => None, + } +} + +/// 条件式を正規化(左辺=変数 の形に統一) +/// +/// # Arguments +/// +/// * `cond` - 条件式ASTノード +/// +/// # Returns +/// +/// - `Some(NormalizedCondition)` - 正規化成功 +/// - `None` - 正規化失敗(複雑条件 or サポート外) +/// +/// # Examples +/// +/// ```rust +/// // i > 0 → NormalizedCondition { left_var: "i", op: Gt, right: Literal(0) } +/// let norm = normalize_comparison(&i_gt_0).unwrap(); +/// assert_eq!(norm.left_var, "i"); +/// assert_eq!(norm.op, CompareOp::Gt); +/// assert_eq!(norm.right, ConditionValue::Literal(0)); +/// +/// // 0 < i → NormalizedCondition { left_var: "i", op: Gt, right: Literal(0) } +/// let norm = normalize_comparison(&zero_lt_i).unwrap(); +/// assert_eq!(norm.left_var, "i"); +/// assert_eq!(norm.op, CompareOp::Gt); // 左右反転により Gt +/// +/// // i > j → NormalizedCondition { left_var: "i", op: Gt, right: Variable("j") } +/// let norm = normalize_comparison(&i_gt_j).unwrap(); +/// assert_eq!(norm.right, ConditionValue::Variable("j".to_string())); +/// ``` +pub fn normalize_comparison(cond: &ASTNode) -> Option { + match cond { + ASTNode::BinaryOp { operator, left, right, .. } => { + // Comparison operator のみ受理 + let compare_op = binary_op_to_compare_op(operator)?; + + // Case 1: var CmpOp literal (e.g., i > 0) + if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Literal { value: LiteralValue::Integer(right_val), .. }) = (left.as_ref(), right.as_ref()) { + return Some(NormalizedCondition { + left_var: left_var.clone(), + op: compare_op, + right: ConditionValue::Literal(*right_val), + }); + } + + // Case 2: literal CmpOp var (e.g., 0 < i) → 左右反転 + if let (ASTNode::Literal { value: LiteralValue::Integer(left_val), .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) { + return Some(NormalizedCondition { + left_var: right_var.clone(), + op: flip_compare_op(compare_op), // 演算子を反転 + right: ConditionValue::Literal(*left_val), + }); + } + + // Case 3: var CmpOp var (e.g., i > j) + if let (ASTNode::Variable { name: left_var, .. }, ASTNode::Variable { name: right_var, .. }) = (left.as_ref(), right.as_ref()) { + return Some(NormalizedCondition { + left_var: left_var.clone(), + op: compare_op, + right: ConditionValue::Variable(right_var.clone()), + }); + } + + // その他(BinaryOp, MethodCall等)→ 正規化失敗 + None + } + _ => None, // 非 BinaryOp → 正規化失敗 + } +} + #[cfg(test)] mod tests { use super::*; @@ -252,4 +404,92 @@ mod tests { assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::Complex); assert!(!is_simple_comparison(&cond)); } + + // ======================================================================== + // Phase 222: Normalization Tests + // ======================================================================== + + #[test] + fn test_normalize_var_cmp_literal() { + // i > 0 → そのまま + let cond = binop(BinaryOperator::Greater, var("i"), int_lit(0)); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Gt); + assert_eq!(norm.right, ConditionValue::Literal(0)); + } + + #[test] + fn test_normalize_literal_cmp_var() { + // 0 < i → i > 0 (左右反転) + let cond = binop(BinaryOperator::Less, int_lit(0), var("i")); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Gt); // 反転により Gt + assert_eq!(norm.right, ConditionValue::Literal(0)); + } + + #[test] + fn test_normalize_literal_gt_var() { + // len > i → i < len (左右反転) + let cond = binop(BinaryOperator::Greater, int_lit(10), var("i")); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Lt); // 反転により Lt + assert_eq!(norm.right, ConditionValue::Literal(10)); + } + + #[test] + fn test_normalize_literal_eq_var() { + // 5 == i → i == 5 (反転だが == は不変) + let cond = binop(BinaryOperator::Equal, int_lit(5), var("i")); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Eq); // == は不変 + assert_eq!(norm.right, ConditionValue::Literal(5)); + } + + #[test] + fn test_normalize_var_cmp_var() { + // i > j → そのまま(変数同士) + let cond = binop(BinaryOperator::Greater, var("i"), var("j")); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Gt); + assert_eq!(norm.right, ConditionValue::Variable("j".to_string())); + } + + #[test] + fn test_normalize_var_lt_var() { + // i < end → そのまま(変数同士) + let cond = binop(BinaryOperator::Less, var("i"), var("end")); + let norm = normalize_comparison(&cond).unwrap(); + assert_eq!(norm.left_var, "i"); + assert_eq!(norm.op, CompareOp::Lt); + assert_eq!(norm.right, ConditionValue::Variable("end".to_string())); + } + + #[test] + fn test_normalize_fails_on_complex() { + // i % 2 == 1 → 正規化失敗(BinaryOp in LHS) + let lhs = binop(BinaryOperator::Modulo, var("i"), int_lit(2)); + let cond = binop(BinaryOperator::Equal, lhs, int_lit(1)); + assert_eq!(normalize_comparison(&cond), None); + } + + #[test] + fn test_analyze_pattern_literal_cmp_var() { + // Phase 222: 0 < i → SimpleComparison + let cond = binop(BinaryOperator::Less, int_lit(0), var("i")); + assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert!(is_simple_comparison(&cond)); + } + + #[test] + fn test_analyze_pattern_var_cmp_var() { + // Phase 222: i > j → SimpleComparison + let cond = binop(BinaryOperator::Greater, var("i"), var("j")); + assert_eq!(analyze_condition_pattern(&cond), ConditionPattern::SimpleComparison); + assert!(is_simple_comparison(&cond)); + } } diff --git a/src/mir/join_ir/lowering/loop_update_summary.rs b/src/mir/join_ir/lowering/loop_update_summary.rs index 42e37b29..a31be7e8 100644 --- a/src/mir/join_ir/lowering/loop_update_summary.rs +++ b/src/mir/join_ir/lowering/loop_update_summary.rs @@ -387,14 +387,16 @@ mod tests { assert_eq!(UpdateKind::Other.name(), "Other"); } - #[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")); - } + // 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")); + // } #[test] fn test_analyze_single_counter() {