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

394 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# inst_meta層とused_values()の設計分析レポート
## 概要
MIRのメタデータシステムinst_meta層と使用値判定used_values())に関する設計的な問題と改善機会について分析します。
## 現状の構造
### 1. Call命令の特殊扱いmethods.rs lines 148-170
```rust
// 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
```rust
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
```rust
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()
**症状**:
```rust
// 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:
```rust
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**:
```rust
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 を持つ同じメソッド呼び出しを「同一」と判定する可能性
**例**:
```mir
%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 を含める
**内容**:
```rust
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<Callee> を追加
```rust
pub enum CallLikeInst {
Call {
dst: Option<ValueId>,
func: ValueId,
callee: Option<Callee>, // 新規
args: Vec<ValueId>,
},
// ...
}
```
**Phase 2**: CallLikeInst::used() を更新して receiver を処理
```rust
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 を削除
```rust
// 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 を正確に処理することを確認
**テストケース**:
```mir
// 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 | 統合テスト追加 |