fix(joinir): Phase 177-3 ValueId collision fix for multi-carrier loops

Root cause: JoinIR ValueId collision between function parameters and condition bindings
- Same ValueId used for both `result_init` (carrier param) and `limit` (condition var)
- Phase 33-21 was overwriting condition bindings when remapping carrier PHIs

Fix implemented (Option B - immediate protection):
1. Phase 177-3: Protect condition-only variables from Phase 33-21 override
   - Collect condition_bindings that are NOT carriers (by checking exit_bindings)
   - Skip remapping for these protected ValueIds
2. Phase 177-3-B: Handle body-only carriers explicitly
   - Carriers that appear in condition_bindings (added by Phase 176-5)
   - Map them to correct PHI dsts by name lookup

Investigation tools added:
- [DEBUG-177] trace logs for remapper state tracking
- Phase 177-3 protection logging
- BoundaryInjector PHI collision detection

Test results:
-  Integer multi-carrier test: Output 3 (expected)
- ⚠️ String test: RC=0 but empty output (separate issue - string concat emit)

Design docs created:
- phase177-parse-string-design.md: _parse_string loop analysis
- phase177-carrier-evolution.md: Carrier progression Phase 174-179

Next: Investigate string concatenation emit for full _parse_string support
This commit is contained in:
nyash-codex
2025-12-08 16:34:04 +09:00
parent 99d329096f
commit 7a01ffe522
6 changed files with 646 additions and 40 deletions

View File

