Extend Pattern6 (ScanWithInit) to handle both forward and reverse scans: - Forward: i=0, loop(i < len), i=i+1 (existing) - Reverse: i=len-1, loop(i >= 0), i=i-1 (NEW) Implementation: - Added ScanDirection enum (Forward/Reverse) - Updated extract_scan_with_init_parts() to detect both patterns - Created lower_scan_with_init_reverse() lowerer - Pattern6 now selects appropriate lowerer based on scan direction Files modified: - src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs - src/mir/join_ir/lowering/scan_with_init_reverse.rs (new) - src/mir/join_ir/lowering/mod.rs Known issue (pre-existing): - PHI predecessor mismatch bug exists in Pattern6 (both forward and reverse) - This bug existed BEFORE Phase 257 P0 implementation - Out of scope for Phase 257 P0 - will be addressed separately Phase 257 P0
Phase 257: Loop with Early Return Pattern
Status: Active Scope: Pattern6(ScanWithInit)拡張で reverse scan + early return を受理する Related:
- Phase 256 完了(Pattern2 boundary SSOT 化、entry_param_mismatch 根治)
- North star:
docs/development/current/main/design/join-explicit-cfg-construction.md
Current Status (SSOT)
- Target first FAIL:
json_lint_vm / StringUtils.last_index_of/2 - Pattern: Loop with early return (backward scan)
- Approach: Extend Pattern6(ScanWithInit)to support reverse scan + early return
Background
失敗詳細
テスト: json_lint_vm (quick profile)
エラー: [joinir/freeze] Loop lowering failed: JoinIR does not support this pattern
関数: StringUtils.last_index_of/2
エラーメッセージ全体
[phase143/debug] Attempting loop_true_if_break/continue pattern (P0/P1)
[phase143/debug] Pattern out-of-scope: NotLoopTrue
[trace:dev] phase121/shadow: shadow=skipped signature_basis=kinds=Block,Stmt(local(i)),Loop,Block,If,Block,Stmt(return(value)),Stmt(assign(i)),Stmt(return(value));exits=return;writes=i;reads=ch,i,s;caps=If,Loop,Return;conds=(var:i >= lit:int:0)|(other:MethodCall == var:ch)
[trace:dev] loop_canonicalizer: Function: StringUtils.last_index_of/2
[trace:dev] loop_canonicalizer: Skeleton steps: 0
[trace:dev] loop_canonicalizer: Carriers: 0
[trace:dev] loop_canonicalizer: Has exits: false
[trace:dev] loop_canonicalizer: Decision: FAIL_FAST
[trace:dev] loop_canonicalizer: Missing caps: [ConstStep]
[trace:dev] loop_canonicalizer: Reason: Phase 143-P2: Loop does not match read_digits(loop(true)), skip_whitespace, parse_number, continue, parse_string, or parse_array pattern
[ERROR] ❌ MIR compilation error: [joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.
Function: StringUtils.last_index_of/2
Hint: This loop pattern is not supported. All loops must use JoinIR lowering.
期待される動作
StringUtils.last_index_of(s, ch) が正常にコンパイルされ、文字列内の最後の文字位置を返すこと。
実際の動作
Loop canonicalizer が ConstStep を要求しているが、このループは早期リターンを持つため Pattern2Break として認識されていない。
最小再現コード
// apps/tests/phase257_p0_last_index_of_min.hako
static box Main {
main() {
local s = "hello world"
local ch = "o"
// Find last occurrence of ch in s
local i = s.length() - 1
loop(i >= 0) {
if s.substring(i, i + 1) == ch {
return i // Early return when found
}
i = i - 1 // Backward scan
}
return -1 // Not found
}
}
分析
ループ構造
- 条件:
i >= 0(backward scan) - ボディ:
- If branch: マッチング検出時
return iで早期リターン
- After if: マッチなし
i = i - 1で 1 つ戻る(定数ステップ)
- If branch: マッチング検出時
- 特徴:
- Early return: break ではなく return を使用
- Backward scan:
i--で逆方向スキャン - Capabilities:
caps=If,Loop,Return(no Break) - Exits:
exits=return(両方の return を検出)
Pattern6(index_of)との比較
| Feature | Pattern6/index_of | last_index_of |
|---|---|---|
| 走査方向 | forward(i = i + 1) |
reverse(i = i - 1) |
| 早期終了 | 見つかったら return / exit PHI | 見つかったら return |
| 通常終了 | not-found return(例: -1) |
not-found return(-1) |
| JoinIR | main/loop_step/k_exit |
同じ語彙で表現できる |
提案される実装アプローチ
Option A: Pattern6 拡張(推奨 / 最小差分)
Pattern6(ScanWithInit)を “scan direction” を持つ形に一般化する(forward / reverse)。
-
検出条件:
- init:
i = s.length() - 1(reverse)またはi = 0(forward) - loop cond:
i >= 0(reverse)またはi < bound(forward) - body:
if s.substring(i, i + 1) == ch { return i }+ step update - step:
i = i - 1(reverse const step)またはi = i + 1(forward const step)
- init:
-
JoinIR lowering:
- Pattern6 の語彙(
main/loop_step/k_exit)を維持 - scan direction に応じて:
- stop 判定(
i < 0ori >= bound) - const step(
+1/-1)
- stop 判定(
- return は既存の “exit PHI + post-loop guard” を必要最小で再利用する(DCE回避は既存Box優先)
- Pattern6 の語彙(
-
Boundary construction:
- Phase 256 系の契約(
JumpArgsLayout/ contract checks)に従い、推測しない expr_resultはreturn iの経路でのみ使用(not-found は-1)
- Phase 256 系の契約(
Option B: Pattern8_ReverseScanReturn 新設
Pattern6 を触らずに、reverse scan 専用パターンとして箱を追加する。 (影響範囲は狭いが、scan 系が分裂する)
Option C: Normalization(将来)
return/break を正規化して共通語彙へ落とし、JoinIR patterns を縮退させる。 (Phase 257 ではやらない)
実装計画
推奨方針
Option A(Pattern6 拡張)を推奨。
理由:
- 既に index_of/find 系で “scan + not-found return” を Pattern6 が担っている
- last_index_of はその自然な派生(reverse scan + const step -1)
- Pattern2(break 前提)を膨らませずに済む
P0 タスク
-
Fixture & integration smokes(完了)
apps/tests/phase257_p0_last_index_of_min.hakotools/smokes/v2/profiles/integration/apps/phase257_p0_last_index_of_vm.shtools/smokes/v2/profiles/integration/apps/phase257_p0_last_index_of_llvm_exe.sh
-
Pattern6 detector/extractor 拡張(reverse scan)
src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs- reverse scan 形を accept し、parts(init/cond/step/return/not-found)を抽出
-
Pattern6 lowerer 拡張
- reverse scan の stop 判定・step を JoinIR へ落とす(語彙は既存 Pattern6 を維持)
-
検証
bash tools/smokes/v2/profiles/integration/apps/phase257_p0_last_index_of_vm.sh./tools/smokes/v2/run.sh --profile quick(最初の FAIL が次へ進む)
注意(P0ではやらない)
- 正規化での return/break 統一(Phase 257 ではやらない)
- scan 系の大改造(まずは reverse scan の受理を最小差分で固定)
備考
- Phase 256 で Pattern2Break の境界構築が SSOT 化済み(entry_param_mismatch 根治)
- この知見を活かし、Pattern2Return も同じ原則で実装する
- "Early exit" として break と return を統一的に扱うことで、将来の拡張性も高まる
進捗(P0)
✅ 実装完了 (2025-12-20)
実装内容:
- ✅
ScanDirectionenum 追加 (Forward/Reverse) - ✅
ScanParts構造体拡張(scan_direction フィールド追加) - ✅
is_const_step_pattern()更新(i + 1/i - 1両対応) - ✅
extract_scan_with_init_parts()更新(forward/reverse 両検出)- Forward:
i < s.length(), step +1 - Reverse:
i >= 0, step -1
- Forward:
- ✅
lower_scan_with_init_reverse()新規作成 - ✅ Pattern6 lowering 分岐実装(scan direction に応じて適切な lowerer 選択)
ファイル変更:
src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rssrc/mir/join_ir/lowering/scan_with_init_reverse.rs(新規)src/mir/join_ir/lowering/mod.rs
ビルド状況:
- ✅ コンパイル成功(0エラー)
- ✅ コード品質維持(既存パターン踏襲、SSOT原則遵守)
🔍 既知の問題(実装前から存在)
PHI predecessor mismatch bug:
- Error:
phi pred mismatch at ValueId(X): no input for predecessor BasicBlockId(Y) - Pattern6 forward scan (phase254_p0_index_of) でも同じエラー発生
- Phase 257 P0 実装前から存在 していたバグ
- phase254 テストは期待終了コード=1 のため "PASS" と表示されるが、実際はエラー終了
- Scope外: Pattern6 全体の PHI 生成バグであり、Phase 257 P0 では修正しない
次のアクション
Phase 257 P1(将来)
- PHI predecessor mismatch bug 修正(JoinIR merger の PHI ノード生成ロジック)
- Pattern6 全体のテスト整備(forward/reverse 両方の正常動作確認)
Phase 258(次フェーズ)
- quick profile の次の FAIL へ進む
- last_index_of が通った後の最初のエラーを調査
最終更新: 2025-12-20 (Phase 257 P0 実装完了、PHI bug は既知の問題として文書化)