Files
hakorune/docs/development/current/main/cse-pass-callee-fix-completed.md
nyash-codex 255517ed58 fix(cse): Include callee in Call instruction key generation
Previously, CSE's instruction_key() ignored the callee field, which could
cause different method calls on the same receiver to be incorrectly merged:

  %r1 = call Method { receiver: %obj, method: "upper" } ()
  %r2 = call Method { receiver: %obj, method: "lower" } ()
  // Both had key "call_<obj>_" - WRONG!

Now generates unique keys for all Callee variants:
- Global(name) → call_global_{name}_{args}
- Method { box, method, recv } → call_method_{box}.{method}_{recv}_{args}
- Value(vid) → call_value_{vid}_{args}
- Extern(name) → call_extern_{name}_{args}
- Constructor { box_type } → call_ctor_{type}_{args}
- Closure { .. } → call_closure_{func}_{args}
- None (legacy) → call_legacy_{func}_{args}

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 23:33:06 +09:00

6.8 KiB
Raw Blame History

CSE Pass Callee フィールド対応修正 - 完了報告

日付

2025-12-05

概要

CSE (Common Subexpression Elimination) パスの instruction_key() 関数で Call 命令の callee フィールドを無視していたバグを修正しました。

問題

根本原因

src/mir/passes/cse.rsinstruction_key() 関数が Call 命令のキー生成時に callee フィールドを無視していました:

// 修正前(バグあり)
MirInstruction::Call { func, args, .. } => {
    format!("call_{}_{}", func.as_u32(), args_str)
    // ← callee フィールドを無視!
}

影響範囲

これにより、以下のような問題が発生する可能性がありました:

  1. 異なるメソッドの誤統合

    %r1 = call Method { receiver: %obj, method: "upper", ... } ()
    %r2 = call Method { receiver: %obj, method: "lower", ... } ()
    // 両方とも "call_<obj>_" という同じキー → 誤って統合される可能性
    
  2. Global 関数の情報損失

    %r1 = call Global("print") (%msg)
    // callee 情報が失われ、func の ValueId だけでキー生成
    

修正内容

コード変更

src/mir/passes/cse.rsinstruction_key() 関数を修正(行 81-126

MirInstruction::Call { callee, func, args, .. } => {
    let args_str = args.iter()
        .map(|v| v.as_u32().to_string())
        .collect::<Vec<_>>()
        .join(",");

    // callee 情報を含めて正確なキー生成
    if let Some(c) = callee {
        use crate::mir::Callee;
        match c {
            Callee::Global(name) => {
                format!("call_global_{}_{}", name, args_str)
            }
            Callee::Method { box_name, method, receiver, .. } => {
                let recv_str = receiver
                    .map(|r| r.as_u32().to_string())
                    .unwrap_or_else(|| "static".to_string());
                format!("call_method_{}.{}_{}_{}",
                        box_name, method, recv_str, args_str)
            }
            Callee::Value(v) => {
                format!("call_value_{}_{}", v.as_u32(), args_str)
            }
            Callee::Extern(name) => {
                format!("call_extern_{}_{}", name, args_str)
            }
            Callee::Constructor { box_type } => {
                format!("call_ctor_{}_{}", box_type, args_str)
            }
            Callee::Closure { .. } => {
                // Closures are unique by definition
                format!("call_closure_{}_{}", func.as_u32(), args_str)
            }
        }
    } else {
        // Legacy path: backward compatibility
        format!("call_legacy_{}_{}", func.as_u32(), args_str)
    }
}

キー生成方式

Callee 種類 キー形式
Global call_global_{name}_{args} call_global_print_200
Method call_method_{box}.{method}_{receiver}_{args} call_method_StringBox.upper_100_
Value call_value_{vid}_{args} call_value_42_200
Extern call_extern_{name}_{args} call_extern_libc.malloc_1024
Constructor call_ctor_{box_type}_{args} call_ctor_StringBox_5
Closure call_closure_{func}_{args} call_closure_99_
None (legacy) call_legacy_{func}_{args} call_legacy_42_200

テスト結果

単体テストstandalone

✅ Test 1: 同じ receiver、異なる method → 異なるキー
   call_method_StringBox.upper_100_ ≠ call_method_StringBox.lower_100_

✅ Test 2: 異なる receiver、同じ method → 異なるキー
   call_method_StringBox.upper_100_ ≠ call_method_StringBox.upper_101_

✅ Test 3: 完全に同じ呼び出し → 同じキーCSE が効く)
   call_method_StringBox.upper_100_ == call_method_StringBox.upper_100_

✅ Test 4: Global 関数呼び出し → 正しいキー
   call_global_print_200

✅ Test 5: Legacy 呼び出しcallee なし)→ 互換性維持
   call_legacy_42_200

統合テスト

✅ apps/tests/loop_if_phi.hako        - sum=9正常
✅ apps/tests/peek_expr_block.hako    - found one正常
✅ apps/tests/loop_min_while.hako     - 0 1 2正常
✅ apps/tests/string_ops_basic.hako   - len=5, sub=bcd正常

ビルド結果

✅ cargo build --release
   Compiling nyash-rust v0.1.0
   Finished `release` profile [optimized] target(s) in 0.10s

達成効果

正確性向上

  • 異なるメソッド呼び出しを正しく区別
  • receiver と method の両方を考慮したキー生成
  • Global/Extern/Constructor 呼び出しを明確に分離

バグ予防

  • 同じ receiver でも異なるメソッドは別のキーに
  • 異なる receiver でも同じメソッドは別のキーに
  • callee 情報の完全活用

後方互換性

  • callee: None の legacy path で既存コード継続動作
  • 段階的移行をサポート

パフォーマンス

  • キー生成コストはわずか(許容範囲)
  • CSE の効果は維持(正確性向上)

技術的洞察

Callee 型の重要性

ChatGPT5 Pro が設計した Callee enum は、MIR Call 命令の型安全性を大幅に向上させました:

  1. コンパイル時解決: 関数呼び出しの種類を型で表現
  2. シャドウイング回避: 実行時文字列解決から脱却
  3. 最適化基盤: CSE などのパスが正確な情報を利用可能

CSE での活用

Callee 情報により、CSE パスは以下を正確に判断できます:

  • 同じメソッド呼び出しの検出receiver + method
  • 異なる呼び出しの区別Global vs Method vs Extern
  • Constructor と Closure の特別扱い

関連ドキュメント

今後の展開

短期Phase 33

  • CSE Pass の callee 対応完了
  • 🔄 他の最適化パスでの callee 活用検討

中期Phase 34

  • 型推論での callee 情報活用
  • インライン化での callee 情報利用

長期Phase 40+

  • Callee ベースの高度な最適化
  • JIT での callee 情報活用

結論

CSE Pass の callee 対応修正は完全に成功しました。修正は最小限約50行で、テストは全て通過し、期待される効果を達成しています。

この修正により、CSE パスは MIR Call 命令の callee 情報を正確に利用し、異なる呼び出しを正しく区別できるようになりました。これは MIR Callee 型革新Phase 15.5)の重要な実装成果の一つです。


実装者: Claude Code レビュー: 完了 マージ準備: Ready