Files
hakorune/docs/development/current/main/phase166-inst-meta-layer-analysis.md
nyash-codex 120fbdb523 fix(mir): Receiver used_values for DCE + trace + cleanup
- Fix: Call with Callee::Method now includes receiver in used_values()
  - Prevents DCE from eliminating Copy instructions that define receivers
  - Pattern 3 (loop_if_phi.hako) now works correctly (sum=9)

- Add: NYASH_DCE_TRACE=1 for debugging eliminated instructions
  - Shows which pure instructions DCE removes and from which block

- Cleanup: Consolidate Call used_values to single source of truth
  - Early return in methods.rs handles all Call variants
  - Removed duplicate match arm (now unreachable!())
  - ChatGPT's suggestion for cleaner architecture

- Docs: Phase 166 analysis of inst_meta layer architecture
  - Identified CSE pass callee bug (to be fixed next)
  - Improvement proposals for CallLikeInst

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

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

12 KiB
Raw Blame History

inst_meta層とused_values()の設計分析レポート

概要

MIRのメタデータシステムinst_meta層と使用値判定used_values())に関する設計的な問題と改善機会について分析します。

現状の構造

1. Call命令の特殊扱いmethods.rs lines 148-170

// methods.rs: used_values()
if let MirInstruction::Call { callee, func, args, .. } = self {
    // Callee::Method { receiver: Some(r), .. } を特殊処理
    match callee {
        Some(Callee::Method { receiver: Some(r), .. }) => {
            used.push(*r);  // ← receiver を明示的に抽出
        }
        None => {
            used.push(*func);  // ← legacy path
        }
        _ => {}
    }
    used.extend(args.iter().copied());
    return used;  // ← Early return: inst_meta をバイパス
}

特徴:

  • Call命令のみ methods.rs で完結
  • CallLikeInst に callee フィールドがないため inst_meta をバイパス
  • receiver を含む unified 経路と legacy 経路を統一的に処理

2. CallLikeInst の部分実装instruction_kinds/mod.rs lines 718-803

pub enum CallLikeInst {
    Call {
        dst: Option<ValueId>,
        func: ValueId,           // ← callee フィールドがない!
        args: Vec<ValueId>,
    },
    BoxCall { dst, box_val, args },
    PluginInvoke { dst, box_val, args },
    ExternCall { dst, args },
}

impl CallLikeInst {
    pub fn used(&self) -> Vec<ValueId> {
        match self {
            CallLikeInst::Call { func, args, .. } => {
                let mut v = Vec::new();
                if *func != ValueId::INVALID {  // ← INVALID チェック?
                    v.push(*func);
                }
                v.extend(args.iter().copied());
                v
            }
            // ... BoxCall, PluginInvoke, ExternCall ...
        }
    }
}

問題:

  • CallLikeInst::Call は callee フィールドを持たない
  • unified 経路Callee::Methodのreceiver 処理が欠落
  • ValueId::INVALID チェックは方針がはっきりしない

3. inst_meta の統合パスinstruction_kinds/mod.rs lines 275-352

pub fn used_via_meta(i: &MirInstruction) -> Option<Vec<ValueId>> {
    // ... 多くの instruction の処理 ...
    
    if let Some(k) = CallLikeInst::from_mir(i) {
        return Some(k.used());  // ← CallLikeInst::used() を呼び出す
    }
    
    // ... rest ...
    None
}

現状:

  • used_via_meta() は CallLikeInst::used() を呼び出す
  • しかし methods.rs の used_values() は early return で inst_meta をバイパス
  • 結果: CallLikeInst::used() は実質的に使われていないDCE等では methods.rs 経路)

問題分析

P1: inst_meta層の役割あいまいさ

症状:

  1. Call命令: methods.rs で early returninst_meta 経由でない)
  2. BoxCall/PluginInvoke: inst_meta 経由で CallLikeInst を使用
  3. ExternCall: inst_meta 経由で CallLikeInst を使用

根本原因:

  • inst_meta はPoCProof of Concept段階の不完全な実装
  • Call命令の callee フィールド対応が遅れている
  • CallLikeInst に callee を追加できない設計的理由がない

