Files
hakorune/docs/development/current/main/mir-nested-if-loop-bug.md

219 lines
4.3 KiB
Markdown
Raw Normal View History

# MIR Nested-If-in-Loop Bug (Critical)
**Date**: 2025-12-04
**Severity**: Critical
**Status**: Workaround Applied, Root Fix Needed
## 🐛 Problem Summary
**Symptom**: Infinite loop when using nested `if-else` statements inside `loop()` blocks.
**Error Message**:
```
[ERROR] VM error: vm step budget exceeded (max_steps=1000000, steps=1000001)
```
## 📊 Root Cause Analysis
### MIR Lowering Bug
The MIR builder generates incorrect control flow for nested if-else statements inside loops:
```hako
// ❌ Causes infinite loop
loop(condition) {
if expr1 {
if expr2 {
// code
} else {
break
}
} else {
break
}
}
```
### Control Flow Issue
**Generated MIR**:
```
bb4: loop header (PHI)
bb6: unconditional jump to bb4
bb11: unconditional jump to bb6
Jump chain: bb11 → bb6 → bb4 → ... (infinite)
```
**Problem**: The PHI node in bb4 never gets updated because the execution gets stuck in the bb11→bb6→bb4 jump chain.
## 🔬 Reproduction Case
### Minimal Test Case
```hako
static box Main {
main() {
local i = 0
loop(i < 3) {
local x = 1
if x == 1 {
if x == 1 {
i = i + 1
} else {
break
}
} else {
break
}
}
return 0
}
}
```
**Result**: `vm step budget exceeded` at bb6
### MIR Dump Analysis
```
bb6:
1: br label bb4
bb11:
1: br label bb6
```
Infinite unconditional jump chain with no PHI update.
## ✅ Workaround
### Strategy: Flatten Nested Ifs
**Before** (infinite loop):
```hako
loop(p < s.length()) {
local ch = s.substring(p, p+1)
if ch >= "0" && ch <= "9" {
num_str = num_str + ch
p = p + 1
} else {
break
}
}
```
**After** (fixed):
```hako
local parsing_done = 0
loop(p < s.length()) {
if parsing_done == 1 { break }
local ch = s.substring(p, p+1)
local digits = "0123456789"
local digit_pos = digits.indexOf(ch)
if digit_pos >= 0 {
num_str = num_str + ch
p = p + 1
} else {
parsing_done = 1
}
}
```
### Patterns to Avoid
1. **Nested if-else in loop**:
```hako
loop(cond) {
if a {
if b { ... } else { break }
} else { break }
}
```
2. **`&&` operator in loop condition**:
```hako
loop(cond) {
if x >= "0" && x <= "9" { ... }
}
```
### Safe Patterns
1. **Flatten with flags**:
```hako
local done = 0
loop(cond) {
if done == 1 { break }
// single-level if statements
}
```
2. **Use indexOf instead of range check**:
```hako
local digits = "0123456789"
if digits.indexOf(ch) >= 0 { ... }
```
## 📋 Affected Code
### Fixed Files
1. **tools/hako_shared/json_parser.hako**:
- `_parse_number()`: Used `indexOf()` workaround
- `_parse_string()`: Flattened escape sequence check
- `_unescape_string()`: Flattened `ch == "\\" && i + 1 < s.length()`
2. **Converted `while` → `loop()`** (10 occurrences):
- All `while` loops converted to `loop()` syntax per language spec
### Commit
- **Commit**: `608693af`
- **Title**: fix(json_parser): Fix infinite loop by working around MIR nested-if bug
- **Files Changed**: 1 file, +45/-23 lines
## 🎯 Root Fix Needed
### MIR Builder Issue
**Location**: `src/mir/builder/` (control flow lowering)
**Problem**: When lowering nested if-else inside loops, the builder creates:
- Unreachable PHI nodes
- Unconditional jump chains
- Missing latch block updates
**Solution Required**:
1. Fix control flow graph generation for nested conditionals
2. Ensure PHI nodes are properly connected
3. Add test cases for nested if-else in loops
### Test Coverage
Add comprehensive tests for:
- Nested if-else in loops (2+ levels)
- `&&` and `||` operators in loop conditions
- `break` and `continue` in nested contexts
## 📚 Related Issues
- **Phase 173 Task 3**: JsonParserBox bug fix (completed with workaround)
- **CLAUDE.md**: `while``loop()` syntax migration
- **Loop Builder**: `src/mir/loop_builder.rs` (potential fix location)
## 🔗 References
- **Test Case**: `test_nested_if_loop.hako` (reproduces bug)
- **JSON Parser**: `tools/hako_shared/json_parser.hako` (workaround applied)
- **CURRENT_TASK.md**: Phase 173 tracking
---
**Status**: Workaround deployed, root fix tracked for future MIR lowering improvements.