Files
hakorune/docs/development/current/main/valueid-nondeterminism-root-cause-analysis.md
nyash-codex 461bdec45a feat(phi): Step 5-5-H完了 - Phantom block検証+PHI決定性向上
 Step 5-5-H: exit_preds検証でphantom block除外
  - loop_snapshot_merge.rs line 268: CFG実在チェック追加

 PHI生成決定性向上(4ファイル)
  - HashMap/HashSet → BTreeMap/BTreeSet変換
  - if_phi.rs, loop_phi.rs, loop_snapshot_merge.rs, loopform_builder.rs

 根本原因分析完了(Task先生調査)
  - ValueId変動の真因: HashMap非決定的イテレーション
  - 詳細: docs/development/current/main/valueid-*.md

📊 テスト結果: 267/268 PASS(退行なし)
  - mir_funcscanner_skip_ws: 非決定性残存(variable_map由来)
  - 後続タスクで対応予定

🎉 PHI Bug Option C実装ほぼ完了!

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 17:10:03 +09:00

340 lines
8.8 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.

# ValueId変動エラーの根本原因分析と修正方針
**日時**: 2025-11-20
**調査者**: Claude Code (Task: ValueId(268/271/280)変動エラー調査)
**重大度**: 🔴 Critical - 非決定性バグ(毎回異なるエラー)
---
## 📊 Executive Summary
### 🎯 **根本原因特定完了!**
**ValueId(268/271/280)が毎回変わる非決定性エラーの真犯人は:**
**`HashMap`/`HashSet`の非決定的イテレーション順序**
Rustの`HashMap`/`HashSet`は、セキュリティのためランダムなハッシュ値を使用し、
イテレーション順序が**実行ごとに異なります**。これにより:
1. PHIード生成順序が変わる
2. ValueIdの割り当て順序が変わる
3. エラーメッセージのValueIdが毎回変わる268 → 271 → 280
4. MIR検証エラーも変動する%307 → %304
### ✅ **Step 5-5-Hの検証結果**
Phantom block検証コードline 268-273は**正しく動作**しています:
- ログが出ない = 全blockが実際のCFG predecessorsに含まれている
- Phantom block問題ではなく、**非決定性が真の問題**
---
## 🔍 詳細分析
### 1. MIRダンプ分析結果
#### FuncScannerBox.skip_whitespace/2 構造
```
Entry: BasicBlockId(210)
Loop: entry=211, header=212, body=213, latch=214, exit=215
Break blocks: [217, 240]
Exit PHI (bb215):
%674 = phi [%55, bb212], [%84, bb217], [%663, bb240]
%673 = phi [%51, bb212], [%82, bb217], [%660, bb240]
%672 = phi [%53, bb212], [%83, bb217], [%661, bb240]
%671 = phi [%57, bb212], [%85, bb217], [%659, bb240]
```
### 2. 非決定性の発生源4ファイル確認
#### 🔴 **loop_snapshot_merge.rs** (最重要)
**Line 65, 124, 215:**
```rust
let mut all_vars: HashSet<String> = HashSet::new();
all_vars.extend(header_vals.keys().cloned());
// ...
for var_name in all_vars { // ← 毎回異なる順序!
// PHI生成処理
}
```
**影響範囲:**
- `merge_continue_for_header()` - Header PHI生成
- `merge_exit()` - Exit PHI生成
- `merge_exit_with_classification()` - Option C Exit PHI生成現在使用中
#### 🔴 **if_phi.rs**
```rust
let mut names: HashSet<&str> = HashSet::new();
for k in then_map_end.keys() { names.insert(k.as_str()); } // ← 非決定的
for k in emap.keys() { names.insert(k.as_str()); } // ← 非決定的
```
#### 🔴 **loop_phi.rs**
```rust
let mut all_vars = std::collections::HashSet::new();
for var_name in header_vars.keys() { // ← 非決定的
all_vars.insert(var_name.clone());
}
```
#### 🔴 **loopform_builder.rs**
```rust
for var_name in snapshot.keys() { // ← 非決定的
// 変数処理
}
```
### 3. MIR検証エラーの変動
**実行1:**
```
Value %307 used in block bb570 but defined in non-dominating block bb568
```
**実行2:**
```
Value %304 used in block bb125 but defined in non-dominating block bb123
```
**実行3:**
```
[rust-vm] use of undefined value ValueId(268)
```
**実行4:**
```
[rust-vm] use of undefined value ValueId(271)
```
**実行5:**
```
[rust-vm] use of undefined value ValueId(280)
```
**エラー内容も毎回変わる = 非決定性の決定的証拠**
### 4. エラーメッセージとMIRダンプの不一致
**エラーメッセージ:**
```
fn=FuncScannerBox.skip_whitespace/2,
last_block=Some(BasicBlockId(19))
```
**実際のMIRダンプ:**
```
Entry block: BasicBlockId(210)
```
**異なる実行/フェーズから来ている可能性**(非決定性により追跡困難)
---
## 🛠️ 修正方針
### Phase 1: 決定的なコレクション型への移行
#### ✅ **推奨修正: `BTreeSet`/`BTreeMap`への置き換え**
**メリット:**
- アルファベット順の決定的なソート
- API互換性が高いdrop-in replacement
- パフォーマンス影響は最小O(log n) vs O(1)、実用上問題なし)
#### 修正対象ファイル(優先順)
1. **`loop_snapshot_merge.rs`** (最重要)
- Line 65, 124, 215: `HashSet<String>``BTreeSet<String>`
2. **`if_phi.rs`**
- `HashSet<&str>``BTreeSet<&str>`
3. **`loop_phi.rs`**
- `HashSet``BTreeSet`
4. **`loopform_builder.rs`**
- `snapshot.keys()``snapshot.keys().sorted()`または`BTreeMap`使用
### Phase 2: イテレーション順序の明示的ソート
**代替案BTreeSet変更が大規模な場合:**
```rust
// Before (非決定的)
for var_name in all_vars {
// PHI生成
}
// After (決定的)
let mut sorted_vars: Vec<_> = all_vars.into_iter().collect();
sorted_vars.sort();
for var_name in sorted_vars {
// PHI生成
}
```
---
## 🧪 検証方法
### 修正後のテスト手順
```bash
# 1. 複数回実行してValueId一貫性確認
for i in {1..10}; do
echo "=== Run $i ==="
NYASH_MIR_TEST_DUMP=1 ./target/release/nyash test_case.hako 2>&1 | grep "ValueId\|%[0-9]"
done
# 2. MIR検証エラーの消滅確認
NYASH_MIR_TEST_DUMP=1 ./target/release/nyash test_case.hako 2>&1 | grep "verification errors"
# 3. Option Cログの確認phantom blockスキップがないこと
NYASH_OPTION_C_DEBUG=1 ./target/release/nyash test_case.hako 2>&1 | grep "Option C"
```
### 期待される結果
**10回実行して全て同じValueIdエラーまたは成功**
**MIR検証エラーが消滅**
**267/268テスト引き続きPASS**
---
## 📚 技術的背景
### なぜHashMapは非決定的
Rustの`HashMap`は、**HashDoS攻撃対策**のため、起動時にランダムなseedを使用します
```rust
// Rustの内部実装簡略化
pub struct HashMap<K, V> {
hash_builder: RandomState, // ← 実行ごとに異なるseed
// ...
}
```
### 既存の`sanitize_inputs()`の限界
`loop_snapshot_merge.rs` line 321-331の`sanitize_inputs()`は:
```rust
pub fn sanitize_inputs(inputs: &mut Vec<(BasicBlockId, ValueId)>) {
let mut seen: HashMap<BasicBlockId, ValueId> = HashMap::new(); // ← ここもHashMap
for (bb, val) in inputs.iter() {
seen.insert(*bb, *val);
}
*inputs = seen.into_iter().collect(); // ← HashMap→Vec変換で非決定的
inputs.sort_by_key(|(bb, _)| bb.0); // ← ソートはされるが、手遅れ
}
```
**問題:**
- `seen.into_iter()`の順序が非決定的
- `sort_by_key()`は**BasicBlockId**でソートするが、**ValueId**の割り当て順序は既に決まっている
**解決策:**
```rust
let mut seen: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
```
---
## 🎯 実装ロードマップ
### Step 1: loop_snapshot_merge.rs 修正(最重要)
```rust
// Line 19
-use std::collections::{HashMap, HashSet};
+use std::collections::{HashMap, BTreeSet};
// Line 65, 124, 215
-let mut all_vars: HashSet<String> = HashSet::new();
+let mut all_vars: BTreeSet<String> = BTreeSet::new();
// Line 323 (sanitize_inputs)
-let mut seen: HashMap<BasicBlockId, ValueId> = HashMap::new();
+let mut seen: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
```
### Step 2: 他のPHIファイル修正
- `if_phi.rs`
- `loop_phi.rs`
- `loopform_builder.rs`
### Step 3: 回帰テスト
```bash
# 267/268テスト
./tools/test_267_268.sh
# 全PHIテスト
cargo test --package nyash --lib mir::phi_core
# スモークテスト
./tools/jit_smoke.sh
```
---
## 📌 重要な教訓
### 🔴 **非決定性は静かに忍び寄る**
- エラーメッセージが毎回変わる → 非決定性を疑え
- PHI生成、SSA構築など「順序依存」処理は特に危険
- `HashMap`/`HashSet`のイテレーションは**常に非決定的**
### ✅ **決定的なコレクション型を使用**
- PHI生成: `BTreeSet`/`BTreeMap`
- ソート必須: 明示的に`sort()`呼び出し
- デバッグ容易性: 決定的 = 再現可能 = デバッグ可能
### 🎯 **Step 5-5-Hは正しかった**
Phantom block検証は完璧に動作しています。
真の問題は**別の場所HashMap/HashSet**にありました。
---
## 🚀 次のアクション
1. [ ] **loop_snapshot_merge.rs修正** (最優先)
2. [ ] **他のPHIファイル修正**
3. [ ] **10回実行テストでValueId一貫性確認**
4. [ ] **MIR検証エラー消滅確認**
5. [ ] **ChatGPTにフィードバック共有**
---
## 📎 関連リソース
- MIRダンプ: `/tmp/mir_280.log` (67KB)
- ソースコード: `src/mir/phi_core/loop_snapshot_merge.rs`
- Step 5-5-H実装: Line 268-273 (Phantom block検証)
- 関連Issue: PHI pred mismatch問題の根本解決
---
**最終診断:** ValueId変動は`HashMap`/`HashSet`の非決定的イテレーション順序が原因。
`BTreeSet`/`BTreeMap`への移行で完全解決可能。Step 5-5-Hは正常動作確認済み。
**推定修正時間:** 30分コード変更 + 1時間テスト検証 = **1.5時間**
**影響範囲:** PHI生成全体、MIR検証、VM実行根本的改善
---
*Generated by Claude Code - Root Cause Analysis Complete*