Files
hakorune/docs/development/current/main/phases/phase-253/README.md
tomoaki 2d9c6ea3c6 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>
2025-12-19 23:32:25 +09:00

3.6 KiB
Raw Blame History

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 quickjson_lint_vm で失敗する。

エラー:

[ERROR] ❌ MIR compilation error: [joinir/mutable-acc-spec] Assignment form not accumulator pattern (required: target = target + x)

背景(なぜここで落ちるか)

Pattern2 の pipeline は、ループ本体の代入から「mutable accumulatorx = 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_nodeBinaryOp ではない(例: i = s.length() - 1
    • BinaryOperatorAdd 以外(例: 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