Files
hakorune/docs/archive/phases/phase-33/phase33-11-exit-phi-bug-analysis.md
nyash-codex a7dbc15878 feat(joinir): Phase 240-EX - Pattern2 header condition ExprLowerer integration
Implementation:
- Add make_pattern2_scope_manager() helper for DRY
- Header conditions use ExprLowerer for supported patterns
- Legacy fallback for unsupported patterns
- Fail-Fast on supported patterns that fail

Tests:
- 4 new tests (all pass)
- test_expr_lowerer_supports_simple_header_condition_i_less_literal
- test_expr_lowerer_supports_header_condition_var_less_var
- test_expr_lowerer_header_condition_generates_expected_instructions
- test_pattern2_header_condition_via_exprlowerer

Also: Archive old phase documentation (34k lines removed)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 00:33:04 +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

Status: Historical