219 lines
4.3 KiB
Markdown
219 lines
4.3 KiB
Markdown
|
|
# 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.
|