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

8.8 KiB
Raw Blame History

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:

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)、実用上問題なし)

修正対象ファイル(優先順)

  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

    • HashSetBTreeSet
  4. loopform_builder.rs

    • snapshot.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.rs
  • loop_phi.rs
  • loopform_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**にありました。


🚀 次のアクション

  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