P2: CallLikeInst::Call の不完全な used()

症状:

// CallLikeInst::Call::used()
if *func != ValueId::INVALID {
    v.push(*func);
}
  • INVALID チェックは unified 経路callee: Some(_))を前提?
  • しかし CallLikeInst には callee フィールドがない
  • どちらの経路か判定不可

結論: 設計的に矛盾している

P3: methods.rs の early return がもたらす非対称性

症状:

  • Call: methods.rs の manual matchcallee 対応)
  • BoxCall/PluginInvoke: inst_meta 経由CallLikeInst 経由)

問題:

  • 新しい Call の用途が追加されたとき、methods.rs と CallLikeInst の両方を修正しないといけない
  • 意図的な分離か偶発的な分割か不明確

P4: DCE の信頼性

症状dce.rs lines 60-87:

let mut used_values: HashSet<ValueId> = HashSet::new();

// Mark values used by side-effecting instructions and terminators
for instruction in &block.instructions {
    if !instruction.effects().is_pure() {
        for u in instruction.used_values() {  // ← used_values() を使用
            used_values.insert(u);
        }
    }
}

// Backward propagation
for instruction in &block.instructions {
    if used_values.contains(&dst) {
        for u in instruction.used_values() {
            if used_values.insert(u) { changed = true; }
        }
    }
}

潜在的リスク:

  • Call命令で Callee::Method { receiver: Some(r), .. } の receiver が使用値に含まれるか?
  • YES (methods.rs の early return で处理)
  • だが、inst_meta::used_via_meta() から入った場合は?
  • NO (CallLikeInst::Call は callee を知らない)

結論: 処理経路によって結果が異なる可能性

設計的な分岐点

Option A: CallLikeInst に callee を追加

メリット:

  • inst_meta を完全統一化できる
  • methods.rs の early return を削除可能
  • CallLikeInst::Call が unified 経路に対応

デメリット:

  • CallLikeInst が大きくなるBox-heavy
  • Clone/from_mir の複雑性増加
  • Callee enum 自体が methods.rs との結合度を上げる

実装量: 中程度

Option B: methods.rs を強化inst_meta 側は軽量に保つ)

メリット:

  • inst_meta をPoC段階のまま保つことができる
  • methods.rs が「Call系の単一ソース・オブ・トゥルース」になる
  • 将来 inst_meta を削除しても影響ない

デメリット:

  • inst_meta の役割があいまい不完全なPoC
  • ドキュメント化が重要になる

実装量: 少ない(コメント追加程度)

Option C: inst_meta を CallLikeInst から分離Method層として実装

メリット:

  • inst_meta と methods.rs の役割を完全に分離
  • 将来の拡張に柔軟

デメリット:

  • コード複製が増える
  • 維持が大変

実装量: 高い

パターンスキャン結果

他の同じ問題がある箇所

1. CSE passpasses/cse.rs lines 72-91:

fn instruction_key(i: &MirInstruction) -> String {
    match i {
        // ...
        MirInstruction::Call { func, args, .. } => {
            format!("call_{}_{}", func.as_u32(), args_str)
            // ← callee を無視している!
        }
        // ...
    }
}

問題: Call命令が Callee::Method { receiver: Some(r), .. } を持つ場合、receiver を含めずにキーを生成

影響: 異なる receiver を持つ同じメソッド呼び出しを「同一」と判定する可能性

:

%r1 = call Method { receiver: Some(%obj1), ... } "upper"()
%r2 = call Method { receiver: Some(%obj2), ... } "upper"()

→ 同じキーになる → CSE で不正な最適化?

他に detected する可能性のある問題

2. 新しい instruction_kinds 追加時:

  • inst_meta に追加する人は effects/dst/used の3つを実装する
  • methods.rs との同期漏れリスク

3. BoxCall/PluginInvoke の method_id:

  • instruction_kinds/mod.rs は method_id を無視している
  • methods.rs は method_id を見ていない(フィールドがない)

改善提案(優先度順)

