Files
hakorune/docs/development/current/main/phase-33-16-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

5.4 KiB
Raw Blame History

Phase 33-16: Loop Header PHI SSOT 設計書

現状

Phase 33-15 で SSA-undef エラーを修正したが、対処療法として exit_phi_inputs と carrier_inputs 収集をスキップした。 結果、joinir_min_loop.hako のループ結果値が正しくない期待値2、実際0

根本原因

JoinIR の関数パラメータi_param, i_exitは MIR 空間で SSA 定義を持たない。

JoinIR:
  fn loop_step(i_param):   // ← i_param は関数パラメータ
    ...
    Jump(k_exit, [i_param]) // ← exit 時に i_param を渡す
    ...
    i_next = i_param + 1
    Call(loop_step, [i_next]) // ← tail call

  fn k_exit(i_exit):       // ← i_exit も関数パラメータ
    Return(i_exit)

MIR にインライン化すると:

  • i_param は BoundaryInjector Copy でエントリー時に設定される
  • tail call は Copy { dst: i_param_remapped, src: i_next_remapped } に変換される
  • しかし exit path では i_param_remapped に有効な値がない!

解決策: LoopHeaderPhiBuilder

ループヘッダーブロックに PHI を配置して、carrier の「現在値」を追跡する。

目標構造

host_entry:
  i_init = 0
  Jump → loop_header

loop_header:
  i_phi = PHI [(host_entry, i_init), (latch, i_next)]  ← NEW!
  exit_cond = !(i_phi < 3)
  Branch exit_cond → exit, body

body:
  break_cond = (i_phi >= 2)
  Branch break_cond → exit, continue

continue:
  i_next = i_phi + 1
  Jump → loop_header  // latch edge

exit:
  // i_phi がループ終了時の正しい値!

実装計画

1. LoopHeaderPhiBuilder (完了)

src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs に実装済み。

pub struct LoopHeaderPhiInfo {
    pub header_block: BasicBlockId,
    pub carrier_phis: BTreeMap<String, CarrierPhiEntry>,
    pub expr_result_phi: Option<ValueId>,
}

2. merge パイプラインへの組み込み (TODO)

merge/mod.rs の merge_joinir_mir_blocks() を修正:

// Phase 3: Remap ValueIds
remap_values(builder, &used_values, &mut remapper, debug)?;

// Phase 3.5: Build loop header PHIs (NEW!)
let loop_header_info = if boundary.is_some() && is_loop_pattern {
    loop_header_phi_builder::LoopHeaderPhiBuilder::build(
        builder,
        header_block_id,      // ← Pattern lowerer から渡す必要あり
        entry_block_id,
        loop_var_name,
        loop_var_init,
        &[], // carriers
        expr_result_is_loop_var,
        debug,
    )?
} else {
    LoopHeaderPhiInfo::empty(BasicBlockId(0))
};

// Phase 4: Merge blocks and rewrite instructions
// instruction_rewriter に loop_header_info を渡す
let merge_result = instruction_rewriter::merge_and_rewrite(
    builder,
    mir_module,
    &mut remapper,
    &value_to_func_name,
    &function_params,
    &mut loop_header_info,  // latch_incoming を設定するため mut
    boundary,
    debug,
)?;

// Phase 4.5: Finalize loop header PHIs (NEW!)
if boundary.is_some() && loop_header_info.carrier_phis.len() > 0 {
    loop_header_phi_builder::LoopHeaderPhiBuilder::finalize(
        builder,
        &loop_header_info,
        debug,
    )?;
}

3. instruction_rewriter 修正 (TODO)

Phase 33-15 の skip ロジックを削除し、以下を実装:

Tail call 処理時 (276-335行):

// tail call の args を latch_incoming として記録
for (i, arg_val_remapped) in args.iter().enumerate() {
    // carrier 名を特定loop_var_name or carrier_name
    let carrier_name = get_carrier_name_for_index(i);
    loop_header_info.set_latch_incoming(
        carrier_name,
        new_block_id,      // latch block
        *arg_val_remapped, // 次のイテレーション値
    );
}

Exit path 処理時 (350-435行):

// exit_phi_inputs に header PHI の dst を使う
if let Some(phi_dst) = loop_header_info.get_carrier_phi(loop_var_name) {
    exit_phi_inputs.push((new_block_id, phi_dst));
}

// carrier_inputs にも header PHI の dst を使う
for (carrier_name, entry) in &loop_header_info.carrier_phis {
    carrier_inputs.entry(carrier_name.clone())
        .or_insert_with(Vec::new)
        .push((new_block_id, entry.phi_dst));
}

4. JoinInlineBoundary 拡張 (TODO)

header_block 情報を Pattern lowerer から渡す:

pub struct JoinInlineBoundary {
    // ... existing fields ...

    /// Phase 33-16: Loop header block ID (for LoopHeaderPhiBuilder)
    /// Set by Pattern lowerers that need header PHI generation.
    pub loop_header_block: Option<BasicBlockId>,
}

5. Pattern 2 lowerer 修正 (TODO)

pattern2_with_break.rs で header_block を設定:

boundary.loop_header_block = Some(loop_step_entry_block);

テスト戦略

テスト 期待値 検証ポイント
joinir_min_loop.hako return i → 2 expr_result PHI が正しい値を持つ
trim 系テスト 回帰なし carrier PHI が正しく動作
SSA-undef エラーなし 定義済み ValueId のみ参照

実装の複雑さ

中程度。主な変更点:

  1. merge/mod.rs: Phase 3.5 と 4.5 追加
  2. instruction_rewriter.rs: latch_incoming 記録と PHI dst 参照
  3. Pattern lowerers: header_block 情報の追加

次のステップ

  1. merge/mod.rs に Phase 3.5/4.5 フックを追加
  2. instruction_rewriter に loop_header_info 引数を追加
  3. Pattern 2 lowerer で header_block を設定
  4. テストで検証