Files
hakorune/docs/development/current/main/refactoring-5-1-pattern3-exitmeta.md
nyash-codex 8394018694 refactor(joinir): Pattern 3 ExitMeta化 - Hardcoded ValueIds削除
Refactoring 5.1: Pattern 3 を Pattern 4 と同じ ExitMeta ベースアーキテクチャに統一化

Changes:
1. loop_with_if_phi_minimal.rs
   - 署名: Option<JoinModule> → Result<(JoinModule, JoinFragmentMeta), String>
   - ExitMeta 動的生成ロジック追加(sum, count)
   - インポート追加: carrier_info::{ExitMeta, JoinFragmentMeta}

2. pattern3_with_if_phi.rs
   - Hardcoded 定数削除(PATTERN3_K_EXIT_*_ID 2個削除)
   - Manual exit binding 42行 → ExitMetaCollector 4行に置き換え
   - インポート追加: ExitMetaCollector

3. loop_patterns/with_if_phi.rs
   - Result型変更に対応(.ok()? で変換)

Benefits:
- Pattern 3/4 アーキテクチャ統一化 
- 19行純削減(+55 -74行、3ファイル合計) 
- Hardcoded ValueIds 完全撤廃 
- Phase 213 AST-based generalization の基盤強化 

Tests: All tests passing, loop_if_phi.hako outputs "sum=9" correctly
2025-12-10 00:29:25 +09:00

9.5 KiB
Raw Blame History

Refactoring 5.1: Pattern 3 Hardcoded ValueIds → ExitMeta化

Date: 2025-12-09 Status: 🚧 In Progress Estimated Time: 3-4 hours Priority: HIGH


📋 目標

Pattern 3 lowererloop_with_if_phi_minimal.rs)を Pattern 4 と同じ ExitMeta ベースのアーキテクチャに統一化する。これにより:

  1. Hardcoded ValueIds 定数削除(PATTERN3_K_EXIT_*_ID
  2. Exit binding の動的生成
  3. Multi-carrier support の完全化
  4. Pattern 3/4 の共通化度向上

🔄 変更対象ファイル

ファイル1: src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs

現在:

pub(crate) fn lower_loop_with_if_phi_pattern(
    _scope: LoopScopeShape,
    join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule>

変更後:

use crate::mir::join_ir::lowering::join_fragment_meta::JoinFragmentMeta;

pub(crate) fn lower_loop_with_if_phi_pattern(
    _scope: LoopScopeShape,
    join_value_space: &mut JoinValueSpace,
) -> Result<(JoinModule, JoinFragmentMeta), String>

変更内容:

  1. 戻り値を Option<JoinModule>Result<(JoinModule, JoinFragmentMeta), String> に変更
  2. JoinFragmentMeta を構築して返す
  3. ExitMeta を動的に生成

ファイル2: src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs

現在:

const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);    // Hardcoded!
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);  // Hardcoded!

// Lines 118-164: has_count 条件分岐で exit_bindings を手動構築
let exit_bindings = if has_count {
    vec![
        LoopExitBinding {
            join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID,    // ← Hardcoded!
            ...
        },
        ...
    ]
} else {
    // Dummy count hack
    ...
}

変更後:

// Hardcoded 定数削除(削除)

// Lines 300-314: ExitMeta から exit_bindings を動的生成
let exit_bindings = ExitMetaCollector::collect(
    self,
    &exit_meta,
    debug,
);

// Carrier validationPattern 4と同じ
for carrier in &carrier_info.carriers {
    if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
        return Err(format!(
            "[cf_loop/pattern3] Carrier '{}' not found in exit bindings",
            carrier.name
        ));
    }
}

🔧 実装ステップ

Step 1: loop_with_if_phi_minimal.rs の k_exit 関数を分析

現在の k_exit 実装lines 401-415:

let mut k_exit_func = JoinFunction::new(
    k_exit_id,
    "k_exit".to_string(),
    vec![sum_final, count_final],  // Phase 195: Multi-carrier
);

k_exit_func.body.push(JoinInst::Ret {
    value: Some(sum_final),
});

分析:

  • k_exit の parameters: [sum_final, count_final] が exit PHI
  • k_exit は sum_final を return最初の carrier
  • ExitMeta として記録すべき情報:
    • sumsum_final (ValueId(24))
    • countcount_final (ValueId(25))

Step 2: ExitMeta 構築ロジック追加

lowerer の最後に以下を追加:

// Phase 213: Build ExitMeta for dynamic exit binding generation
use crate::mir::join_ir::lowering::join_fragment_meta::JoinFragmentMeta;
use crate::mir::join_ir::lowering::exit_meta::ExitMeta;
use std::collections::HashMap;

let mut exit_values = HashMap::new();

// Map carrier names to their k_exit parameter ValueIds
exit_values.insert("sum".to_string(), sum_final);
if has_count {
    exit_values.insert("count".to_string(), count_final);
}

let exit_meta = ExitMeta {
    exit_values,
    exit_func_id: k_exit_id,
};

let fragment_meta = JoinFragmentMeta {
    exit_meta,
    // その他フィールド
};

