Files
hakorune/docs/development/current/main/phase33-16-loop-header-phi-design.md
nyash-codex 4e32a803a7 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

246 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 として必ず更新すること。