444 lines
14 KiB
Markdown
444 lines
14 KiB
Markdown
|
|
# Phase 223-1: LoopBodyLocal in Condition - Comprehensive Inventory
|
||
|
|
|
||
|
|
## Purpose
|
||
|
|
|
||
|
|
This document inventories all loops that are currently **blocked** by the LoopConditionScopeBox Fail-Fast mechanism because they have `LoopBodyLocal` variables appearing in loop conditions (header, break, or continue).
|
||
|
|
|
||
|
|
The goal is to:
|
||
|
|
1. Identify **safe patterns** (Trim/JsonParser-style) that can be promoted to carriers
|
||
|
|
2. Identify **complex patterns** that should continue to Fail-Fast
|
||
|
|
3. Provide design input for Phase 223-2 carrier promotion system
|
||
|
|
|
||
|
|
## Detection Methodology
|
||
|
|
|
||
|
|
### Rust-side Detection
|
||
|
|
|
||
|
|
**Fail-Fast Location**: `src/mir/join_ir/lowering/loop_with_break_minimal.rs`
|
||
|
|
|
||
|
|
```rust
|
||
|
|
if loop_cond_scope.has_loop_body_local() {
|
||
|
|
let body_local_names = extract_body_local_names(&loop_cond_scope.vars);
|
||
|
|
return Err(format_unsupported_condition_error("pattern2", &body_local_names));
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Also checked in**:
|
||
|
|
- Pattern 4 (with continue): `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||
|
|
- TrimLoopLowering: `src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs` (tries promotion first)
|
||
|
|
|
||
|
|
### .hako File Patterns
|
||
|
|
|
||
|
|
Search patterns used:
|
||
|
|
```bash
|
||
|
|
# Pattern 1: local variable followed by loop
|
||
|
|
rg "local\s+\w+.*loop\(" apps/tests tools/hako_shared
|
||
|
|
|
||
|
|
# Pattern 2: substring/indexOf assignments (common in parsers)
|
||
|
|
rg "local\s+\w+\s*=.*substring|local\s+\w+\s*=.*indexOf" apps/tests tools/hako_shared
|
||
|
|
|
||
|
|
# Pattern 3: Loop conditions with character comparison
|
||
|
|
grep -r "loop.*ch.*==" apps/tests tools/hako_shared
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Category A: Safe Trim/JsonParser Patterns (昇格候補)
|
||
|
|
|
||
|
|
These patterns are **safe for carrier promotion** because:
|
||
|
|
- LoopBodyLocal is a simple value extraction (substring/indexOf)
|
||
|
|
- Condition is a simple boolean expression (equality/comparison)
|
||
|
|
- No complex control flow in the extraction
|
||
|
|
- Carrier update is straightforward
|
||
|
|
|
||
|
|
### Pattern A-1: Trim Leading Whitespace
|
||
|
|
|
||
|
|
**Example**: `tools/hako_shared/json_parser.hako` (line 330-336)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(start < end) {
|
||
|
|
local ch = s.substring(start, start+1)
|
||
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||
|
|
start = start + 1
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `ch` (String)
|
||
|
|
**Definition**: `local ch = s.substring(start, start+1)`
|
||
|
|
**Condition Usage**: Break condition uses `ch` in OR chain: `ch == " " || ch == "\t" || ch == "\n" || ch == "\r"`
|
||
|
|
**Promotion Target**: `is_whitespace` (bool carrier)
|
||
|
|
**Status**: ✅ **Already handled by TrimLoopHelper** (Phase 171-C)
|
||
|
|
**Carrier Initialization**: `is_whitespace = (ch == " " || ch == "\t" || ch == "\n" || ch == "\r")`
|
||
|
|
**Carrier Update**: Same as initialization (at end of loop body)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern A-2: Trim Trailing Whitespace
|
||
|
|
|
||
|
|
**Example**: `tools/hako_shared/json_parser.hako` (line 340-346)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(end > start) {
|
||
|
|
local ch = s.substring(end-1, end)
|
||
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||
|
|
end = end - 1
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `ch` (String)
|
||
|
|
**Definition**: `local ch = s.substring(end-1, end)` (note: backward indexing)
|
||
|
|
**Condition Usage**: Break condition uses `ch` in OR chain
|
||
|
|
**Promotion Target**: `is_whitespace` (bool carrier)
|
||
|
|
**Status**: ✅ **Already handled by TrimLoopHelper** (Phase 171-C)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern A-3: Skip Whitespace (Parser Pattern)
|
||
|
|
|
||
|
|
**Example**: `apps/tests/parser_box_minimal.hako` (line 30-41)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < n) {
|
||
|
|
local ch = src.substring(i, i + 1)
|
||
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||
|
|
i = i + 1
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `ch` (String)
|
||
|
|
**Definition**: `local ch = src.substring(i, i + 1)`
|
||
|
|
**Condition Usage**: Continue condition uses `ch` in OR chain
|
||
|
|
**Promotion Target**: `is_whitespace` (bool carrier)
|
||
|
|
**Status**: ⚠️ **Pattern 4 (with continue)** - needs promotion support
|
||
|
|
**Notes**: Uses `continue` instead of `break` - currently blocked in Pattern 4
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern A-4: JsonParser Number Parsing (Digit Detection)
|
||
|
|
|
||
|
|
**Example**: `tools/hako_shared/json_parser.hako` (line 121-133)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
local digits = "0123456789"
|
||
|
|
loop(p < s.length()) {
|
||
|
|
local ch = s.substring(p, p+1)
|
||
|
|
local digit_pos = digits.indexOf(ch)
|
||
|
|
|
||
|
|
// Exit condition: non-digit character found
|
||
|
|
if digit_pos < 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
// Continue parsing: digit found
|
||
|
|
num_str = num_str + ch
|
||
|
|
p = p + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `digit_pos` (Integer)
|
||
|
|
**Definition**: `local digit_pos = digits.indexOf(ch)` (depends on another LoopBodyLocal `ch`)
|
||
|
|
**Condition Usage**: Break condition `if digit_pos < 0`
|
||
|
|
**Promotion Target**: `is_digit` (bool carrier)
|
||
|
|
**Status**: ⚠️ **More complex** - depends on TWO LoopBodyLocal variables (`ch` and `digit_pos`)
|
||
|
|
**Notes**: This is the **cascading LoopBodyLocal pattern** - needs special handling
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern A-5: Simple Character Comparison Loop
|
||
|
|
|
||
|
|
**Example**: `apps/tests/phase182_p1_match_literal.hako` (line 356-364)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < len) {
|
||
|
|
local ch_s = s.substring(pos + i, pos + i + 1)
|
||
|
|
local ch_lit = literal.substring(i, i + 1)
|
||
|
|
if ch_s != ch_lit {
|
||
|
|
print("Result: NOMATCH")
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `ch_s` and `ch_lit` (both String)
|
||
|
|
**Definition**: Two substring extractions
|
||
|
|
**Condition Usage**: Break condition `if ch_s != ch_lit`
|
||
|
|
**Promotion Target**: `chars_match` (bool carrier)
|
||
|
|
**Status**: ⚠️ **Multiple LoopBodyLocal** - needs multi-variable promotion
|
||
|
|
**Notes**: This is a **string comparison pattern** - safe but needs multi-var support
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern A-6: Parser atoi with Range Check
|
||
|
|
|
||
|
|
**Example**: `apps/tests/parser_box_minimal.hako` (line 20-25) and `tools/hako_shared/json_parser.hako` (line 453-460)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < n) {
|
||
|
|
local ch = s.substring(i, i+1)
|
||
|
|
if ch < "0" || ch > "9" { break }
|
||
|
|
local pos = digits.indexOf(ch)
|
||
|
|
if pos < 0 { break }
|
||
|
|
v = v * 10 + pos
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `ch` (String), `pos` (Integer)
|
||
|
|
**Definition**:
|
||
|
|
- `local ch = s.substring(i, i+1)`
|
||
|
|
- `local pos = digits.indexOf(ch)`
|
||
|
|
**Condition Usage**:
|
||
|
|
- Break condition 1: `ch < "0" || ch > "9"` (range check)
|
||
|
|
- Break condition 2: `pos < 0` (indexOf check)
|
||
|
|
**Promotion Target**: `is_digit` (bool carrier)
|
||
|
|
**Status**: ⚠️ **Cascading + Multiple Break Conditions** - complex but safe
|
||
|
|
**Notes**: Two break conditions using different LoopBodyLocal variables
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Category B: Complex Patterns (Fail-Fast 維持)
|
||
|
|
|
||
|
|
These patterns should **continue to Fail-Fast** because:
|
||
|
|
- Multiple complex LoopBodyLocal dependencies
|
||
|
|
- Nested method calls in conditions
|
||
|
|
- Complex control flow that cannot be safely promoted
|
||
|
|
|
||
|
|
### Pattern B-1: Nested If with LoopBodyLocal
|
||
|
|
|
||
|
|
**Example**: `apps/tests/minimal_ssa_bug_loop.hako`
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < n) {
|
||
|
|
local line = src.substring(i, i + 10)
|
||
|
|
|
||
|
|
// Problem pattern: nested conditions with reassignment + immediate use
|
||
|
|
if line.length() > 0 {
|
||
|
|
line = line.substring(0, 5) // reassign line
|
||
|
|
if line.length() > 0 && line.substring(0, 1) == "u" { // immediate use
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `line` (String)
|
||
|
|
**Why Fail-Fast**:
|
||
|
|
- LoopBodyLocal is **reassigned** inside the loop body
|
||
|
|
- **Nested conditions** with method calls (`line.length()`, `line.substring()`)
|
||
|
|
- Cannot create a simple bool carrier - would need complex state tracking
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern B-2: Method Call Chain in Condition
|
||
|
|
|
||
|
|
**Example**: Hypothetical (not found in current codebase)
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < n) {
|
||
|
|
local item = array.get(i)
|
||
|
|
if item.process().isValid() {
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why Fail-Fast**:
|
||
|
|
- Method call chain makes carrier initialization complex
|
||
|
|
- Side effects in `process()` cannot be promoted
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Category C: Body-Only LoopBodyLocal (不要 - Already Handled)
|
||
|
|
|
||
|
|
These patterns have LoopBodyLocal variables that **only appear in the loop body**, NOT in conditions. They **do not trigger Fail-Fast**.
|
||
|
|
|
||
|
|
### Pattern C-1: Body-Only Computation
|
||
|
|
|
||
|
|
**Example**: `apps/tests/phase183_body_only_loopbodylocal.hako`
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < 5) {
|
||
|
|
// Body-only LoopBodyLocal: temp is computed but never appears in any condition
|
||
|
|
local temp = i * 2
|
||
|
|
|
||
|
|
// Break condition doesn't use temp - only uses outer variable i
|
||
|
|
if i == 3 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
result = result + temp
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `temp` (Integer)
|
||
|
|
**Condition Usage**: **NONE** - only used in body expression `result = result + temp`
|
||
|
|
**Status**: ✅ **No Fail-Fast** - these are already handled correctly
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern C-2: Body-Local Update Variable
|
||
|
|
|
||
|
|
**Example**: `apps/tests/phase184_body_local_with_break.hako`
|
||
|
|
|
||
|
|
```hako
|
||
|
|
loop(i < 10) {
|
||
|
|
local temp = i * 3 // Body-local variable
|
||
|
|
sum = sum + temp // Use body-local in update expression
|
||
|
|
|
||
|
|
if (sum >= 15) { // Break condition uses 'sum', NOT 'temp'
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**LoopBodyLocal**: `temp` (Integer)
|
||
|
|
**Condition Usage**: **NONE** - break condition uses `sum`, not `temp`
|
||
|
|
**Status**: ✅ **No Fail-Fast** - already handled correctly
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary Statistics
|
||
|
|
|
||
|
|
| Category | Count | Description |
|
||
|
|
|----------|-------|-------------|
|
||
|
|
| **Category A** (Safe for Promotion) | 6 patterns | Trim/JsonParser-style, simple boolean extraction |
|
||
|
|
| **Category B** (Fail-Fast Maintained) | 1 pattern | Complex nested conditions, reassignment |
|
||
|
|
| **Category C** (Body-Only, Not Blocked) | 2 patterns | LoopBodyLocal only in body, not in conditions |
|
||
|
|
|
||
|
|
### Category A Breakdown
|
||
|
|
|
||
|
|
| Pattern | Status | Complexity | Priority |
|
||
|
|
|---------|--------|-----------|----------|
|
||
|
|
| A-1: Trim Leading | ✅ **Handled** (TrimLoopHelper) | Simple | - |
|
||
|
|
| A-2: Trim Trailing | ✅ **Handled** (TrimLoopHelper) | Simple | - |
|
||
|
|
| A-3: Skip Whitespace (Pattern 4) | ⚠️ **Needs Pattern 4 Support** | Simple | **P0** |
|
||
|
|
| A-4: Digit Detection (Cascading) | ⚠️ **Cascading LoopBodyLocal** | Medium | **P1** |
|
||
|
|
| A-5: String Comparison | ⚠️ **Multi-Variable** | Medium | P2 |
|
||
|
|
| A-6: atoi Range Check | ⚠️ **Cascading + Multi-Break** | High | P2 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Key Insights for Phase 223-2 Design
|
||
|
|
|
||
|
|
### 1. **Simple Trim Pattern is Solved** ✅
|
||
|
|
|
||
|
|
Phase 171-C's TrimLoopHelper already handles patterns A-1 and A-2 successfully. This is the foundation to build on.
|
||
|
|
|
||
|
|
### 2. **Pattern 4 (with continue) Needs Promotion Support** ⚠️ **P0**
|
||
|
|
|
||
|
|
Pattern A-3 (skip_whitespace) is a **critical blocker** for JsonParser. It's the same as Trim pattern but uses `continue` instead of `break`.
|
||
|
|
|
||
|
|
**Action Required**: Extend LoopBodyCarrierPromoter to support Pattern 4 (Phase 223-2-P0).
|
||
|
|
|
||
|
|
### 3. **Cascading LoopBodyLocal is Common** ⚠️ **P1**
|
||
|
|
|
||
|
|
Pattern A-4 shows a **cascading dependency**:
|
||
|
|
```hako
|
||
|
|
local ch = s.substring(p, p+1) // First LoopBodyLocal
|
||
|
|
local digit_pos = digits.indexOf(ch) // Second LoopBodyLocal (depends on ch)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Design Question**:
|
||
|
|
- Promote both to carriers? (`ch_carrier`, `is_digit_carrier`)
|
||
|
|
- Or only promote the "leaf" variable (`is_digit_carrier`)?
|
||
|
|
|
||
|
|
**Recommendation**: Promote only the **leaf** variable that appears in conditions. In Pattern A-4, only `digit_pos` appears in the break condition (`if digit_pos < 0`), so promote that.
|
||
|
|
|
||
|
|
### 4. **Multi-Variable Patterns Need Special Handling** (P2)
|
||
|
|
|
||
|
|
Pattern A-5 (string comparison) uses TWO LoopBodyLocal variables in the same condition. This is less common but should be supported eventually.
|
||
|
|
|
||
|
|
### 5. **Fail-Fast for Complex Patterns is Correct** ✅
|
||
|
|
|
||
|
|
Pattern B-1 (nested if with reassignment) correctly Fail-Fasts. These patterns are too complex for safe carrier promotion.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Next Steps (Phase 223-2)
|
||
|
|
|
||
|
|
### Phase 223-2-P0: Pattern 4 Promotion (Critical)
|
||
|
|
|
||
|
|
**Goal**: Enable `skip_whitespace` pattern (A-3) by supporting carrier promotion in Pattern 4 (with continue).
|
||
|
|
|
||
|
|
**Files to Modify**:
|
||
|
|
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` (add promotion logic)
|
||
|
|
- `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs` (extend to handle continue)
|
||
|
|
|
||
|
|
**Test Case**: `apps/tests/parser_box_minimal.hako` (skip_ws method)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 223-2-P1: Cascading LoopBodyLocal (High Priority)
|
||
|
|
|
||
|
|
**Goal**: Enable JsonParser number parsing (Pattern A-4) by promoting leaf variables in cascading dependencies.
|
||
|
|
|
||
|
|
**Design**:
|
||
|
|
1. Detect cascading pattern: `local ch = ...; local digit_pos = indexOf(ch)`
|
||
|
|
2. Identify leaf variable: `digit_pos` (appears in condition)
|
||
|
|
3. Promote leaf variable to carrier: `is_digit` (bool)
|
||
|
|
4. Initialize carrier: `is_digit = (digit_pos >= 0)`
|
||
|
|
5. Update carrier: Same as initialization (at end of loop)
|
||
|
|
|
||
|
|
**Test Case**: `tools/hako_shared/json_parser.hako` (_parse_number method)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 223-2-P2: Multi-Variable Patterns (Lower Priority)
|
||
|
|
|
||
|
|
**Goal**: Enable string comparison pattern (A-5) by promoting multiple variables.
|
||
|
|
|
||
|
|
**Design**: TBD (after P0/P1 experience)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Appendix: Full File Locations
|
||
|
|
|
||
|
|
### Trim Patterns (Already Handled)
|
||
|
|
|
||
|
|
- ✅ `tools/hako_shared/json_parser.hako:330-336` (_trim leading)
|
||
|
|
- ✅ `tools/hako_shared/json_parser.hako:340-346` (_trim trailing)
|
||
|
|
|
||
|
|
### Pattern 4 (Needs P0)
|
||
|
|
|
||
|
|
- ⚠️ `apps/tests/parser_box_minimal.hako:30-41` (skip_ws with continue)
|
||
|
|
- ⚠️ `tools/hako_shared/json_parser.hako:310-321` (_skip_whitespace)
|
||
|
|
|
||
|
|
### Cascading LoopBodyLocal (Needs P1)
|
||
|
|
|
||
|
|
- ⚠️ `tools/hako_shared/json_parser.hako:121-133` (_parse_number digit detection)
|
||
|
|
- ⚠️ `tools/hako_shared/json_parser.hako:453-460` (_atoi digit parsing)
|
||
|
|
- ⚠️ `apps/tests/parser_box_minimal.hako:20-25` (to_int)
|
||
|
|
|
||
|
|
### Multi-Variable (Needs P2)
|
||
|
|
|
||
|
|
- ⚠️ `apps/tests/phase182_p1_match_literal.hako:356-364` (_match_literal)
|
||
|
|
|
||
|
|
### Complex (Fail-Fast Maintained)
|
||
|
|
|
||
|
|
- ✅ `apps/tests/minimal_ssa_bug_loop.hako` (nested if with reassignment)
|
||
|
|
|
||
|
|
### Body-Only (Not Blocked)
|
||
|
|
|
||
|
|
- ✅ `apps/tests/phase183_body_only_loopbodylocal.hako` (temp variable)
|
||
|
|
- ✅ `apps/tests/phase184_body_local_with_break.hako` (temp in update)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Revision History
|
||
|
|
|
||
|
|
- **2025-12-10**: Phase 223-1 initial inventory created
|