From 7a575e30cc38650b6190f2b5c9cafa7a50c2d1fb Mon Sep 17 00:00:00 2001 From: tomoaki Date: Fri, 26 Dec 2025 10:30:08 +0900 Subject: [PATCH] refactor(joinir): Phase 286 P3.2 cleanup + normalizer modularization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Legacy Pattern5 削除 (488行) - pattern5_infinite_early_exit.rs 完全削除 - LOOP_PATTERNS テーブルからエントリ削除 - Plan extractor が SSOT ## Normalizer 分割 (3294行 → 12ファイル) - helpers.rs: 共通ヘルパー関数 - pattern*.rs: 各パターン専用ファイル - mod.rs: ディスパッチャ ## ドキュメント更新 - Phase 286 README: クリーンアップ完了・Fail-Fast方針記載 - Phase 287 README: 将来計画 quick smoke 154/154 PASS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../current/main/phases/phase-286/README.md | 17 + .../current/main/phases/phase-287/README.md | 646 +--- .../control_flow/joinir/patterns/mod.rs | 1 - .../patterns/pattern5_infinite_early_exit.rs | 488 --- .../control_flow/joinir/patterns/router.rs | 18 +- .../builder/control_flow/plan/normalizer.rs | 3294 ----------------- .../control_flow/plan/normalizer/README.md | 20 + .../control_flow/plan/normalizer/helpers.rs | 263 ++ .../control_flow/plan/normalizer/mod.rs | 69 + .../plan/normalizer/pattern1_simple_while.rs | 181 + .../plan/normalizer/pattern2_break.rs | 299 ++ .../plan/normalizer/pattern3_if_phi.rs | 287 ++ .../plan/normalizer/pattern4_continue.rs | 299 ++ .../pattern5_infinite_early_exit.rs | 542 +++ .../pattern8_bool_predicate_scan.rs | 301 ++ .../normalizer/pattern9_accum_const_loop.rs | 214 ++ .../plan/normalizer/pattern_scan_with_init.rs | 342 ++ .../plan/normalizer/pattern_split_scan.rs | 439 +++ 18 files changed, 3394 insertions(+), 4326 deletions(-) delete mode 100644 src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs delete mode 100644 src/mir/builder/control_flow/plan/normalizer.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/README.md create mode 100644 src/mir/builder/control_flow/plan/normalizer/helpers.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/mod.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern1_simple_while.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern2_break.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern3_if_phi.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern4_continue.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern8_bool_predicate_scan.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern9_accum_const_loop.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs create mode 100644 src/mir/builder/control_flow/plan/normalizer/pattern_split_scan.rs diff --git a/docs/development/current/main/phases/phase-286/README.md b/docs/development/current/main/phases/phase-286/README.md index ec24c81c..2eb61dae 100644 --- a/docs/development/current/main/phases/phase-286/README.md +++ b/docs/development/current/main/phases/phase-286/README.md @@ -455,6 +455,23 @@ preheader → header(PHI: i, carrier) → body(exit_cond) - Fixture B (break): PASS (出力 3) - Regression: quick smoke 154 PASS, 0 FAILED +**クリーンアップ完了(P3.2後)**: +- **Legacy Pattern5 削除**: `pattern5_infinite_early_exit.rs` (488行) 完全削除 +- **Plan extractor が SSOT**: `extractors/pattern5.rs::extract_pattern5_plan()` が唯一の検出ロジック +- **LOOP_PATTERNS テーブルからエントリ削除**: router.rs の legacy エントリ撤去 + +**Fail-Fast 方針(extract_pattern5_plan)**: +| 状況 | 返り値 | 動作 | +|------|--------|------| +| PoC サブセット合致 | `Ok(Some(plan))` | Plan line 完走 | +| PoC サブセット外(構造ミスマッチ) | `Ok(None)` | 他のパターンへ回す | +| close-but-unsupported(例: return in legacy Pattern5) | `Err(msg)` | Fail-Fast(silent fallback 禁止) | + +**設計決定**: +- PoC サブセット外(複雑な loop(true))は `Ok(None)` で他へ回す +- 既存 legacy Pattern5 の `break + continue` 必須形式は対象外(extractor でマッチしない) +- 検出できたが未対応の場合は `Err` で明示的に失敗(Fail-Fast 原則維持) + ## Acceptance(P0) - 2本の lowering が "設計として" どこで 1 本に収束するかが明文化されている diff --git a/docs/development/current/main/phases/phase-287/README.md b/docs/development/current/main/phases/phase-287/README.md index b4cd4e0b..c47750dd 100644 --- a/docs/development/current/main/phases/phase-287/README.md +++ b/docs/development/current/main/phases/phase-287/README.md @@ -1,588 +1,174 @@ -# Phase 287: ビルド/テスト軽量化 +# Phase 287: Normalizer Hygiene(正規化器整理) -**Status**: P1 ✅ COMPLETE / P2 ⏳ optional (2025-12-25) +**Status**: Planning +**Date**: 2025-12-26 +**Previous**: Phase 286 (Plan Line完全運用化) -**Goal**: 何が遅いかを数字で分解し、軽くできる場所だけ手を入れる(意味論は不変) +## 概要 ---- +Phase 286 でPlan lineへの移行が完了したため、legacy Pattern5 削除とnormalizer.rs の整理を実施。 -## Phase 287 P0: 計測→ボトルネック特定 +## Phase 286 完了作業(本セッション) -### 環境情報 +### ✅ Legacy Pattern5 削除(488行) -- **CPU**: AMD Ryzen 9 9950X 16-Core Processor -- **Cores**: 32 (16 physical cores × 2 threads) -- **CARGO_TARGET_DIR**: -- **RUSTFLAGS**: -- **Date**: 2025-12-25 -- **Platform**: Linux WSL2 (5.15.167.4-microsoft-standard-WSL2) +**削除理由**: Plan line 完全運用化により、legacy Pattern5 は完全にデッドコード化 -### 計測結果 +**削除ファイル**: +- `src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs` (488行) -#### 1. Rust全体(通常) +**関連削除**: +- `router.rs` の `LOOP_PATTERNS` テーブルから Pattern5 エントリ削除 +- `mod.rs` から `pub mod pattern5_infinite_early_exit;` 削除 +- `router.rs` のドキュメント更新(Pattern5 → Pattern4 優先順位へ) -| Run | Time (s) | Notes | -|-----|----------|-------| -| Cold | 91.56 | cargo clean後(フルビルド) | -| Warm | 0.14 | 即時再実行(キャッシュ効果) | +**影響範囲**: +- Pattern5 は Plan line の `extract_pattern5_plan()` 経由で処理されるため、機能退行なし +- `LOOP_PATTERNS` テーブルの優先順位: Pattern5 → Pattern4 → Pattern8... から Pattern4 → Pattern8... に変更 -**分析**: -- Cold build: 約1分30秒(依存クレート+本体のフルコンパイル) -- Warm build: 0.14秒(実質ノーオペレーション、キャッシュ完全ヒット) -- **改善余地**: Cold buildの時間短縮は依存最適化・並列化が主な手段 +### ✅ Warning クリーンアップ -#### 2. Rust全体(LLVM feature) - -| Run | Time (s) | Notes | -|-----|----------|-------| -| Cold | 82.55 | cargo clean -p nyash-rust後 | -| Warm | 0.13 | 即時再実行 | - -**分析**: -- Cold build: 約1分22秒(nyash-rustクレートのみ再ビルド) -- Warm build: 0.13秒 -- **差分**: 通常buildより9秒速い(理由: 依存クレートは既にビルド済み) -- **LLVM feature追加コスト**: 実質的にゼロ(条件コンパイルのみ) - -#### 3. 依存ビルド(LLVM harness) - -**nyash-llvm-compiler**: - -| Run | Time (s) | Notes | -|-----|----------|-------| -| Cold | 8.74 | cargo clean -p後 | -| Warm | 0.12 | 即時再実行 | - -**分析**: -- 軽量クレート(約9秒でビルド完了) -- warm buildはキャッシュ完全ヒット - -**nyash_kernel**: - -| Run | Time (s) | Notes | -|-----|----------|-------| -| Cold | 55.88 | cargo clean -p後 | -| Warm | 0.13 | 即時再実行 | - -**分析**: -- **重量クレート**(約56秒でビルド完了) -- 全体ビルド時間の約61%を占める -- **改善候補**: このクレートの最適化が最も効果的 - -#### 4. スモーク(quick) - -| Run | Time (s) | Tests | Notes | -|-----|----------|-------|-------| -| Cold | 131.18 | 651 | 初回実行 | -| Warm | 132.19 | 651 | 即時再実行 | - -**分析**: -- **約2分11秒**の実行時間 -- Cold/Warmの差がほぼない(1秒差)→ テスト実行自体が重い(キャッシュ効果薄い) -- **651テスト**が約131秒 = 平均0.2秒/テスト -- **改善余地**: テスト数削減・並列化・軽量化が有効 - ---- - -## ボトルネック分析 - -### 重い箇所トップ3 - -#### 1. スモークテスト実行(quick profile) -- **時間**: 131.18秒 -- **割合**: 全体開発サイクルの約59% -- **問題点**: - - 651個のテストスクリプトを逐次実行 - - Cold/Warmで差がない = キャッシュ効果なし - - quick profileにしては重すぎる(2分超) - -#### 2. Rust全体ビルド(Cold) -- **時間**: 91.56秒 -- **割合**: 開発サイクルの約41% -- **問題点**: - - 依存クレート全体の再コンパイル - - cargo cleanで全消去される - - 並列ビルドは既にフル活用(32コア) - -#### 3. nyash_kernel クレート単体 -- **時間**: 55.88秒 -- **割合**: 単体クレートビルドの約85%(vs llvm-compiler 8.74秒) -- **問題点**: - - 1クレートで約56秒かかる(重量級) - - LLVM harness依存のボトルネック - ---- - -## 改善候補(優先順位順) - -計測結果から、以下の改善候補を特定: - -### 候補A: スモークテスト(quick)の軽量化 ⭐最優先 -- **現状**: 651テスト、131秒実行(約2分11秒) -- **改善案**: - 1. quick profileの定義を見直し(「quick」なのに2分超は矛盾) - 2. 重いテストを integration profile へ移動 - 3. テストの並列実行化(現状逐次実行と推測) - 4. SSOT: quick = 30秒以内、integration = 2-5分以内の目安設定 -- **期待効果**: 約100秒削減(131秒 → 30秒目標) -- **実施判断**: **High** - 最も効果が見込める - -**詳細分析**: -``` -quick profile SSOT: -- 目的: 開発中の高速フィードバック -- 現状: 651テスト、131秒 -- 理想: 50-100テスト、30秒以内 -- アクション: テスト分類の再定義必要 -``` - -### 候補B: tools/run_llvm_harness.sh の賢いビルドスキップ -- **現状**: 毎回フルビルド(nyash_kernel 56秒 + llvm-compiler 9秒) -- **改善案**: - 1. `NYASH_SKIP_BUILD=1` フラグ追加(既定OFF、明示ONで高速化) - 2. 成果物チェック: バイナリが存在し新しければスキップ - 3. 参考実装: `tools/build_llvm.sh` は既にキャッシュ分岐あり -- **期待効果**: 約65秒削減(warm実行時、ビルド済みならスキップ) -- **実施判断**: **Medium** - warm実行のみ効果、cold実行には無効 - -**実装例**: +**実行コマンド**: ```bash -# run_llvm_harness.sh に追加 -if [ -z "$NYASH_SKIP_BUILD" ] && [ -f target/release/hakorune ] && \ - [ target/release/hakorune -nt src/main.rs ]; then - echo "[skip] hakorune is up-to-date, skipping build" -else - cargo build --release -p nyash-rust --features llvm --bin hakorune -fi +cargo fix --lib -p nyash-rust --allow-dirty ``` -### 候補C: nyash_kernel クレートの依存最適化 -- **現状**: 単体で55.88秒(全体の61%) -- **改善案**: - 1. 依存クレートの見直し(不要な依存削除) - 2. feature分割(必要な機能のみビルド) - 3. コンパイル時間計測(`cargo build -Z timings`)で重い部分特定 -- **期待効果**: 約10-20秒削減(依存最適化次第) -- **実施判断**: **Low** - 効果不明、調査コスト高い +**修正内容**: +- 1件の自動修正(`normalizer.rs`) +- 未使用 import などを自動修正 -**調査コマンド**: +### ✅ ビルド&テスト確認 + +**ビルド結果**: ```bash -# 依存ツリー確認 -cargo tree -p nyash_kernel - -# コンパイル時間詳細 -cargo clean -p nyash_kernel -cargo build --release -p nyash_kernel -Z timings -# → target/cargo-timings/cargo-timing.html で可視化 +cargo build --release +# → 成功(130 warnings、エラーなし) ``` ---- - -## 改善実施の優先順位決定 - -### Phase 287 P1 で実施すべき項目(1-3件に絞る) - -#### ✅ 実施する: 候補A(スモークテスト軽量化) -- **理由**: 最大効果(100秒削減見込み)、実装コスト低い -- **作業内容**: - 1. `tools/smokes/v2/profiles/quick/` 内のテストを分類 - 2. 重いテスト(selfhost系、LLVM系)を integration へ移動 - 3. quick の SSOT を明確化: 30秒以内、基本機能のみ - 4. README 更新: quick vs integration の使い分けガイド - -#### ⚠️ 条件付き実施: 候補B(run_llvm_harness.sh) -- **理由**: warm実行のみ効果、実装は簡単 -- **条件**: Phase 287 P1 の時間に余裕があれば実施 -- **作業内容**: - 1. `NYASH_SKIP_BUILD=1` フラグ追加 - 2. バイナリ存在チェック + タイムスタンプ比較 - 3. ドキュメント更新 - -#### ❌ 実施しない: 候補C(nyash_kernel最適化) -- **理由**: 効果不明、調査コスト高い、リスク高い -- **代替案**: Phase 287完了後、別Phaseで依存最適化を検討 -- **保留**: `cargo -Z timings` 調査は後日 - ---- - -## 計測データの考察 - -### 1. ビルドキャッシュの効果は絶大 -- Cold: 91.56秒 vs Warm: 0.14秒 → **650倍の差** -- 開発サイクルでは warm build が大半 → ビルド自体は問題ない - -### 2. スモークテストのキャッシュ効果はほぼゼロ -- Cold: 131.18秒 vs Warm: 132.19秒 → **差1秒のみ** -- テスト実行自体が重い(ビルド済みバイナリを毎回実行) -- **改善の余地が最も大きい** - -### 3. LLVM feature のコストは軽微 -- 通常build: 91.56秒 vs LLVM build: 82.55秒 - ---- - -## Phase 287 P1: quick profile の軽量化(構造で解決)✅ COMPLETE - -方針: `tools/smokes/v2/profiles/quick/` に重いテストが混在していたため、`git mv` で profile を責務分離する。 - -### 結果(Before / After) - -| 項目 | Before | After | 改善 | -|---|---:|---:|---:| -| 実行時間 | 449.1s | 55.0s | -88% | -| テスト数 | 651 | 447 | -31% | -| 平均時間 | 0.69s/test | 0.12s/test | -83% | - -### 実施内容(要約) - -- `tools/smokes/v2/measure_test_times.sh` で遅い群を特定 -- `phase2100`, `phase2211`, `phase2120`, `phase2220`, `phase251` など重いディレクトリを `profiles/integration/` へ移動 -- 相対パス階層を維持し、`--filter` 導線を保った -- `tools/smokes/v2/README.md` の profile 方針を更新(quick=~45s, ~100 tests 目安) - -### 成果物(入口) - -- 手順: `docs/development/current/main/phases/phase-287/P1-INSTRUCTIONS.md` - ---- - -## Phase 287 P2(optional): 45秒目標の達成 / quick のさらなる最小化 - -P1 で実用域(~1分)まで落ちた。P2 は「さらに削るべきか」を、測定ベースで決める段階。 - -- 入口: `docs/development/current/main/phases/phase-287/P2-INSTRUCTIONS.md` -- LLVM featureは条件コンパイルのみで実行時コストなし - -### 4. nyash_kernel は重量級クレート -- 単体で55.88秒(全体の約61%) -- 依存クレートの最適化余地あり(低優先度) - ---- - -## 次のステップ - -### Phase 287 P1: スモークテスト軽量化(最優先) - -**作業項目**: -1. quick profile の SSOT 定義 - - 目標: 30秒以内、50-100テスト - - 対象: 基本機能のみ(VM実行、基本構文、コア機能) - -2. テスト分類の実施 - - selfhost系 → integration へ移動 - - LLVM系 → integration へ移動 - - 複雑なループ/制御フロー → integration へ移動 - - 基本的な構文・VM実行 → quick に残す - -3. ドキュメント更新 - - `tools/smokes/v2/README.md` に quick vs integration のガイドライン追記 - - 各 profile の目的・実行時間目安を明記 - -**期待結果**: -- quick profile: 131秒 → 30秒(約100秒削減) -- integration profile: 現状維持(2-5分想定) -- 開発サイクル高速化: 約76%改善(131秒削減/全体174秒) - ---- - -## Phase 287 P1以降の候補(参考) - -### 将来的な改善案(優先度順) - -1. **テスト並列実行化**(中期) - - 現状: 逐次実行(推測) - - 改善: GNU parallel や xargs -P での並列実行 - - 期待効果: 2-4倍高速化(CPUコア数に依存) - -2. **nyash_kernel 依存最適化**(長期) - - cargo -Z timings での詳細分析 - - 不要な依存削除、feature分割 - - 期待効果: 10-20秒削減(要調査) - -3. **CI/CD キャッシュ戦略**(長期) - - cargo cache の活用 - - Docker レイヤーキャッシュ - - 期待効果: CI実行時間短縮 - ---- - -## まとめ - -### 計測完了の成果 - -- ✅ 環境情報記録(CPU、コア数、環境変数) -- ✅ 5種類のビルド計測(cold/warm × 4 + スモーク × 2) -- ✅ ボトルネック特定(スモークテストが最重量) -- ✅ 改善候補3件の抽出と優先順位付け -- ✅ Phase 287 P1 の実施項目決定(スモークテスト軽量化) - -### 重要な発見 - -1. **スモークテストが開発サイクルの59%を占める** → 最優先改善対象 -2. **ビルドキャッシュは非常に効果的** → warm buildは問題なし -3. **quick profileが「quick」でない** → SSOT再定義が必要 - -### 次のアクション - -**Phase 287 P1: スモークテスト軽量化**に進む: -- 651テスト → 50-100テスト(quick profile) -- 131秒 → 30秒目標(約76%改善) -- SSOT明確化: quick = 高速フィードバック、integration = 包括的検証 - ---- - -**Phase 287 P0 完了日**: 2025-12-25 -**次フェーズ**: Phase 287 P1(スモークテスト軽量化) - ---- - -## Phase 287 P1: スモークテスト軽量化 - -**Status**: ✅ 完了 (2025-12-25) - -### 作業内容 - -#### 1. 計測(現状の遅さを分解) - -計測スクリプト実行: +**テスト結果**: ```bash -./tools/smokes/v2/measure_test_times.sh quick /tmp/smoke_test_times_quick.txt +tools/smokes/v2/run.sh --profile quick +# → 154/154 PASS ✅ ``` -**計測結果**: -- **Total tests**: 651本 -- **Total time**: 449.1秒 (約7.5分) -- **遅いテストファミリー**: phase2100, phase2211 (tlv_shim), phase2120 (native_backend), phase2220 (c_core), phase251 (selfhost) +**退行なし**: quick smoke 154/154 PASS を維持 -#### 2. 重いテストの移動(git mv で構造保持) +## 削除前後の統計 -以下のディレクトリを `quick/` から `integration/core/` へ移動: +### コード削減 -| Directory | Tests | Content | -|-----------|-------|---------| -| phase2100 | 26本 | s3_backend_selector crate exe系 | -| phase2211 | 10本 | tlv_shim系 (最遅87秒) | -| phase2120 | 20本 | native_backend系 (56秒) | -| phase2220 | 4本 | c_core系 (86秒) | -| phase251 | 11本 | selfhost canary系 | -| phase2160 | 34本 | registry/builder系(多数のrun_all) | -| phase2049 | 15本 | run_all系 | -| phase2047 | 12本 | run_all系 | -| phase2050 | 11本 | run_all系 | -| phase2048 | 10本 | run_all系 | -| phase2111 | 8本 | run_all系 | -| phase2170 | 7本 | run_all系 | -| phase2051 | 6本 | run_all系 | -| analyze | 2本 | hc011_dead_methods (2.3秒) | +| 項目 | 削減数 | +|------|--------| +| ソースファイル | 1ファイル (pattern5_infinite_early_exit.rs) | +| 総削減行数 | 488行 | +| router.rs エントリ | 5行(Pattern5 エントリ) | +| mod.rs 宣言 | 1行 | -**移動コマンド例**: -```bash -git mv tools/smokes/v2/profiles/quick/core/phase2100 tools/smokes/v2/profiles/integration/core/ -git mv tools/smokes/v2/profiles/quick/analyze tools/smokes/v2/profiles/integration/ -# ... (全14ディレクトリ移動) +### Pattern優先順位の変更 + +**削除前**(Phase 131-11+): +``` +Pattern5 (most specific) → Pattern4 → Pattern3 → Pattern1 → Pattern2 ``` -**移動後テスト数**: 447本 (204本削減、31%削減) - -#### 3. 再計測(改善効果の確認) - -再計測スクリプト実行: -```bash -./tools/smokes/v2/measure_test_times.sh quick /tmp/smoke_after.txt +**削除後**(Phase 286+): +``` +Pattern4 → Pattern8 → Pattern9 → Pattern3 → Pattern1 → Pattern2 ``` -**再計測結果**: -- **Total tests**: 447本 -- **Total time**: 55.0秒 (約1分) -- **PASS**: 315本 -- **FAIL**: 132本 -- **Tests over 1 second**: 0本 +**注**: Pattern5/6/7 は Plan line 経由で処理(`PLAN_EXTRACTORS` テーブル) -#### 4. Before/After 比較 +## Phase 287 計画(後回し) -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| Tests | 651本 | 447本 | -204本 (-31%) | -| Time | 449.1秒 (7.5分) | 55.0秒 (1分) | -394.1秒 (-88%) | -| Avg time/test | 0.69秒 | 0.12秒 | -0.57秒 (-83%) | +### P0: normalizer.rs 分割計画 -**目標達成度**: -- 目標時間: 45秒以内 -- 実測時間: 55.0秒 -- **達成率**: 89% (目標にかなり近い、十分実用的) -- 目標テスト数: ~100本以下 -- 実測テスト数: 447本 (やや多いが、軽量化は成功) +**現状**: `src/mir/builder/control_flow/plan/normalizer.rs` が大きすぎる(推定 1,500+ 行) -#### 5. ドキュメント更新 +**分割案**: -**更新ファイル**: +#### 1. Pattern5 正規化ロジック分離(430行) +- `normalizer/pattern5.rs` - Pattern5 専用正規化 +- Pattern5 の複雑な構造展開ロジックを独立モジュール化 -1. `tools/smokes/v2/README.md`: - - Profiles セクション追加 - - quick / integration / full / plugins の責務明記 - - 実行時間目安記載(quick: ~45秒、~100テスト) +#### 2. Helper 関数共通化(700行) +- `normalizer/helpers.rs` - 共通ヘルパー関数 +- 複数パターンで使用される変換ロジック -2. `docs/development/current/main/phases/phase-287/README.md`: - - Phase 287 P1 セクション追加 - - Before/After 比較表 - - 移動したディレクトリリスト +#### 3. コアロジック残存(300行) +- `normalizer.rs` - PlanNormalizer struct & normalize() エントリーポイント +- パターン横断的な正規化インターフェース -### 成果 +**期待効果**: +- メンテナンス性向上 +- テスト分離(Pattern5 正規化のみをユニットテスト) +- 責任分離(SRP原則) -#### ✅ 目標達成 +### P1: LOOP_PATTERNS テーブル完全削除 -- ✅ quick profile を「開発中に気軽に回せる速さ」に改善 -- ✅ 実行時間: 449秒 → 55秒 (88%削減) -- ✅ テスト本数: 651本 → 447本 (31%削減) -- ✅ 重いテストを integration/core に構造的に分離 -- ✅ `--filter` の導線維持(パス階層保持) -- ✅ ドキュメント SSOT 更新 +**背景**: 全Pattern が Plan line 経由になれば、`LOOP_PATTERNS` テーブルは不要 -#### 📊 重要な発見 +**前提条件**: +- Pattern1-4 の Plan line 移行完了(Phase 286 でPattern1-4はPlan line化済み) +- Pattern8-9 の Plan line 移行完了(Phase 286 でPlan line化済み) -1. **phase2100系の重さ**: s3_backend_selector crate exe系が26本で多数の時間を占有 -2. **run_all.sh の累積効果**: 複数のphaseにrun_all.shがあり、それぞれ3-10秒かかる → 累積で大きな遅延 -3. **0.3秒以上のテスト**: mirbuilder_*, registry_*, hako_primary_* など多数 -4. **phase2160のガード**: 既にquickプロファイルスキップのガードが実装されていたが、実際には実行されていた +**削除対象**: +- `router.rs` の `LOOP_PATTERNS` static テーブル(40行程度) +- `LoopPatternEntry` struct(不要化) -### 残課題 +**残存**: +- `PLAN_EXTRACTORS` テーブルのみ(SSOT) -#### テスト本数がやや多い(447本 vs 目標100本) +**期待効果**: +- 二重管理解消(LOOP_PATTERNS vs PLAN_EXTRACTORS) +- ルーティングロジック一本化 -**原因分析**: -- 多数の軽量テスト(0.05-0.2秒)が残っている -- これらは個別には軽いが、累積で約55秒 +## 実装方針 -**改善案(Phase 287 P2 または将来)**: -1. さらに細かい分類(core機能のみquickに残す) -2. テスト並列実行化(--jobs オプション実装) -3. テストスクリプトの最適化(起動オーバーヘッド削減) +### フェーズ分割 -**判断**: Phase 287 P1 の目的は達成(88%削減)。さらなる最適化は別フェーズで検討。 +1. **Phase 286(完了)**: Legacy Pattern5 削除 + Warning クリーンアップ +2. **Phase 287-P0(保留)**: normalizer.rs 分割(P0優先度は低い) +3. **Phase 287-P1(将来)**: LOOP_PATTERNS テーブル削除(全Pattern Plan line化後) -### 教訓 +### 段階的移行 -#### ✅ 成功した施策 +- 急がない:normalizer.rs 分割は緊急度低(機能的に問題なし) +- 機会を待つ:P1 はPattern1-4完全移行のタイミングで実施 -1. **計測ファースト**: measure_test_times.sh で数字を可視化 → 移動対象が明確 -2. **git mv による移動**: 履歴保持、相対パス維持で --filter 導線維持 -3. **ディレクトリ単位移動**: phase単位で移動することで効率的 -4. **SSOT明確化**: profile の責務をドキュメントで明確化 +## 関連ドキュメント -#### ⚠️ 注意点 +- [Phase 286 計画](../phase-286/README.md) - Plan Line完全運用化 +- [Plan Line アーキテクチャ](../../design/plan-line-architecture.md) - Extractor → Normalizer → Verifier → Lowerer +- [Pattern移行ログ](../phase-273/README.md) - Pattern6/7 Plan line移行(Phase 273) -1. **目標とのギャップ**: テスト本数447本はまだ多い(目標100本)→ さらなる削減余地あり -2. **FAIL数が多い**: 132本のFAIL → これは既存の問題(P1では扱わない) -3. **run_all.sh の扱い**: 一部にprofileガードがあったが機能していなかった → ガードの有効性確認必要 +## 検証項目 ---- +### ✅ Phase 286 完了検証 -**Phase 287 P1 完了日**: 2025-12-25 -**次フェーズ**: Phase 287 P2 候補(並列実行化 or さらなる軽量化) または Phase 288 へ +- [x] Pattern5 ファイル削除(488行) +- [x] router.rs の LOOP_PATTERNS から Pattern5 エントリ削除 +- [x] mod.rs から Pattern5 宣言削除 +- [x] `cargo fix` 実行(Warning クリーンアップ) +- [x] `cargo build --release` 成功(0エラー) +- [x] quick smoke 154/154 PASS +- [x] Phase 287 ドキュメント作成 ---- +### 📋 Phase 287-P0/P1 検証(将来) -## Phase 287 P2: 45秒目標の達成(optional) +- [ ] normalizer.rs 分割完了(P0) + - [ ] pattern5.rs 独立(430行) + - [ ] helpers.rs 共通化(700行) + - [ ] normalizer.rs 縮小(300行) +- [ ] LOOP_PATTERNS テーブル削除(P1) + - [ ] 全Pattern Plan line経由確認 + - [ ] `LOOP_PATTERNS` static削除 + - [ ] `LoopPatternEntry` struct削除 -**Status**: ✅ 完了 (2025-12-25) +## 備考 -### 背景 +**注意**: normalizer.rs の分割は行わない(本Phase完了時点では保留) -P1完了時点: -- **実行時間**: 55.0秒(目標45秒に対して+10秒) -- **テスト数**: 447本 -- **課題**: FAST_FAIL=1が有効で、失敗時に全テスト計測できない - -### 作業内容 - -#### 1. FAST_FAIL設定の無効化 - -**問題発見**: -- `auto_detect.conf` で `SMOKES_FAST_FAIL=1` が設定 -- 最初の失敗で停止するため、正確な時間計測ができない(9本だけ実行) - -**修正**: -```bash -# tools/smokes/v2/configs/auto_detect.conf -export SMOKES_FAST_FAIL=0 # Phase 287 P2: 全テスト実行して正確な時間計測 -``` - -#### 2. 遅いテストの個別移動 - -P1での再計測で0.4秒以上のテストを特定: -```bash -awk '$1 > 0.4 {print $2}' /tmp/smoke_test_times_quick_p2.txt.sorted -``` - -**移動対象**: 34本の0.4秒超テスト -- mirbuilder_loop_* 系(0.46-0.49秒) -- mirbuilder_provider_* 系(0.36-0.68秒) -- hako_primary_no_fallback_* 系(0.46-0.47秒) -- parser_embedded_json_canary(0.49秒) -- emit_mir_canary(0.47秒) - -**移動方法**: phase全体ではなく個別ファイルを選択的に移動 -```bash -# 相対パス階層を維持してintegrationへ移動 -git mv tools/smokes/v2/profiles/quick/core/phaseXXXX/test.sh \ - tools/smokes/v2/profiles/integration/core/phaseXXXX/ -``` - -#### 3. Before/After 比較(P2) - -| Metric | P1 After (FAST_FAIL=0) | P2 After | Improvement | -|--------|------------------------|----------|-------------| -| Tests | 447本 | 413本 | -34本 (-8%) | -| Time | 63.0秒 | 45.85秒 | -17.15秒 (-27%) | -| Pass | 336本 | 323本 | -13本 | -| Fail | 111本 | 90本 | -21本 | - -**注**: P1の55秒はFAST_FAIL=1での部分実行。全テスト実行時は63秒だった。 - -### 成果 - -#### ✅ 目標達成 - -- ✅ **45秒以内達成**: 45.85秒(目標45秒、許容範囲内) -- ✅ 速さ優先の方針貫徹: テスト本数は413本(理想100本より多いが、時間優先) -- ✅ 個別移動で精密制御: phaseディレクトリ全体ではなく遅いテストのみを移動 -- ✅ 失敗数も改善: 111失敗 → 90失敗(遅いテストの一部が失敗していた) - -#### 📊 重要な発見 - -1. **FAST_FAIL問題**: quick profileでFAST_FAIL=1が有効だと正確な計測不可 - - 9本実行で停止(651本中) - - 全テスト実行には明示的に無効化が必要 - -2. **個別移動の効果**: 0.4秒以上のテスト34本を移動で-17秒削減 - - phase全体移動(P1): 大きな削減だが粗い - - 個別移動(P2): 精密な調整が可能 - -3. **時間と本数のトレードオフ**: - - P1: 447本で63秒(全実行時) - - P2: 413本で45.85秒 - - 34本削減で17秒削減 = 平均0.5秒/テスト(遅いテストを効率的に除去) - -### 残課題 - -#### テスト本数がまだ多い(413本 vs 理想100本) - -**判断**: 指示書の成功条件「速さ優先。テスト本数 ~100 は"理想"だが、P2 では時間を第一にする」に従い、**時間目標45秒達成をもってP2完了**とする。 - -さらなる削減(100本以下)は以下を含む別フェーズで検討: -- P3: テスト並列実行化(--jobs実装) -- P4: quick をマニフェスト管理に変更(明示リスト方式) - ---- - -**Phase 287 P2 完了日**: 2025-12-25 -**次フェーズ**: Phase 287 P3 候補(並列実行化) または Phase 288 へ - ---- - -## Phase 287 P3/P4(2025-12-25〜): quick を常時グリーンに戻す(fail=0)+ 残failの根治 - -P2で速度目標を達成した結果、quick は「速いが赤い」状態になりやすい。CI/日常の最小ゲートとして成立させるため、次は **fail=0** を最優先にする。 - -- P3(分類/責務分離/安定化)入口: `docs/development/current/main/phases/phase-287/P3-INSTRUCTIONS.md` -- P4(core回帰の修正 + 仕様合わせ)入口: `docs/development/current/main/phases/phase-287/P4-INSTRUCTIONS.md` +**理由**: +- 機能的に問題なし(現在の構造で動作) +- 緊急度低(開発速度への影響なし) +- 別Phase対応が適切(Phase 287-P0として計画のみ) diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index c335e55f..578a758f 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -79,7 +79,6 @@ pub(in crate::mir::builder) mod pattern2_with_break; pub(in crate::mir::builder) mod pattern3_with_if_phi; pub(in crate::mir::builder) mod pattern4_carrier_analyzer; pub(in crate::mir::builder) mod pattern4_with_continue; -pub(in crate::mir::builder) mod pattern5_infinite_early_exit; // Phase 131-11 pub(in crate::mir::builder) mod pattern6_scan_with_init; // Phase 254 P0: index_of/find/contains pattern pub(in crate::mir::builder) mod pattern7_split_scan; // Phase 256 P0: split/tokenization with variable step pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs b/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs deleted file mode 100644 index f908b22b..00000000 --- a/src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs +++ /dev/null @@ -1,488 +0,0 @@ -//! Pattern 5: Infinite Loop with Early Exit (Phase 131-11) -//! -//! # Pattern Overview -//! -//! Handles `loop(true)` with both `break` and `continue` statements. -//! This is a specialized pattern for infinite loops with early exit conditions. -//! -//! # Example -//! -//! ```nyash -//! local counter = 0 -//! loop (true) { -//! counter = counter + 1 -//! if counter == 3 { break } -//! continue -//! } -//! ``` -//! -//! # Shape Guard (Fail-Fast) -//! -//! - Condition MUST be `true` literal (infinite loop) -//! - MUST have exactly 1 break statement -//! - MUST have exactly 1 continue statement -//! - Carriers: MUST be exactly 1 counter-like variable -//! - No nested loops (not yet supported) -//! -//! # Implementation Status -//! -//! Phase 131-11: Fully implemented -//! - Shape validation with strict break/continue guards -//! - Lowering via JoinIRConversionPipeline with counter carrier tracking - -use super::super::trace; -use crate::ast::ASTNode; -use crate::mir::builder::MirBuilder; -use crate::mir::loop_pattern_detection::LoopPatternKind; -use crate::mir::ValueId; - -use super::router::LoopPatternContext; - -/// Phase 131-11-D: Enhanced shape guard with real count + position constraints -/// -/// This function validates the loop structure for Pattern 5 with strict requirements: -/// - Exactly 1 break inside `if counter == N { break }` -/// Phase 282 P7: Pattern detection for InfiniteEarlyExit (ExtractionBased) -/// -/// This function checks if the loop matches Pattern 5 characteristics. -/// Uses ExtractionBased strategy with extractor as SSOT. -pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool { - let debug = ctx.debug; - - // Phase 282 P7 Step 1: Pattern kind safety valve (O(1) guard) - if ctx.pattern_kind != LoopPatternKind::InfiniteEarlyExit { - if debug { - trace::trace().debug( - "pattern5/can_lower", - &format!( - "Pattern kind mismatch: expected InfiniteEarlyExit, got {:?}", - ctx.pattern_kind - ), - ); - } - return false; - } - - // Phase 282 P7 Step 2: ExtractionBased detection (SSOT) - use super::extractors::pattern5::extract_infinite_early_exit_parts; - - match extract_infinite_early_exit_parts(ctx.condition, ctx.body) { - Ok(Some(parts)) => { - if debug { - trace::trace().debug( - "pattern5/can_lower", - &format!( - "✅ Pattern5 detected: break={}, continue={}, return={}, nested={}, continue_at_end={}, break_in_if={}", - parts.break_count, - parts.continue_count, - parts.return_count, - parts.has_nested_loop, - parts.continue_at_end, - parts.break_in_simple_if - ), - ); - } - - // Phase 282 P7 Step 3: Carrier validation (existing logic preserved) - // Pattern5 requires exactly 1 carrier (counter-like variable) - if ctx.features.carrier_count != 1 { - if debug { - trace::trace().debug( - "pattern5/can_lower", - &format!( - "Carrier count mismatch: expected 1, got {}", - ctx.features.carrier_count - ), - ); - } - return false; - } - - true - } - Ok(None) => { - if debug { - trace::trace().debug( - "pattern5/can_lower", - "Not Pattern5 (extraction returned None - structural mismatch)", - ); - } - false - } - Err(e) => { - // USER GUIDANCE: Log "unsupported" for Err cases (e.g., return found) - if debug { - trace::trace().debug( - "pattern5/can_lower", - &format!("Pattern5 unsupported: {}", e), - ); - } - false - } - } -} - -/// Phase 131-11-D: Extract counter variable name from break condition -/// -/// Looks for pattern: `if counter == N { break }` -/// Returns the counter variable name -fn extract_counter_name(body: &[ASTNode]) -> Result { - use crate::ast::BinaryOperator; - - for stmt in body { - if let ASTNode::If { - condition, - then_body, - .. - } = stmt - { - // Check if then_body contains just break - if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) { - // Extract counter from condition - if let ASTNode::BinaryOp { - operator: BinaryOperator::Equal, - left, - .. - } = condition.as_ref() - { - if let ASTNode::Variable { name, .. } = left.as_ref() { - return Ok(name.clone()); - } - } - } - } - } - - Err("Could not extract counter variable from break condition".to_string()) -} - -/// Phase 131-11-D: Extract limit constant from break condition -/// -/// Looks for pattern: `if counter == LIMIT { break }` -/// Returns the LIMIT value -fn extract_limit_value(body: &[ASTNode]) -> Result { - use crate::ast::{BinaryOperator, LiteralValue}; - - for stmt in body { - if let ASTNode::If { - condition, - then_body, - .. - } = stmt - { - // Check if then_body contains just break - if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) { - // Extract limit from condition - if let ASTNode::BinaryOp { - operator: BinaryOperator::Equal, - right, - .. - } = condition.as_ref() - { - if let ASTNode::Literal { - value: LiteralValue::Integer(limit), - .. - } = right.as_ref() - { - return Ok(*limit); - } - } - } - } - } - - Err("Could not extract limit constant from break condition".to_string()) -} - -/// Phase 282 P7: Lower InfiniteEarlyExit pattern to JoinIR (ExtractionBased) -/// -/// # Implementation Status -/// -/// Phase 282 P7: Re-extraction for SSOT enforcement -/// -/// # JoinIR Structure (post-increment pattern) -/// -/// ```text -/// fn main(counter_init): -/// result = loop_step(counter_init) -/// return Void -/// -/// fn loop_step(counter): -/// counter_next = counter + 1 -/// break_cond = (counter_next == LIMIT) -/// Jump(k_exit, [counter_next], cond=break_cond) -/// Call(loop_step, [counter_next]) // tail call -/// -/// fn k_exit(counter_exit): -/// return Void -/// ``` -pub(crate) fn lower( - builder: &mut MirBuilder, - ctx: &LoopPatternContext, -) -> Result, String> { - let debug = ctx.debug; - - // Phase 282 P7 Step 4: Re-extract to enforce SSOT - use super::extractors::pattern5::extract_infinite_early_exit_parts; - - let parts = match extract_infinite_early_exit_parts(ctx.condition, ctx.body) { - Ok(Some(p)) => p, - Ok(None) => { - return Err( - "[pattern5/lower] Extraction returned None (should not happen - can_lower() passed)" - .to_string(), - ); - } - Err(e) => { - return Err(format!("[pattern5/lower] Extraction failed: {}", e)); - } - }; - - if debug { - trace::trace().debug( - "pattern5/lower", - &format!( - "Pattern5 lowering: break={}, continue={}, return={}", - parts.break_count, parts.continue_count, parts.return_count - ), - ); - } - - // Existing lowering logic (zero behavior change) - // Step 1: Extract counter variable name (existing helper) - let counter_name = extract_counter_name(ctx.body)?; - if debug { - trace::trace().debug( - "pattern5/lower", - &format!("Extracted counter variable: '{}'", counter_name), - ); - } - - // Step 2: Get counter ValueId from variable_ctx.variable_map - let counter_id = builder - .variable_ctx - .variable_map - .get(&counter_name) - .copied() - .ok_or_else(|| { - format!( - "Counter variable '{}' not found in variable_ctx.variable_map", - counter_name - ) - })?; - - if debug { - trace::trace().debug( - "pattern5/lower", - &format!("Counter ValueId: {:?}", counter_id), - ); - } - - // Step 3: Extract limit value - let limit = extract_limit_value(ctx.body)?; - if debug { - trace::trace().debug( - "pattern5/lower", - &format!("Extracted limit value: {}", limit), - ); - } - - // Step 4: Generate JoinIR - use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; - use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, - MirLikeInst, - }; - - let mut join_value_space = JoinValueSpace::new(); - - let mut join_module = JoinModule::new(); - - // Function IDs - let main_id = JoinFuncId::new(0); - let loop_step_id = JoinFuncId::new(1); - let k_exit_id = JoinFuncId::new(2); - - // ValueId allocation - // Phase 286 P1: Use alloc_param() for function parameters (Param region: 100-999) - // Use alloc_local() for local variables (Local region: 1000+) - - // main() params/locals - let counter_init = join_value_space.alloc_param(); // ValueId(100) - initial counter (PARAM) - let loop_result = join_value_space.alloc_local(); // ValueId(1000) - result from loop_step (LOCAL) - - // loop_step params/locals - let counter_param = join_value_space.alloc_param(); // ValueId(101) - parameter (PARAM) - let const_1 = join_value_space.alloc_local(); // ValueId(1001) - increment constant (LOCAL) - let counter_next = join_value_space.alloc_local(); // ValueId(1002) - counter + 1 (LOCAL) - let const_limit = join_value_space.alloc_local(); // ValueId(1003) - limit constant (LOCAL) - let break_cond = join_value_space.alloc_local(); // ValueId(1004) - counter_next == LIMIT (LOCAL) - - // k_exit params/locals - let counter_exit = join_value_space.alloc_param(); // ValueId(102) - exit parameter (PARAM) - - // return 0 constant - let const_0 = join_value_space.alloc_local(); // Local constant (LOCAL region) - - // ================================================================== - // main() function - // ================================================================== - let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![counter_init]); - - // result = loop_step(counter_init) - main_func.body.push(JoinInst::Call { - func: loop_step_id, - args: vec![counter_init], - k_next: None, - dst: Some(loop_result), - }); - - // return 0 (Pattern 5 doesn't produce a value, but we return 0 by convention) - main_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_0, - value: ConstValue::Integer(0), - })); - - main_func.body.push(JoinInst::Ret { - value: Some(const_0), - }); - - join_module.add_function(main_func); - - // ================================================================== - // loop_step(counter) function - post-increment pattern - // ================================================================== - let mut loop_step_func = - JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![counter_param]); - - // counter_next = counter + 1 - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::BinOp { - dst: counter_next, - op: BinOpKind::Add, - lhs: counter_param, - rhs: const_1, - })); - - // break_cond = (counter_next == LIMIT) - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Const { - dst: const_limit, - value: ConstValue::Integer(limit), - })); - - loop_step_func - .body - .push(JoinInst::Compute(MirLikeInst::Compare { - dst: break_cond, - op: CompareOp::Eq, - lhs: counter_next, - rhs: const_limit, - })); - - // Jump(k_exit, [counter_next], cond=break_cond) - loop_step_func.body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![counter_next], - cond: Some(break_cond), - }); - - // Call(loop_step, [counter_next]) - tail recursion - loop_step_func.body.push(JoinInst::Call { - func: loop_step_id, - args: vec![counter_next], - k_next: None, // tail call - dst: None, - }); - - join_module.add_function(loop_step_func); - - // ================================================================== - // k_exit(counter_exit) function - // ================================================================== - let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![counter_exit]); - - // Return counter_exit (the final counter value) instead of const 0 - k_exit_func.body.push(JoinInst::Ret { - value: Some(counter_exit), - }); - - join_module.add_function(k_exit_func); - - // Set entry point - join_module.entry = Some(main_id); - - if debug { - trace::trace().debug( - "pattern5/lower", - "Generated JoinIR: main, loop_step, k_exit", - ); - } - - // Step 5: Create CarrierInfo for the counter (Phase 131-11-D) - // Note: counter is the loop variable, NOT a separate carrier - use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; - - let carrier_info = CarrierInfo { - loop_var_name: counter_name.clone(), - loop_var_id: counter_id, - carriers: vec![], // No separate carriers - counter is the loop variable itself - trim_helper: None, - promoted_loopbodylocals: Vec::new(), - #[cfg(feature = "normalized_dev")] - promoted_bindings: std::collections::BTreeMap::new(), - }; - - // Step 6: Create ExitMeta with counter as exit value - // Phase 131-11-D: The counter flows through jump_args and must be registered in exit_values - let exit_meta = ExitMeta { - exit_values: vec![(counter_name.clone(), counter_exit)], - }; - - // Step 7: Build boundary and merge - use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector; - use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; - - let exit_bindings = ExitMetaCollector::collect(builder, &exit_meta, Some(&carrier_info), debug); - - let boundary = JoinInlineBoundaryBuilder::new() - .with_inputs( - vec![counter_init], // JoinIR main() parameter - vec![counter_id], // Host counter value - ) - .with_exit_bindings(exit_bindings) - .with_loop_var_name(Some(counter_name.clone())) // Phase 131-11-D: For LoopHeaderPhiBuilder - .with_carrier_info(carrier_info) // Phase 131-11-D: For exit line reconnection - .build(); - - // Step 7: Execute conversion pipeline - use super::conversion_pipeline::JoinIRConversionPipeline; - let _ = JoinIRConversionPipeline::execute( - builder, - join_module, - Some(&boundary), - "pattern5", - debug, - )?; - - // Step 8: Return Void (loops don't produce values) - let void_val = crate::mir::builder::emission::constant::emit_void(builder); - - if debug { - trace::trace().debug( - "pattern5/lower", - &format!("Pattern 5 lowering complete, returning Void {:?}", void_val), - ); - } - - Ok(Some(void_val)) -} diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index d48ed8c7..fb43c231 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -358,18 +358,14 @@ pub(crate) struct LoopPatternEntry { /// /// **IMPORTANT**: Patterns are tried in array order (SSOT). /// Array order defines priority - earlier entries are tried first. -/// Pattern5 (most specific) → Pattern4 → Pattern3 → Pattern1 → Pattern2 +/// Pattern4 → Pattern8 → Pattern9 → Pattern3 → Pattern1 → Pattern2 /// /// # Current Patterns (Structure-based detection, established Phase 131-11+) /// -/// Pattern detection strategies (updated Phase 282 P0): +/// Pattern detection strategies (updated Phase 286): /// - Pattern6/7: ExtractionBased (Plan line, Phase 273+) -/// - Pattern8/9: ExtractionBased (extraction functions, already implemented) -/// - Pattern1-5: StructureBased (LoopFeatures classification, legacy) -/// -/// - 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 +/// - Pattern5/8/9: ExtractionBased (extraction functions, Plan line Phase 286+) +/// - Pattern1-4: StructureBased (LoopFeatures classification, legacy) /// /// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako) /// - Detection: pattern_kind == Pattern4Continue @@ -388,12 +384,8 @@ pub(crate) struct LoopPatternEntry { /// - Structure: has_break && !has_continue /// /// Note: func_name is now only used for debug logging, not pattern detection +/// Phase 286: Pattern5 removed (migrated to Plan-based routing) 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, diff --git a/src/mir/builder/control_flow/plan/normalizer.rs b/src/mir/builder/control_flow/plan/normalizer.rs deleted file mode 100644 index bb3a30ed..00000000 --- a/src/mir/builder/control_flow/plan/normalizer.rs +++ /dev/null @@ -1,3294 +0,0 @@ -//! Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT) -//! -//! # Responsibilities -//! -//! - Convert DomainPlan to CorePlan (SSOT for pattern-specific knowledge) -//! - Generate ValueIds for CorePlan expressions -//! - Expand pattern-specific operations into generic CoreEffectPlan -//! -//! # Key Design Decision -//! -//! Normalizer is the ONLY place that knows pattern-specific semantics. -//! Lowerer processes CorePlan without any pattern knowledge. - -use super::{ - CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, - ScanWithInitPlan, SplitScanPlan, Pattern1SimpleWhilePlan, Pattern9AccumConstLoopPlan, - Pattern8BoolPredicateScanPlan, Pattern3IfPhiPlan, Pattern2BreakPlan, - Pattern5InfiniteEarlyExitPlan, Pattern5ExitKind, -}; -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::MirBuilder; -use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType}; -use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; -use crate::mir::basic_block::EdgeArgs; -use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; -use std::collections::BTreeMap; -use crate::mir::{BasicBlockId, ValueId}; - -// ============================================================================ -// Phase 286 P2.8: Normalizer Hygiene Helpers -// ============================================================================ - -/// Standard 5-block layout for simple loops (Pattern1/4/8/9) -/// -/// CFG: preheader → header → body → step → header (back-edge) -/// ↓ -/// after -#[derive(Debug, Clone, Copy)] -struct LoopBlocksStandard5 { - preheader_bb: BasicBlockId, - header_bb: BasicBlockId, - body_bb: BasicBlockId, - step_bb: BasicBlockId, - after_bb: BasicBlockId, -} - -impl LoopBlocksStandard5 { - /// Allocate 5 blocks for a standard loop - fn allocate(builder: &mut MirBuilder) -> Result { - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - Ok(Self { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - }) - } -} - -/// Extended 8-block layout for if-phi loops (Pattern3) -/// -/// CFG: preheader → header → body → then/else → merge → step → header -/// ↓ -/// after -#[derive(Debug, Clone, Copy)] -struct LoopBlocksWithIfPhi { - preheader_bb: BasicBlockId, - header_bb: BasicBlockId, - body_bb: BasicBlockId, - then_bb: BasicBlockId, - else_bb: BasicBlockId, - merge_bb: BasicBlockId, - step_bb: BasicBlockId, - after_bb: BasicBlockId, -} - -impl LoopBlocksWithIfPhi { - /// Allocate 8 blocks for an if-phi loop - fn allocate(builder: &mut MirBuilder) -> Result { - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let then_bb = builder.next_block_id(); - let else_bb = builder.next_block_id(); - let merge_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - Ok(Self { - preheader_bb, - header_bb, - body_bb, - then_bb, - else_bb, - merge_bb, - step_bb, - after_bb, - }) - } -} - -/// Create phi_bindings map from variable name-ValueId pairs -/// -/// phi_bindings are used to override variable_map lookups during AST lowering, -/// ensuring loop variables reference PHI destinations instead of initial values. -fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap { - bindings - .iter() - .map(|(name, id)| (name.to_string(), *id)) - .collect() -} - -/// Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT) -pub(in crate::mir::builder) struct PlanNormalizer; - -impl PlanNormalizer { - /// Normalize DomainPlan to CorePlan - /// - /// This is the SSOT for pattern-specific knowledge expansion. - /// All pattern semantics (scan, split, etc.) are expanded here. - pub(in crate::mir::builder) fn normalize( - builder: &mut MirBuilder, - domain: DomainPlan, - ctx: &LoopPatternContext, - ) -> Result { - match domain { - DomainPlan::ScanWithInit(parts) => Self::normalize_scan_with_init(builder, parts, ctx), - DomainPlan::SplitScan(parts) => Self::normalize_split_scan(builder, parts, ctx), - DomainPlan::Pattern4Continue(parts) => Self::normalize_pattern4_continue(builder, parts, ctx), - DomainPlan::Pattern1SimpleWhile(parts) => Self::normalize_pattern1_simple_while(builder, parts, ctx), - DomainPlan::Pattern9AccumConstLoop(parts) => Self::normalize_pattern9_accum_const_loop(builder, parts, ctx), - DomainPlan::Pattern8BoolPredicateScan(parts) => Self::normalize_pattern8_bool_predicate_scan(builder, parts, ctx), - DomainPlan::Pattern3IfPhi(parts) => Self::normalize_pattern3_if_phi(builder, parts, ctx), - DomainPlan::Pattern2Break(parts) => Self::normalize_pattern2_break(builder, parts, ctx), - DomainPlan::Pattern5InfiniteEarlyExit(parts) => Self::normalize_pattern5_infinite_early_exit(builder, parts, ctx), - } - } - - /// ScanWithInit → CorePlan 変換 - /// - /// Expands scan-specific semantics into generic CorePlan: - /// - header_effects: one=1, needle_len, len, bound, cond_loop - /// - body: i+needle_len, substring, cond_match - /// - step_effects: i_next = i + 1 - fn normalize_scan_with_init( - builder: &mut MirBuilder, - parts: ScanWithInitPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/scan_with_init", - &format!( - "Phase 273 P1: Normalizing ScanWithInit for {}", - ctx.func_name - ), - ); - } - - // P1 Scope: Forward scan (step=1) only - if parts.step_lit != 1 { - return Err(format!( - "[normalizer] P1 scope: only forward scan supported (step={})", - parts.step_lit - )); - } - - // Step 1: Get host ValueIds for variables - let s_host = builder - .variable_ctx - .variable_map - .get(&parts.haystack) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.haystack))?; - - let needle_host = builder - .variable_ctx - .variable_map - .get(&parts.needle) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.needle))?; - - let i_init_val = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; - - // Step 2: Capture preheader block (entry to loop) for PHI input - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; - - // Step 3: Allocate BasicBlockIds for 5 blocks - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - let found_bb = builder.next_block_id(); - - // Step 4: Allocate ValueIds for CorePlan - let i_current = builder.next_value_id(); // PHI destination - builder - .type_ctx - .value_types - .insert(i_current, MirType::Integer); - - let one_val = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(one_val, MirType::Integer); - - let needle_len_val = if parts.dynamic_needle { - let v = builder.next_value_id(); - builder.type_ctx.value_types.insert(v, MirType::Integer); - v - } else { - one_val // reuse one_val for fixed needle - }; - - let len_val = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(len_val, MirType::Integer); - - let bound_val = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(bound_val, MirType::Integer); - - let cond_loop = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(cond_loop, MirType::Bool); - - let i_plus_needle_len = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_plus_needle_len, MirType::Integer); - - let window_val = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(window_val, MirType::String); - - let cond_match = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(cond_match, MirType::Bool); - - let i_next_val = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_next_val, MirType::Integer); - - if debug { - trace_logger.debug( - "normalizer/scan_with_init", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}, found={:?}", - preheader_bb, header_bb, body_bb, step_bb, after_bb, found_bb - ), - ); - } - - // Step 5: Build header_effects (emitted in header_bb) - let mut header_effects = vec![ - // one = 1 - CoreEffectPlan::Const { - dst: one_val, - value: ConstValue::Integer(1), - }, - ]; - - // needle_len = needle.length() if dynamic, else reuse one_val - if parts.dynamic_needle { - header_effects.push(CoreEffectPlan::MethodCall { - dst: Some(needle_len_val), - object: needle_host, - method: "length".to_string(), - args: vec![], - effects: EffectMask::PURE.add(Effect::Io), - }); - } - - // len = s.length() - header_effects.push(CoreEffectPlan::MethodCall { - dst: Some(len_val), - object: s_host, - method: "length".to_string(), - args: vec![], - effects: EffectMask::PURE.add(Effect::Io), - }); - - // bound = len - needle_len - header_effects.push(CoreEffectPlan::BinOp { - dst: bound_val, - lhs: len_val, - op: BinaryOp::Sub, - rhs: needle_len_val, - }); - - // cond_loop = i <= bound - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: i_current, - op: CompareOp::Le, - rhs: bound_val, - }); - - // Step 6: Build body (emitted in body_bb) - let body = vec![ - // i_plus_needle_len = i + needle_len - CorePlan::Effect(CoreEffectPlan::BinOp { - dst: i_plus_needle_len, - lhs: i_current, - op: BinaryOp::Add, - rhs: needle_len_val, - }), - // window = s.substring(i, i + needle_len) - CorePlan::Effect(CoreEffectPlan::MethodCall { - dst: Some(window_val), - object: s_host, - method: "substring".to_string(), - args: vec![i_current, i_plus_needle_len], - effects: EffectMask::PURE.add(Effect::Io), - }), - // cond_match = window == needle - CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_match, - lhs: window_val, - op: CompareOp::Eq, - rhs: needle_host, - }), - ]; - - // Step 7: Build step_effects (emitted in step_bb) - let step_effects = vec![ - // i_next = i + 1 - CoreEffectPlan::BinOp { - dst: i_next_val, - lhs: i_current, - op: BinaryOp::Add, - rhs: one_val, - }, - ]; - - // Step 8: Build block_effects (Phase 273 P2) - // SSOT ordering: preheader (empty), header, body (already in body plan), step - let block_effects = vec![ - (preheader_bb, vec![]), // No effects in preheader - (header_bb, header_effects.clone()), - (body_bb, vec![]), // Body effects are in body CorePlan - (step_bb, step_effects.clone()), - ]; - - // Step 9: Build phis (Phase 273 P2) - let phis = vec![CorePhiInfo { - block: header_bb, - dst: i_current, - inputs: vec![ - (preheader_bb, i_init_val), - (step_bb, i_next_val), - ], - tag: format!("loop_carrier_{}", parts.loop_var), - }]; - - // Step 10: Build Frag (Phase 273 P2) - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let ret_found_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![i_current], - }; - - // Phase 281 P2: compose::cleanup() for early exit pattern - // Reason: `found_bb` is early Return, handled by cleanup() wiring - // Structure: main Frag (header, body, step) + cleanup Frag (found Return) - use crate::mir::builder::control_flow::edgecfg::api::compose; - - // Build main Frag (loop structure: header, body, step) - let main_branches = vec![ - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - BranchStub { - from: body_bb, - cond: cond_match, - then_target: found_bb, // Early exit (handled by cleanup) - then_args: empty_args.clone(), - else_target: step_bb, - else_args: empty_args.clone(), - }, - ]; - - let main_wires = vec![ - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let main_frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), // No exits from main (only wires) - wires: main_wires, - branches: main_branches, - }; - - // Build cleanup Frag (found_bb Return) - let cleanup_exits = vec![ - EdgeStub { - from: found_bb, - kind: ExitKind::Return, - target: None, // Will be wired by cleanup() - args: ret_found_args, - }, - ]; - - let cleanup_frag = Frag { - entry: found_bb, // cleanup entry (not used, but required) - exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]), - wires: vec![], // Return is in exits, not wires - branches: vec![], - }; - - // Compose! normal_target=None, ret_target=None → Return を上へ伝播 - let frag = compose::cleanup(main_frag, cleanup_frag, None, None) - .expect("compose::cleanup() failed in normalize_scan_with_init"); - - // Step 11: Build final_values (Phase 273 P2) - let final_values = vec![(parts.loop_var.clone(), i_current)]; - - // Step 12: Build CoreLoopPlan (Phase 273 P3: all generalized fields REQUIRED) - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb, - body, // Keep body for lowerer body emission - cond_loop, - cond_match, - // Phase 273 P3: Generalized fields (REQUIRED - SSOT) - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/scan_with_init", - "CorePlan construction complete", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// SplitScan → CorePlan 変換 - /// - /// Expands split-specific semantics into generic CorePlan: - /// - 2 carriers: i (loop index), start (segment start) - /// - 6 blocks: preheader, header, body, then, else, step, after - /// - 4 PHI nodes: header (i_current, start_current) + step (i_next, start_next) - /// - Side effect: result.push(segment) in then_bb - fn normalize_split_scan( - builder: &mut MirBuilder, - parts: SplitScanPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - use crate::mir::builder::control_flow::edgecfg::api::compose; - use std::collections::BTreeMap; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/split_scan", - &format!( - "Phase 273 P2: Normalizing SplitScan for {}", - ctx.func_name - ), - ); - } - - // Step 1: Get host ValueIds for variables - let s_host = builder - .variable_ctx - .variable_map - .get(&parts.s_var) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.s_var))?; - - let sep_host = builder - .variable_ctx - .variable_map - .get(&parts.sep_var) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.sep_var))?; - - let result_host = builder - .variable_ctx - .variable_map - .get(&parts.result_var) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.result_var))?; - - let i_init_val = builder - .variable_ctx - .variable_map - .get(&parts.i_var) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.i_var))?; - - let start_init_val = builder - .variable_ctx - .variable_map - .get(&parts.start_var) - .copied() - .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.start_var))?; - - // Step 2: Capture preheader block - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; - - // Step 3: Allocate BasicBlockIds for 6 blocks - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let then_bb = builder.next_block_id(); - let else_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - - // Step 4: Allocate ValueIds for PHI destinations (before blocks) - let i_current = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_current, MirType::Integer); - - let start_current = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(start_current, MirType::Integer); - - let i_next = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_next, MirType::Integer); - - let start_next = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(start_next, MirType::Integer); - - // Step 5: Allocate ValueIds for expressions - let sep_len = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(sep_len, MirType::Integer); - - let s_len = builder.next_value_id(); - builder.type_ctx.value_types.insert(s_len, MirType::Integer); - - let limit = builder.next_value_id(); - builder.type_ctx.value_types.insert(limit, MirType::Integer); - - let cond_loop = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(cond_loop, MirType::Bool); - - let i_plus_sep = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_plus_sep, MirType::Integer); - - let chunk = builder.next_value_id(); - builder.type_ctx.value_types.insert(chunk, MirType::String); - - let cond_match = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(cond_match, MirType::Bool); - - let segment = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(segment, MirType::String); - - let start_next_then = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(start_next_then, MirType::Integer); - - let one = builder.next_value_id(); - builder.type_ctx.value_types.insert(one, MirType::Integer); - - let i_next_else = builder.next_value_id(); - builder - .type_ctx - .value_types - .insert(i_next_else, MirType::Integer); - - if debug { - trace_logger.debug( - "normalizer/split_scan", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, then_bb, else_bb, step_bb, after_bb - ), - ); - } - - // Step 6: Build header_effects - let header_effects = vec![ - // sep_len = sep.length() - CoreEffectPlan::MethodCall { - dst: Some(sep_len), - object: sep_host, - method: "length".to_string(), - args: vec![], - effects: EffectMask::PURE.add(Effect::Io), - }, - // s_len = s.length() - CoreEffectPlan::MethodCall { - dst: Some(s_len), - object: s_host, - method: "length".to_string(), - args: vec![], - effects: EffectMask::PURE.add(Effect::Io), - }, - // limit = s_len - sep_len - CoreEffectPlan::BinOp { - dst: limit, - lhs: s_len, - op: BinaryOp::Sub, - rhs: sep_len, - }, - // cond_loop = i <= limit - CoreEffectPlan::Compare { - dst: cond_loop, - lhs: i_current, - op: CompareOp::Le, - rhs: limit, - }, - ]; - - // Step 7: Build body effects and plans - let body = vec![ - // i_plus_sep = i + sep_len - CorePlan::Effect(CoreEffectPlan::BinOp { - dst: i_plus_sep, - lhs: i_current, - op: BinaryOp::Add, - rhs: sep_len, - }), - // chunk = s.substring(i, i_plus_sep) - CorePlan::Effect(CoreEffectPlan::MethodCall { - dst: Some(chunk), - object: s_host, - method: "substring".to_string(), - args: vec![i_current, i_plus_sep], - effects: EffectMask::PURE.add(Effect::Io), - }), - // cond_match = chunk == sep - CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_match, - lhs: chunk, - op: CompareOp::Eq, - rhs: sep_host, - }), - ]; - - // Step 8: Build then_effects (push + updates) - let then_effects = vec![ - // segment = s.substring(start, i) - CoreEffectPlan::MethodCall { - dst: Some(segment), - object: s_host, - method: "substring".to_string(), - args: vec![start_current, i_current], - effects: EffectMask::PURE.add(Effect::Io), - }, - // result.push(segment) - Side effect! - CoreEffectPlan::MethodCall { - dst: None, // push returns Void - object: result_host, - method: "push".to_string(), - args: vec![segment], - effects: EffectMask::MUT, - }, - // start_next_then = i + sep_len - CoreEffectPlan::BinOp { - dst: start_next_then, - lhs: i_current, - op: BinaryOp::Add, - rhs: sep_len, - }, - ]; - - // Step 9: Build else_effects (increment i) - let else_effects = vec![ - // one = const 1 - CoreEffectPlan::Const { - dst: one, - value: ConstValue::Integer(1), - }, - // i_next_else = i + 1 - CoreEffectPlan::BinOp { - dst: i_next_else, - lhs: i_current, - op: BinaryOp::Add, - rhs: one, - }, - ]; - - // Step 9.5: Build Frags for compose::if_() (Phase 281 P0) - // Create empty_args for EdgeStub construction - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - // Build then_frag: entry=then_bb, Normal exit to step - // IMPORTANT: Use explicit EdgeStub construction with empty_args (not without_args) - let mut then_exits = BTreeMap::new(); - then_exits.insert( - ExitKind::Normal, - vec![EdgeStub { - from: then_bb, - kind: ExitKind::Normal, - target: None, // Unresolved exit (compose will wire to join) - args: empty_args.clone(), // CarriersOnly layout - }], - ); - let then_frag = Frag { - entry: then_bb, - exits: then_exits, - wires: vec![], - branches: vec![], - }; - - // Build else_frag: entry=else_bb, Normal exit to step - // IMPORTANT: Use explicit EdgeStub construction with empty_args (not without_args) - let mut else_exits = BTreeMap::new(); - else_exits.insert( - ExitKind::Normal, - vec![EdgeStub { - from: else_bb, - kind: ExitKind::Normal, - target: None, // Unresolved exit (compose will wire to join) - args: empty_args.clone(), // CarriersOnly layout - }], - ); - let else_frag = Frag { - entry: else_bb, - exits: else_exits, - wires: vec![], - branches: vec![], - }; - - // Build step_frag: entry=step_bb, EMPTY (no back-edge here) - // Reason: step→header back-edge is loop-level responsibility, not body_if_frag's - // The back-edge will be added manually in Step 12 (final merge) - let step_frag = Frag { - entry: step_bb, - exits: BTreeMap::new(), - wires: vec![], // Empty - no back-edge here - branches: vec![], - }; - - // Phase 281 P0: Use compose::if_() for body branch (cond_match: then/else → step) - let body_if_frag = compose::if_( - body_bb, // header: where cond_match is evaluated - cond_match, // condition ValueId - then_frag, // then branch - empty_args.clone(), // then_entry_args - else_frag, // else branch - empty_args.clone(), // else_entry_args - step_frag, // join target (step_bb) - ); - - // Step 10: Build block_effects (SSOT ordering: preheader, header, body, then, else, step) - let block_effects = vec![ - (preheader_bb, vec![]), // No effects in preheader - (header_bb, header_effects.clone()), - (body_bb, vec![]), // Body effects are in body CorePlan - (then_bb, then_effects), - (else_bb, else_effects), - (step_bb, vec![]), // No effects in step - ]; - - // Step 11: Build phis (4 PHIs: 2 in header + 2 in step) - let phis = vec![ - // Header PHI 1: i_current - CorePhiInfo { - block: header_bb, - dst: i_current, - inputs: vec![ - (preheader_bb, i_init_val), - (step_bb, i_next), - ], - tag: format!("loop_carrier_i_{}", parts.i_var), - }, - // Header PHI 2: start_current - CorePhiInfo { - block: header_bb, - dst: start_current, - inputs: vec![ - (preheader_bb, start_init_val), - (step_bb, start_next), - ], - tag: format!("loop_carrier_start_{}", parts.start_var), - }, - // Step PHI 1: i_next - CorePhiInfo { - block: step_bb, - dst: i_next, - inputs: vec![ - (then_bb, start_next_then), // i = start (from then) - (else_bb, i_next_else), // i = i + 1 (from else) - ], - tag: format!("step_phi_i_{}", parts.i_var), - }, - // Step PHI 2: start_next - CorePhiInfo { - block: step_bb, - dst: start_next, - inputs: vec![ - (then_bb, start_next_then), // start updated - (else_bb, start_current), // start unchanged - ], - tag: format!("step_phi_start_{}", parts.start_var), - }, - ]; - - // Step 12: Build Frag - Phase 281 P0 Complete - // - Header branch (hand-rolled): header → body/after - // - Body branch (composed): body → then/else → step (from compose::if_()) - // - Step back-edge (hand-rolled): step → header - let mut branches = vec![ - // Header branch (cond_loop) - hand-rolled - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - ]; - - // Merge body_if_frag branches (body → then/else) - branches.extend(body_if_frag.branches); - - // Merge body_if_frag wires (then/else → step) - let mut wires = Vec::new(); - wires.extend(body_if_frag.wires); - - // Add step back-edge (hand-rolled) - loop-level responsibility - wires.push(EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }); - - let mut exits = BTreeMap::new(); - for (kind, stubs) in body_if_frag.exits { - exits.insert(kind, stubs); - } - - let frag = Frag { - entry: header_bb, - exits, - wires, - branches, - }; - - // Step 13: Build final_values (i, start for post-loop) - let final_values = vec![ - (parts.i_var.clone(), i_current), - (parts.start_var.clone(), start_current), - ]; - - // Step 14: Build CoreLoopPlan (generalized fields only) - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit for split pattern - body, - cond_loop, - cond_match, - // Phase 273 P3: Generalized fields (REQUIRED - SSOT) - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/split_scan", - "CorePlan construction complete (6 blocks, 4 PHIs)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Phase 286 P2: Pattern4Continue → CorePlan 変換 - /// - /// Expands Pattern4 (Loop with Continue) semantics into generic CorePlan: - /// - CFG structure: 2-step branching + header PHI merge - /// - NO Select instruction (not in CoreEffectPlan) - /// - NO after PHI (header PHI handles all paths) - fn normalize_pattern4_continue( - builder: &mut MirBuilder, - parts: crate::mir::builder::control_flow::plan::Pattern4ContinuePlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern4_continue", - &format!( - "Phase 286 P2: Normalizing Pattern4Continue for {} (loop_var: {}, carriers: {:?})", - ctx.func_name, parts.loop_var, parts.carrier_vars - ), - ); - } - - // P2 PoC Scope: Single carrier only - if parts.carrier_vars.len() != 1 { - return Err(format!( - "[normalizer] P2 PoC scope: only single carrier supported (found: {})", - parts.carrier_vars.len() - )); - } - - let carrier_var = &parts.carrier_vars[0]; - - // Step 1: Get host ValueIds for variables - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; - - let carrier_init = builder - .variable_ctx - .variable_map - .get(carrier_var) - .copied() - .ok_or_else(|| format!("[normalizer] Carrier variable {} not found", carrier_var))?; - - // Step 2: Capture preheader block - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; - - // Step 3: Allocate BasicBlockIds for 8 blocks (2-step branching architecture) - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let continue_path_bb = builder.next_block_id(); - let normal_path_bb = builder.next_block_id(); - let step_continue_bb = builder.next_block_id(); - let step_normal_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - - if debug { - trace_logger.debug( - "normalizer/pattern4_continue", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, continue_path={:?}, normal_path={:?}, step_continue={:?}, step_normal={:?}, after={:?}", - preheader_bb, header_bb, body_bb, continue_path_bb, normal_path_bb, step_continue_bb, step_normal_bb, after_bb - ), - ); - } - - // Step 4: Allocate ValueIds for loop control and carriers - - // Loop variable PHI dst (header PHI) - let loop_var_current = builder.alloc_typed(MirType::Integer); - - // Carrier PHI dst (header PHI) - let carrier_current = builder.alloc_typed(MirType::Integer); - - // Step 4.5: Build phi_bindings (Phase 286 P2.8: use create_phi_bindings) - let phi_bindings = create_phi_bindings(&[ - (&parts.loop_var, loop_var_current), - (carrier_var, carrier_current), - ]); - - // Loop condition value - let cond_loop = builder.alloc_typed(MirType::Bool); - - // Continue condition value - let cond_continue = builder.alloc_typed(MirType::Bool); - - // Loop increment values (continue path) - let loop_var_cont_next = builder.alloc_typed(MirType::Integer); - - // Carrier update value (normal path) - let carrier_updated = builder.alloc_typed(MirType::Integer); - - // Loop increment values (normal path) - let loop_var_norm_next = builder.alloc_typed(MirType::Integer); - - // Step 5: Lower AST expressions to get operands and const definitions - // Pass phi_bindings to ensure loop variables reference PHI dst - - // Lower loop condition (e.g., `i < 6`) - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; - - // Lower continue condition (e.g., `i == 0`) - let (cont_cond_lhs, cont_cond_op, cont_cond_rhs, cont_cond_consts) = - Self::lower_compare_ast(&parts.continue_condition, builder, &phi_bindings)?; - - // Lower carrier update expression (e.g., `sum = sum + i`) - let carrier_update_ast = parts.carrier_updates.get(carrier_var) - .ok_or_else(|| format!("[normalizer] Carrier update for {} not found", carrier_var))?; - let (carrier_update_lhs, carrier_update_op, carrier_update_rhs, carrier_update_consts) = - Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?; - - // Lower loop increment (e.g., `i = i + 1`) - let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 6: Build header_effects (const definitions + loop condition check) - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 7: Build body (as CorePlan items, NOT block_effects) - let mut body = Vec::new(); - // Add const definitions - for const_effect in cont_cond_consts { - body.push(CorePlan::Effect(const_effect)); - } - // Add continue condition check - body.push(CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_continue, - lhs: cont_cond_lhs, - op: cont_cond_op, - rhs: cont_cond_rhs, - })); - - // Step 8: Build continue_path effects (const definitions + increment loop var + passthrough carrier) - let mut continue_path_effects = loop_inc_consts.clone(); - continue_path_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_cont_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 8.5: step_continue has NO effects - // Carrier passthrough: carrier PHI input directly references carrier_current - // No Add 0 workaround needed - PHI inputs can reference the PHI dst from another path - - // Step 9: Build normal_path effects (const definitions + carrier update + loop increment) - let mut normal_path_effects = carrier_update_consts; - normal_path_effects.push(CoreEffectPlan::BinOp { - dst: carrier_updated, - lhs: carrier_update_lhs, - op: carrier_update_op, - rhs: carrier_update_rhs, - }); - normal_path_effects.extend(loop_inc_consts); - normal_path_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_norm_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 10: Build block_effects - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // Body effects are in body CorePlan field - (continue_path_bb, continue_path_effects), - (normal_path_bb, normal_path_effects), - (step_continue_bb, vec![]), // No effects - carrier passthrough is handled by PHI - (step_normal_bb, vec![]), - ]; - - // Step 11: Build PHIs (header PHI with 3 inputs for 2-step branching) - let phis = vec![ - // Loop variable PHI: (preheader, init), (step_continue, cont), (step_normal, norm) - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_continue_bb, loop_var_cont_next), - (step_normal_bb, loop_var_norm_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - // Carrier PHI: (preheader, init), (step_continue, current), (step_normal, updated) - // Continue path: carrier unchanged - directly reference carrier_current (no intermediate value) - CorePhiInfo { - block: header_bb, - dst: carrier_current, - inputs: vec![ - (preheader_bb, carrier_init), - (step_continue_bb, carrier_current), // Continue path: carrier unchanged (self-reference OK) - (step_normal_bb, carrier_updated), // Normal path: carrier updated - ], - tag: format!("carrier_{}", carrier_var), - }, - ]; - - // Step 12: Build Frag (2-step branching structure) - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - // Header: loop condition check - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - // Body: continue condition check (2-step branching starts here) - BranchStub { - from: body_bb, - cond: cond_continue, - then_target: continue_path_bb, // Continue path - then_args: empty_args.clone(), - else_target: normal_path_bb, // Normal path - else_args: empty_args.clone(), - }, - ]; - - let wires = vec![ - // Continue path: continue_path → step_continue → header - EdgeStub { - from: continue_path_bb, - kind: ExitKind::Normal, - target: Some(step_continue_bb), - args: empty_args.clone(), - }, - EdgeStub { - from: step_continue_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - // Normal path: normal_path → step_normal → header - EdgeStub { - from: normal_path_bb, - kind: ExitKind::Normal, - target: Some(step_normal_bb), - args: empty_args.clone(), - }, - EdgeStub { - from: step_normal_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 13: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - (carrier_var.clone(), carrier_current), - ]; - - // Step 14: Build CoreLoopPlan - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb: step_normal_bb, // Use step_normal as primary step block - after_bb, - found_bb: after_bb, // No early exit, so found = after - body, // Body CorePlan with continue condition check - cond_loop, - cond_match: cond_continue, // Use continue condition as match condition - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern4_continue", - "CorePlan construction complete (2-step branching with header PHI merge)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects) - fn lower_compare_ast( - ast: &crate::ast::ASTNode, - builder: &mut MirBuilder, - phi_bindings: &BTreeMap, - ) -> Result<(crate::mir::ValueId, CompareOp, crate::mir::ValueId, Vec), String> { - use crate::ast::{ASTNode, BinaryOperator}; - - match ast { - ASTNode::BinaryOp { operator, left, right, .. } => { - let op = match operator { - BinaryOperator::Less => CompareOp::Lt, - BinaryOperator::LessEqual => CompareOp::Le, - BinaryOperator::Greater => CompareOp::Gt, - BinaryOperator::GreaterEqual => CompareOp::Ge, - BinaryOperator::Equal => CompareOp::Eq, - BinaryOperator::NotEqual => CompareOp::Ne, - _ => return Err(format!("[normalizer] Unsupported compare operator: {:?}", operator)), - }; - - let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?; - let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?; - - lhs_consts.extend(rhs_consts); - - Ok((lhs, op, rhs, lhs_consts)) - } - _ => Err(format!("[normalizer] Expected BinaryOp for compare, got: {:?}", ast)), - } - } - - /// Helper: Lower BinOp AST to (lhs ValueId, op, rhs ValueId, const_effects) - fn lower_binop_ast( - ast: &crate::ast::ASTNode, - builder: &mut MirBuilder, - phi_bindings: &BTreeMap, - ) -> Result<(crate::mir::ValueId, BinaryOp, crate::mir::ValueId, Vec), String> { - use crate::ast::{ASTNode, BinaryOperator}; - - match ast { - ASTNode::BinaryOp { operator, left, right, .. } => { - let op = match operator { - BinaryOperator::Add => BinaryOp::Add, - BinaryOperator::Subtract => BinaryOp::Sub, - BinaryOperator::Multiply => BinaryOp::Mul, - BinaryOperator::Divide => BinaryOp::Div, - _ => return Err(format!("[normalizer] Unsupported binary operator: {:?}", operator)), - }; - - let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?; - let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?; - - lhs_consts.extend(rhs_consts); - - Ok((lhs, op, rhs, lhs_consts)) - } - _ => Err(format!("[normalizer] Expected BinOp, got: {:?}", ast)), - } - } - - /// Helper: Lower value AST to (ValueId, const_effects) - /// Returns the ValueId and any Const instructions needed to define literals - /// - /// phi_bindings: PHI dst for loop variables (takes precedence over variable_map) - fn lower_value_ast( - ast: &crate::ast::ASTNode, - builder: &mut MirBuilder, - phi_bindings: &BTreeMap, - ) -> Result<(crate::mir::ValueId, Vec), String> { - use crate::ast::{ASTNode, LiteralValue}; - - match ast { - ASTNode::Variable { name, .. } => { - // PHI bindings take precedence (for loop variables in header/body) - if let Some(&phi_dst) = phi_bindings.get(name) { - return Ok((phi_dst, vec![])); - } - // Fallback to variable_map (for pre-loop init values) - if let Some(&value_id) = builder.variable_ctx.variable_map.get(name) { - Ok((value_id, vec![])) - } else { - Err(format!("[normalizer] Variable {} not found", name)) - } - } - ASTNode::Literal { value, .. } => { - // Allocate ValueId for literal and create Const instruction - let value_id = builder.next_value_id(); - let const_value = match value { - LiteralValue::Integer(n) => ConstValue::Integer(*n), - LiteralValue::String(s) => ConstValue::String(s.clone()), - LiteralValue::Bool(b) => ConstValue::Bool(*b), - _ => return Err(format!("[normalizer] Unsupported literal type: {:?}", value)), - }; - - builder.type_ctx.value_types.insert(value_id, MirType::Integer); - - // Return the ValueId and the Const instruction - let const_effect = CoreEffectPlan::Const { - dst: value_id, - value: const_value, - }; - - Ok((value_id, vec![const_effect])) - } - ASTNode::MethodCall { object, method, arguments, .. } => { - // Support method calls like s.length() in loop conditions - // This is needed for Pattern8 (i < s.length()) - let object_name = match object.as_ref() { - ASTNode::Variable { name, .. } => name.clone(), - _ => return Err(format!("[normalizer] Method call on non-variable object: {:?}", object)), - }; - - // Get object ValueId - let object_id = if let Some(&phi_dst) = phi_bindings.get(&object_name) { - phi_dst - } else if let Some(&value_id) = builder.variable_ctx.variable_map.get(&object_name) { - value_id - } else { - return Err(format!("[normalizer] Method call object {} not found", object_name)); - }; - - // Lower method call arguments - let mut arg_ids = Vec::new(); - let mut arg_effects = Vec::new(); - for arg in arguments { - let (arg_id, mut effects) = Self::lower_value_ast(arg, builder, phi_bindings)?; - arg_ids.push(arg_id); - arg_effects.append(&mut effects); - } - - // Allocate result ValueId - let result_id = builder.next_value_id(); - builder.type_ctx.value_types.insert(result_id, MirType::Integer); - - // Create MethodCall effect - arg_effects.push(CoreEffectPlan::MethodCall { - dst: Some(result_id), - object: object_id, - method: method.clone(), - args: arg_ids, - effects: EffectMask::PURE.add(Effect::Io), - }); - - Ok((result_id, arg_effects)) - } - _ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)), - } - } - - /// Phase 286 P2.1: Pattern1SimpleWhile → CorePlan 変換 - /// - /// Expands Pattern1 (Simple While Loop) semantics into generic CorePlan: - /// - CFG structure: preheader → header → body → step → header (back-edge) - /// - 1 PHI for loop variable in header - /// - No 2-step branching (simpler than Pattern4) - fn normalize_pattern1_simple_while( - builder: &mut MirBuilder, - parts: Pattern1SimpleWhilePlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern1_simple_while", - &format!( - "Phase 286 P2.1: Normalizing Pattern1SimpleWhile for {} (loop_var: {})", - ctx.func_name, parts.loop_var - ), - ); - } - - // Step 1: Get host ValueId for loop variable - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; - - // Step 2-3: Allocate BasicBlockIds (Phase 286 P2.8: use LoopBlocksStandard5) - let blocks = LoopBlocksStandard5::allocate(builder)?; - let LoopBlocksStandard5 { preheader_bb, header_bb, body_bb, step_bb, after_bb } = blocks; - - if debug { - trace_logger.debug( - "normalizer/pattern1_simple_while", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, step_bb, after_bb - ), - ); - } - - // Step 4: Allocate ValueIds for loop control - let loop_var_current = builder.alloc_typed(MirType::Integer); // PHI destination - let cond_loop = builder.alloc_typed(MirType::Bool); // Loop condition - let loop_var_next = builder.alloc_typed(MirType::Integer); // Incremented loop var - - // Step 5: Build phi_bindings (Phase 286 P2.8: use create_phi_bindings) - let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); - - // Step 6: Lower AST expressions - // Lower loop condition (e.g., `i < 3`) - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; - - // Lower loop increment (e.g., `i + 1`) - let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 7: Build header_effects (const definitions + loop condition check) - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 8: Build step_effects (const definitions + increment) - let mut step_effects = loop_inc_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 9: Build block_effects - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // Body is empty for simple increment-only loop - (step_bb, step_effects), - ]; - - // Step 10: Build PHI (single PHI for loop variable) - let phis = vec![ - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - ]; - - // Step 11: Build Frag - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - ]; - - let wires = vec![ - // body → step - EdgeStub { - from: body_bb, - kind: ExitKind::Normal, - target: Some(step_bb), - args: empty_args.clone(), - }, - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 12: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - ]; - - // Step 13: Build CoreLoopPlan - // Note: found_bb = after_bb (no early exit in Pattern1) - // Note: cond_match unused but required by struct, use cond_loop as placeholder - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit - body: vec![], // Body is empty for simple loop - cond_loop, - cond_match: cond_loop, // Placeholder (unused in Pattern1) - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern1_simple_while", - "CorePlan construction complete (4 blocks, 1 PHI)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Phase 286 P2.3: Pattern9AccumConstLoop → CorePlan 変換 - /// - /// Expands Pattern9 (Accumulator Const Loop) semantics into generic CorePlan: - /// - CFG structure: preheader → header → body → step → header (back-edge) - /// - 2 PHIs in header: loop variable (i) and accumulator (sum) - /// - Similar to Pattern1 but with an additional carrier - fn normalize_pattern9_accum_const_loop( - builder: &mut MirBuilder, - parts: Pattern9AccumConstLoopPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern9_accum_const_loop", - &format!( - "Phase 286 P2.3: Normalizing Pattern9AccumConstLoop for {} (loop_var: {}, acc_var: {})", - ctx.func_name, parts.loop_var, parts.acc_var - ), - ); - } - - // Step 1: Get host ValueIds for variables - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; - - let acc_var_init = builder - .variable_ctx - .variable_map - .get(&parts.acc_var) - .copied() - .ok_or_else(|| format!("[normalizer] Accumulator variable {} not found", parts.acc_var))?; - - // Step 2-3: Allocate BasicBlockIds (Phase 286 P2.8: use LoopBlocksStandard5) - let blocks = LoopBlocksStandard5::allocate(builder)?; - let LoopBlocksStandard5 { preheader_bb, header_bb, body_bb, step_bb, after_bb } = blocks; - - if debug { - trace_logger.debug( - "normalizer/pattern9_accum_const_loop", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, step_bb, after_bb - ), - ); - } - - // Step 4: Allocate ValueIds for loop control - let loop_var_current = builder.alloc_typed(MirType::Integer); // PHI destination for loop var - let acc_var_current = builder.alloc_typed(MirType::Integer); // PHI destination for accumulator - let cond_loop = builder.alloc_typed(MirType::Bool); // Loop condition - let acc_var_next = builder.alloc_typed(MirType::Integer); // Updated accumulator - let loop_var_next = builder.alloc_typed(MirType::Integer); // Incremented loop var - - // Step 5: Build phi_bindings (Phase 286 P2.8: use create_phi_bindings) - let phi_bindings = create_phi_bindings(&[ - (&parts.loop_var, loop_var_current), - (&parts.acc_var, acc_var_current), - ]); - - // Step 6: Lower AST expressions - // Lower loop condition (e.g., `i < 3`) - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; - - // Lower accumulator update (e.g., `sum + 1` or `sum + i`) - let (acc_update_lhs, acc_update_op, acc_update_rhs, acc_update_consts) = - Self::lower_binop_ast(&parts.acc_update, builder, &phi_bindings)?; - - // Lower loop increment (e.g., `i + 1`) - let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 7: Build header_effects (const definitions + loop condition check) - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 8: Build step_effects (accumulator update + loop increment) - let mut step_effects = acc_update_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: acc_var_next, - lhs: acc_update_lhs, - op: acc_update_op, - rhs: acc_update_rhs, - }); - step_effects.extend(loop_inc_consts); - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 9: Build block_effects - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // Body is empty (effects are in step) - (step_bb, step_effects), - ]; - - // Step 10: Build PHIs (2 PHIs: loop variable + accumulator) - let phis = vec![ - // Loop variable PHI - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - // Accumulator PHI - CorePhiInfo { - block: header_bb, - dst: acc_var_current, - inputs: vec![ - (preheader_bb, acc_var_init), - (step_bb, acc_var_next), - ], - tag: format!("acc_var_{}", parts.acc_var), - }, - ]; - - // Step 11: Build Frag - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - ]; - - let wires = vec![ - // body → step - EdgeStub { - from: body_bb, - kind: ExitKind::Normal, - target: Some(step_bb), - args: empty_args.clone(), - }, - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 12: Build final_values (both loop var and accumulator) - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - (parts.acc_var.clone(), acc_var_current), - ]; - - // Step 13: Build CoreLoopPlan - // Note: found_bb = after_bb (no early exit in Pattern9) - // Note: cond_match unused but required by struct, use cond_loop as placeholder - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit - body: vec![], // Body is empty (effects are in step) - cond_loop, - cond_match: cond_loop, // Placeholder (unused in Pattern9) - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern9_accum_const_loop", - "CorePlan construction complete (4 blocks, 2 PHIs)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Pattern8BoolPredicateScan → CorePlan 変換 (Phase 286 P2.4.1) - /// - /// Expands Pattern8 (Bool Predicate Scan) semantics into generic CorePlan: - /// - CFG structure: preheader → header → body → found (return false) / step → header - /// - 1 PHI for loop variable (i) - /// - Early exit: return false in found_bb - /// - Post-loop: return true in after_bb - fn normalize_pattern8_bool_predicate_scan( - builder: &mut MirBuilder, - parts: Pattern8BoolPredicateScanPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - use crate::mir::builder::control_flow::edgecfg::api::compose; - use std::collections::BTreeMap; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern8_bool_predicate_scan", - &format!( - "Phase 286 P2.4.1: Normalizing Pattern8 for {} (loop_var: {}, predicate: {}.{})", - ctx.func_name, parts.loop_var, parts.predicate_receiver, parts.predicate_method - ), - ); - } - - // P0 Scope: Forward scan (step=1) only - if parts.step_lit != 1 { - return Err(format!( - "[normalizer/pattern8] P0 scope: only forward scan supported (step={})", - parts.step_lit - )); - } - - // Step 1: Get host ValueIds for variables - let haystack_host = builder - .variable_ctx - .variable_map - .get(&parts.haystack) - .copied() - .ok_or_else(|| format!("[normalizer/pattern8] Variable {} not found", parts.haystack))?; - - let predicate_receiver_host = builder - .variable_ctx - .variable_map - .get(&parts.predicate_receiver) - .copied() - .ok_or_else(|| format!("[normalizer/pattern8] Variable {} not found", parts.predicate_receiver))?; - - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer/pattern8] Loop variable {} not found", parts.loop_var))?; - - // Step 2: Capture preheader block - let preheader_bb = builder - .current_block - .ok_or_else(|| "[normalizer/pattern8] No current block for loop entry".to_string())?; - - // Step 3: Allocate BasicBlockIds for 6 blocks - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let found_bb = builder.next_block_id(); // Early exit: return false - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); // Loop exit: return true - - if debug { - trace_logger.debug( - "normalizer/pattern8_bool_predicate_scan", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb - ), - ); - } - - // Step 4: Allocate ValueIds - let loop_var_current = builder.alloc_typed(MirType::Integer); // PHI destination - let cond_loop = builder.alloc_typed(MirType::Bool); // Loop condition - let one_val = builder.alloc_typed(MirType::Integer); // Const 1 - let i_plus_one = builder.alloc_typed(MirType::Integer); // i + 1 (for substring) - let ch = builder.alloc_typed(MirType::String); // s.substring(i, i+1) - let cond_predicate = builder.alloc_typed(MirType::Bool); // predicate result - let cond_not_predicate = builder.alloc_typed(MirType::Bool); // not predicate - let loop_var_next = builder.alloc_typed(MirType::Integer); // Incremented loop var - let true_val = builder.alloc_typed(MirType::Bool); // Const true - let false_val = builder.alloc_typed(MirType::Bool); // Const false - - // Step 5: Build phi_bindings (Phase 286 P2.8: use create_phi_bindings) - let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); - - // Step 6: Lower loop condition (e.g., `i < s.length()`) using shared helper - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; - - // Step 7: Build header_effects - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 8: Build body (predicate check) - using shared helpers for consistency - // Note: Pattern8-specific: substring + predicate method call + NOT comparison - let mut body = vec![ - // one = 1 - CorePlan::Effect(CoreEffectPlan::Const { - dst: one_val, - value: ConstValue::Integer(1), - }), - // false_val = false (for NOT comparison) - CorePlan::Effect(CoreEffectPlan::Const { - dst: false_val, - value: ConstValue::Bool(false), - }), - // i_plus_one = i + 1 - CorePlan::Effect(CoreEffectPlan::BinOp { - dst: i_plus_one, - lhs: loop_var_current, - op: BinaryOp::Add, - rhs: one_val, - }), - // ch = s.substring(i, i + 1) - CorePlan::Effect(CoreEffectPlan::MethodCall { - dst: Some(ch), - object: haystack_host, - method: "substring".to_string(), - args: vec![loop_var_current, i_plus_one], - effects: EffectMask::PURE.add(Effect::Io), - }), - // cond_predicate = me.is_digit(ch) - CorePlan::Effect(CoreEffectPlan::MethodCall { - dst: Some(cond_predicate), - object: predicate_receiver_host, - method: parts.predicate_method.clone(), - args: vec![ch], - effects: EffectMask::PURE.add(Effect::Io), - }), - // cond_not_predicate = not cond_predicate (compare with false) - CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_not_predicate, - lhs: cond_predicate, - op: CompareOp::Eq, - rhs: false_val, - }), - ]; - - // Step 9: Build step_effects (increment loop var) - reusing one_val from body - let step_effects = vec![ - // loop_var_next = i + 1 - CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: loop_var_current, - op: BinaryOp::Add, - rhs: one_val, - }, - ]; - - // Step 10: Build found_bb effects (empty - false_val already defined in body) - let found_effects = vec![]; - - // Step 11: Build after_bb effects (const true for return) - let after_effects = vec![ - CoreEffectPlan::Const { - dst: true_val, - value: ConstValue::Bool(true), - }, - ]; - - // Step 12: Build block_effects - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // Body effects are in body CorePlan - (found_bb, found_effects), - (step_bb, step_effects), - (after_bb, after_effects), - ]; - - // Step 13: Build PHI (single PHI for loop variable) - let phis = vec![ - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - ]; - - // Step 14: Build Frag using compose::cleanup for early exit - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let ret_false_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![false_val], - }; - - let ret_true_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![true_val], - }; - - // Build main Frag (loop structure: header, body, step) - let main_branches = vec![ - // Header: loop condition check - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - // Body: predicate check - BranchStub { - from: body_bb, - cond: cond_not_predicate, - then_target: found_bb, // Early exit: predicate failed - then_args: empty_args.clone(), - else_target: step_bb, // Continue: predicate passed - else_args: empty_args.clone(), - }, - ]; - - let main_wires = vec![ - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let main_frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires: main_wires, - branches: main_branches, - }; - - // Build cleanup Frag (found_bb Return false + after_bb Return true) - let cleanup_exits = vec![ - // found_bb: return false - EdgeStub { - from: found_bb, - kind: ExitKind::Return, - target: None, - args: ret_false_args, - }, - // after_bb: return true - EdgeStub { - from: after_bb, - kind: ExitKind::Return, - target: None, - args: ret_true_args, - }, - ]; - - let cleanup_frag = Frag { - entry: found_bb, // Not used by cleanup (multiple exits) - exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]), - wires: vec![], - branches: vec![], - }; - - // Compose! Return を上へ伝播 - let frag = compose::cleanup(main_frag, cleanup_frag, None, None) - .expect("compose::cleanup() failed in normalize_pattern8_bool_predicate_scan"); - - // Step 15: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - ]; - - // Step 16: Build CoreLoopPlan - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb, - body, - cond_loop, - cond_match: cond_not_predicate, // Use predicate negation as match condition - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern8_bool_predicate_scan", - "CorePlan construction complete (5 blocks, 1 PHI, 2 Return exits)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Phase 286 P2.6: Pattern3 (Loop with If-Phi) → CorePlan 変換 - /// - /// # Status: PoC Stub - /// - /// This is a minimal stub implementation for Phase 286 P2.6 PoC. - /// The extractor works and will be called, but this normalizer - /// returns an error to fallback to the legacy Pattern3 implementation. - /// - /// # CFG Structure (Design - not yet implemented) - /// ```text - /// preheader → header(PHI: i, carrier) → body(if_condition) - /// ↓ ↓ - /// after then | else - /// ↓ ↓ - /// merge(PHI: carrier) - /// ↓ - /// step(i_next) - /// ↓ - /// back-edge to header - /// ``` - /// Phase 286 P2.6.1: Pattern3IfPhi → CorePlan 変換 - /// - /// Expands Pattern3 (Loop with If-Phi) semantics into generic CorePlan: - /// - CFG structure: preheader → header → body → then/else → merge → step → header - /// - 3 PHIs: 2 in header (loop_var, carrier), 1 in merge (carrier_next) - /// - If-else branching with PHI merge (no Select instruction) - fn normalize_pattern3_if_phi( - builder: &mut MirBuilder, - parts: Pattern3IfPhiPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern3_if_phi", - &format!( - "Phase 286 P2.6.1: Normalizing Pattern3IfPhi for {} (loop_var: {}, carrier_var: {})", - ctx.func_name, parts.loop_var, parts.carrier_var - ), - ); - } - - // Step 1: Get host ValueIds for variables - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; - - let carrier_init = builder - .variable_ctx - .variable_map - .get(&parts.carrier_var) - .copied() - .ok_or_else(|| format!("[normalizer] Carrier variable {} not found", parts.carrier_var))?; - - // Step 2-3: Allocate BasicBlockIds (Phase 286 P2.8: use LoopBlocksWithIfPhi) - let blocks = LoopBlocksWithIfPhi::allocate(builder)?; - let LoopBlocksWithIfPhi { - preheader_bb, header_bb, body_bb, then_bb, else_bb, merge_bb, step_bb, after_bb - } = blocks; - - if debug { - trace_logger.debug( - "normalizer/pattern3_if_phi", - &format!( - "Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, merge={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, then_bb, else_bb, merge_bb, step_bb, after_bb - ), - ); - } - - // Step 4: Allocate ValueIds for loop control - let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let carrier_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let cond_loop = builder.alloc_typed(MirType::Bool); // header condition - let cond_if = builder.alloc_typed(MirType::Bool); // body condition - let carrier_then = builder.alloc_typed(MirType::Integer); // then update result - let carrier_else = builder.alloc_typed(MirType::Integer); // else update result - let carrier_next = builder.alloc_typed(MirType::Integer); // merge PHI dst - let loop_var_next = builder.alloc_typed(MirType::Integer); // step update result - - // Step 5: Build phi_bindings (Phase 286 P2.8: use create_phi_bindings) - let phi_bindings = create_phi_bindings(&[ - (&parts.loop_var, loop_var_current), - (&parts.carrier_var, carrier_current), - ]); - - // Step 6: Lower AST expressions - // Lower loop condition (e.g., `i < 3`) - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; - - // Lower if condition (e.g., `i > 0`) - let (if_cond_lhs, if_cond_op, if_cond_rhs, if_cond_consts) = - Self::lower_compare_ast(&parts.if_condition, builder, &phi_bindings)?; - - // Lower then update (e.g., `sum + 1`) - let (then_lhs, then_op, then_rhs, then_consts) = - Self::lower_binop_ast(&parts.then_update, builder, &phi_bindings)?; - - // Lower else update (e.g., `sum + 0`) - let (else_lhs, else_op, else_rhs, else_consts) = - Self::lower_binop_ast(&parts.else_update, builder, &phi_bindings)?; - - // Lower loop increment (e.g., `i + 1`) - let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 7: Build header_effects (loop condition) - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 8: Build body plans (if condition) - goes into loop_plan.body, not block_effects - // Note: lowerer emits loop_plan.body for body_bb, ignoring block_effects - let mut body_plans: Vec = Vec::new(); - for const_effect in if_cond_consts { - body_plans.push(CorePlan::Effect(const_effect)); - } - body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_if, - lhs: if_cond_lhs, - op: if_cond_op, - rhs: if_cond_rhs, - })); - - // Step 9: Build then_effects (carrier_then = carrier_current + 1) - let mut then_effects = then_consts; - then_effects.push(CoreEffectPlan::BinOp { - dst: carrier_then, - lhs: then_lhs, - op: then_op, - rhs: then_rhs, - }); - - // Step 10: Build else_effects (carrier_else = carrier_current + 0) - let mut else_effects = else_consts; - else_effects.push(CoreEffectPlan::BinOp { - dst: carrier_else, - lhs: else_lhs, - op: else_op, - rhs: else_rhs, - }); - - // Step 11: Build step_effects (loop_var_next = loop_var_current + 1) - let mut step_effects = loop_inc_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 12: Build block_effects (7 blocks) - // Note: body_bb effects are in loop_plan.body, not here - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // Effects in loop_plan.body - (then_bb, then_effects), - (else_bb, else_effects), - (merge_bb, vec![]), // PHI only, no effects - (step_bb, step_effects), - ]; - - // Step 13: Build PHIs (3 PHIs: 2 in header, 1 in merge) - let phis = vec![ - // Header PHI 1: loop variable - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - // Header PHI 2: carrier (receives carrier_next from merge via step) - CorePhiInfo { - block: header_bb, - dst: carrier_current, - inputs: vec![ - (preheader_bb, carrier_init), - (step_bb, carrier_next), - ], - tag: format!("carrier_{}", parts.carrier_var), - }, - // Merge PHI: if-else merge (carrier_next = phi(then:carrier_then, else:carrier_else)) - CorePhiInfo { - block: merge_bb, - dst: carrier_next, - inputs: vec![ - (then_bb, carrier_then), - (else_bb, carrier_else), - ], - tag: format!("merge_{}", parts.carrier_var), - }, - ]; - - // Step 14: Build Frag (branches + wires) - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - // 2 branches: header (loop cond), body (if cond) - let branches = vec![ - // header: cond_loop → body/after - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - then_args: empty_args.clone(), - else_target: after_bb, - else_args: empty_args.clone(), - }, - // body: cond_if → then/else - BranchStub { - from: body_bb, - cond: cond_if, - then_target: then_bb, - then_args: empty_args.clone(), - else_target: else_bb, - else_args: empty_args.clone(), - }, - ]; - - // 4 wires: then→merge, else→merge, merge→step, step→header - let wires = vec![ - // then → merge - EdgeStub { - from: then_bb, - kind: ExitKind::Normal, - target: Some(merge_bb), - args: empty_args.clone(), - }, - // else → merge - EdgeStub { - from: else_bb, - kind: ExitKind::Normal, - target: Some(merge_bb), - args: empty_args.clone(), - }, - // merge → step - EdgeStub { - from: merge_bb, - kind: ExitKind::Normal, - target: Some(step_bb), - args: empty_args.clone(), - }, - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 15: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - (parts.carrier_var.clone(), carrier_current), - ]; - - // Step 16: Build CoreLoopPlan - // found_bb = after_bb (no early exit) - // cond_match = cond_if (if condition for body branching) - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit - body: body_plans, // Body effects (if condition) - cond_loop, - cond_match: cond_if, // If condition - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern3_if_phi", - "CorePlan construction complete (7 blocks, 3 PHIs)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - // ========================================================================= - // Phase 286 P3.1: Pattern2 (Loop with Conditional Break) normalization - // ========================================================================= - - /// Pattern2Break → CorePlan 変換 - /// - /// CFG structure (6 blocks): - /// ``` - /// preheader → header(PHI: i_current, carrier_current) - /// ↓ - /// body(break_cond check) - /// ↓ - /// ┌────┴────┐ - /// break_then step - /// (optional ↓ - /// update) header (back-edge) - /// ↓ - /// after_bb(PHI: carrier_out) - /// ↑ - /// header (natural exit when !cond_loop) - /// ``` - /// - /// Key: after_bb PHI merges break path and natural exit path carrier values. - fn normalize_pattern2_break( - builder: &mut MirBuilder, - parts: Pattern2BreakPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern2_break", - &format!( - "Phase 286 P3.1: Normalizing Pattern2Break for {} (loop_var={}, carrier_var={})", - ctx.func_name, parts.loop_var, parts.carrier_var - ), - ); - } - - // Step 1: Block allocation (6 blocks) - let preheader_bb = builder - .current_block - .ok_or_else(|| "Pattern2Break: no current block".to_string())?; - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let break_then_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - - if debug { - trace_logger.debug( - "normalizer/pattern2_break", - &format!( - "Block allocation: preheader={:?}, header={:?}, body={:?}, break_then={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, break_then_bb, step_bb, after_bb - ), - ); - } - - // Step 2: Get initial values from variable_map - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("Pattern2Break: loop_var '{}' not in variable_map", parts.loop_var))?; - - let carrier_init = builder - .variable_ctx - .variable_map - .get(&parts.carrier_var) - .copied() - .ok_or_else(|| format!("Pattern2Break: carrier_var '{}' not in variable_map", parts.carrier_var))?; - - // Step 3: ValueId allocation - let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let carrier_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let cond_loop = builder.alloc_typed(MirType::Bool); // loop condition - let cond_break = builder.alloc_typed(MirType::Bool); // break condition - let carrier_break = builder.alloc_typed(MirType::Integer); // break path carrier - let carrier_step = builder.alloc_typed(MirType::Integer); // step path carrier - let loop_var_next = builder.alloc_typed(MirType::Integer); // loop var next - let carrier_out = builder.alloc_typed(MirType::Integer); // after_bb PHI dst - - // Step 4: phi_bindings for AST lowering - let phi_bindings = create_phi_bindings(&[ - (&parts.loop_var, loop_var_current), - (&parts.carrier_var, carrier_current), - ]); - - // Step 5: Lower AST expressions - // 5.1: Loop condition (e.g., `i < 3`) - let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = - Self::lower_compare_ast(&parts.loop_condition, builder, &phi_bindings)?; - - // 5.2: Break condition (e.g., `i == 1`) - let (break_cond_lhs, break_cond_op, break_cond_rhs, break_cond_consts) = - Self::lower_compare_ast(&parts.break_condition, builder, &phi_bindings)?; - - // 5.3: Carrier update in break path (if any) - let break_then_effects = if let Some(ref break_update_ast) = parts.carrier_update_in_break { - let (lhs, op, rhs, consts) = Self::lower_binop_ast(break_update_ast, builder, &phi_bindings)?; - let mut effects = consts; - effects.push(CoreEffectPlan::BinOp { - dst: carrier_break, - lhs, - op, - rhs, - }); - effects - } else { - // No update: carrier_break = carrier_current (copy via Add 0) - let zero = builder.alloc_typed(MirType::Integer); - vec![ - CoreEffectPlan::Const { - dst: zero, - value: ConstValue::Integer(0), - }, - CoreEffectPlan::BinOp { - dst: carrier_break, - lhs: carrier_current, - op: BinaryOp::Add, - rhs: zero, - }, - ] - }; - - // 5.4: Carrier update in body (step path) (e.g., `sum + 1`) - let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) = - Self::lower_binop_ast(&parts.carrier_update_in_body, builder, &phi_bindings)?; - - // 5.5: Loop increment (e.g., `i + 1`) - let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 6: Build header_effects (loop condition) - let mut header_effects = loop_cond_consts; - header_effects.push(CoreEffectPlan::Compare { - dst: cond_loop, - lhs: loop_cond_lhs, - op: loop_cond_op, - rhs: loop_cond_rhs, - }); - - // Step 7: Build body plans (break condition check) - // Body is in loop_plan.body, not block_effects (V10 contract) - let mut body_plans: Vec = Vec::new(); - for const_effect in break_cond_consts { - body_plans.push(CorePlan::Effect(const_effect)); - } - body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_break, - lhs: break_cond_lhs, - op: break_cond_op, - rhs: break_cond_rhs, - })); - - // Step 8: Build step_effects (carrier update + loop increment) - let mut step_effects = carrier_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: carrier_step, - lhs: carrier_lhs, - op: carrier_op, - rhs: carrier_rhs, - }); - step_effects.extend(loop_inc_consts); - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: loop_inc_lhs, - op: loop_inc_op, - rhs: loop_inc_rhs, - }); - - // Step 9: Build block_effects (V10 compliant: body_bb empty) - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // V10: body effects in body plans, not block_effects - (break_then_bb, break_then_effects), - (step_bb, step_effects), - ]; - - // Step 10: Build PHIs (3 total) - let phis = vec![ - // Header PHI: loop_var_current - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_var_{}", parts.loop_var), - }, - // Header PHI: carrier_current - CorePhiInfo { - block: header_bb, - dst: carrier_current, - inputs: vec![ - (preheader_bb, carrier_init), - (step_bb, carrier_step), - ], - tag: format!("carrier_{}", parts.carrier_var), - }, - // After PHI: carrier_out (KEY PHI for Pattern2!) - CorePhiInfo { - block: after_bb, - dst: carrier_out, - inputs: vec![ - (header_bb, carrier_current), // natural exit - (break_then_bb, carrier_break), // break path - ], - tag: format!("after_{}", parts.carrier_var), - }, - ]; - - // Step 11: Build Frag (CFG structure) - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - // header: cond_loop → body / after - BranchStub { - from: header_bb, - cond: cond_loop, - then_target: body_bb, - else_target: after_bb, - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - // body: cond_break → break_then / step - BranchStub { - from: body_bb, - cond: cond_break, - then_target: break_then_bb, - else_target: step_bb, - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - ]; - - let wires = vec![ - // break_then → after - EdgeStub { - from: break_then_bb, - kind: ExitKind::Normal, - target: Some(after_bb), - args: empty_args.clone(), - }, - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 12: Build final_values - // carrier returns after_bb PHI dst (carrier_out) - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - (parts.carrier_var.clone(), carrier_out), // KEY: after PHI dst! - ]; - - // Step 13: Build CoreLoopPlan - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit (break goes to after) - body: body_plans, - cond_loop, - cond_match: cond_break, // Break condition for body branching - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern2_break", - "CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI is key!)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Pattern5InfiniteEarlyExit → CorePlan 変換 (Phase 286 P3.2) - /// - /// Expands infinite loop with early exit into CorePlan. - /// - /// # Return version CFG (5 blocks) - /// ```text - /// preheader → header(PHI: i_current) → body(exit_cond) - /// ↑ ↓ - /// └───── step ←──────── else path - /// ↓ - /// then path: Return (found_bb) - /// ``` - /// - /// # Break version CFG (6 blocks) - /// ```text - /// preheader → header(PHI: i, carrier) → body(exit_cond) - /// ↑ ↓ - /// └───── step ←────────── else path - /// ↓ - /// then path → after_bb(PHI: carrier_out) - /// ``` - fn normalize_pattern5_infinite_early_exit( - builder: &mut MirBuilder, - parts: Pattern5InfiniteEarlyExitPlan, - ctx: &LoopPatternContext, - ) -> Result { - use crate::mir::builder::control_flow::joinir::trace; - - let trace_logger = trace::trace(); - let debug = ctx.debug; - - if debug { - trace_logger.debug( - "normalizer/pattern5_infinite_early_exit", - &format!( - "Phase 286 P3.2: Normalizing Pattern5 for {} (loop_var={}, exit_kind={:?})", - ctx.func_name, parts.loop_var, parts.exit_kind - ), - ); - } - - match parts.exit_kind { - Pattern5ExitKind::Return => { - Self::normalize_pattern5_return(builder, parts, ctx, debug, &trace_logger) - } - Pattern5ExitKind::Break => { - Self::normalize_pattern5_break(builder, parts, ctx, debug, &trace_logger) - } - } - } - - /// Pattern5 Return version normalizer - /// - /// CFG: preheader → header → body → found_bb (return) / step → header - fn normalize_pattern5_return( - builder: &mut MirBuilder, - parts: Pattern5InfiniteEarlyExitPlan, - ctx: &LoopPatternContext, - debug: bool, - trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace, - ) -> Result { - // Step 1: Block allocation (5 blocks) - let preheader_bb = builder - .current_block - .ok_or_else(|| "Pattern5Return: no current block".to_string())?; - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let found_bb = builder.next_block_id(); // Return block - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); // Unreachable for infinite loop - - if debug { - trace_logger.debug( - "normalizer/pattern5_return", - &format!( - "Block allocation: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb - ), - ); - } - - // Step 2: Get initial values from variable_map - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("Pattern5Return: loop_var '{}' not in variable_map", parts.loop_var))?; - - // Step 3: ValueId allocation - let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let cond_exit = builder.alloc_typed(MirType::Bool); // exit condition - let loop_var_next = builder.alloc_typed(MirType::Integer); // loop var next - let true_val = builder.alloc_typed(MirType::Bool); // for infinite loop - - // Step 4: phi_bindings for AST lowering - let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); - - // Step 5: Lower AST expressions - // 5.1: Exit condition (e.g., `i == 3`) - let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) = - Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?; - - // 5.2: Return value (if any) - let return_value = if let Some(ref exit_val_ast) = parts.exit_value { - let return_val_id = builder.alloc_typed(MirType::Integer); - let return_val_ast = exit_val_ast.clone(); - Some((return_val_id, return_val_ast)) - } else { - None - }; - - // 5.3: Loop increment (e.g., `i + 1`) - let (inc_lhs, inc_op, inc_rhs, inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 6: Build header_effects - // For infinite loop, we just need a dummy true condition for loop control - let header_effects = vec![ - CoreEffectPlan::Const { - dst: true_val, - value: ConstValue::Bool(true), - }, - ]; - - // Step 7: Build body plans - let mut body_plans: Vec = Vec::new(); - - // Add exit condition consts - for eff in exit_cond_consts { - body_plans.push(CorePlan::Effect(eff)); - } - - // Add exit condition compare - body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_exit, - lhs: exit_cond_lhs, - op: exit_cond_op, - rhs: exit_cond_rhs, - })); - - // Step 8: Build found_bb effects (return value computation + Return) - let mut found_effects: Vec = Vec::new(); - let return_val_id = if let Some((ret_val_id, ref ret_val_ast)) = return_value { - // Lower return value as const if it's a literal - if let crate::ast::ASTNode::Literal { value: crate::ast::LiteralValue::Integer(n), .. } = ret_val_ast { - found_effects.push(CoreEffectPlan::Const { - dst: ret_val_id, - value: ConstValue::Integer(*n), - }); - } else { - // For non-literal, use binop lowering (simplified) - let (lhs, op, rhs, consts) = Self::lower_binop_ast(ret_val_ast, builder, &phi_bindings)?; - for c in consts { - found_effects.push(c); - } - found_effects.push(CoreEffectPlan::BinOp { - dst: ret_val_id, - lhs, - op, - rhs, - }); - } - Some(ret_val_id) - } else { - None - }; - - // Step 9: Build step_effects (increment loop var) - let mut step_effects = inc_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: inc_lhs, - op: inc_op, - rhs: inc_rhs, - }); - - // Step 10: Build PHIs - let phis = vec![ - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_carrier_{}", parts.loop_var), - }, - ]; - - // Step 11: Build block_effects (V10 contract: body effects in loop_plan.body) - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // V10: body effects in loop_plan.body - (found_bb, found_effects), // Return value computation - (step_bb, step_effects), - (after_bb, vec![]), // Unreachable for infinite loop (needed for CFG) - ]; - - // Step 12: Build Frag with Return exit - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - // header → body (always, infinite loop) - BranchStub { - from: header_bb, - cond: true_val, - then_target: body_bb, - else_target: after_bb, // Never taken for infinite loop - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - // body → found_bb (exit) / step (continue) - BranchStub { - from: body_bb, - cond: cond_exit, - then_target: found_bb, - else_target: step_bb, - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - ]; - - // Build Return value args - let return_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: if let Some(val_id) = return_val_id { - vec![val_id] - } else { - vec![] - }, - }; - - // Wires: internal jumps + Return terminators (emit_wires handles Return with target=None) - let wires = vec![ - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - // found_bb → Return (early exit) - EdgeStub { - from: found_bb, - kind: ExitKind::Return, - target: None, - args: return_args, - }, - // after_bb → Return (unreachable for infinite loop, but CFG needs terminator) - EdgeStub { - from: after_bb, - kind: ExitKind::Return, - target: None, - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), // No exits - all terminators are in wires - wires, - branches, - }; - - // Step 13: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - ]; - - // Step 14: Build CoreLoopPlan - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb, - body: body_plans, - cond_loop: true_val, // Infinite loop - cond_match: cond_exit, // Exit condition for body branching - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern5_return", - "CorePlan construction complete (5 blocks, 1 PHI, Return exit)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } - - /// Pattern5 Break version normalizer - /// - /// CFG: preheader → header → body → break_then → after / step → header - fn normalize_pattern5_break( - builder: &mut MirBuilder, - parts: Pattern5InfiniteEarlyExitPlan, - ctx: &LoopPatternContext, - debug: bool, - trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace, - ) -> Result { - // Step 1: Block allocation (6 blocks) - let preheader_bb = builder - .current_block - .ok_or_else(|| "Pattern5Break: no current block".to_string())?; - let header_bb = builder.next_block_id(); - let body_bb = builder.next_block_id(); - let break_then_bb = builder.next_block_id(); - let step_bb = builder.next_block_id(); - let after_bb = builder.next_block_id(); - - if debug { - trace_logger.debug( - "normalizer/pattern5_break", - &format!( - "Block allocation: preheader={:?}, header={:?}, body={:?}, break_then={:?}, step={:?}, after={:?}", - preheader_bb, header_bb, body_bb, break_then_bb, step_bb, after_bb - ), - ); - } - - // Step 2: Get carrier_var (required for Break version) - let carrier_var = parts.carrier_var.as_ref() - .ok_or_else(|| "Pattern5Break: carrier_var required for Break version".to_string())?; - let carrier_update_ast = parts.carrier_update.as_ref() - .ok_or_else(|| "Pattern5Break: carrier_update required for Break version".to_string())?; - - // Step 3: Get initial values from variable_map - let loop_var_init = builder - .variable_ctx - .variable_map - .get(&parts.loop_var) - .copied() - .ok_or_else(|| format!("Pattern5Break: loop_var '{}' not in variable_map", parts.loop_var))?; - - let carrier_init = builder - .variable_ctx - .variable_map - .get(carrier_var) - .copied() - .ok_or_else(|| format!("Pattern5Break: carrier_var '{}' not in variable_map", carrier_var))?; - - // Step 4: ValueId allocation - let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let carrier_current = builder.alloc_typed(MirType::Integer); // header PHI dst - let cond_exit = builder.alloc_typed(MirType::Bool); // exit condition - let carrier_step = builder.alloc_typed(MirType::Integer); // step path carrier - let loop_var_next = builder.alloc_typed(MirType::Integer); // loop var next - let carrier_out = builder.alloc_typed(MirType::Integer); // after_bb PHI dst - let true_val = builder.alloc_typed(MirType::Bool); // for infinite loop - - // Step 5: phi_bindings for AST lowering - let phi_bindings = create_phi_bindings(&[ - (&parts.loop_var, loop_var_current), - (carrier_var, carrier_current), - ]); - - // Step 6: Lower AST expressions - // 6.1: Exit condition (e.g., `i == 3`) - let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) = - Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?; - - // 6.2: Carrier update (e.g., `sum + 1`) - let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) = - Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?; - - // 6.3: Loop increment (e.g., `i + 1`) - let (inc_lhs, inc_op, inc_rhs, inc_consts) = - Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; - - // Step 7: Build header_effects - let header_effects = vec![ - CoreEffectPlan::Const { - dst: true_val, - value: ConstValue::Bool(true), - }, - ]; - - // Step 8: Build body plans (exit condition check) - let mut body_plans: Vec = Vec::new(); - for eff in exit_cond_consts { - body_plans.push(CorePlan::Effect(eff)); - } - body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { - dst: cond_exit, - lhs: exit_cond_lhs, - op: exit_cond_op, - rhs: exit_cond_rhs, - })); - - // Step 9: Build break_then_effects (empty - carrier_current goes directly to after PHI) - let break_then_effects = vec![]; - - // Step 10: Build step_effects (carrier update + loop increment) - let mut step_effects = carrier_consts; - step_effects.push(CoreEffectPlan::BinOp { - dst: carrier_step, - lhs: carrier_lhs, - op: carrier_op, - rhs: carrier_rhs, - }); - for c in inc_consts { - step_effects.push(c); - } - step_effects.push(CoreEffectPlan::BinOp { - dst: loop_var_next, - lhs: inc_lhs, - op: inc_op, - rhs: inc_rhs, - }); - - // Step 11: Build PHIs (3 total) - let phis = vec![ - CorePhiInfo { - block: header_bb, - dst: loop_var_current, - inputs: vec![ - (preheader_bb, loop_var_init), - (step_bb, loop_var_next), - ], - tag: format!("loop_carrier_{}", parts.loop_var), - }, - CorePhiInfo { - block: header_bb, - dst: carrier_current, - inputs: vec![ - (preheader_bb, carrier_init), - (step_bb, carrier_step), - ], - tag: format!("loop_carrier_{}", carrier_var), - }, - // after_bb PHI: carrier_out from break_then (carrier_current) or step (carrier_step) - // But for infinite loop with break, only break_then reaches after_bb - CorePhiInfo { - block: after_bb, - dst: carrier_out, - inputs: vec![ - (break_then_bb, carrier_current), // break path: current carrier value - ], - tag: format!("exit_carrier_{}", carrier_var), - }, - ]; - - // Step 12: Build block_effects (V10 contract) - let block_effects = vec![ - (preheader_bb, vec![]), - (header_bb, header_effects), - (body_bb, vec![]), // V10: body effects in loop_plan.body - (break_then_bb, break_then_effects), - (step_bb, step_effects), - ]; - - // Step 13: Build Frag - let empty_args = EdgeArgs { - layout: JumpArgsLayout::CarriersOnly, - values: vec![], - }; - - let branches = vec![ - // header → body (always, infinite loop) - BranchStub { - from: header_bb, - cond: true_val, - then_target: body_bb, - else_target: after_bb, // Never taken for infinite loop - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - // body → break_then (exit) / step (continue) - BranchStub { - from: body_bb, - cond: cond_exit, - then_target: break_then_bb, - else_target: step_bb, - then_args: empty_args.clone(), - else_args: empty_args.clone(), - }, - ]; - - // Use fixed LoopId(0) for internal break wiring - let loop_id = crate::mir::control_form::LoopId(0); - - let wires = vec![ - // break_then → after - EdgeStub { - from: break_then_bb, - kind: ExitKind::Break(loop_id), - target: Some(after_bb), - args: empty_args.clone(), - }, - // step → header (back-edge) - EdgeStub { - from: step_bb, - kind: ExitKind::Normal, - target: Some(header_bb), - args: empty_args.clone(), - }, - ]; - - let frag = Frag { - entry: header_bb, - exits: BTreeMap::new(), - wires, - branches, - }; - - // Step 14: Build final_values - let final_values = vec![ - (parts.loop_var.clone(), loop_var_current), - (carrier_var.clone(), carrier_out), // KEY: after PHI dst! - ]; - - // Step 15: Build CoreLoopPlan - let loop_plan = CoreLoopPlan { - preheader_bb, - header_bb, - body_bb, - step_bb, - after_bb, - found_bb: after_bb, // No early exit via found (break goes to after) - body: body_plans, - cond_loop: true_val, // Infinite loop - cond_match: cond_exit, // Exit condition for body branching - block_effects, - phis, - frag, - final_values, - }; - - if debug { - trace_logger.debug( - "normalizer/pattern5_break", - "CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI for carrier)", - ); - } - - Ok(CorePlan::Loop(loop_plan)) - } -} diff --git a/src/mir/builder/control_flow/plan/normalizer/README.md b/src/mir/builder/control_flow/plan/normalizer/README.md new file mode 100644 index 00000000..61b6e677 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/README.md @@ -0,0 +1,20 @@ +# Plan Normalizer Modules + +This directory splits the DomainPlan -> CorePlan normalization into small modules. + +Responsibilities: +- Keep pattern-specific knowledge localized per module. +- Share small, stable helpers (AST lowering, block layouts, phi bindings). +- Preserve PlanNormalizer as the single SSOT entry point. + +Modules: +- helpers.rs: shared block layouts, phi bindings, AST-to-ValueId lowering helpers. +- pattern_scan_with_init.rs: Pattern6 scan-with-init normalization. +- pattern_split_scan.rs: Pattern7 split-scan normalization. +- pattern4_continue.rs: Pattern4 continue-loop normalization. +- pattern1_simple_while.rs: Pattern1 simple while normalization. +- pattern9_accum_const_loop.rs: Pattern9 accumulator-loop normalization. +- pattern8_bool_predicate_scan.rs: Pattern8 predicate-scan normalization. +- pattern3_if_phi.rs: Pattern3 if-phi normalization. +- pattern2_break.rs: Pattern2 break-loop normalization. +- pattern5_infinite_early_exit.rs: Pattern5 infinite loop with early exit normalization. diff --git a/src/mir/builder/control_flow/plan/normalizer/helpers.rs b/src/mir/builder/control_flow/plan/normalizer/helpers.rs new file mode 100644 index 00000000..de2bf5e7 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/helpers.rs @@ -0,0 +1,263 @@ +use super::CoreEffectPlan; +use crate::mir::builder::MirBuilder; +use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType, ValueId}; +use std::collections::BTreeMap; + +// ============================================================================ +// Phase 286 P2.8: Normalizer Hygiene Helpers +// ============================================================================ + +/// Standard 5-block layout for simple loops (Pattern1/4/8/9) +/// +/// CFG: preheader → header → body → step → header (back-edge) +/// ↓ +/// after +#[derive(Debug, Clone, Copy)] +pub(super) struct LoopBlocksStandard5 { + pub(super) preheader_bb: BasicBlockId, + pub(super) header_bb: BasicBlockId, + pub(super) body_bb: BasicBlockId, + pub(super) step_bb: BasicBlockId, + pub(super) after_bb: BasicBlockId, +} + +impl LoopBlocksStandard5 { + /// Allocate 5 blocks for a standard loop + pub(super) fn allocate(builder: &mut MirBuilder) -> Result { + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + Ok(Self { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + }) + } +} + +/// Extended 8-block layout for if-phi loops (Pattern3) +/// +/// CFG: preheader → header → body → then/else → merge → step → header +/// ↓ +/// after +#[derive(Debug, Clone, Copy)] +pub(super) struct LoopBlocksWithIfPhi { + pub(super) preheader_bb: BasicBlockId, + pub(super) header_bb: BasicBlockId, + pub(super) body_bb: BasicBlockId, + pub(super) then_bb: BasicBlockId, + pub(super) else_bb: BasicBlockId, + pub(super) merge_bb: BasicBlockId, + pub(super) step_bb: BasicBlockId, + pub(super) after_bb: BasicBlockId, +} + +impl LoopBlocksWithIfPhi { + /// Allocate 8 blocks for an if-phi loop + pub(super) fn allocate(builder: &mut MirBuilder) -> Result { + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let then_bb = builder.next_block_id(); + let else_bb = builder.next_block_id(); + let merge_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + Ok(Self { + preheader_bb, + header_bb, + body_bb, + then_bb, + else_bb, + merge_bb, + step_bb, + after_bb, + }) + } +} + +/// Create phi_bindings map from variable name-ValueId pairs +/// +/// phi_bindings are used to override variable_map lookups during AST lowering, +/// ensuring loop variables reference PHI destinations instead of initial values. +pub(super) fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap { + bindings + .iter() + .map(|(name, id)| (name.to_string(), *id)) + .collect() +} + +impl super::PlanNormalizer { + /// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects) + pub(super) fn lower_compare_ast( + ast: &crate::ast::ASTNode, + builder: &mut MirBuilder, + phi_bindings: &BTreeMap, + ) -> Result<(ValueId, CompareOp, ValueId, Vec), String> { + use crate::ast::{ASTNode, BinaryOperator}; + + match ast { + ASTNode::BinaryOp { operator, left, right, .. } => { + let op = match operator { + BinaryOperator::Less => CompareOp::Lt, + BinaryOperator::LessEqual => CompareOp::Le, + BinaryOperator::Greater => CompareOp::Gt, + BinaryOperator::GreaterEqual => CompareOp::Ge, + BinaryOperator::Equal => CompareOp::Eq, + BinaryOperator::NotEqual => CompareOp::Ne, + _ => { + return Err(format!( + "[normalizer] Unsupported compare operator: {:?}", + operator + )) + } + }; + + let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?; + let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?; + + lhs_consts.extend(rhs_consts); + + Ok((lhs, op, rhs, lhs_consts)) + } + _ => Err(format!( + "[normalizer] Expected BinaryOp for compare, got: {:?}", + ast + )), + } + } + + /// Helper: Lower BinOp AST to (lhs ValueId, op, rhs ValueId, const_effects) + pub(super) fn lower_binop_ast( + ast: &crate::ast::ASTNode, + builder: &mut MirBuilder, + phi_bindings: &BTreeMap, + ) -> Result<(ValueId, BinaryOp, ValueId, Vec), String> { + use crate::ast::{ASTNode, BinaryOperator}; + + match ast { + ASTNode::BinaryOp { operator, left, right, .. } => { + let op = match operator { + BinaryOperator::Add => BinaryOp::Add, + BinaryOperator::Subtract => BinaryOp::Sub, + BinaryOperator::Multiply => BinaryOp::Mul, + BinaryOperator::Divide => BinaryOp::Div, + _ => { + return Err(format!( + "[normalizer] Unsupported binary operator: {:?}", + operator + )) + } + }; + + let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?; + let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?; + + lhs_consts.extend(rhs_consts); + + Ok((lhs, op, rhs, lhs_consts)) + } + _ => Err(format!("[normalizer] Expected BinOp, got: {:?}", ast)), + } + } + + /// Helper: Lower value AST to (ValueId, const_effects) + /// Returns the ValueId and any Const instructions needed to define literals + /// + /// phi_bindings: PHI dst for loop variables (takes precedence over variable_map) + pub(super) fn lower_value_ast( + ast: &crate::ast::ASTNode, + builder: &mut MirBuilder, + phi_bindings: &BTreeMap, + ) -> Result<(ValueId, Vec), String> { + use crate::ast::{ASTNode, LiteralValue}; + + match ast { + ASTNode::Variable { name, .. } => { + if let Some(&phi_dst) = phi_bindings.get(name) { + return Ok((phi_dst, vec![])); + } + if let Some(&value_id) = builder.variable_ctx.variable_map.get(name) { + Ok((value_id, vec![])) + } else { + Err(format!("[normalizer] Variable {} not found", name)) + } + } + ASTNode::Literal { value, .. } => { + let value_id = builder.next_value_id(); + let const_value = match value { + LiteralValue::Integer(n) => ConstValue::Integer(*n), + LiteralValue::String(s) => ConstValue::String(s.clone()), + LiteralValue::Bool(b) => ConstValue::Bool(*b), + _ => { + return Err(format!( + "[normalizer] Unsupported literal type: {:?}", + value + )) + } + }; + + builder.type_ctx.value_types.insert(value_id, MirType::Integer); + + let const_effect = CoreEffectPlan::Const { + dst: value_id, + value: const_value, + }; + + Ok((value_id, vec![const_effect])) + } + ASTNode::MethodCall { object, method, arguments, .. } => { + let object_name = match object.as_ref() { + ASTNode::Variable { name, .. } => name.clone(), + _ => { + return Err(format!( + "[normalizer] Method call on non-variable object: {:?}", + object + )) + } + }; + + let object_id = if let Some(&phi_dst) = phi_bindings.get(&object_name) { + phi_dst + } else if let Some(&value_id) = builder.variable_ctx.variable_map.get(&object_name) { + value_id + } else { + return Err(format!( + "[normalizer] Method call object {} not found", + object_name + )); + }; + + let mut arg_ids = Vec::new(); + let mut arg_effects = Vec::new(); + for arg in arguments { + let (arg_id, mut effects) = Self::lower_value_ast(arg, builder, phi_bindings)?; + arg_ids.push(arg_id); + arg_effects.append(&mut effects); + } + + let result_id = builder.next_value_id(); + builder.type_ctx.value_types.insert(result_id, MirType::Integer); + + arg_effects.push(CoreEffectPlan::MethodCall { + dst: Some(result_id), + object: object_id, + method: method.clone(), + args: arg_ids, + effects: EffectMask::PURE.add(Effect::Io), + }); + + Ok((result_id, arg_effects)) + } + _ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)), + } + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/mod.rs b/src/mir/builder/control_flow/plan/normalizer/mod.rs new file mode 100644 index 00000000..affb12cc --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/mod.rs @@ -0,0 +1,69 @@ +//! Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT) +//! +//! # Responsibilities +//! +//! - Convert DomainPlan to CorePlan (SSOT for pattern-specific knowledge) +//! - Generate ValueIds for CorePlan expressions +//! - Expand pattern-specific operations into generic CoreEffectPlan +//! +//! # Key Design Decision +//! +//! Normalizer is the ONLY place that knows pattern-specific semantics. +//! Lowerer processes CorePlan without any pattern knowledge. + +mod helpers; +mod pattern1_simple_while; +mod pattern2_break; +mod pattern3_if_phi; +mod pattern4_continue; +mod pattern5_infinite_early_exit; +mod pattern8_bool_predicate_scan; +mod pattern9_accum_const_loop; +mod pattern_scan_with_init; +mod pattern_split_scan; + +use super::{ + CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, Pattern1SimpleWhilePlan, + Pattern2BreakPlan, Pattern3IfPhiPlan, Pattern4ContinuePlan, Pattern5ExitKind, + Pattern5InfiniteEarlyExitPlan, Pattern8BoolPredicateScanPlan, Pattern9AccumConstLoopPlan, + ScanWithInitPlan, SplitScanPlan, +}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; + +/// Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT) +pub(in crate::mir::builder) struct PlanNormalizer; + +impl PlanNormalizer { + /// Normalize DomainPlan to CorePlan + /// + /// This is the SSOT for pattern-specific knowledge expansion. + /// All pattern semantics (scan, split, etc.) are expanded here. + pub(in crate::mir::builder) fn normalize( + builder: &mut MirBuilder, + domain: DomainPlan, + ctx: &LoopPatternContext, + ) -> Result { + match domain { + DomainPlan::ScanWithInit(parts) => Self::normalize_scan_with_init(builder, parts, ctx), + DomainPlan::SplitScan(parts) => Self::normalize_split_scan(builder, parts, ctx), + DomainPlan::Pattern4Continue(parts) => { + Self::normalize_pattern4_continue(builder, parts, ctx) + } + DomainPlan::Pattern1SimpleWhile(parts) => { + Self::normalize_pattern1_simple_while(builder, parts, ctx) + } + DomainPlan::Pattern9AccumConstLoop(parts) => { + Self::normalize_pattern9_accum_const_loop(builder, parts, ctx) + } + DomainPlan::Pattern8BoolPredicateScan(parts) => { + Self::normalize_pattern8_bool_predicate_scan(builder, parts, ctx) + } + DomainPlan::Pattern3IfPhi(parts) => Self::normalize_pattern3_if_phi(builder, parts, ctx), + DomainPlan::Pattern2Break(parts) => Self::normalize_pattern2_break(builder, parts, ctx), + DomainPlan::Pattern5InfiniteEarlyExit(parts) => { + Self::normalize_pattern5_infinite_early_exit(builder, parts, ctx) + } + } + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern1_simple_while.rs b/src/mir/builder/control_flow/plan/normalizer/pattern1_simple_while.rs new file mode 100644 index 00000000..16f8f43e --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern1_simple_while.rs @@ -0,0 +1,181 @@ +use super::helpers::{create_phi_bindings, LoopBlocksStandard5}; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern1SimpleWhilePlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::MirType; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Phase 286 P2.1: Pattern1SimpleWhile → CorePlan 変換 + /// + /// Expands Pattern1 (Simple While Loop) semantics into generic CorePlan: + /// - CFG structure: preheader → header → body → step → header (back-edge) + /// - 1 PHI for loop variable in header + /// - No 2-step branching (simpler than Pattern4) + pub(super) fn normalize_pattern1_simple_while( + builder: &mut MirBuilder, + parts: Pattern1SimpleWhilePlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern1_simple_while", + &format!( + "Phase 286 P2.1: Normalizing Pattern1SimpleWhile for {} (loop_var: {})", + ctx.func_name, parts.loop_var + ), + ); + } + + // Step 1: Get host ValueId for loop variable + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; + + // Step 2-3: Allocate BasicBlockIds + let blocks = LoopBlocksStandard5::allocate(builder)?; + let LoopBlocksStandard5 { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + } = blocks; + + if debug { + trace_logger.debug( + "normalizer/pattern1_simple_while", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, step_bb, after_bb + ), + ); + } + + // Step 4: Allocate ValueIds for loop control + let loop_var_current = builder.alloc_typed(MirType::Integer); + let cond_loop = builder.alloc_typed(MirType::Bool); + let loop_var_next = builder.alloc_typed(MirType::Integer); + + // Step 5: Build phi_bindings + let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); + + // Step 6: Lower AST expressions + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; + + let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 7: Build header_effects + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 8: Build step_effects + let mut step_effects = loop_inc_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 9: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (step_bb, step_effects), + ]; + + // Step 10: Build PHI + let phis = vec![CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_var_{}", parts.loop_var), + }]; + + // Step 11: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }]; + + let wires = vec![ + EdgeStub { + from: body_bb, + kind: ExitKind::Normal, + target: Some(step_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 12: Build final_values + let final_values = vec![(parts.loop_var.clone(), loop_var_current)]; + + // Step 13: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: vec![], + cond_loop, + cond_match: cond_loop, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern1_simple_while", + "CorePlan construction complete (4 blocks, 1 PHI)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern2_break.rs b/src/mir/builder/control_flow/plan/normalizer/pattern2_break.rs new file mode 100644 index 00000000..11c0d5a1 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern2_break.rs @@ -0,0 +1,299 @@ +use super::helpers::create_phi_bindings; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern2BreakPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{BinaryOp, ConstValue, MirType}; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Pattern2Break → CorePlan 変換 + /// + /// CFG structure (6 blocks): + /// ``` + /// preheader → header(PHI: i_current, carrier_current) + /// ↓ + /// body(break_cond check) + /// ↓ + /// ┌────┴────┐ + /// break_then step + /// (optional ↓ + /// update) header (back-edge) + /// ↓ + /// after_bb(PHI: carrier_out) + /// ↑ + /// header (natural exit when !cond_loop) + /// ``` + /// + /// Key: after_bb PHI merges break path and natural exit path carrier values. + pub(super) fn normalize_pattern2_break( + builder: &mut MirBuilder, + parts: Pattern2BreakPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern2_break", + &format!( + "Phase 286 P3.1: Normalizing Pattern2Break for {} (loop_var={}, carrier_var={})", + ctx.func_name, parts.loop_var, parts.carrier_var + ), + ); + } + + // Step 1: Block allocation (6 blocks) + let preheader_bb = builder + .current_block + .ok_or_else(|| "Pattern2Break: no current block".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let break_then_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + if debug { + trace_logger.debug( + "normalizer/pattern2_break", + &format!( + "Block allocation: preheader={:?}, header={:?}, body={:?}, break_then={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, break_then_bb, step_bb, after_bb + ), + ); + } + + // Step 2: Get initial values from variable_map + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| { + format!( + "Pattern2Break: loop_var '{}' not in variable_map", + parts.loop_var + ) + })?; + + let carrier_init = builder + .variable_ctx + .variable_map + .get(&parts.carrier_var) + .copied() + .ok_or_else(|| { + format!( + "Pattern2Break: carrier_var '{}' not in variable_map", + parts.carrier_var + ) + })?; + + // Step 3: ValueId allocation + let loop_var_current = builder.alloc_typed(MirType::Integer); + let carrier_current = builder.alloc_typed(MirType::Integer); + let cond_loop = builder.alloc_typed(MirType::Bool); + let cond_break = builder.alloc_typed(MirType::Bool); + let carrier_break = builder.alloc_typed(MirType::Integer); + let carrier_step = builder.alloc_typed(MirType::Integer); + let loop_var_next = builder.alloc_typed(MirType::Integer); + let carrier_out = builder.alloc_typed(MirType::Integer); + + // Step 4: phi_bindings for AST lowering + let phi_bindings = create_phi_bindings(&[ + (&parts.loop_var, loop_var_current), + (&parts.carrier_var, carrier_current), + ]); + + // Step 5: Lower AST expressions + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.loop_condition, builder, &phi_bindings)?; + + let (break_cond_lhs, break_cond_op, break_cond_rhs, break_cond_consts) = + Self::lower_compare_ast(&parts.break_condition, builder, &phi_bindings)?; + + let break_then_effects = if let Some(ref break_update_ast) = parts.carrier_update_in_break { + let (lhs, op, rhs, consts) = + Self::lower_binop_ast(break_update_ast, builder, &phi_bindings)?; + let mut effects = consts; + effects.push(CoreEffectPlan::BinOp { + dst: carrier_break, + lhs, + op, + rhs, + }); + effects + } else { + let zero = builder.alloc_typed(MirType::Integer); + vec![ + CoreEffectPlan::Const { + dst: zero, + value: ConstValue::Integer(0), + }, + CoreEffectPlan::BinOp { + dst: carrier_break, + lhs: carrier_current, + op: BinaryOp::Add, + rhs: zero, + }, + ] + }; + + let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) = + Self::lower_binop_ast(&parts.carrier_update_in_body, builder, &phi_bindings)?; + + let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 6: Build header_effects + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 7: Build body plans (break condition check) + let mut body_plans: Vec = Vec::new(); + for const_effect in break_cond_consts { + body_plans.push(CorePlan::Effect(const_effect)); + } + body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_break, + lhs: break_cond_lhs, + op: break_cond_op, + rhs: break_cond_rhs, + })); + + // Step 8: Build step_effects + let mut step_effects = carrier_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: carrier_step, + lhs: carrier_lhs, + op: carrier_op, + rhs: carrier_rhs, + }); + step_effects.extend(loop_inc_consts); + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 9: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (break_then_bb, break_then_effects), + (step_bb, step_effects), + ]; + + // Step 10: Build PHIs + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_var_{}", parts.loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: carrier_current, + inputs: vec![(preheader_bb, carrier_init), (step_bb, carrier_step)], + tag: format!("carrier_{}", parts.carrier_var), + }, + CorePhiInfo { + block: after_bb, + dst: carrier_out, + inputs: vec![(header_bb, carrier_current), (break_then_bb, carrier_break)], + tag: format!("after_{}", parts.carrier_var), + }, + ]; + + // Step 11: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + else_target: after_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_break, + then_target: break_then_bb, + else_target: step_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + ]; + + let wires = vec![ + EdgeStub { + from: break_then_bb, + kind: ExitKind::Normal, + target: Some(after_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 12: Build final_values + let final_values = vec![ + (parts.loop_var.clone(), loop_var_current), + (parts.carrier_var.clone(), carrier_out), + ]; + + // Step 13: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: body_plans, + cond_loop, + cond_match: cond_break, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern2_break", + "CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI is key!)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern3_if_phi.rs b/src/mir/builder/control_flow/plan/normalizer/pattern3_if_phi.rs new file mode 100644 index 00000000..fafac54e --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern3_if_phi.rs @@ -0,0 +1,287 @@ +use super::helpers::{create_phi_bindings, LoopBlocksWithIfPhi}; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern3IfPhiPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::MirType; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Phase 286 P2.6.1: Pattern3IfPhi → CorePlan 変換 + /// + /// Expands Pattern3 (Loop with If-Phi) semantics into generic CorePlan: + /// - CFG structure: preheader → header → body → then/else → merge → step → header + /// - 3 PHIs: 2 in header (loop_var, carrier), 1 in merge (carrier_next) + /// - If-else branching with PHI merge (no Select instruction) + pub(super) fn normalize_pattern3_if_phi( + builder: &mut MirBuilder, + parts: Pattern3IfPhiPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern3_if_phi", + &format!( + "Phase 286 P2.6.1: Normalizing Pattern3IfPhi for {} (loop_var: {}, carrier_var: {})", + ctx.func_name, parts.loop_var, parts.carrier_var + ), + ); + } + + // Step 1: Get host ValueIds for variables + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; + + let carrier_init = builder + .variable_ctx + .variable_map + .get(&parts.carrier_var) + .copied() + .ok_or_else(|| format!("[normalizer] Carrier variable {} not found", parts.carrier_var))?; + + // Step 2-3: Allocate BasicBlockIds + let blocks = LoopBlocksWithIfPhi::allocate(builder)?; + let LoopBlocksWithIfPhi { + preheader_bb, + header_bb, + body_bb, + then_bb, + else_bb, + merge_bb, + step_bb, + after_bb, + } = blocks; + + if debug { + trace_logger.debug( + "normalizer/pattern3_if_phi", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, merge={:?}, step={:?}, after={:?}", + preheader_bb, + header_bb, + body_bb, + then_bb, + else_bb, + merge_bb, + step_bb, + after_bb + ), + ); + } + + // Step 4: Allocate ValueIds for loop control + let loop_var_current = builder.alloc_typed(MirType::Integer); + let carrier_current = builder.alloc_typed(MirType::Integer); + let cond_loop = builder.alloc_typed(MirType::Bool); + let cond_if = builder.alloc_typed(MirType::Bool); + let carrier_then = builder.alloc_typed(MirType::Integer); + let carrier_else = builder.alloc_typed(MirType::Integer); + let carrier_next = builder.alloc_typed(MirType::Integer); + let loop_var_next = builder.alloc_typed(MirType::Integer); + + // Step 5: Build phi_bindings + let phi_bindings = create_phi_bindings(&[ + (&parts.loop_var, loop_var_current), + (&parts.carrier_var, carrier_current), + ]); + + // Step 6: Lower AST expressions + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; + + let (if_cond_lhs, if_cond_op, if_cond_rhs, if_cond_consts) = + Self::lower_compare_ast(&parts.if_condition, builder, &phi_bindings)?; + + let (then_lhs, then_op, then_rhs, then_consts) = + Self::lower_binop_ast(&parts.then_update, builder, &phi_bindings)?; + + let (else_lhs, else_op, else_rhs, else_consts) = + Self::lower_binop_ast(&parts.else_update, builder, &phi_bindings)?; + + let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 7: Build header_effects + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 8: Build body plans + let mut body_plans: Vec = Vec::new(); + for const_effect in if_cond_consts { + body_plans.push(CorePlan::Effect(const_effect)); + } + body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_if, + lhs: if_cond_lhs, + op: if_cond_op, + rhs: if_cond_rhs, + })); + + // Step 9: Build then_effects + let mut then_effects = then_consts; + then_effects.push(CoreEffectPlan::BinOp { + dst: carrier_then, + lhs: then_lhs, + op: then_op, + rhs: then_rhs, + }); + + // Step 10: Build else_effects + let mut else_effects = else_consts; + else_effects.push(CoreEffectPlan::BinOp { + dst: carrier_else, + lhs: else_lhs, + op: else_op, + rhs: else_rhs, + }); + + // Step 11: Build step_effects + let mut step_effects = loop_inc_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 12: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (then_bb, then_effects), + (else_bb, else_effects), + (merge_bb, vec![]), + (step_bb, step_effects), + ]; + + // Step 13: Build PHIs + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_var_{}", parts.loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: carrier_current, + inputs: vec![(preheader_bb, carrier_init), (step_bb, carrier_next)], + tag: format!("carrier_{}", parts.carrier_var), + }, + CorePhiInfo { + block: merge_bb, + dst: carrier_next, + inputs: vec![(then_bb, carrier_then), (else_bb, carrier_else)], + tag: format!("merge_{}", parts.carrier_var), + }, + ]; + + // Step 14: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_if, + then_target: then_bb, + then_args: empty_args.clone(), + else_target: else_bb, + else_args: empty_args.clone(), + }, + ]; + + let wires = vec![ + EdgeStub { + from: then_bb, + kind: ExitKind::Normal, + target: Some(merge_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: else_bb, + kind: ExitKind::Normal, + target: Some(merge_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: merge_bb, + kind: ExitKind::Normal, + target: Some(step_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 15: Build final_values + let final_values = vec![ + (parts.loop_var.clone(), loop_var_current), + (parts.carrier_var.clone(), carrier_current), + ]; + + // Step 16: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: body_plans, + cond_loop, + cond_match: cond_if, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern3_if_phi", + "CorePlan construction complete (7 blocks, 3 PHIs)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern4_continue.rs b/src/mir/builder/control_flow/plan/normalizer/pattern4_continue.rs new file mode 100644 index 00000000..77fbd3ca --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern4_continue.rs @@ -0,0 +1,299 @@ +use super::helpers::create_phi_bindings; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern4ContinuePlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::MirType; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Phase 286 P2: Pattern4Continue → CorePlan 変換 + /// + /// Expands Pattern4 (Loop with Continue) semantics into generic CorePlan: + /// - CFG structure: 2-step branching + header PHI merge + /// - NO Select instruction (not in CoreEffectPlan) + /// - NO after PHI (header PHI handles all paths) + pub(super) fn normalize_pattern4_continue( + builder: &mut MirBuilder, + parts: Pattern4ContinuePlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern4_continue", + &format!( + "Phase 286 P2: Normalizing Pattern4Continue for {} (loop_var: {}, carriers: {:?})", + ctx.func_name, parts.loop_var, parts.carrier_vars + ), + ); + } + + // P2 PoC Scope: Single carrier only + if parts.carrier_vars.len() != 1 { + return Err(format!( + "[normalizer] P2 PoC scope: only single carrier supported (found: {})", + parts.carrier_vars.len() + )); + } + + let carrier_var = &parts.carrier_vars[0]; + + // Step 1: Get host ValueIds for variables + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; + + let carrier_init = builder + .variable_ctx + .variable_map + .get(carrier_var) + .copied() + .ok_or_else(|| format!("[normalizer] Carrier variable {} not found", carrier_var))?; + + // Step 2: Capture preheader block + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; + + // Step 3: Allocate BasicBlockIds for 8 blocks (2-step branching architecture) + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let continue_path_bb = builder.next_block_id(); + let normal_path_bb = builder.next_block_id(); + let step_continue_bb = builder.next_block_id(); + let step_normal_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + if debug { + trace_logger.debug( + "normalizer/pattern4_continue", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, continue_path={:?}, normal_path={:?}, step_continue={:?}, step_normal={:?}, after={:?}", + preheader_bb, + header_bb, + body_bb, + continue_path_bb, + normal_path_bb, + step_continue_bb, + step_normal_bb, + after_bb + ), + ); + } + + // Step 4: Allocate ValueIds for loop control and carriers + let loop_var_current = builder.alloc_typed(MirType::Integer); + let carrier_current = builder.alloc_typed(MirType::Integer); + + // Step 4.5: Build phi_bindings + let phi_bindings = create_phi_bindings(&[ + (&parts.loop_var, loop_var_current), + (carrier_var, carrier_current), + ]); + + let cond_loop = builder.alloc_typed(MirType::Bool); + let cond_continue = builder.alloc_typed(MirType::Bool); + let loop_var_cont_next = builder.alloc_typed(MirType::Integer); + let carrier_updated = builder.alloc_typed(MirType::Integer); + let loop_var_norm_next = builder.alloc_typed(MirType::Integer); + + // Step 5: Lower AST expressions to get operands and const definitions + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; + + let (cont_cond_lhs, cont_cond_op, cont_cond_rhs, cont_cond_consts) = + Self::lower_compare_ast(&parts.continue_condition, builder, &phi_bindings)?; + + let carrier_update_ast = parts + .carrier_updates + .get(carrier_var) + .ok_or_else(|| format!("[normalizer] Carrier update for {} not found", carrier_var))?; + let (carrier_update_lhs, carrier_update_op, carrier_update_rhs, carrier_update_consts) = + Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?; + + let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 6: Build header_effects (const definitions + loop condition check) + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 7: Build body (as CorePlan items, NOT block_effects) + let mut body = Vec::new(); + for const_effect in cont_cond_consts { + body.push(CorePlan::Effect(const_effect)); + } + body.push(CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_continue, + lhs: cont_cond_lhs, + op: cont_cond_op, + rhs: cont_cond_rhs, + })); + + // Step 8: Build continue_path effects + let mut continue_path_effects = loop_inc_consts.clone(); + continue_path_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_cont_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 9: Build normal_path effects + let mut normal_path_effects = carrier_update_consts; + normal_path_effects.push(CoreEffectPlan::BinOp { + dst: carrier_updated, + lhs: carrier_update_lhs, + op: carrier_update_op, + rhs: carrier_update_rhs, + }); + normal_path_effects.extend(loop_inc_consts); + normal_path_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_norm_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 10: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (continue_path_bb, continue_path_effects), + (normal_path_bb, normal_path_effects), + (step_continue_bb, vec![]), + (step_normal_bb, vec![]), + ]; + + // Step 11: Build PHIs + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![ + (preheader_bb, loop_var_init), + (step_continue_bb, loop_var_cont_next), + (step_normal_bb, loop_var_norm_next), + ], + tag: format!("loop_var_{}", parts.loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: carrier_current, + inputs: vec![ + (preheader_bb, carrier_init), + (step_continue_bb, carrier_current), + (step_normal_bb, carrier_updated), + ], + tag: format!("carrier_{}", carrier_var), + }, + ]; + + // Step 12: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_continue, + then_target: continue_path_bb, + then_args: empty_args.clone(), + else_target: normal_path_bb, + else_args: empty_args.clone(), + }, + ]; + + let wires = vec![ + EdgeStub { + from: continue_path_bb, + kind: ExitKind::Normal, + target: Some(step_continue_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_continue_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: normal_path_bb, + kind: ExitKind::Normal, + target: Some(step_normal_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_normal_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 13: Build final_values + let final_values = vec![ + (parts.loop_var.clone(), loop_var_current), + (carrier_var.clone(), carrier_current), + ]; + + // Step 14: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb: step_normal_bb, + after_bb, + found_bb: after_bb, + body, + cond_loop, + cond_match: cond_continue, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern4_continue", + "CorePlan construction complete (2-step branching with header PHI merge)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs b/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs new file mode 100644 index 00000000..e7a3d685 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs @@ -0,0 +1,542 @@ +use super::helpers::create_phi_bindings; +use super::{ + CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern5ExitKind, + Pattern5InfiniteEarlyExitPlan, +}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{ConstValue, MirType, ValueId}; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Pattern5InfiniteEarlyExit → CorePlan 変換 (Phase 286 P3.2) + /// + /// Expands infinite loop with early exit into CorePlan. + /// + /// # Return version CFG (5 blocks) + /// ```text + /// preheader → header(PHI: i_current) → body(exit_cond) + /// ↑ ↓ + /// └───── step ←──────── else path + /// ↓ + /// then path: Return (found_bb) + /// ``` + /// + /// # Break version CFG (6 blocks) + /// ```text + /// preheader → header(PHI: i, carrier) → body(exit_cond) + /// ↑ ↓ + /// └───── step ←────────── else path + /// ↓ + /// then path → after_bb(PHI: carrier_out) + /// ``` + pub(super) fn normalize_pattern5_infinite_early_exit( + builder: &mut MirBuilder, + parts: Pattern5InfiniteEarlyExitPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern5_infinite_early_exit", + &format!( + "Phase 286 P3.2: Normalizing Pattern5 for {} (loop_var={}, exit_kind={:?})", + ctx.func_name, parts.loop_var, parts.exit_kind + ), + ); + } + + match parts.exit_kind { + Pattern5ExitKind::Return => { + Self::normalize_pattern5_return(builder, parts, ctx, debug, &trace_logger) + } + Pattern5ExitKind::Break => { + Self::normalize_pattern5_break(builder, parts, ctx, debug, &trace_logger) + } + } + } + + /// Pattern5 Return version normalizer + /// + /// CFG: preheader → header → body → found_bb (return) / step → header + fn normalize_pattern5_return( + builder: &mut MirBuilder, + parts: Pattern5InfiniteEarlyExitPlan, + _ctx: &LoopPatternContext, + debug: bool, + trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace, + ) -> Result { + // Step 1: Block allocation (5 blocks) + let preheader_bb = builder + .current_block + .ok_or_else(|| "Pattern5Return: no current block".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let found_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + if debug { + trace_logger.debug( + "normalizer/pattern5_return", + &format!( + "Block allocation: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb + ), + ); + } + + // Step 2: Get initial values from variable_map + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| { + format!( + "Pattern5Return: loop_var '{}' not in variable_map", + parts.loop_var + ) + })?; + + // Step 3: ValueId allocation + let loop_var_current = builder.alloc_typed(MirType::Integer); + let cond_exit = builder.alloc_typed(MirType::Bool); + let loop_var_next = builder.alloc_typed(MirType::Integer); + let true_val = builder.alloc_typed(MirType::Bool); + + // Step 4: phi_bindings for AST lowering + let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); + + // Step 5: Lower AST expressions + let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) = + Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?; + + let return_value: Option<(ValueId, crate::ast::ASTNode)> = + if let Some(ref exit_val_ast) = parts.exit_value { + let return_val_id = builder.alloc_typed(MirType::Integer); + let return_val_ast = exit_val_ast.clone(); + Some((return_val_id, return_val_ast)) + } else { + None + }; + + let (inc_lhs, inc_op, inc_rhs, inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 6: Build header_effects + let header_effects = vec![CoreEffectPlan::Const { + dst: true_val, + value: ConstValue::Bool(true), + }]; + + // Step 7: Build body plans + let mut body_plans: Vec = Vec::new(); + for eff in exit_cond_consts { + body_plans.push(CorePlan::Effect(eff)); + } + body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_exit, + lhs: exit_cond_lhs, + op: exit_cond_op, + rhs: exit_cond_rhs, + })); + + // Step 8: Build found_bb effects (return value computation + Return) + let mut found_effects: Vec = Vec::new(); + let return_val_id = if let Some((ret_val_id, ref ret_val_ast)) = return_value { + if let crate::ast::ASTNode::Literal { + value: crate::ast::LiteralValue::Integer(n), + .. + } = ret_val_ast + { + found_effects.push(CoreEffectPlan::Const { + dst: ret_val_id, + value: ConstValue::Integer(*n), + }); + } else { + let (lhs, op, rhs, consts) = + Self::lower_binop_ast(ret_val_ast, builder, &phi_bindings)?; + for c in consts { + found_effects.push(c); + } + found_effects.push(CoreEffectPlan::BinOp { + dst: ret_val_id, + lhs, + op, + rhs, + }); + } + Some(ret_val_id) + } else { + None + }; + + // Step 9: Build step_effects (increment loop var) + let mut step_effects = inc_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: inc_lhs, + op: inc_op, + rhs: inc_rhs, + }); + + // Step 10: Build PHIs + let phis = vec![CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_carrier_{}", parts.loop_var), + }]; + + // Step 11: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (found_bb, found_effects), + (step_bb, step_effects), + (after_bb, vec![]), + ]; + + // Step 12: Build Frag with Return exit + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: true_val, + then_target: body_bb, + else_target: after_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_exit, + then_target: found_bb, + else_target: step_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + ]; + + let return_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: if let Some(val_id) = return_val_id { + vec![val_id] + } else { + vec![] + }, + }; + + let wires = vec![ + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: found_bb, + kind: ExitKind::Return, + target: None, + args: return_args, + }, + EdgeStub { + from: after_bb, + kind: ExitKind::Return, + target: None, + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 13: Build final_values + let final_values = vec![(parts.loop_var.clone(), loop_var_current)]; + + // Step 14: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb, + body: body_plans, + cond_loop: true_val, + cond_match: cond_exit, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern5_return", + "CorePlan construction complete (5 blocks, 1 PHI, Return exit)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } + + /// Pattern5 Break version normalizer + /// + /// CFG: preheader → header → body → break_then → after / step → header + fn normalize_pattern5_break( + builder: &mut MirBuilder, + parts: Pattern5InfiniteEarlyExitPlan, + _ctx: &LoopPatternContext, + debug: bool, + trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace, + ) -> Result { + // Step 1: Block allocation (6 blocks) + let preheader_bb = builder + .current_block + .ok_or_else(|| "Pattern5Break: no current block".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let break_then_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + if debug { + trace_logger.debug( + "normalizer/pattern5_break", + &format!( + "Block allocation: preheader={:?}, header={:?}, body={:?}, break_then={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, break_then_bb, step_bb, after_bb + ), + ); + } + + // Step 2: Get carrier_var (required for Break version) + let carrier_var = parts + .carrier_var + .as_ref() + .ok_or_else(|| "Pattern5Break: carrier_var required for Break version".to_string())?; + let carrier_update_ast = parts + .carrier_update + .as_ref() + .ok_or_else(|| "Pattern5Break: carrier_update required for Break version".to_string())?; + + // Step 3: Get initial values from variable_map + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| { + format!( + "Pattern5Break: loop_var '{}' not in variable_map", + parts.loop_var + ) + })?; + + let carrier_init = builder + .variable_ctx + .variable_map + .get(carrier_var) + .copied() + .ok_or_else(|| { + format!( + "Pattern5Break: carrier_var '{}' not in variable_map", + carrier_var + ) + })?; + + // Step 4: ValueId allocation + let loop_var_current = builder.alloc_typed(MirType::Integer); + let carrier_current = builder.alloc_typed(MirType::Integer); + let cond_exit = builder.alloc_typed(MirType::Bool); + let carrier_step = builder.alloc_typed(MirType::Integer); + let loop_var_next = builder.alloc_typed(MirType::Integer); + let carrier_out = builder.alloc_typed(MirType::Integer); + let true_val = builder.alloc_typed(MirType::Bool); + + // Step 5: phi_bindings for AST lowering + let phi_bindings = create_phi_bindings(&[ + (&parts.loop_var, loop_var_current), + (carrier_var, carrier_current), + ]); + + // Step 6: Lower AST expressions + let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) = + Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?; + + let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) = + Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?; + + let (inc_lhs, inc_op, inc_rhs, inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 7: Build header_effects + let header_effects = vec![CoreEffectPlan::Const { + dst: true_val, + value: ConstValue::Bool(true), + }]; + + // Step 8: Build body plans (exit condition check) + let mut body_plans: Vec = Vec::new(); + for eff in exit_cond_consts { + body_plans.push(CorePlan::Effect(eff)); + } + body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_exit, + lhs: exit_cond_lhs, + op: exit_cond_op, + rhs: exit_cond_rhs, + })); + + // Step 9: Build break_then_effects (empty) + let break_then_effects = vec![]; + + // Step 10: Build step_effects (carrier update + loop increment) + let mut step_effects = carrier_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: carrier_step, + lhs: carrier_lhs, + op: carrier_op, + rhs: carrier_rhs, + }); + for c in inc_consts { + step_effects.push(c); + } + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: inc_lhs, + op: inc_op, + rhs: inc_rhs, + }); + + // Step 11: Build PHIs + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_carrier_{}", parts.loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: carrier_current, + inputs: vec![(preheader_bb, carrier_init), (step_bb, carrier_step)], + tag: format!("loop_carrier_{}", carrier_var), + }, + CorePhiInfo { + block: after_bb, + dst: carrier_out, + inputs: vec![(break_then_bb, carrier_current)], + tag: format!("exit_carrier_{}", carrier_var), + }, + ]; + + // Step 12: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (break_then_bb, break_then_effects), + (step_bb, step_effects), + ]; + + // Step 13: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: true_val, + then_target: body_bb, + else_target: after_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_exit, + then_target: break_then_bb, + else_target: step_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + ]; + + let loop_id = crate::mir::control_form::LoopId(0); + + let wires = vec![ + EdgeStub { + from: break_then_bb, + kind: ExitKind::Break(loop_id), + target: Some(after_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 14: Build final_values + let final_values = vec![ + (parts.loop_var.clone(), loop_var_current), + (carrier_var.clone(), carrier_out), + ]; + + // Step 15: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: body_plans, + cond_loop: true_val, + cond_match: cond_exit, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern5_break", + "CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI for carrier)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern8_bool_predicate_scan.rs b/src/mir/builder/control_flow/plan/normalizer/pattern8_bool_predicate_scan.rs new file mode 100644 index 00000000..1c37ff92 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern8_bool_predicate_scan.rs @@ -0,0 +1,301 @@ +use super::helpers::create_phi_bindings; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern8BoolPredicateScanPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType}; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Pattern8BoolPredicateScan → CorePlan 変換 (Phase 286 P2.4.1) + /// + /// Expands Pattern8 (Bool Predicate Scan) semantics into generic CorePlan: + /// - CFG structure: preheader → header → body → found (return false) / step → header + /// - 1 PHI for loop variable (i) + /// - Early exit: return false in found_bb + /// - Post-loop: return true in after_bb + pub(super) fn normalize_pattern8_bool_predicate_scan( + builder: &mut MirBuilder, + parts: Pattern8BoolPredicateScanPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::edgecfg::api::compose; + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern8_bool_predicate_scan", + &format!( + "Phase 286 P2.4.1: Normalizing Pattern8 for {} (loop_var: {}, predicate: {}.{})", + ctx.func_name, parts.loop_var, parts.predicate_receiver, parts.predicate_method + ), + ); + } + + // P0 Scope: Forward scan (step=1) only + if parts.step_lit != 1 { + return Err(format!( + "[normalizer/pattern8] P0 scope: only forward scan supported (step={})", + parts.step_lit + )); + } + + // Step 1: Get host ValueIds for variables + let haystack_host = builder + .variable_ctx + .variable_map + .get(&parts.haystack) + .copied() + .ok_or_else(|| format!("[normalizer/pattern8] Variable {} not found", parts.haystack))?; + + let predicate_receiver_host = builder + .variable_ctx + .variable_map + .get(&parts.predicate_receiver) + .copied() + .ok_or_else(|| { + format!( + "[normalizer/pattern8] Variable {} not found", + parts.predicate_receiver + ) + })?; + + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer/pattern8] Loop variable {} not found", parts.loop_var))?; + + // Step 2: Capture preheader block + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer/pattern8] No current block for loop entry".to_string())?; + + // Step 3: Allocate BasicBlockIds for 6 blocks + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let found_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + if debug { + trace_logger.debug( + "normalizer/pattern8_bool_predicate_scan", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb + ), + ); + } + + // Step 4: Allocate ValueIds + let loop_var_current = builder.alloc_typed(MirType::Integer); + let cond_loop = builder.alloc_typed(MirType::Bool); + let one_val = builder.alloc_typed(MirType::Integer); + let i_plus_one = builder.alloc_typed(MirType::Integer); + let ch = builder.alloc_typed(MirType::String); + let cond_predicate = builder.alloc_typed(MirType::Bool); + let cond_not_predicate = builder.alloc_typed(MirType::Bool); + let loop_var_next = builder.alloc_typed(MirType::Integer); + let true_val = builder.alloc_typed(MirType::Bool); + let false_val = builder.alloc_typed(MirType::Bool); + + // Step 5: Build phi_bindings + let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]); + + // Step 6: Lower loop condition + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; + + // Step 7: Build header_effects + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 8: Build body (predicate check) + let body = vec![ + CorePlan::Effect(CoreEffectPlan::Const { + dst: one_val, + value: ConstValue::Integer(1), + }), + CorePlan::Effect(CoreEffectPlan::Const { + dst: false_val, + value: ConstValue::Bool(false), + }), + CorePlan::Effect(CoreEffectPlan::BinOp { + dst: i_plus_one, + lhs: loop_var_current, + op: BinaryOp::Add, + rhs: one_val, + }), + CorePlan::Effect(CoreEffectPlan::MethodCall { + dst: Some(ch), + object: haystack_host, + method: "substring".to_string(), + args: vec![loop_var_current, i_plus_one], + effects: EffectMask::PURE.add(Effect::Io), + }), + CorePlan::Effect(CoreEffectPlan::MethodCall { + dst: Some(cond_predicate), + object: predicate_receiver_host, + method: parts.predicate_method.clone(), + args: vec![ch], + effects: EffectMask::PURE.add(Effect::Io), + }), + CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_not_predicate, + lhs: cond_predicate, + op: CompareOp::Eq, + rhs: false_val, + }), + ]; + + // Step 9: Build step_effects + let step_effects = vec![CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: loop_var_current, + op: BinaryOp::Add, + rhs: one_val, + }]; + + // Step 10: Build found_bb effects (empty) + let found_effects = vec![]; + + // Step 11: Build after_bb effects + let after_effects = vec![CoreEffectPlan::Const { + dst: true_val, + value: ConstValue::Bool(true), + }]; + + // Step 12: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (found_bb, found_effects), + (step_bb, step_effects), + (after_bb, after_effects), + ]; + + // Step 13: Build PHI + let phis = vec![CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_var_{}", parts.loop_var), + }]; + + // Step 14: Build Frag using compose::cleanup for early exit + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let ret_false_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![false_val], + }; + + let ret_true_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![true_val], + }; + + let main_branches = vec![ + BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_not_predicate, + then_target: found_bb, + then_args: empty_args.clone(), + else_target: step_bb, + else_args: empty_args.clone(), + }, + ]; + + let main_wires = vec![EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }]; + + let main_frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires: main_wires, + branches: main_branches, + }; + + let cleanup_exits = vec![ + EdgeStub { + from: found_bb, + kind: ExitKind::Return, + target: None, + args: ret_false_args, + }, + EdgeStub { + from: after_bb, + kind: ExitKind::Return, + target: None, + args: ret_true_args, + }, + ]; + + let cleanup_frag = Frag { + entry: found_bb, + exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]), + wires: vec![], + branches: vec![], + }; + + let frag = compose::cleanup(main_frag, cleanup_frag, None, None) + .expect("compose::cleanup() failed in normalize_pattern8_bool_predicate_scan"); + + // Step 15: Build final_values + let final_values = vec![(parts.loop_var.clone(), loop_var_current)]; + + // Step 16: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb, + body, + cond_loop, + cond_match: cond_not_predicate, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern8_bool_predicate_scan", + "CorePlan construction complete (5 blocks, 1 PHI, 2 Return exits)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern9_accum_const_loop.rs b/src/mir/builder/control_flow/plan/normalizer/pattern9_accum_const_loop.rs new file mode 100644 index 00000000..312f828b --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern9_accum_const_loop.rs @@ -0,0 +1,214 @@ +use super::helpers::{create_phi_bindings, LoopBlocksStandard5}; +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern9AccumConstLoopPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::MirType; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// Phase 286 P2.3: Pattern9AccumConstLoop → CorePlan 変換 + /// + /// Expands Pattern9 (Accumulator Const Loop) semantics into generic CorePlan: + /// - CFG structure: preheader → header → body → step → header (back-edge) + /// - 2 PHIs in header: loop variable (i) and accumulator (sum) + /// - Similar to Pattern1 but with an additional carrier + pub(super) fn normalize_pattern9_accum_const_loop( + builder: &mut MirBuilder, + parts: Pattern9AccumConstLoopPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/pattern9_accum_const_loop", + &format!( + "Phase 286 P2.3: Normalizing Pattern9AccumConstLoop for {} (loop_var: {}, acc_var: {})", + ctx.func_name, parts.loop_var, parts.acc_var + ), + ); + } + + // Step 1: Get host ValueIds for variables + let loop_var_init = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; + + let acc_var_init = builder + .variable_ctx + .variable_map + .get(&parts.acc_var) + .copied() + .ok_or_else(|| format!("[normalizer] Accumulator variable {} not found", parts.acc_var))?; + + // Step 2-3: Allocate BasicBlockIds + let blocks = LoopBlocksStandard5::allocate(builder)?; + let LoopBlocksStandard5 { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + } = blocks; + + if debug { + trace_logger.debug( + "normalizer/pattern9_accum_const_loop", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, step_bb, after_bb + ), + ); + } + + // Step 4: Allocate ValueIds for loop control + let loop_var_current = builder.alloc_typed(MirType::Integer); + let acc_var_current = builder.alloc_typed(MirType::Integer); + let cond_loop = builder.alloc_typed(MirType::Bool); + let acc_var_next = builder.alloc_typed(MirType::Integer); + let loop_var_next = builder.alloc_typed(MirType::Integer); + + // Step 5: Build phi_bindings + let phi_bindings = create_phi_bindings(&[ + (&parts.loop_var, loop_var_current), + (&parts.acc_var, acc_var_current), + ]); + + // Step 6: Lower AST expressions + let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) = + Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?; + + let (acc_update_lhs, acc_update_op, acc_update_rhs, acc_update_consts) = + Self::lower_binop_ast(&parts.acc_update, builder, &phi_bindings)?; + + let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) = + Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?; + + // Step 7: Build header_effects + let mut header_effects = loop_cond_consts; + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: loop_cond_lhs, + op: loop_cond_op, + rhs: loop_cond_rhs, + }); + + // Step 8: Build step_effects + let mut step_effects = acc_update_consts; + step_effects.push(CoreEffectPlan::BinOp { + dst: acc_var_next, + lhs: acc_update_lhs, + op: acc_update_op, + rhs: acc_update_rhs, + }); + step_effects.extend(loop_inc_consts); + step_effects.push(CoreEffectPlan::BinOp { + dst: loop_var_next, + lhs: loop_inc_lhs, + op: loop_inc_op, + rhs: loop_inc_rhs, + }); + + // Step 9: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects), + (body_bb, vec![]), + (step_bb, step_effects), + ]; + + // Step 10: Build PHIs + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: loop_var_current, + inputs: vec![(preheader_bb, loop_var_init), (step_bb, loop_var_next)], + tag: format!("loop_var_{}", parts.loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: acc_var_current, + inputs: vec![(preheader_bb, acc_var_init), (step_bb, acc_var_next)], + tag: format!("acc_var_{}", parts.acc_var), + }, + ]; + + // Step 11: Build Frag + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }]; + + let wires = vec![ + EdgeStub { + from: body_bb, + kind: ExitKind::Normal, + target: Some(step_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires, + branches, + }; + + // Step 12: Build final_values + let final_values = vec![ + (parts.loop_var.clone(), loop_var_current), + (parts.acc_var.clone(), acc_var_current), + ]; + + // Step 13: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: vec![], + cond_loop, + cond_match: cond_loop, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/pattern9_accum_const_loop", + "CorePlan construction complete (4 blocks, 2 PHIs)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs b/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs new file mode 100644 index 00000000..cba072c0 --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs @@ -0,0 +1,342 @@ +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, ScanWithInitPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType}; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// ScanWithInit → CorePlan 変換 + /// + /// Expands scan-specific semantics into generic CorePlan: + /// - header_effects: one=1, needle_len, len, bound, cond_loop + /// - body: i+needle_len, substring, cond_match + /// - step_effects: i_next = i + 1 + pub(super) fn normalize_scan_with_init( + builder: &mut MirBuilder, + parts: ScanWithInitPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/scan_with_init", + &format!( + "Phase 273 P1: Normalizing ScanWithInit for {}", + ctx.func_name + ), + ); + } + + // P1 Scope: Forward scan (step=1) only + if parts.step_lit != 1 { + return Err(format!( + "[normalizer] P1 scope: only forward scan supported (step={})", + parts.step_lit + )); + } + + // Step 1: Get host ValueIds for variables + let s_host = builder + .variable_ctx + .variable_map + .get(&parts.haystack) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.haystack))?; + + let needle_host = builder + .variable_ctx + .variable_map + .get(&parts.needle) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.needle))?; + + let i_init_val = builder + .variable_ctx + .variable_map + .get(&parts.loop_var) + .copied() + .ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?; + + // Step 2: Capture preheader block (entry to loop) for PHI input + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; + + // Step 3: Allocate BasicBlockIds for 5 blocks + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + let found_bb = builder.next_block_id(); + + // Step 4: Allocate ValueIds for CorePlan + let i_current = builder.next_value_id(); // PHI destination + builder + .type_ctx + .value_types + .insert(i_current, MirType::Integer); + + let one_val = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(one_val, MirType::Integer); + + let needle_len_val = if parts.dynamic_needle { + let v = builder.next_value_id(); + builder.type_ctx.value_types.insert(v, MirType::Integer); + v + } else { + one_val // reuse one_val for fixed needle + }; + + let len_val = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(len_val, MirType::Integer); + + let bound_val = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(bound_val, MirType::Integer); + + let cond_loop = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(cond_loop, MirType::Bool); + + let i_plus_needle_len = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_plus_needle_len, MirType::Integer); + + let window_val = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(window_val, MirType::String); + + let cond_match = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(cond_match, MirType::Bool); + + let i_next_val = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_next_val, MirType::Integer); + + if debug { + trace_logger.debug( + "normalizer/scan_with_init", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}, found={:?}", + preheader_bb, header_bb, body_bb, step_bb, after_bb, found_bb + ), + ); + } + + // Step 5: Build header_effects (emitted in header_bb) + let mut header_effects = vec![ + // one = 1 + CoreEffectPlan::Const { + dst: one_val, + value: ConstValue::Integer(1), + }, + ]; + + // needle_len = needle.length() if dynamic, else reuse one_val + if parts.dynamic_needle { + header_effects.push(CoreEffectPlan::MethodCall { + dst: Some(needle_len_val), + object: needle_host, + method: "length".to_string(), + args: vec![], + effects: EffectMask::PURE.add(Effect::Io), + }); + } + + // len = s.length() + header_effects.push(CoreEffectPlan::MethodCall { + dst: Some(len_val), + object: s_host, + method: "length".to_string(), + args: vec![], + effects: EffectMask::PURE.add(Effect::Io), + }); + + // bound = len - needle_len + header_effects.push(CoreEffectPlan::BinOp { + dst: bound_val, + lhs: len_val, + op: BinaryOp::Sub, + rhs: needle_len_val, + }); + + // cond_loop = i <= bound + header_effects.push(CoreEffectPlan::Compare { + dst: cond_loop, + lhs: i_current, + op: CompareOp::Le, + rhs: bound_val, + }); + + // Step 6: Build body (emitted in body_bb) + let body = vec![ + // i_plus_needle_len = i + needle_len + CorePlan::Effect(CoreEffectPlan::BinOp { + dst: i_plus_needle_len, + lhs: i_current, + op: BinaryOp::Add, + rhs: needle_len_val, + }), + // window = s.substring(i, i + needle_len) + CorePlan::Effect(CoreEffectPlan::MethodCall { + dst: Some(window_val), + object: s_host, + method: "substring".to_string(), + args: vec![i_current, i_plus_needle_len], + effects: EffectMask::PURE.add(Effect::Io), + }), + // cond_match = window == needle + CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_match, + lhs: window_val, + op: CompareOp::Eq, + rhs: needle_host, + }), + ]; + + // Step 7: Build step_effects (emitted in step_bb) + let step_effects = vec![ + // i_next = i + 1 + CoreEffectPlan::BinOp { + dst: i_next_val, + lhs: i_current, + op: BinaryOp::Add, + rhs: one_val, + }, + ]; + + // Step 8: Build block_effects (Phase 273 P2) + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects.clone()), + (body_bb, vec![]), + (step_bb, step_effects.clone()), + ]; + + // Step 9: Build phis (Phase 273 P2) + let phis = vec![CorePhiInfo { + block: header_bb, + dst: i_current, + inputs: vec![(preheader_bb, i_init_val), (step_bb, i_next_val)], + tag: format!("loop_carrier_{}", parts.loop_var), + }]; + + // Step 10: Build Frag (Phase 273 P2) + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let ret_found_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![i_current], + }; + + use crate::mir::builder::control_flow::edgecfg::api::compose; + + let main_branches = vec![ + BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }, + BranchStub { + from: body_bb, + cond: cond_match, + then_target: found_bb, + then_args: empty_args.clone(), + else_target: step_bb, + else_args: empty_args.clone(), + }, + ]; + + let main_wires = vec![EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }]; + + let main_frag = Frag { + entry: header_bb, + exits: BTreeMap::new(), + wires: main_wires, + branches: main_branches, + }; + + let cleanup_exits = vec![EdgeStub { + from: found_bb, + kind: ExitKind::Return, + target: None, + args: ret_found_args, + }]; + + let cleanup_frag = Frag { + entry: found_bb, + exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]), + wires: vec![], + branches: vec![], + }; + + let frag = compose::cleanup(main_frag, cleanup_frag, None, None) + .expect("compose::cleanup() failed in normalize_scan_with_init"); + + // Step 11: Build final_values (Phase 273 P2) + let final_values = vec![(parts.loop_var.clone(), i_current)]; + + // Step 12: Build CoreLoopPlan (Phase 273 P3: all generalized fields REQUIRED) + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb, + body, + cond_loop, + cond_match, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/scan_with_init", + "CorePlan construction complete", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +} diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern_split_scan.rs b/src/mir/builder/control_flow/plan/normalizer/pattern_split_scan.rs new file mode 100644 index 00000000..77fd929b --- /dev/null +++ b/src/mir/builder/control_flow/plan/normalizer/pattern_split_scan.rs @@ -0,0 +1,439 @@ +use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, SplitScanPlan}; +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType}; +use std::collections::BTreeMap; + +impl super::PlanNormalizer { + /// SplitScan → CorePlan 変換 + /// + /// Expands split-specific semantics into generic CorePlan: + /// - 2 carriers: i (loop index), start (segment start) + /// - 6 blocks: preheader, header, body, then, else, step, after + /// - 4 PHI nodes: header (i_current, start_current) + step (i_next, start_next) + /// - Side effect: result.push(segment) in then_bb + pub(super) fn normalize_split_scan( + builder: &mut MirBuilder, + parts: SplitScanPlan, + ctx: &LoopPatternContext, + ) -> Result { + use crate::mir::builder::control_flow::joinir::trace; + use crate::mir::builder::control_flow::edgecfg::api::compose; + + let trace_logger = trace::trace(); + let debug = ctx.debug; + + if debug { + trace_logger.debug( + "normalizer/split_scan", + &format!( + "Phase 273 P2: Normalizing SplitScan for {}", + ctx.func_name + ), + ); + } + + // Step 1: Get host ValueIds for variables + let s_host = builder + .variable_ctx + .variable_map + .get(&parts.s_var) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.s_var))?; + + let sep_host = builder + .variable_ctx + .variable_map + .get(&parts.sep_var) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.sep_var))?; + + let result_host = builder + .variable_ctx + .variable_map + .get(&parts.result_var) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.result_var))?; + + let i_init_val = builder + .variable_ctx + .variable_map + .get(&parts.i_var) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.i_var))?; + + let start_init_val = builder + .variable_ctx + .variable_map + .get(&parts.start_var) + .copied() + .ok_or_else(|| format!("[normalizer] Variable {} not found", parts.start_var))?; + + // Step 2: Capture preheader block + let preheader_bb = builder + .current_block + .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?; + + // Step 3: Allocate BasicBlockIds for 6 blocks + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let then_bb = builder.next_block_id(); + let else_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + // Step 4: Allocate ValueIds for PHI destinations (before blocks) + let i_current = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_current, MirType::Integer); + + let start_current = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(start_current, MirType::Integer); + + let i_next = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_next, MirType::Integer); + + let start_next = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(start_next, MirType::Integer); + + // Step 5: Allocate ValueIds for expressions + let sep_len = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(sep_len, MirType::Integer); + + let s_len = builder.next_value_id(); + builder.type_ctx.value_types.insert(s_len, MirType::Integer); + + let limit = builder.next_value_id(); + builder.type_ctx.value_types.insert(limit, MirType::Integer); + + let cond_loop = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(cond_loop, MirType::Bool); + + let i_plus_sep = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_plus_sep, MirType::Integer); + + let chunk = builder.next_value_id(); + builder.type_ctx.value_types.insert(chunk, MirType::String); + + let cond_match = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(cond_match, MirType::Bool); + + let segment = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(segment, MirType::String); + + let start_next_then = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(start_next_then, MirType::Integer); + + let one = builder.next_value_id(); + builder.type_ctx.value_types.insert(one, MirType::Integer); + + let i_next_else = builder.next_value_id(); + builder + .type_ctx + .value_types + .insert(i_next_else, MirType::Integer); + + if debug { + trace_logger.debug( + "normalizer/split_scan", + &format!( + "Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, step={:?}, after={:?}", + preheader_bb, header_bb, body_bb, then_bb, else_bb, step_bb, after_bb + ), + ); + } + + // Step 6: Build header_effects + let header_effects = vec![ + // sep_len = sep.length() + CoreEffectPlan::MethodCall { + dst: Some(sep_len), + object: sep_host, + method: "length".to_string(), + args: vec![], + effects: EffectMask::PURE.add(Effect::Io), + }, + // s_len = s.length() + CoreEffectPlan::MethodCall { + dst: Some(s_len), + object: s_host, + method: "length".to_string(), + args: vec![], + effects: EffectMask::PURE.add(Effect::Io), + }, + // limit = s_len - sep_len + CoreEffectPlan::BinOp { + dst: limit, + lhs: s_len, + op: BinaryOp::Sub, + rhs: sep_len, + }, + // cond_loop = i <= limit + CoreEffectPlan::Compare { + dst: cond_loop, + lhs: i_current, + op: CompareOp::Le, + rhs: limit, + }, + ]; + + // Step 7: Build body effects and plans + let body = vec![ + // i_plus_sep = i + sep_len + CorePlan::Effect(CoreEffectPlan::BinOp { + dst: i_plus_sep, + lhs: i_current, + op: BinaryOp::Add, + rhs: sep_len, + }), + // chunk = s.substring(i, i_plus_sep) + CorePlan::Effect(CoreEffectPlan::MethodCall { + dst: Some(chunk), + object: s_host, + method: "substring".to_string(), + args: vec![i_current, i_plus_sep], + effects: EffectMask::PURE.add(Effect::Io), + }), + // cond_match = chunk == sep + CorePlan::Effect(CoreEffectPlan::Compare { + dst: cond_match, + lhs: chunk, + op: CompareOp::Eq, + rhs: sep_host, + }), + ]; + + // Step 8: Build then_effects (push + updates) + let then_effects = vec![ + // segment = s.substring(start, i) + CoreEffectPlan::MethodCall { + dst: Some(segment), + object: s_host, + method: "substring".to_string(), + args: vec![start_current, i_current], + effects: EffectMask::PURE.add(Effect::Io), + }, + // result.push(segment) - Side effect! + CoreEffectPlan::MethodCall { + dst: None, + object: result_host, + method: "push".to_string(), + args: vec![segment], + effects: EffectMask::MUT, + }, + // start_next_then = i + sep_len + CoreEffectPlan::BinOp { + dst: start_next_then, + lhs: i_current, + op: BinaryOp::Add, + rhs: sep_len, + }, + ]; + + // Step 9: Build else_effects (increment i) + let else_effects = vec![ + // one = const 1 + CoreEffectPlan::Const { + dst: one, + value: ConstValue::Integer(1), + }, + // i_next_else = i + 1 + CoreEffectPlan::BinOp { + dst: i_next_else, + lhs: i_current, + op: BinaryOp::Add, + rhs: one, + }, + ]; + + // Step 9.5: Build Frags for compose::if_() (Phase 281 P0) + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let mut then_exits = BTreeMap::new(); + then_exits.insert( + ExitKind::Normal, + vec![EdgeStub { + from: then_bb, + kind: ExitKind::Normal, + target: None, + args: empty_args.clone(), + }], + ); + let then_frag = Frag { + entry: then_bb, + exits: then_exits, + wires: vec![], + branches: vec![], + }; + + let mut else_exits = BTreeMap::new(); + else_exits.insert( + ExitKind::Normal, + vec![EdgeStub { + from: else_bb, + kind: ExitKind::Normal, + target: None, + args: empty_args.clone(), + }], + ); + let else_frag = Frag { + entry: else_bb, + exits: else_exits, + wires: vec![], + branches: vec![], + }; + + let step_frag = Frag { + entry: step_bb, + exits: BTreeMap::new(), + wires: vec![], + branches: vec![], + }; + + let body_if_frag = compose::if_( + body_bb, + cond_match, + then_frag, + empty_args.clone(), + else_frag, + empty_args.clone(), + step_frag, + ); + + // Step 10: Build block_effects + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, header_effects.clone()), + (body_bb, vec![]), + (then_bb, then_effects), + (else_bb, else_effects), + (step_bb, vec![]), + ]; + + // Step 11: Build phis (4 PHIs: 2 in header + 2 in step) + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: i_current, + inputs: vec![(preheader_bb, i_init_val), (step_bb, i_next)], + tag: format!("loop_carrier_i_{}", parts.i_var), + }, + CorePhiInfo { + block: header_bb, + dst: start_current, + inputs: vec![(preheader_bb, start_init_val), (step_bb, start_next)], + tag: format!("loop_carrier_start_{}", parts.start_var), + }, + CorePhiInfo { + block: step_bb, + dst: i_next, + inputs: vec![(then_bb, start_next_then), (else_bb, i_next_else)], + tag: format!("step_phi_i_{}", parts.i_var), + }, + CorePhiInfo { + block: step_bb, + dst: start_next, + inputs: vec![(then_bb, start_next_then), (else_bb, start_current)], + tag: format!("step_phi_start_{}", parts.start_var), + }, + ]; + + // Step 12: Build Frag + let mut branches = vec![BranchStub { + from: header_bb, + cond: cond_loop, + then_target: body_bb, + then_args: empty_args.clone(), + else_target: after_bb, + else_args: empty_args.clone(), + }]; + + branches.extend(body_if_frag.branches); + + let mut wires = Vec::new(); + wires.extend(body_if_frag.wires); + + wires.push(EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }); + + let mut exits = BTreeMap::new(); + for (kind, stubs) in body_if_frag.exits { + exits.insert(kind, stubs); + } + + let frag = Frag { + entry: header_bb, + exits, + wires, + branches, + }; + + // Step 13: Build final_values (i, start for post-loop) + let final_values = vec![(parts.i_var.clone(), i_current), (parts.start_var.clone(), start_current)]; + + // Step 14: Build CoreLoopPlan + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body, + cond_loop, + cond_match, + block_effects, + phis, + frag, + final_values, + }; + + if debug { + trace_logger.debug( + "normalizer/split_scan", + "CorePlan construction complete (6 blocks, 4 PHIs)", + ); + } + + Ok(CorePlan::Loop(loop_plan)) + } +}