4.3 KiB
4.3 KiB
Phase 132: Exit Values Parity (VM == LLVM)
Date: 2025-12-15
Status: ✅ Done
Scope: ループ exit 値(exit PHI / boundary)が VM/LLVM で一致することを固定する(Pattern 1 と Case C を含む)
背景
最小ケース:
static box Main {
main() {
local i = 0
loop(i < 3) { i = i + 1 }
return i // 期待: 3
}
}
- VM:
RC: 3✅ - LLVM:
Result: 0❌(修正前)
根本原因(2層)
1) JoinIR/Boundary 層: exit 値が境界を通っていない
- Pattern 1 の JoinIR で
k_exitに渡す args が空のままだと、exit 側でホストのvariable_map["i"]が更新されない。 - その結果
return iが初期値(0)を返し得る。
2) LLVM Python 層: PHI の SSOT が壊れている
phi.basic_blockが llvmlite で確定前はNoneになり得るため、PHI を落とす filter を信頼できない。builder.vmapにある PHI placeholder を、Pass A の “sync created values” が上書きしてしまい、retが PHI ではなく0を参照することがある。
修正内容
JoinIR/Boundary 層(exit 値を明示的に渡す)
src/mir/join_ir/lowering/simple_while_minimal.rsJump(k_exit, [i_param])として exit 値を渡す
src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rsLoopExitBindingを作成してwith_exit_bindings()で境界に設定
LLVM Python 層(PHI SSOT を保護する)
src/llvm_py/builders/block_lower.py- PHI filtering を
predeclared_ret_phisベースにし、phi.basic_block依存を排除 builder.vmapの既存 PHI(placeholder)を上書きしない(PHI を SSOT として保護)
- PHI filtering を
検証
/tmp/p1_return_i.hakoが VM/LLVM で3を返すNYASH_LLVM_STRICT=1でも “miss→0” に落ちない(Fail-Fast が維持される)
詳細ログ:
docs/development/current/main/investigations/phase132-llvm-exit-phi-wrong-result.md
追加: Phase 132-P2(Case C)Exit PHI ValueId collision fix
Case C(apps/tests/llvm_stage3_loop_only.hako)の LLVM EXE 検証で、
exit PHI の ValueId 衝突が原因で Result: 0 になる問題が見つかった。
Root Cause
src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rsが PHI dst の割り当てにbuilder.value_gen.next()(module-level)を使っており、 同一関数内で ValueId が衝突し得た。
Fix
- PHI dst の割り当てを
func.next_value_id()(function-level)へ統一。
コミット:
bd07b7f4 fix(joinir): Phase 132-P2 - Exit PHI ValueId collision fix
Verification
- Pattern 1: VM/LLVM ともに
3 - Case C: VM/LLVM ともに
Result: 3
調査ログ:
docs/development/current/main/investigations/phase132-case-c-llvm-exe.md
追加: Phase 132-P3(debug-only)Exit PHI collision early detection
同種の ValueId 衝突が “LLVM 実行時” にしか露見しないと切り分けコストが高いので、 JoinIR merge の契約検証(debug build)で 早期に Fail-Fast する検証 Box を追加した。
- 検証:
verify_exit_phi_no_collision() - 実装:
src/mir/builder/control_flow/joinir/merge/contract_checks.rs - 呼び出し元:
verify_exit_line(...)(#[cfg(debug_assertions)])
検出例(概念):
bb0: %1 = const 0exit block: %1 = phi ...(衝突)
期待される振る舞い:
- debug build では compile/run 中に panic で即停止(原因と修正案が出る)
- release build の既定挙動は変えない(検証は
debug_assertionsのみ)
追加: Phase 132-P1(LLVM Python)FunctionLowerContext Box
Phase 131–132 で顕在化した「関数間の状態漏洩(block id / value id の衝突)」を、 tuple-key の小手先ではなく 構造で根治するために、関数ローカル状態を 1 箱に隔離した。
- 実装:
src/llvm_py/context/function_lower_context.py - 統合:
src/llvm_py/builders/function_lower.py
箱の責務(SSOT):
block_end_values(snapshot)def_blocksjump_only_blocksphi_manager- resolver caches(i64/ptr/f64/end_i64 など)
狙い:
- 関数単位で state を自動破棄し、cross-function collision を “起こせない構造” にする
- 追加の手動クリアや tuple-key を不要にする