✅ 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>
8.8 KiB
ValueId変動エラーの根本原因分析と修正方針
日時: 2025-11-20 調査者: Claude Code (Task: ValueId(268/271/280)変動エラー調査) 重大度: 🔴 Critical - 非決定性バグ(毎回異なるエラー)
📊 Executive Summary
🎯 根本原因特定完了!
ValueId(268/271/280)が毎回変わる非決定性エラーの真犯人は:
HashMap/HashSetの非決定的イテレーション順序
RustのHashMap/HashSetは、セキュリティのためランダムなハッシュ値を使用し、
イテレーション順序が実行ごとに異なります。これにより:
- PHIノード生成順序が変わる
- ValueIdの割り当て順序が変わる
- エラーメッセージのValueIdが毎回変わる(268 → 271 → 280)
- 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:
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
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
let mut all_vars = std::collections::HashSet::new();
for var_name in header_vars.keys() { // ← 非決定的
all_vars.insert(var_name.clone());
}
🔴 loopform_builder.rs
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)、実用上問題なし)
修正対象ファイル(優先順)
-
loop_snapshot_merge.rs(最重要)- Line 65, 124, 215:
HashSet<String>→BTreeSet<String>
- Line 65, 124, 215:
-
if_phi.rsHashSet<&str>→BTreeSet<&str>
-
loop_phi.rsHashSet→BTreeSet
-
loopform_builder.rssnapshot.keys()→snapshot.keys().sorted()またはBTreeMap使用
Phase 2: イテレーション順序の明示的ソート
代替案(BTreeSet変更が大規模な場合):
// 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生成
}
🧪 検証方法
修正後のテスト手順
# 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の内部実装(簡略化)
pub struct HashMap<K, V> {
hash_builder: RandomState, // ← 実行ごとに異なるseed!
// ...
}
既存のsanitize_inputs()の限界
loop_snapshot_merge.rs line 321-331のsanitize_inputs()は:
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の割り当て順序は既に決まっている
解決策:
let mut seen: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
🎯 実装ロードマップ
Step 1: loop_snapshot_merge.rs 修正(最重要)
// 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.rsloop_phi.rsloopform_builder.rs
Step 3: 回帰テスト
# 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)**にありました。
🚀 次のアクション
- loop_snapshot_merge.rs修正 (最優先)
- 他のPHIファイル修正
- 10回実行テストでValueId一貫性確認
- MIR検証エラー消滅確認
- 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