【優先度1】docs: inst_meta の役割と制約をドキュメント化

内容:

  • inst_meta は PoC であること
  • methods.rs が「単一ソース・オブ・トゥルース」であること
  • CallLikeInst は callee フィールドがないこと(意図的)
  • 将来統一する際の手順

ファイル: docs/development/current/main/inst-meta-layer-design.md

実装量: 1-2時間

効果: 中(開発者の混乱を減らす)

【優先度2】fix: CSE の instruction_key に callee を含める

内容:

fn instruction_key(i: &MirInstruction) -> String {
    match i {
        MirInstruction::Call { callee, args, .. } => {
            // callee をキーに含める
            let callee_str = format!("{:?}", callee);  // or structured key
            let args_str = args.iter()...
            format!("call_{}_{}_{}", callee_str, ...)
        }
        // ...
    }
}

ファイル: src/mir/passes/cse.rs

実装量: 1-2時間

効果: 高CSEの正確性向上

【優先度3】refactor: CallLikeInst に callee を追加(段階的)

Phase 1: CallLikeInst::Call に callee: Option を追加

pub enum CallLikeInst {
    Call {
        dst: Option<ValueId>,
        func: ValueId,
        callee: Option<Callee>,  // 新規
        args: Vec<ValueId>,
    },
    // ...
}

Phase 2: CallLikeInst::used() を更新して receiver を処理

CallLikeInst::Call { func, callee, args, .. } => {
    let mut v = Vec::new();
    if let Some(Callee::Method { receiver: Some(r), .. }) = callee {
        v.push(*r);
    } else if *func != ValueId::INVALID {
        v.push(*func);
    }
    v.extend(args.iter().copied());
    v
}

Phase 3: methods.rs の early return を削除

// methods.rs: Remove early return for Call
// Let inst_meta::used_via_meta handle it

ファイル:

  • src/mir/instruction_kinds/mod.rs
  • src/mir/instruction/methods.rs

実装量: 4-6時間

効果: 高inst_meta 統一化)

【優先度4】test: 統合テストCallee::Method の receiver 判定)

内容: DCE/CSE で receiver を含む Call を正確に処理することを確認

テストケース:

// Case 1: Method call with receiver
%obj = new StringBox()
%r1 = call Method { receiver: Some(%obj), ... } "upper"()
// ↑ obj は使用値に含まれるべき

// Case 2: Different receivers
%s1 = new StringBox()
%s2 = new StringBox()
%r1 = call Method { receiver: Some(%s1), ... } "upper"()
%r2 = call Method { receiver: Some(%s2), ... } "upper"()
// ↑ CSE key は異なるべき

ファイル:

  • src/mir/instruction_kinds/tests.rs (新規)
  • または既存テストに統合

実装量: 2-3時間

効果: 中(回帰テスト)

設計原則の推奨

「箱化」の視点から見た改善

現状の問題:

  • inst_meta 層が「箱」として完全ではない
  • methods.rs との責任の分離がはっきりしていない

推奨アーキテクチャ:

┌─────────────────────────────────┐
│      MIRInstruction             │
│  (Callee enum を含む確定形)      │
└─────────────────────────────────┘
            ↓
┌─────────────────────────────────┐
│      methods.rs                 │
│  effects() / dst_value()        │
│  used_values() (single source)  │
└─────────────────────────────────┘
            ↓
┌─────────────────────────────────┐
│  inst_meta (PoC: optional)      │
│  高速化用スキップ層              │
│  (検証/デバッグ用)               │
└─────────────────────────────────┘

原則:

  1. methods.rs が SSOTSingle Source of Truth
  2. inst_meta は最適化用レイヤー(将来削除可)
  3. CallLikeInst は methods.rs を完全ミラー

まとめ

問題 影響 優先度 改善方針
inst_meta 役割あいまい 開発者混乱 1 ドキュメント化
CSE の callee 無視 最適化誤り可能 2 fix CSE
CallLikeInst::Call 不完全 潜在バグ 3 callee 追加
DCE 処理経路の非対称 テスト困難 3 統合テスト追加