- 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
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:
- main(): Calls loop_step, returns 0
- loop_step(): Tail recursion OR Jump to k_exit
- k_exit(): Returns 0
All Return instructions are converted to Jump to bb8 (exit block).
Solution Strategy
Option A: Collect Return Values + Generate PHI
- While converting Return→Jump, collect all return values
- After merging, generate PHI node in exit block
- Add Return instruction that returns PHI result
Option B: Direct Value Propagation
- Since Pattern 1 always returns 0, directly emit const 0 in exit block
- Simpler but less general
Option C: Track Return Values in Hash Map
- Create
HashMap<BasicBlockId, ValueId>to track returns - Use as PHI incoming values
- 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