- 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
142 lines
3.0 KiB
Markdown
142 lines
3.0 KiB
Markdown
# 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`
|
|
|
|
```nyash
|
|
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
|
|
|
|
```mir
|
|
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)
|
|
```rust
|
|
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)
|
|
```rust
|
|
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:
|
|
|
|
```mir
|
|
bb8:
|
|
1: %16 = phi [%9 from bb7], [%2 from bb5], ...
|
|
1: ret %16
|
|
```
|
|
|
|
Or simpler, if all functions return the same constant:
|
|
|
|
```mir
|
|
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
|
|
|
|
```bash
|
|
NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
|
```
|
|
|
|
Expected output:
|
|
```
|
|
0
|
|
1
|
|
2
|
|
```
|