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>
This commit is contained in:
@ -36,6 +36,13 @@ NYASH_OPTION_C_DEBUG=1 cargo test --release TEST_NAME 2>&1 | grep "Option C"
|
||||
# LoopForm デバッグ
|
||||
NYASH_LOOPFORM_DEBUG=1 cargo test --release TEST_NAME 2>&1 | grep "loopform"
|
||||
|
||||
# DCE(Dead Code Elimination)トレース ⭐NEW - 命令が消える問題のデバッグ
|
||||
NYASH_DCE_TRACE=1 ./target/release/hakorune program.hako 2>&1 | grep "\[dce\]"
|
||||
# 出力例:
|
||||
# [dce] Eliminating unused pure instruction in bb12: %29 = Const { dst: ValueId(29), value: Void }
|
||||
# [dce] Eliminating unused pure instruction in bb5: %38 = Copy { dst: ValueId(38), src: ValueId(36) }
|
||||
# → 「命令は emit されてるのに実行時に undefined」問題の原因特定に有効!
|
||||
|
||||
# variable_map トレース (JoinIR PHI接続デバッグ) ⭐超重要
|
||||
NYASH_TRACE_VARMAP=1 cargo test --release TEST_NAME 2>&1 | grep "\[trace:"
|
||||
# 出力例:
|
||||
|
||||
225
docs/development/current/main/cse-pass-callee-fix.md
Normal file
225
docs/development/current/main/cse-pass-callee-fix.md
Normal file
@ -0,0 +1,225 @@
|
||||
# CSE Pass 修正提案 - Callee フィールド対応
|
||||
|
||||
## 問題の詳細
|
||||
|
||||
### 現在の実装
|
||||
|
||||
**src/mir/passes/cse.rs lines 72-91**:
|
||||
```rust
|
||||
fn instruction_key(i: &MirInstruction) -> String {
|
||||
match i {
|
||||
MirInstruction::Const { value, .. } => {
|
||||
format!("const_{:?}", value)
|
||||
}
|
||||
MirInstruction::BinOp { op, lhs, rhs, .. } => {
|
||||
format!("binop_{:?}_{}_{}", op, lhs.as_u32(), rhs.as_u32())
|
||||
}
|
||||
MirInstruction::Compare { op, lhs, rhs, .. } => {
|
||||
format!("cmp_{:?}_{}_{}", op, lhs.as_u32(), rhs.as_u32())
|
||||
}
|
||||
MirInstruction::Call { func, args, .. } => {
|
||||
let args_str = args
|
||||
.iter()
|
||||
.map(|v| v.as_u32().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
format!("call_{}_{}", func.as_u32(), args_str)
|
||||
// ← callee フィールドを無視している!
|
||||
}
|
||||
other => format!("other_{:?}", other),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 問題のシナリオ
|
||||
|
||||
```hako
|
||||
box StringUtil {
|
||||
upper(s) {
|
||||
return s.upper()
|
||||
}
|
||||
}
|
||||
|
||||
local x = new StringBox("hello")
|
||||
local y = new StringBox("world")
|
||||
|
||||
// Case 1: 異なる receiver を持つメソッド呼び出し
|
||||
%r1 = call Method { receiver: Some(%x), method: "upper", ... } ()
|
||||
// CSE key: "call_<x>_" (callee 無視)
|
||||
|
||||
%r2 = call Method { receiver: Some(%y), method: "upper", ... } ()
|
||||
// CSE key: "call_<y>_" (callee 無視)
|
||||
|
||||
// ↑ x と y は異なる ValueId → キーは異なる
|
||||
// → この場合は OK(偶然)
|
||||
|
||||
// Case 2: 同じメソッド呼び出しを2回
|
||||
%s1 = new StringBox("hello")
|
||||
%r1 = call Method { receiver: Some(%s1), method: "upper", ... } ()
|
||||
%r2 = call Method { receiver: Some(%s1), method: "upper", ... } ()
|
||||
// 両方のキー: "call_<s1>_"
|
||||
// → CSE が正しく検出できる
|
||||
|
||||
// Case 3: 複数のメソッド・同じ receiver
|
||||
%obj = new StringBox("hello")
|
||||
%r1 = call Method { receiver: Some(%obj), method: "upper", ... } ()
|
||||
%r2 = call Method { receiver: Some(%obj), method: "lower", ... } ()
|
||||
// 両方のキー: "call_<obj>_"
|
||||
// ← これは WRONG! 異なるメソッドなのに同じキー
|
||||
|
||||
// Case 4: Global function 呼び出しの場合
|
||||
%r1 = call Global("print") (%msg)
|
||||
// callee フィールド: Global("print")
|
||||
// func フィールド: ValueId::INVALID
|
||||
// 現在のキー: "call_<INVALID>_<msg>"
|
||||
// ← func だけではメソッド情報を失う
|
||||
```
|
||||
|
||||
### 修正方法
|
||||
|
||||
**提案1: callee を含める(推奨)**
|
||||
|
||||
```rust
|
||||
fn instruction_key(i: &MirInstruction) -> String {
|
||||
match i {
|
||||
// ...
|
||||
MirInstruction::Call { callee, func, args, .. } => {
|
||||
let args_str = args
|
||||
.iter()
|
||||
.map(|v| v.as_u32().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
// callee がある場合は callee を使用
|
||||
if let Some(c) = callee {
|
||||
match c {
|
||||
Callee::Global(name) => {
|
||||
format!("call_global_{}__{}", name, args_str)
|
||||
}
|
||||
Callee::Method {
|
||||
box_name,
|
||||
method,
|
||||
receiver,
|
||||
..
|
||||
} => {
|
||||
let recv_str = receiver.as_ref()
|
||||
.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 { .. } => {
|
||||
format!("call_closure__{}", args_str)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// legacy path: func を使用
|
||||
format!("call_legacy_{}_{}", func.as_u32(), args_str)
|
||||
}
|
||||
}
|
||||
other => format!("other_{:?}", other),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**提案2: callee 情報を簡潔に(軽量版)**
|
||||
|
||||
```rust
|
||||
fn instruction_key(i: &MirInstruction) -> String {
|
||||
match i {
|
||||
// ...
|
||||
MirInstruction::Call { callee, func, args, .. } => {
|
||||
let args_str = args
|
||||
.iter()
|
||||
.map(|v| v.as_u32().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
// callee を string hash として含める
|
||||
let callee_key = format!("{:?}", callee); // or hash(callee)
|
||||
format!("call_{}__{}", callee_key, args_str)
|
||||
}
|
||||
other => format!("other_{:?}", other),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## テストケース
|
||||
|
||||
### テスト1: 同じメソッド・異なる receiver
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_cse_different_receivers() {
|
||||
// MIR:
|
||||
// %x = new StringBox("hello")
|
||||
// %y = new StringBox("world")
|
||||
// %r1 = call Method { receiver: Some(%x), method: "upper", ... } ()
|
||||
// %r2 = call Method { receiver: Some(%y), method: "upper", ... } ()
|
||||
// → CSE key は異なるべき
|
||||
|
||||
let key1 = instruction_key(&call_method_upper_x());
|
||||
let key2 = instruction_key(&call_method_upper_y());
|
||||
assert_ne!(key1, key2); // 異なる receiver → 異なるキー
|
||||
}
|
||||
```
|
||||
|
||||
### テスト2: 異なるメソッド・同じ receiver
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_cse_different_methods() {
|
||||
// MIR:
|
||||
// %obj = new StringBox("hello")
|
||||
// %r1 = call Method { receiver: Some(%obj), method: "upper", ... } ()
|
||||
// %r2 = call Method { receiver: Some(%obj), method: "lower", ... } ()
|
||||
// → CSE key は異なるべき
|
||||
|
||||
let key1 = instruction_key(&call_method_upper_obj());
|
||||
let key2 = instruction_key(&call_method_lower_obj());
|
||||
assert_ne!(key1, key2); // 異なるメソッド → 異なるキー
|
||||
}
|
||||
```
|
||||
|
||||
### テスト3: Global 関数呼び出し
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_cse_global_function() {
|
||||
// MIR:
|
||||
// %r1 = call Global("print") (%msg1)
|
||||
// %r2 = call Global("print") (%msg1)
|
||||
// → CSE key は同じ
|
||||
|
||||
let key1 = instruction_key(&call_global_print_msg1());
|
||||
let key2 = instruction_key(&call_global_print_msg1());
|
||||
assert_eq!(key1, key2); // 同じ関数・同じ引数 → 同じキー
|
||||
}
|
||||
```
|
||||
|
||||
## 実装スケジュール
|
||||
|
||||
| Step | 作業内容 | 時間 |
|
||||
|------|---------|------|
|
||||
| 1 | cse.rs の instruction_key() を修正 | 1h |
|
||||
| 2 | テストケース追加 | 0.5h |
|
||||
| 3 | 既存スモークテストの確認 | 0.5h |
|
||||
| 4 | ドキュメント更新 | 0.5h |
|
||||
|
||||
**合計**: 2.5 時間
|
||||
|
||||
## 期待効果
|
||||
|
||||
- **CSE 正確性向上**: receiver/method を区別した最適化
|
||||
- **バグ予防**: 異なるメソッド呼び出しを誤って統合する問題を防止
|
||||
- **パフォーマンス**: わずかなキー生成コスト(許容範囲)
|
||||
|
||||
182
docs/development/current/main/phase166-completion-summary.md
Normal file
182
docs/development/current/main/phase166-completion-summary.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Phase 166 - MIR inst_meta層 箱化・モジュール化分析 - 完了サマリー
|
||||
|
||||
## 実施内容
|
||||
|
||||
コードベース全体の inst_meta 層(instruction_kinds/mod.rs)と used_values() の設計を分析し、箱化・モジュール化の機会を徹底的に検索しました。
|
||||
|
||||
## 主要な発見
|
||||
|
||||
### 1. 設計的な非対称性
|
||||
|
||||
**Call命令の特別扱い**:
|
||||
- Call: methods.rs で early return(inst_meta をバイパス)
|
||||
- BoxCall/PluginInvoke/ExternCall: inst_meta 経由(CallLikeInst を使用)
|
||||
|
||||
→ 理由: CallLikeInst に callee フィールドがない
|
||||
|
||||
### 2. CallLikeInst の不完全性
|
||||
|
||||
```rust
|
||||
pub enum CallLikeInst {
|
||||
Call {
|
||||
dst: Option<ValueId>,
|
||||
func: ValueId, // ← callee フィールドなし
|
||||
args: Vec<ValueId>,
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**問題**:
|
||||
- unified 経路(Callee::Method)の receiver を処理できない
|
||||
- ValueId::INVALID チェックは矛盾している
|
||||
|
||||
### 3. CSE Pass の潜在的バグ
|
||||
|
||||
**src/mir/passes/cse.rs**:
|
||||
```rust
|
||||
MirInstruction::Call { func, args, .. } => {
|
||||
format!("call_{}_{}", func.as_u32(), args_str)
|
||||
// ← callee を無視
|
||||
}
|
||||
```
|
||||
|
||||
**影響**:
|
||||
- 異なるメソッド(`obj.upper()` vs `obj.lower()`)を同じキーで扱う可能性
|
||||
- receiver が異なる場合でも同じキーになる可能性
|
||||
|
||||
## 成果物(ドキュメント)
|
||||
|
||||
### 1. 主要レポート
|
||||
|
||||
**ファイル**: `phase166-inst-meta-layer-analysis.md`
|
||||
|
||||
**内容**:
|
||||
- 現状の詳細分析(3つの視点から)
|
||||
- 4つの問題(P1-P4)の診断
|
||||
- 3つの設計オプション(A/B/C)の評価
|
||||
- 優先度付き改善提案(4項目)
|
||||
- 箱化の観点からの設計原則
|
||||
|
||||
**ボリューム**: 393行
|
||||
|
||||
### 2. CSE修正提案
|
||||
|
||||
**ファイル**: `cse-pass-callee-fix.md`
|
||||
|
||||
**内容**:
|
||||
- 問題の詳細なシナリオ分析(4つのケース)
|
||||
- 修正方法の2つの提案(推奨版と軽量版)
|
||||
- テストケース(3項目)
|
||||
- 実装スケジュール
|
||||
|
||||
**ボリューム**: 225行
|
||||
|
||||
## 改善提案(優先度順)
|
||||
|
||||
| # | 項目 | 優先度 | 影響 | 実装量 | ファイル |
|
||||
|---|------|--------|------|--------|---------|
|
||||
| 1 | inst_meta役割をドキュメント化 | ⭐⭐⭐ | 中 | 1-2h | docs新規 |
|
||||
| 2 | CSE の callee 対応修正 | ⭐⭐⭐ | 高 | 1-2h | cse.rs |
|
||||
| 3 | CallLikeInst に callee を追加 | ⭐⭐ | 高 | 4-6h | inst_meta/methods |
|
||||
| 4 | 統合テスト追加 | ⭐⭐ | 中 | 2-3h | tests新規 |
|
||||
|
||||
**合計**: 8-13時間
|
||||
|
||||
## 設計原則の推奨
|
||||
|
||||
### 「箱化」の視点
|
||||
|
||||
```
|
||||
MIRInstruction(確定形)
|
||||
↓
|
||||
methods.rs(SSOT: Single Source of Truth)
|
||||
↓
|
||||
inst_meta(PoC: Optional, Deletable)
|
||||
```
|
||||
|
||||
**原則**:
|
||||
1. methods.rs が唯一の正当な実装源
|
||||
2. inst_meta は最適化レイヤー(削除可能)
|
||||
3. CallLikeInst は完全ミラー必須
|
||||
|
||||
## 潜在的な問題と対応
|
||||
|
||||
### 現状で起こる可能性のある問題
|
||||
|
||||
**1. CSE の不正な最適化** (HIGH)
|
||||
- 異なるメソッド呼び出しを統合してしまう可能性
|
||||
- 修正: CSE fix(優先度2)
|
||||
|
||||
**2. DCE の処理経路依存** (MEDIUM)
|
||||
- methods.rs 経由では receiver を含む
|
||||
- inst_meta 経由では receiver を含まない?
|
||||
- 現状では methods.rs が使われているため実害なし
|
||||
|
||||
**3. 将来の保守性低下** (MEDIUM)
|
||||
- inst_meta の役割が不明確
|
||||
- new instruction を追加時に両方を修正する必要あり
|
||||
|
||||
## 検索スコープ確認
|
||||
|
||||
**スキャン対象**:
|
||||
- MIR instruction_kinds/: ✅ 全4ファイル確認
|
||||
- MIR passes/: ✅ dce.rs, cse.rs 確認
|
||||
- MIR instruction/: ✅ methods.rs 確認
|
||||
- Callee enum用途: ✅ call_unified.rs 確認
|
||||
- used_values() 用途: ✅ dce/cse 確認
|
||||
|
||||
**検出率**: 100% (known problem areas)
|
||||
|
||||
## 追加分析
|
||||
|
||||
### パターンマッチング結果
|
||||
|
||||
**Call系命令の分布**:
|
||||
```
|
||||
Call: methods.rs + inst_meta(CallLikeInst) + cse.rs
|
||||
→ 3箇所で異なる処理!
|
||||
|
||||
BoxCall: inst_meta(CallLikeInst) + cse.rs
|
||||
→ 2箇所で処理
|
||||
|
||||
PluginInvoke: inst_meta(CallLikeInst) + cse.rs
|
||||
→ 2箇所で処理
|
||||
|
||||
ExternCall: inst_meta(CallLikeInst) + cse.rs
|
||||
→ 2箇所で処理
|
||||
```
|
||||
|
||||
→ **複製度: 高い** (統一化の機会あり)
|
||||
|
||||
## 今後のアクション
|
||||
|
||||
### Near-term(1-2週間)
|
||||
|
||||
1. ✅ phase166-inst-meta-layer-analysis.md を作成
|
||||
2. ✅ cse-pass-callee-fix.md を作成
|
||||
3. CSE修正を実装(優先度2)
|
||||
4. テスト追加
|
||||
|
||||
### Mid-term(1-2月)
|
||||
|
||||
5. CallLikeInst に callee 追加(優先度3)
|
||||
6. methods.rs の early return 削除
|
||||
7. inst_meta ドキュメント化(優先度1)
|
||||
|
||||
### Long-term(3-6月)
|
||||
|
||||
8. inst_meta を削除して methods.rs に統一?
|
||||
9. 他の instruction の同様分析
|
||||
|
||||
## 備考
|
||||
|
||||
**発見のタイプ**:
|
||||
- [ ] 新しい バグ(実際に動作不正)
|
||||
- [x] 設計的な 矛盾(整合性の問題)
|
||||
- [x] 保守性 低下(複製/非対称)
|
||||
- [x] パフォーマンス低下 (CSE 誤り)
|
||||
- [ ] セキュリティ問題
|
||||
|
||||
**推奨対応**: ドキュメント化 + 段階的リファクタ
|
||||
|
||||
@ -0,0 +1,393 @@
|
||||
# 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 return(inst_meta 経由でない)
|
||||
2. BoxCall/PluginInvoke: inst_meta 経由で CallLikeInst を使用
|
||||
3. ExternCall: inst_meta 経由で CallLikeInst を使用
|
||||
|
||||
**根本原因**:
|
||||
- inst_meta はPoC(Proof 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 match(callee 対応)
|
||||
- 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 pass(passes/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 が SSOT(Single Source of Truth)
|
||||
2. inst_meta は最適化用レイヤー(将来削除可)
|
||||
3. CallLikeInst は methods.rs を完全ミラー
|
||||
|
||||
## まとめ
|
||||
|
||||
| 問題 | 影響 | 優先度 | 改善方針 |
|
||||
|------|------|--------|---------|
|
||||
| inst_meta 役割あいまい | 開発者混乱 | 1 | ドキュメント化 |
|
||||
| CSE の callee 無視 | 最適化誤り可能 | 2 | fix CSE |
|
||||
| CallLikeInst::Call 不完全 | 潜在バグ | 3 | callee 追加 |
|
||||
| DCE 処理経路の非対称 | テスト困難 | 3 | 統合テスト追加 |
|
||||
|
||||
@ -212,6 +212,7 @@ env NYASH_FEATURES=stage3 NYASH_LLVM_USE_HARNESS=1 \
|
||||
| `NYASH_ME_CALL_ARITY_STRICT=1` | OFF | Any | me.method の arity 不一致でエラー |
|
||||
| `NYASH_MIR_DISABLE_OPT=1` | OFF | Any | MIR Optimizer 全体を無効化(開発/診断用、`src/mir/optimizer.rs`) |
|
||||
| `NYASH_TRACE_VARMAP=1` | OFF | Any | `MirBuilder.variable_map` の状態をトレース出力(`[varmap/<tag>] {name=ValueId(..),..}`)。JoinIR loop 統合のデバッグ用。 |
|
||||
| `NYASH_DCE_TRACE=1` | OFF | Any | DCE パスが削除した純粋命令を stderr にログ出力(`src/mir/passes/dce.rs`)。 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -778,7 +778,7 @@ impl MirBuilder {
|
||||
}
|
||||
);
|
||||
}
|
||||
block.add_instruction_with_span(instruction, self.current_span);
|
||||
block.add_instruction_with_span(instruction.clone(), self.current_span);
|
||||
// Drop the mutable borrow of `block` before updating other blocks
|
||||
}
|
||||
// Update predecessor sets for branch/jump immediately so that
|
||||
|
||||
@ -147,6 +147,28 @@ impl MirInstruction {
|
||||
|
||||
/// Get all ValueIds used by this instruction
|
||||
pub fn used_values(&self) -> Vec<ValueId> {
|
||||
// Handle Call instructions here (not in inst_meta) because CallLikeInst
|
||||
// doesn't have the callee field needed for Callee::Method receiver handling.
|
||||
// This is the single source of truth for Call's used values.
|
||||
if let MirInstruction::Call { callee, func, args, .. } = self {
|
||||
use crate::mir::definitions::call_unified::Callee;
|
||||
let mut used: Vec<ValueId> = Vec::new();
|
||||
match callee {
|
||||
// Unified path: Callee::Method with receiver
|
||||
Some(Callee::Method { receiver: Some(r), .. }) => {
|
||||
used.push(*r);
|
||||
}
|
||||
// Legacy path: func ValueId is the callable
|
||||
None => {
|
||||
used.push(*func);
|
||||
}
|
||||
// Other Callee variants (Global, Value, Extern) - no extra used values
|
||||
_ => {}
|
||||
}
|
||||
used.extend(args.iter().copied());
|
||||
return used;
|
||||
}
|
||||
|
||||
if let Some(used) = inst_meta::used_via_meta(self) {
|
||||
return used;
|
||||
}
|
||||
@ -185,19 +207,9 @@ impl MirInstruction {
|
||||
|
||||
MirInstruction::Return { value } => value.map(|v| vec![v]).unwrap_or_default(),
|
||||
|
||||
MirInstruction::Call {
|
||||
func, callee, args, ..
|
||||
} => {
|
||||
// func は legacy 経路では「関数値」を指すが、現在の unified 経路では
|
||||
// callee にメタ情報が入り、func はダミー (0) になることがある。
|
||||
// callee が None のときだけ func を SSA 値として扱い、それ以外
|
||||
// (callee=Some(..))では args のみを使用値とみなす。
|
||||
let mut used: Vec<ValueId> = Vec::new();
|
||||
if callee.is_none() {
|
||||
used.push(*func);
|
||||
}
|
||||
used.extend(args);
|
||||
used
|
||||
// Call is handled by early return above (single source of truth)
|
||||
MirInstruction::Call { .. } => {
|
||||
unreachable!("Call should be handled by early return in used_values()")
|
||||
}
|
||||
MirInstruction::NewClosure { captures, me, .. } => {
|
||||
let mut used: Vec<ValueId> = Vec::new();
|
||||
|
||||
@ -59,13 +59,20 @@ fn eliminate_dead_code_in_function(function: &mut MirFunction) -> usize {
|
||||
|
||||
// Remove unused pure instructions
|
||||
let mut eliminated = 0usize;
|
||||
for (_bbid, block) in &mut function.blocks {
|
||||
let dce_trace = std::env::var("NYASH_DCE_TRACE").ok().as_deref() == Some("1");
|
||||
for (bbid, block) in &mut function.blocks {
|
||||
block.instructions.retain(|inst| {
|
||||
if inst.effects().is_pure() {
|
||||
if let Some(dst) = inst.dst_value() {
|
||||
if !used_values.contains(&dst) {
|
||||
// Keep indices stable is not required here; remove entirely
|
||||
// Logging is suppressed to keep pass quiet by default
|
||||
// NYASH_DCE_TRACE=1 enables logging for debugging DCE issues
|
||||
if dce_trace {
|
||||
eprintln!(
|
||||
"[dce] Eliminating unused pure instruction in bb{}: %{} = {:?}",
|
||||
bbid.0, dst.0, inst
|
||||
);
|
||||
}
|
||||
eliminated += 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user