Files
hakorune/docs/development/current/main/phase33-16-loop-header-phi-design.md

246 lines
8.9 KiB
Markdown
Raw Normal View History

feat(joinir): Phase 33-22 CommonPatternInitializer & JoinIRConversionPipeline integration Unifies initialization and conversion logic across all 4 loop patterns, eliminating code duplication and establishing single source of truth. ## Changes ### Infrastructure (New) - CommonPatternInitializer (117 lines): Unified loop var extraction + CarrierInfo building - JoinIRConversionPipeline (127 lines): Unified JoinIR→MIR→Merge flow ### Pattern Refactoring - Pattern 1: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 2: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 3: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 4: Uses CommonPatternInitializer + JoinIRConversionPipeline (-40 lines) ### Code Reduction - Total reduction: ~115 lines across all patterns - Zero code duplication in initialization/conversion - Pattern files: 806 lines total (down from ~920) ### Quality Improvements - Single source of truth for initialization - Consistent conversion flow across all patterns - Guaranteed boundary.loop_var_name setting (prevents SSA-undef bugs) - Improved maintainability and testability ### Testing - All 4 patterns tested and passing: - Pattern 1 (Simple While): ✅ - Pattern 2 (With Break): ✅ - Pattern 3 (If-Else PHI): ✅ - Pattern 4 (With Continue): ✅ ### Documentation - Phase 33-22 inventory and results document - Updated joinir-architecture-overview.md with new infrastructure ## Breaking Changes None - pure refactoring with no API changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-07 21:02:20 +09:00
# Phase 3316: Loop Header PHI as Exit SSOT — Design
日付: 20251207
状態: 設計フェーズ完了(実装前の設計メモ)
このフェーズでは、JoinIR → MIR マージ時の「ループ出口値の扱い」を、
- expr 結果ライン(`loop` を式として使うケース)
- carrier ライン(`start/end/sum` など状態更新だけを行うケース)
で構造的に整理し直すよ。
目的は:
1. SSAundef を根本的に防ぐ(継続関数パラメータを PHI 入力に使わない)
2. ループヘッダ PHI を「ループ変数の真の現在値」の SSOT にする
3. ExitLineExitMeta/ExitBinding/ExitLineReconnectorと expr PHI をきれいに分離する
---
## 1. 現状の問題整理
### 1.1 何が壊れているか
- Pattern2`joinir_min_loop.hako`)などで:
- 以前は `Return { value: i_param }` をそのまま exit PHI の入力に使っていた
- JoinIR のパラメータ `i_param` は、MIR に inline された時点では **SSA 定義を持たない**
- そのため PHI 入力に remap された ValueId が未定義になり、SSAundef が発生していた
- Phase 3315 では:
- `value_collector` から JoinIR パラメータを除外
- `instruction_rewriter``exit_phi_inputs` / `carrier_inputs` 収集を一時停止
- → SSAundef は解消したが、「ループ出口値」が初期値のままになっている
### 1.2 何が足りていないか
本来必要だったものは:
1. ループヘッダに置かれる PHI`i_phi`, `sum_phi` など)を、
「ループの現在値」として扱う仕組み(構造上の SSOT
2. expr 結果ライン:
- 「どの ValueId が `loop` 式の最終結果か」を Loop ヘッダ PHI を通じて知ること。
3. carrier ライン:
- ExitLine が、ヘッダ PHI の `dst` をキャリア出口として利用できるようにすること。
今まではこの部分が暗黙のまま進んでいたため、JoinIR パラメータに依存した不安定な出口線になっていた。
---
## 2. マージパイプラインと Phase 3.5 追加
現在の JoinIR → MIR マージパイプラインは概念的にこうなっている:
1. Phase 1: block_allocator
JoinIR 関数群をホスト MIR 関数に inline するためのブロック ID の「型」を決める。
2. Phase 2: value_collector
JoinIR 内で必要となる ValueId を収集し、remap のための準備をする。
3. Phase 3: remap_values
JoinIR の ValueId → MIR 側の ValueId にマップする。
4. Phase 4: instruction_rewriter
Return → Jump, Call → Jump など命令を書き換える。
5. Phase 5: exit_phi_builder
expr 結果用の exit PHI を生成する。
6. Phase 6: exit_line
ExitMetaCollector + ExitLineReconnector で carrier を variable_map に反映。
Phase 3316 ではここに **Phase 3.5: LoopHeaderPhiBuilder** を追加する:
```text
Phase 1: block_allocator
Phase 2: value_collector
Phase 3: remap_values
Phase 3.5: loop_header_phi_builder ← NEW
Phase 4: instruction_rewriter
Phase 5: exit_phi_builder
Phase 6: exit_line
```
LoopHeaderPhiBuilder の責務は:
- ループヘッダブロックを特定し
- そのブロックに PHI を生成して
- 各キャリアの「現在値」の `dst` を決める
- expr 結果として使うべき値(あれば)を決める
- それらを構造体で返す
---
## 3. LoopHeaderPhiInfo 箱
### 3.1 構造
```rust
/// ループヘッダ PHI の SSOT
pub struct LoopHeaderPhiInfo {
/// ヘッダブロックPHI が置かれるブロック)
pub header_block: BasicBlockId,
/// 各キャリアごとに「ヘッダ PHI の dst」を持つ
/// 例: [("i", v_i_phi), ("sum", v_sum_phi)]
pub carrier_phis: Vec<CarrierPhiEntry>,
/// ループを式として使う場合の expr 結果 PHI
/// ない場合は Noneキャリアのみのループ
pub expr_result_phi: Option<ValueId>,
}
pub struct CarrierPhiEntry {
pub name: String, // carrier 名 ("i", "sum" 等)
pub dst: ValueId, // その carrier のヘッダ PHI の dst
}
```
### 3.2 入口 / 出口
LoopHeaderPhiBuilder は次の入力を取るイメージだよ:
- `loop_header: BasicBlockId`
Pattern lowerer / ルーティングで「ここがヘッダ」と決めたブロック。
- `carrier_names: &[String]`
`CarrierInfo``LoopFeatures` から得たキャリア名一覧。
- `join_fragment_meta: &JoinFragmentMeta`
expr 結果があるかどうかのフラグ(後述)。
そして `LoopHeaderPhiInfo` を返す:
```rust
fn build_loop_header_phis(
func: &mut MirFunction,
loop_header: BasicBlockId,
carrier_names: &[String],
fragment_meta: &JoinFragmentMeta,
) -> LoopHeaderPhiInfo;
```
---
## 4. JoinFragmentMeta / ExitMeta / Boundary との接続
### 4.1 JoinFragmentMeta
すでに次のような形になっている:
```rust
pub struct JoinFragmentMeta {
pub expr_result: Option<ValueId>, // JoinIR ローカルの expr 結果
pub exit_meta: ExitMeta, // carrier 用メタ
}
```
Phase 3316 では:
- lowerer 側Pattern2 等は「Loop header PHI を前提にした expr_result」を設定する方向に揃える。
- もしくは LoopHeaderPhiBuilder の結果から `expr_result_phi` を JoinFragmentMeta に反映する。
- 重要なのは、「expr_result に JoinIR パラメータ(`i_param` 等)を直接入れない」こと。
### 4.2 ExitMeta / ExitLine 側
ExitMeta / ExitLine は既に carrier 専用ラインになっているので、方針は:
- ExitMeta 内の `join_exit_value` は「ヘッダ PHI の dst」を指すようにする。
- LoopHeaderPhiInfo の `carrier_phis` から情報を取る形にリファクタリングする。
- これにより、ExitLineReconnector は単に
```rust
variable_map[carrier_name] = remapper.remap(phi_dst);
```
と書けるようになる。
### 4.3 JoinInlineBoundary
Boundary 構造体自体には新フィールドを足さず、
- join_inputs / host_inputsループパラメータ
- condition_bindings条件専用変数
- exit_bindingsExitMetaCollector が作るキャリア出口)
の契約はそのまま維持する。LoopHeaderPhiInfo は merge フェーズ内部のメタとして扱う。
---
## 5. 変更範囲Module boundaries
### 5.1 触る予定のファイル
| ファイル | 予定される変更 |
|---------------------------------------------------|-----------------------------------------------|
| `src/mir/builder/control_flow/joinir/merge/mod.rs` | merge パイプラインに Phase 3.5 呼び出しを追加 |
| `merge/loop_header_phi_builder.rs` (NEW) | LoopHeaderPhiBuilder 実装 |
| `merge/exit_phi_builder.rs` | LoopHeaderPhiInfo を受け取って expr PHI を構築 |
| `merge/instruction_rewriter.rs` | 3315 の暫定 skip ロジックを削除し、LoopHeaderPhiInfo を前提に整理 |
| `merge/exit_line/reconnector.rs` | carrier_phis を入口として使うように変更ExitMeta と連携) |
| `join_ir/lowering/loop_with_break_minimal.rs` 等 | LoopHeaderPhiBuilder に必要なメタの受け渡し |
### 5.2 触らない場所
- `CarrierInfo` / `LoopUpdateAnalyzer`
- `ExitMetaCollector`
- `JoinInlineBoundary` 構造体(フィールド追加なし)
- BoolExprLowerer / condition_to_joinir
これらは既に箱化されており、Loop ヘッダ PHI だけを中継する小箱を増やせば整合性を保てる設計になっている。
---
## 6. テスト戦略(完了条件)
### 6.1 expr 結果ライン
- `apps/tests/joinir_min_loop.hako`
- 期待: RC が「ループ終了時の i」の値になること現在は 0
- SSA トレース: `NYASH_SSA_UNDEF_DEBUG=1` で undefined が出ないこと。
### 6.2 carrier ライン
- `local_tests/test_trim_main_pattern.hako` など trim 系:
- 期待: start/end が正しく更新され、既存の期待出力から変わらないこと。
- ExitLineReconnector が LoopHeaderPhiInfo 経由でも正常に variable_map を更新すること。
### 6.3 回帰
- 既存 Pattern14 のループテスト:
- 結果・RC・SSA すべて元と同じであること。
---
## 7. 次のフェーズ
この設計フェーズ3316はここまで。
次の 3316 実装フェーズでは:
1. `loop_header_phi_builder.rs` を実装し、LoopHeaderPhiInfo を実際に構築。
2. `merge/mod.rs` に Phase 3.5 を組み込み。
3. `exit_phi_builder.rs` / `exit_line/reconnector.rs` / `instruction_rewriter.rs` を LoopHeaderPhiInfo 前提で整理。
4. 上記テスト戦略に沿って回帰テスト・SSA 検証を行う。
実装時に設計が変わった場合は、このファイルと `joinir-architecture-overview.md` を SSOT として必ず更新すること。