## 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>
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があるケース
- 例: ループ body に
受け入れ基準
./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