Files
hakorune/docs/development/current/main/phases/phase-132
nyash-codex 771bf6b0d1 feat(joinir): Phase 132-P3 - Exit PHI collision early detection
Added verify_exit_phi_no_collision() to contract_checks.rs for early detection
of ValueId collisions between exit PHIs and other instructions.

Problem detected:
- If exit_phi_builder uses builder.value_gen.next() (module-level) instead of
  func.next_value_id() (function-level), ValueIds can collide:

  Example:
  - bb0: %1 = const 0   (counter init)
  - bb3: %1 = phi ...   (exit PHI - collision!)

Previous behavior:
- Error only detected at LLVM backend runtime
- Cryptic error: "Cannot overwrite PHI dst=1"

New behavior:
- Panic at Rust compile time (debug build)
- Clear error message with fix suggestion:
  "Exit PHI dst %1 collides with instruction in block 0
   Fix: Use func.next_value_id() in exit_phi_builder.rs"

Benefits:
- 🔥 Fail-Fast: Catch errors during Rust compilation, not LLVM execution
- 📋 Clear messages: Exact collision point + fix suggestion
- 🧪 Testable: verify_exit_phi_no_collision() can be unit tested

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 06:00:48 +09:00
..

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.rs
    • Jump(k_exit, [i_param]) として exit 値を渡す
  • src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs
    • LoopExitBinding を作成して with_exit_bindings() で境界に設定

LLVM Python 層PHI SSOT を保護する)

  • src/llvm_py/builders/block_lower.py
    • PHI filtering を predeclared_ret_phis ベースにし、phi.basic_block 依存を排除
    • builder.vmap の既存 PHIplaceholderを上書きしないPHI を SSOT として保護)

検証

  • /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-P2Case CExit PHI ValueId collision fix

Case Capps/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