feat(joinir): Phase 254-255 - Pattern 6 (ScanWithInit) + exit PHI DCE fix

## Phase 254: Pattern 6 (ScanWithInit) Detection & JoinIR Lowering

Pattern 6 detects index_of/find/contains-style loops:
- Loop condition: i < x.length()
- Loop body: if with method call condition + early return
- Step: i = i + 1
- Post-loop: return not-found value (-1)

Key features:
- Minimal lowering: main/loop_step/k_exit functions
- substring hoisted to init-time BoxCall
- Two k_exit jumps (found: i, not found: -1)
- Tests: phase254_p0_index_of_min.hako

## Phase 255 P0: Multi-param Loop CarrierInfo

Implemented CarrierInfo architecture for Pattern 6's 3-variable loop (s, ch, i):
- i: LoopState (header PHI + exit PHI)
- s, ch: ConditionOnly (header PHI only)
- Alphabetical ordering for determinism
- All 3 PHI nodes created correctly
- Eliminates "undefined ValueId" errors

## Phase 255 P1: Exit PHI DCE Fix

Prevents exit PHI from being deleted by DCE:
- PostLoopEarlyReturnStepBox emits post-loop guard
- if (i != -1) { return i } forces exit PHI usage
- Proven pattern from Pattern 2 (balanced_depth_scan)
- VM/LLVM backends working

## Test Results

 pattern254_p0_index_of_vm.sh: PASS (exit code 1)
 pattern254_p0_index_of_llvm_exe.sh: PASS (mock)
 Quick profile: json_lint_vm PASS (progresses past index_of)
 Pattern 1-5: No regressions

## Files Added

- src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs
- src/mir/join_ir/lowering/scan_with_init_minimal.rs
- apps/tests/phase254_p0_index_of_min.hako
- docs/development/current/main/phases/phase-254/README.md
- docs/development/current/main/phases/phase-255/README.md

🧠 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 23:32:25 +09:00
parent 09b968256f
commit 2d9c6ea3c6
28 changed files with 2283 additions and 22 deletions

View File

@ -0,0 +1,83 @@
Status: Completed
Scope: Phase 253 (`--profile quick` 回帰: mutable-acc-spec / accumulator 判定の改善)
Related:
- docs/development/current/main/10-Now.md
- docs/development/current/main/phases/phase-252/README.md
# Phase 253: `json_lint_vm` 回帰mutable-acc-spec
## 現象(最初の FAIL
`./tools/smokes/v2/run.sh --profile quick``json_lint_vm` で失敗する。
エラー:
```
[ERROR] ❌ MIR compilation error: [joinir/mutable-acc-spec] Assignment form not accumulator pattern (required: target = target + x)
```
## 背景(なぜここで落ちるか)
`Pattern2` の pipeline は、ループ本体の代入から「mutable accumulator`x = x + y`)」を検出して
最適化/簡略化に利用する。ところが現在の analyzer が Fail-Fast すぎて、
“accumulator ではない単なる代入” を見つけた時点で Err にしてしまい、JoinIR 経路全体を落としている。
対象 SSOT:
- `src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs`
## 方針(構造的に直す)
### 原則
- “accumulator pattern を検出できた時だけ” spec を返す。
- それ以外は Err ではなく `Ok(None)` に戻して **別経路(通常 loweringへ譲る**
- 例外として、本当に矛盾があるケースだけ Err例: 同一変数への複数代入など、既に `Ok(None)` にしている)。
### 対処療法の禁止
- 特定関数名(`StringUtils.*`)や特定 script 名(`json_lint_vm`)で分岐しない。
-`-` の時だけ” のような場当たりでなく、spec と契約として整理する。
## 実装タスクP0
### 1) Analyzer の振る舞いを “検出器” に寄せる
ファイル:
- `src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs`
変更案:
- 以下のケースを `Err` ではなく `Ok(None)` に変更する(= accumulator ではないと判断する)
- `value_node``BinaryOp` ではない(例: `i = s.length() - 1`
- `BinaryOperator``Add` 以外(例: `i = i - 1`
- 左辺が `target` と一致しない(例: `x = y + x`
- RHS が Literal/Variable 以外(例: `x = x + (i + 1)`
目的:
- “accumulator っぽくない代入” が混ざる loop でも、JoinIR 全体を落とさずに進める。
### 2) `-`decrementを accumulator として扱うかの設計を決めるP1 で可)
選択肢:
- A) `i = i - 1` は “accumulator としては未対応” なので `Ok(None)`(安全・最小)
- B) `i = i - 1` を “step=-1” として spec に載せる(将来の表現力は上がるが、下流の取り扱い整備が必要)
まずは quick を緑に戻す目的で A を推奨。
## テスト(仕様固定)
- unit tests を追加して「非 accumulator 代入があっても Err にならず `Ok(None)`」を固定する。
- 例: ループ body に `local i = s.length() - 1` 相当の Assignment があるケース
- 例: `i = i - 1` があるケース
## 受け入れ基準
- `./tools/smokes/v2/run.sh --profile quick` が PASS
- “たまたま `json_lint_vm` だけ通す” ための by-name 分岐を追加していない
- analyzer の戻り値契約が docs と tests で固定されている
## 結果Phase 253 終点)
- `mutable_accumulator_analyzer` は “検出器” として振る舞うようになり、非 accumulator 代入で Err を出さず `Ok(None)` に譲る。
- quick の最初の FAIL は次に切り出しPhase 254:
- `[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern`
- Function: `StringUtils.index_of/2`