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:
nyash-codex
2025-12-05 23:26:55 +09:00
parent 02c471ccd4
commit 120fbdb523
8 changed files with 843 additions and 16 deletions

View File

@ -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"
# DCEDead 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:"
# 出力例:

View 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 を区別した最適化
- **バグ予防**: 異なるメソッド呼び出しを誤って統合する問題を防止
- **パフォーマンス**: わずかなキー生成コスト(許容範囲)

View File

@ -0,0 +1,182 @@
# Phase 166 - MIR inst_meta層 箱化・モジュール化分析 - 完了サマリー
## 実施内容
コードベース全体の inst_meta 層instruction_kinds/mod.rsと used_values() の設計を分析し、箱化・モジュール化の機会を徹底的に検索しました。
## 主要な発見
### 1. 設計的な非対称性
**Call命令の特別扱い**:
- Call: methods.rs で early returninst_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.rsSSOT: Single Source of Truth
inst_metaPoC: 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-term1-2週間
1. ✅ phase166-inst-meta-layer-analysis.md を作成
2. ✅ cse-pass-callee-fix.md を作成
3. CSE修正を実装優先度2
4. テスト追加
### Mid-term1-2月
5. CallLikeInst に callee 追加優先度3
6. methods.rs の early return 削除
7. inst_meta ドキュメント化優先度1
### Long-term3-6月
8. inst_meta を削除して methods.rs に統一?
9. 他の instruction の同様分析
## 備考
**発見のタイプ**:
- [ ] 新しい バグ(実際に動作不正)
- [x] 設計的な 矛盾(整合性の問題)
- [x] 保守性 低下(複製/非対称)
- [x] パフォーマンス低下 (CSE 誤り)
- [ ] セキュリティ問題
**推奨対応**: ドキュメント化 + 段階的リファクタ

View File

@ -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 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 | 統合テスト追加 |

View File

@ -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`)。 |
---

View File

@ -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

View File

@ -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();

View File

@ -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;
}