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

8.9 KiB
Raw Blame History

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 何が壊れているか

  • Pattern2joinir_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_rewriterexit_phi_inputs / carrier_inputs 収集を一時停止
    • → SSAundef は解消したが、「ループ出口値」が初期値のままになっている

1.2 何が足りていないか

本来必要だったものは:

  1. ループヘッダに置かれる PHIi_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 を追加する:

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 構造

/// ループヘッダ 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]
    CarrierInfoLoopFeatures から得たキャリア名一覧。
  • join_fragment_meta: &JoinFragmentMeta
    expr 結果があるかどうかのフラグ(後述)。

そして LoopHeaderPhiInfo を返す:

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

すでに次のような形になっている:

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