Ok((join_module, fragment_meta))

注: has_count フラグは必要。multi-carrier に対応するため。

Step 3: pattern3_with_if_phi.rs での lowerer 呼び出し変更

現在lines 109-116:

let join_module = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
    Some(module) => module,
    None => {
        trace::trace().debug("pattern3", "Pattern 3 lowerer returned None");
        return Ok(None);
    }
};

変更後:

let (join_module, exit_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
    Ok(result) => result,
    Err(e) => {
        trace::trace().debug("pattern3", &format!("Pattern 3 lowerer failed: {}", e));
        return Err(format!("[cf_loop/pattern3] Lowering failed: {}", e));
    }
};

trace::trace().debug(
    "pattern3",
    &format!("ExitMeta: {} exit values", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
    trace::trace().debug(
        "pattern3",
        &format!("  {} → ValueId({})", carrier_name, join_value.0)
    );
}

Step 4: Exit binding 動的生成Hardcoded 定数削除)

現在lines 8-30, 118-164:

// Hardcoded constants
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);

// Manual exit_bindings construction with has_count branching
let exit_bindings = if has_count {
    vec![
        LoopExitBinding {
            carrier_name: "sum".to_string(),
            join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID,  // ← Hardcoded!
            host_slot: sum_var_id,
        },
        LoopExitBinding {
            carrier_name: "count".to_string(),
            join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ← Hardcoded!
            host_slot: count_var_id,
        }
    ]
} else {
    // Single-carrier hack
    ...
}

削除/変更:

// Hardcoded constants削除
// const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);
// const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);

// Dynamic exit binding generation追加
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(
    self,
    &exit_meta,
    debug,
);

// Carrier validation
for carrier in &carrier_info.carriers {
    if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
        return Err(format!(
            "[cf_loop/pattern3] Carrier '{}' not found in exit bindings",
            carrier.name
        ));
    }
}

Step 5: Dummy Count Backward Compat 簡略化

現在lines 145-164:

if has_count {
    // Multi-carrier case
    (vec![...], vec![...], vec![...])
} else {
    // Single-carrier case with Dummy void
    let dummy_count_id = constant::emit_void(self);
    (vec![...], vec![..., dummy_count_id], vec![...])
}

簡略化:

// Phase 213: Always use multi-carrier structure
// Single-carrier tests will use dummy void internally
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
let mut host_inputs = vec![ctx.loop_var_id, sum_var_id];

if has_count {
    host_inputs.push(carrier_count_var_id);
} else {
    // Use void dummy for backward compat
    host_inputs.push(constant::emit_void(self));
}

📊 Before/After Code Change Summary

loop_with_if_phi_minimal.rs

項目 Before After 削減
関数署名 Option<JoinModule> Result<(JoinModule, JoinFragmentMeta)> -
k_exit 構築 あり あり(変更なし) 0行
ExitMeta 構築 なし 新規追加 +20行
428行 448行 +20行

pattern3_with_if_phi.rs

項目 Before After 削減
Hardcoded 定数 2個lines 8-30 削除 -2行
Manual exit binding ありlines 118-164 ExitMetaCollector化 -40行
Dummy count hack あり 簡略化 -5行
Lowerer呼び出し None/Some Ok/Err -5行
ExitMeta debug なし 新規追加 +10行
191行 149行 -42行22%削減)

テスト戦略

Test 1: loop_if_phi.hako (既存 test)

動作確認:

./target/release/hakorune --dump-mir apps/tests/loop_if_phi.hako 2>&1 | grep -A 5 "k_exit"

期待: k_exit が sum/count を正しく処理(変わらず)

Test 2: Multi-carrier testsPhase 195

確認対象:

  • test_pattern3_multi_carrier_sum_count
  • Carrier binding が動的に生成されることを確認

Test 3: Cargo test

cargo test --release pattern3 2>&1 | tail -20

🚨 リスク & ミティゲーション

リスク 1: ExitMeta 構築が複雑

ミティゲーション:

  • Pattern 4 のコードをコピーペースト基準にする
  • 最小限の変更に留める

リスク 2: ExitMetaCollector の動作確認

ミティゲーション:

  • Pattern 4 で既に使用済み(実績あり)
  • Carrier validation で エラー検出

リスク 3: Dummy count backward compat 破損

ミティゲーション:

  • has_count フラグで分岐を保つ
  • 古い単一キャリア テストは動作のまま

📝 Checklist

  • Step 1: k_exit 実装分析完了
  • Step 2: ExitMeta 構築ロジック追加
  • Step 3: lowerer 呼び出し変更
  • Step 4: Hardcoded 定数削除 + ExitMetaCollector 導入
  • Step 5: Dummy count 簡略化
  • Step 6: Build 成功確認
  • Step 7: Test 実行確認
  • Step 8: Commit & Document 更新
  • Refactoring 5.1 COMPLETE

📚 参考資料

  • Pattern 4 実装: src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs (lines 300-380)
  • ExitMetaCollector: src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs
  • JoinFragmentMeta: src/mir/join_ir/lowering/join_fragment_meta.rs