Files
hakorune/docs/development/current/main/phase33-11-exit-phi-bug-analysis.md
nyash-codex 7c55baa818 refactor(joinir): Phase 190 convert.rs modularization
- Created joinir_function_converter.rs (~133 lines): Function-level conversion
- Created joinir_block_converter.rs (~691 lines): Block-level conversion
- Reduced convert.rs from 943 → 120 lines (87% reduction)
- Total: 944 lines (original 943 lines, minimal overhead)
- Separation of concerns: Function vs Block responsibilities
- All handlers moved to block_converter for better organization
- Maintained backward compatibility with existing API
- Build successful, simple tests passing
2025-12-05 14:41:24 +09:00

3.0 KiB

Phase 33-11: Exit Block PHI Node Missing Bug Analysis

Problem Summary

The MIR generated from JoinIR lowering is missing PHI nodes in the exit block, causing "use of undefined value" errors.

Test Case

File: apps/tests/loop_min_while.hako

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

Error

[ERROR] use of undefined value ValueId(16)

MIR Dump Analysis

bb8:
    1: ret %16    # %16 is never defined!

bb8 is the exit block, but ValueId(16) is never defined anywhere.

Root Cause

In src/mir/builder/control_flow.rs::merge_joinir_mir_blocks():

Step 1: Exit block is created empty (line 618-621)

let exit_block_id = self.block_gen.next();
// ...
let exit_block = BasicBlock::new(exit_block_id);  // Empty!
func.add_block(exit_block);

Step 2: Return instructions are converted to Jump (line 827-841)

MirInstruction::Return { value } => {
    if let Some(ret_val) = value {
        let remapped_val = value_map.get(ret_val).copied().unwrap_or(*ret_val);
        if debug {
            eprintln!(
                "[cf_loop/joinir]   Return({:?}) → Jump to exit",
                remapped_val
            );
        }
    }
    MirInstruction::Jump {
        target: exit_block_id,
    }
}

Problem: The return value (remapped_val) is logged but NOT STORED anywhere!

Step 3: Exit block stays empty

The exit block is never populated with:

  • PHI nodes to collect return values
  • Return instruction to return the PHI'd value

Expected Fix

The exit block should look like:

bb8:
    1: %16 = phi [%9 from bb7], [%2 from bb5], ...
    1: ret %16

Or simpler, if all functions return the same constant:

bb8:
    1: %16 = const 0
    1: ret %16

JoinIR Functions Structure

The Pattern 1 lowerer generates 3 functions:

  1. main(): Calls loop_step, returns 0
  2. loop_step(): Tail recursion OR Jump to k_exit
  3. k_exit(): Returns 0

All Return instructions are converted to Jump to bb8 (exit block).

Solution Strategy

Option A: Collect Return Values + Generate PHI

  1. While converting Return→Jump, collect all return values
  2. After merging, generate PHI node in exit block
  3. Add Return instruction that returns PHI result

Option B: Direct Value Propagation

  1. Since Pattern 1 always returns 0, directly emit const 0 in exit block
  2. Simpler but less general

Option C: Track Return Values in Hash Map

  1. Create HashMap<BasicBlockId, ValueId> to track returns
  2. Use as PHI incoming values
  3. Most robust, handles all patterns

Recommendation

Start with Option B (simplest fix for Pattern 1), then generalize to Option C for future patterns.

Implementation Location

File: src/mir/builder/control_flow.rs Function: merge_joinir_mir_blocks() Lines: ~827-950

Test Validation

NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/loop_min_while.hako

Expected output:

0
1
2