@ -0,0 +1,165 @@
# Phase 177: Carrier Evolution - min から Production へ
## 視覚的比較: ループ構造の進化
### Phase 174: min1-carrier
```
Input: "hello world""
^start ^target
loop(pos < len) {
ch = s[pos]
if ch == '"' → break ✓
else → pos++
}
Carriers: 1
- pos (ループ変数)
Pattern: 4 (Loop + If PHI)
Exit: pos = 11
```
### Phase 175/176: min22-carrier
```
Input: "hello world""
^start ^target
loop(pos < len) {
ch = s[pos]
if ch == '"' → break ✓
else → result += ch
pos++
}
Carriers: 2
- pos (ループ変数)
- result (バッファ)
Pattern: 4 (Loop + If PHI)
Exit: pos = 11, result = "hello world"
```
### Phase 177-A: Simple Case2-carrier, Production-like
```
Input: "hello world""
^start ^target
loop(p < len) {
ch = s[p]
if ch == '"' → break ✓
else → str += ch
p++
}
Carriers: 2
- p (ループ変数)
- str (バッファ)
Pattern: 4 (Loop + If PHI)
Exit: p = 11, str = "hello world"
```
### Phase 178: Escape Handling2-carrier + continue
```
Input: "hello \"world\"""
^start ^escape ^target
loop(p < len) {
ch = s[p]
if ch == '"' → break ✓
if ch == '\\' → str += ch
p++
str += s[p]
p++
continue ←← 新要素
else → str += ch
p++
}
Carriers: 2 (変わらず)
- p (ループ変数)
- str (バッファ)
Pattern: 4? (continue 対応は未検証)
Exit: p = 15, str = "hello \"world\""
```
### Phase 179: Full Production2-carrier + early return
```
Input: "hello \"world\"""
^start ^escape ^target
loop(p < len) {
ch = s[p]
if ch == '"' → return MapBox {...} ✓ ←← early return
if ch == '\\' → if p+1 >= len → return null ←← error
str += ch
p++
str += s[p]
p++
continue
else → str += ch
p++
}
return null ←← ループ終端エラー
Carriers: 2 (変わらず)
- p (ループ変数)
- str (バッファ)
Pattern: 4? (early return 対応は未検証)
Exit: 正常: MapBox, 異常: null
```
## Carrier 安定性分析
### 重要な発見
**Phase 174 → 179 を通じて Carrier 数は安定1 → 2**
| Phase | Carriers | 新要素 | P5昇格候補 |
|-------|----------|--------|-----------|
| 174 | 1 (pos) | - | なし |
| 175/176 | 2 (pos + result) | バッファ追加 | なし |
| 177-A | 2 (p + str) | - | なし |
| 178 | 2 (p + str) | continue | なし(確認中) |
| 179 | 2 (p + str) | early return | なし(確認中) |
### P5昇格候補の不在
**Trim と異なり、`is_ch_match` 相当は不要**
理由:
- Trim: `is_ch_match` が「次も空白か?」を決定(**次の判断に影響**
- _parse_string: `ch == '"'` は「今終了か?」のみ(**次に影響しない**
```
Trim の制御フロー:
is_ch_match = (ch == ' ')
if is_ch_match → pos++ → 次も is_ch_match を評価 ←← 連鎖
_parse_string の制御フロー:
if ch == '"' → break → 終了
else → str += ch, p++ → 次は独立判断 ←← 連鎖なし
```
## JoinIR Pattern 対応予測
| Phase | Pattern 候補 | 理由 |
|-------|-------------|------|
| 177-A | Pattern4 | Loop + If PHI + break実装済み |
| 178 | Pattern4? | continue は Pattern4-with-continue要実装確認 |
| 179 | Pattern5? | early return は新パターン候補(要設計) |
## まとめ
### 段階的検証戦略
1. **Phase 177-A**: min2 と同型 → P5 安定性確認
2. **Phase 178**: continue 追加 → JoinIR 拡張必要性評価
3. **Phase 179**: early return 追加 → Pattern5 設計判断
### Carrier 設計の教訓
- **最小構成で開始**: 1-carrier (Phase 174)
- **段階的拡張**: 2-carrier (Phase 175/176)
- **Production 適用**: 構造は変えず、制御フローのみ追加Phase 177+
**「Carrier 数を固定して制御フローを段階的に複雑化」が正解**

View File

@ -0,0 +1,178 @@
# Phase 177: _parse_string 本体 JoinIR 適用設計
## 目的
Production `JsonParserBox._parse_string` メソッドに JoinIR P5 パイプラインを適用する。
Phase 174/175/176 で確立した「1-carrier → 2-carrier」検証の成果を、実際の JSON パーサーに展開する。
## 本番ループ構造lines 144-181
### 前提条件
- 開始位置 `pos``"` を指しているline 145 でチェック)
- ループ開始時 `p = pos + 1`(引用符の次の文字から開始)
### ループ本体lines 150-178
```hako
loop(p < s.length()) {
local ch = s.substring(p, p+1)
if ch == '"' {
// End of string (lines 153-160)
local result = new MapBox()
result.set("value", me._unescape_string(str))
result.set("pos", p + 1)
result.set("type", "string")
return result // ← Early return (通常の exit)
}
if ch == "\\" {
// Escape sequence (lines 162-174)
local has_next = 0
if p + 1 < s.length() { has_next = 1 }
if has_next == 0 { return null } // ← Early return (エラー)
str = str + ch
p = p + 1
str = str + s.substring(p, p+1)
p = p + 1
continue // ← ループ継続
}
str = str + ch // 通常文字の追加
p = p + 1
}
return null // ← ループ終端に到達(エラー)
```
### 制御フロー分岐
1. **終端クォート検出** (`ch == '"'`): 成功 return
2. **エスケープ検出** (`ch == "\\"`) + continue: 2文字消費してループ継続
3. **通常文字**: バッファ蓄積 + 位置進行
4. **ループ終端**: 引用符が閉じていない(エラー)
## Carriers 分析
| 変数 | 初期値 | 役割 | 更新式 | P5 昇格対象? | 備考 |
|------|--------|------|--------|---------------|------|
| `p` | `pos + 1` | カウンタ(位置) | `p = p + 1` | **Nループ変数** | 条件式に使用 |
| `str` | `""` | バッファ(蓄積) | `str = str + ch` | **N通常キャリア** | エスケープ時2回更新 |
| `has_next` | - | 一時変数(フラグ) | - | - | ループ内のみ有効 |
| `is_escape` | - | (潜在的フラグ) | - | - | 明示的変数なし |
### 重要な発見
- **エスケープ処理は continue 経由**: `p``str` を 2 回更新してから continue
- **Early return が 2 箇所**: 成功 return (line 159) とエラー return (line 167)
- **通常キャリアのみ**: P5 昇格対象(`is_ch_match` 相当)は**不要**
## min ケースとの差分
### 共通点
| 項目 | min (Phase 174) | min2 (Phase 175) | 本番 (Phase 177) |
|------|-----------------|------------------|------------------|
| ループ変数 | `pos` | `pos` | `p` |
| バッファ | なし | `result` | `str` |
| 終端条件 | `ch == '"'` → break | `ch == '"'` → break | `ch == '"'` → return |
| 通常処理 | `pos++` | `result += ch; pos++` | `str += ch; p++` |
### 差分
| 項目 | min/min2 | 本番 |
|------|----------|------|
| 終端処理 | `break` のみ | Early `return` (MapBox 返却) |
| エスケープ処理 | なし | `continue` を使った複雑な分岐 |
| エラー処理 | なし | ループ内・外に `return null` |
## 方針決定: 段階的アプローチ
### Phase 177-A: Simple Case今回
**対象**: エスケープ**なし**・終端クォート検出のみ
- **ループ構造**: min2 と完全同型(`p` + `str` の 2-carrier
- **終端処理**: `break` → 直後に MapBox 構築return 代替)
- **目的**: P5 パイプラインが「2-carrier + break」で動作することを確認
```hako
// Simplified for Phase 177-A
loop(p < s.length()) {
local ch = s.substring(p, p+1)
if ch == '"' {
break // P5: Pattern4 対応Loop+If PHI merge
} else {
str = str + ch
p = p + 1
}
}
// break 後: MapBox 構築
```
### Phase 178: Escape Handling次回
**追加要素**:
- `continue` 分岐(エスケープ処理)
- Early returnエラーハンドリング
- P5 昇格キャリア候補の検討(`is_escape` フラグ?)
### Phase 179: Full Production最終
**統合**:
- `_unescape_string()` 呼び出し
- 完全なエラーハンドリング
- 本番同等の MapBox 返却
## Phase 177-A 実装計画
### Test Case: `test_jsonparser_parse_string_simple.hako`
```hako
// Phase 177-A: Production-like simple case
static box JsonParserStringTest3 {
parse_string_simple() {
local s = "hello world\""
local p = 0 // pos + 1 相当(簡略化のため 0 から開始)
local str = ""
local len = s.length()
// 2-carrier loop (min2 と同型)
loop(p < len) {
local ch = s.substring(p, p+1)
if ch == "\"" {
break
} else {
str = str + ch
p = p + 1
}
}
// Post-loop: 結果出力MapBox 構築の代替)
print("Parsed string: ")
print(str)
print(", final pos: ")
print(p)
}
main() {
me.parse_string_simple()
return "OK"
}
}
```
### 期待される MIR 構造
- **Pattern4 検出**: Loop + If PHI mergePhase 170 実装済み)
- **2 carriers**: `p` (ループ変数) + `str` (バッファ)
- **Exit PHI**: ループ後の `p``str` が正しく伝播
### 検証項目
1. ✅ P5 パイプライン通過JoinIR → MIR
2. ✅ 2-carrier の正しい伝播(`p``str`
3.`break` 後の変数値が正しく使用可能
## 成功基準
- [ ] `test_jsonparser_parse_string_simple.hako` が実行成功
- [ ] MIR ダンプで Pattern4 検出確認
- [ ] 出力: `Parsed string: hello world, final pos: 11`
## 次のステップPhase 178
- `continue` 分岐の追加(エスケープ処理)
- P5 昇格キャリア候補の検討(必要性を再評価)
- Early return 対応JoinIR での処理検討)
## まとめ
**Phase 177-A では、min2 と同型の Simple Case を Production 環境に適用する。**
エスケープ処理は Phase 178 以降に回し、まず「2-carrier + break」の動作確認を優先する。