Files
hakorune/docs/development/current/main/phase131-6-ssa-dominance-diagnosis.md
nyash-codex 18bf35e6d4 fix(llvm): Phase 131-10 - Smart console.log routing(Segfault修正)
## 問題
- Integer値をi8*ポインタに変換 → Segfault(Exit 139)

## 解決策
- String literal → nyash.console.log(i8*)
- Integer/Handle → nyash.console.log_handle(i64)

## 結果
- Case B (loop_min_while): LLVM outputs `0,1,2` 
- VM/LLVM完全パリティ達成

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

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

5.8 KiB

Phase 131-6: MIR SSA Dominance Diagnosis

Update (Phase 131-10)

Case B (apps/tests/loop_min_while.hako) は LLVM AOT でも 0,1,2 を出して終了するところまで復旧済み。

この文書は Phase 131-6 時点の「切り分けログ(歴史)」として残し、最終的な到達点と修正の全体像は次を SSOT とする:

  • docs/development/current/main/phase131-3-llvm-lowering-inventory.md

Executive Summary

Status (Phase 131-6 時点): LLVM Backend Bug Confirmed Severity: P0 - Breaks basic loop functionality Root Cause (Phase 131-6 時点の仮説): PHI node incoming values not properly wired in LLVM IR generation

Evidence Chain

1. Test Case SSOT

File: /tmp/simple_add.hako

static box Main {
    main() {
        local i
        i = 0
        i = i + 1
        print(i)
        return 0
    }
}

Expected: Prints 1 Actual (VM): Prints 1 Actual (LLVM): Prints 1

File: apps/tests/loop_min_while.hako

static box Main {
  main() {
    local i = 0
    loop(i < 3) {
      print(i)
      i = i + 1
    }
    return 0
  }
}

Expected: Prints 0\n1\n2 Actual (VM): Prints 0\n1\n2 Actual (LLVM): Prints 0 infinitely (infinite loop)

2. MIR Verification

Command: ./target/release/hakorune --dump-mir apps/tests/loop_min_while.hako

MIR Output (relevant blocks):

define i64 @main() {
bb0:
    %1 = const 0
    %2 = copy %1
    br label bb4

bb3:
    %17 = const 0
    ret %17

bb4:
    %3 = phi [%2, bb0], [%12, bb7]   // ← PHI node for loop variable
    br label bb5

bb5:
    %8 = const 3
    %9 = icmp Lt %3, %8
    %10 = Not %9
    br %10, label bb6, label bb7

bb6:
    br label bb3

bb7:
    extern_call env.console.log(%3)   // ← Prints %3 (should increment each iteration)
    %11 = const 1
    %12 = %3 Add %11                  // ← %12 = %3 + 1 (updated value)
    br label bb4                      // ← Jumps back with %12
}

Analysis:

  • SSA form is correct
  • All values defined before use within each block
  • PHI node properly declares incoming values: [%2, bb0] (initial) and [%12, bb7] (loop update)
  • No use-before-def violations

3. VM Execution Verification

Command: timeout 2 ./target/release/hakorune apps/tests/loop_min_while.hako

Output:

0
1
2
RC: 0

Conclusion: MIR is correct, VM interprets it correctly

4. LLVM Execution Failure

Build Command: bash tools/build_llvm.sh apps/tests/loop_min_while.hako -o /tmp/loop_test

Build Result: Success (no errors)

Run Command: /tmp/loop_test

Output (truncated):

0
0
0
0
... (repeats infinitely)

Conclusion: LLVM backend bug - PHI node not working

Root Cause Analysis

Affected Component

File: /home/tomoaki/git/hakorune-selfhost/src/llvm_py/llvm_builder.py Function: finalize_phis() (lines 601-735+)

Bug Mechanism

The PHI node %3 = phi [%2, bb0], [%12, bb7] should:

  1. On first iteration: Use %2 (value 0 from bb0)
  2. On subsequent iterations: Use %12 (updated value from bb7)

What's happening:

  • %3 always resolves to 0 (initial value from %2)
  • The incoming value from bb7 (%12) is not being properly connected
  • Loop variable never increments → infinite loop

Suspected Code Location

In finalize_phis() around lines 670-688:

chosen: Dict[int, ir.Value] = {}
for (b_decl, v_src) in incoming:
    try:
        bd = int(b_decl); vs = int(v_src)
    except Exception:
        continue
    pred_match = nearest_pred_on_path(bd)
    if pred_match is None:
        continue
    # If self-carry is specified (vs == dst_vid), map to init_src_vid when available
    if vs == int(dst_vid) and init_src_vid is not None:
        vs = int(init_src_vid)   # ← SUSPICIOUS: May cause %12 to be ignored
    try:
        val = self.resolver._value_at_end_i64(vs, pred_match, self.preds, self.block_end_values, self.vmap, self.bb_map)
    except Exception:
        val = None
    if val is None:
        val = ir.Constant(self.i64, 0)   # ← Falls back to 0
    chosen[pred_match] = val

Hypothesis

The self-carry logic (lines 679-681) or value resolution (line 683) may be incorrectly mapping or failing to retrieve %12 from bb7, causing the PHI to always use the fallback value of 0.

Next Steps

Immediate Action Required

  1. Add Trace Logging:

    • Enable NYASH_CLI_VERBOSE=1 or similar PHI-specific tracing
    • Log what values are being wired to each PHI incoming edge
  2. Minimal Fix Verification:

    • Verify _value_at_end_i64(12, 7, ...) returns the correct LLVM value
    • Check if nearest_pred_on_path() correctly identifies bb7 as predecessor of bb4
  3. Test Matrix:

    • Simple Add: (already passing)
    • Loop Min While: (currently failing)
    • Case A/B2 from previous phases: (regression check needed)

Long-term Solution

Implement structural dominance verification:

  • MIR verifier pass to check SSA properties
  • LLVM IR verification before object emission
  • Automated test for PHI node correctness

Acceptance Criteria

Must Pass

  1. tools/build_llvm.sh apps/tests/loop_min_while.hako -o /tmp/loop_test && /tmp/loop_test outputs 0\n1\n2 and exits
  2. Simple Add still works: /tmp/simple_add outputs 1
  3. No regression in existing LLVM smoke tests

Documentation

  1. This diagnosis added to docs/development/current/main/
  2. Fix explanation added to phase131-3-llvm-lowering-inventory.md
  3. Test case added to prevent regression

Files Modified (To Be Updated)

  • src/llvm_py/llvm_builder.py - PHI wiring logic
  • docs/development/current/main/phase131-3-llvm-lowering-inventory.md - Add Phase 131-6 section
  • (potential) Test case addition

Timeline

  • Diagnosis: 2025-12-14 (Complete)
  • Fix: TBD
  • Verification: TBD