fix(joinir): Phase 287 P2 - Pattern6 nested loop latch overwrite fix

Fix infinite loop in Pattern6 (nested loop minimal) caused by main→loop_step
overwriting k_inner_exit→loop_step latch values.

Root cause: JoinIR main entry block was incorrectly treated as BackEdge,
causing it to overwrite the correct latch incoming values set by the true
back edge (k_inner_exit → loop_step).

Solution:
- Restrict latch recording to TailCallKind::BackEdge only
- Treat only MAIN's entry block as entry-like (not loop_step's entry block)
- Add debug_assert! to detect double latch set in future

Refactoring:
- Extract latch recording to latch_incoming_recorder module (SSOT)
- Add boundary.loop_header_func_name for explicit header identification
- Strengthen tail_call_classifier with is_source_entry_like parameter

Tests: apps/tests/phase1883_nested_minimal.hako → RC:9 (was infinite loop)
Smoke: 154/154 PASS, no regressions

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 09:39:29 +09:00
parent bbfc3c1d94
commit a04b48416e
20 changed files with 401 additions and 171 deletions

View File

@ -1,13 +1,19 @@
# Self Current Task — Now (main)
## Current Focus: Phase 188.3 (Nested Loop Lowering)
## Current Focus: Post Phase 188.3 (Refactoring Window)
**2025-12-27: Phase 188.3 完了**
- Pattern6NestedLoopMinimal: `apps/tests/phase1883_nested_minimal.hako` が RC=9
- Merge SSOTlatch/entry-like/double latchを固定BackEdgeのみlatch記録、main entry blockのみentry-like、二重latchはdebug_assert
- `./tools/smokes/v2/run.sh --profile quick` 154/154 PASS 維持
- 入口: `docs/development/current/main/phases/phase-188.3/README.md`
- 次の指示書refactor挟み込み: `docs/development/current/main/phases/phase-188.3/P2-REFACTORING-INSTRUCTIONS.md`
**2025-12-27: Phase 188.2 完了**
- StepTreeの `max_loop_depth` を SSOT に採用Option A
- strict mode で depth > 2 を明示エラー化Fail-Fast
- quick 154/154 PASS、integration selfhost FAIL=0 維持
- 次: `docs/development/current/main/phases/phase-188.3/README.md`depth=2 を JoinIR lowering で通す / “最小write-back”は carrier として明示
- 実装導線(手順書): `docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md`merge/rewriter の “undef ValueId” 典型罠もここに固定)
- 次: `docs/development/current/main/phases/phase-188.3/P2-REFACTORING-INSTRUCTIONS.md`(意味論不変での整理を優先
**2025-12-27: Phase S0.1 完了**
- integration selfhost を「落ちない状態」に収束FAIL=0

View File

@ -0,0 +1,74 @@
# Phase 188.3 P2: Pattern6 merge/latch 周りのリファクタ指示書(意味論不変)
**Date**: 2025-12-27
**Scope**: JoinIR merge の tail-call 分類・latch 記録の可読性/SSOT化
**Non-goals**: Pattern6 の選定/Lowering の機能追加、既定挙動変更、フォールバック追加
---
## 目的
Pattern6NestedLoopMinimalで露出した merge/rewriter の暗黙ルールを「構造」で固定し、次の拡張Phase 188.4+)や回帰検知を楽にする。
---
## SSOT固定する契約
- `latch_incoming` を記録してよいのは `TailCallKind::BackEdge` のみLoopEntry は上書き禁止)
- entry-like は “JoinIR main の entry block のみ”(`loop_step` の entry block を entry-like と誤認しない)
- latch 二重設定は `debug_assert!` で fail-fast回帰検知
実装の現行入口:
- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
- `src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs`
---
## リファクタ方針(構造で解決)
### A) tail-call 分類を「1箇所」で作って再利用する
現状は plan stage の中で `classify_tail_call(...)` を複数回呼び出している。
`TailCallFacts`structを作って、各 block の tail-call に対して以下を 1 回だけ確定し、後続が参照する形にする:
- `target_func_name`
- `is_target_continuation`
- `is_target_loop_entry`
- `is_entry_like_block`MAIN + entry block
- `tail_call_kind`
### B) latch 記録を “専用の小箱” に隔離する
`instruction_rewriter.rs` の末尾にある “latch 記録” を以下の形で隔離し、責務を固定する:
- `LatchIncomingRecorder`new moduleを追加し、`record_if_backedge(...)` だけを公開
- 引数: `tail_call_kind`, `boundary`, `new_block_id`, `args`, `loop_header_phi_info`
- 返り値: `()`(失敗は `debug_assert!` / 既存の `Result` に委譲)
これで「BackEdge 以外で記録しない」契約が局所化され、将来の変更点が明確になる。
### C) “entry-like” 判定を SSOT helper に寄せる
`func_name == MAIN && old_block_id == entry_block` の判定を helper 化する:
- `is_entry_like_block(func_name, old_block_id, func_entry_block) -> bool`
判定の重複と誤用loop_step の entry を entry-like と見なす)を構造で防ぐ。
---
## テスト(仕様固定)
最小で良いので、契約をテストで固定する(既存のユニットテストがある範囲で)。
- `LoopHeaderPhiInfo::set_latch_incoming()` の二重セットが debug で fail-fast すること(`#[should_panic]`
- Pattern6 fixture は既にあるので、スモークで RC を固定するquick の回帰検知)
---
## 検証手順
1. `cargo build --release`
2. `./target/release/hakorune --backend vm apps/tests/phase1883_nested_minimal.hako`RC=9
3. `./tools/smokes/v2/run.sh --profile quick`154/154 PASS

View File

@ -1,7 +1,7 @@
# Phase 188.3: Nested loop lowering (1-level) — make Pattern 6 real
**Date**: 2025-12-27
**Status**: In progress (Phase 2 done: selection / Phase 3: lowering)
**Status**: Complete (Pattern6 lowering + merge/latch fix)
**Prereq**: Phase 188.2 Option A is complete (StepTree depth SSOT + strict Fail-Fast)
---
@ -44,15 +44,19 @@ Phase 188.2 の strict ガードは「depth > 2 を明示エラー」にして
## Current Code Status (reality)
Phase 188.3 は “選択ロジックPattern6選定” まで実装済みで、lowering が未実装stubな状態
Phase 188.3 は “選択ロジックPattern6選定→ lowering → merge/rewriter 安定化” まで完了している
- Selection SSOT: `src/mir/builder/control_flow/joinir/routing.rs``choose_pattern_kind()`
- cheap check → StepTree → AST validation
- `max_loop_depth == 2` かつ “Pattern6 lowerable” のときだけ `Pattern6NestedLoopMinimal` を返す
- Lowering stub: `src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs`
- 現在は `Err("[Pattern6] ... not yet implemented")` で Fail-Fast
この README のゴールは、stub を “実装済み” にして fixture を通すこと。
- Lowering: `src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs`
- JoinIR pipeline で `inner_step/k_inner_exit/k_exit` を含む関数群を生成して merge する
- Fixture: `apps/tests/phase1883_nested_minimal.hako`RC=9
- Merge/Rewrite contractSSOT:
- `latch_incoming` を記録してよいのは `TailCallKind::BackEdge` のみLoopEntry は上書き禁止)
- entry-like は “JoinIR main の entry block のみ”
- 二重 latch は `debug_assert!` で fail-fast
- 実装: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`, `src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs`
---
@ -167,6 +171,7 @@ JoinIR merge は「JoinIR の param ValueId は SSA 命令で定義されない
- `./tools/smokes/v2/run.sh --profile quick` が常にグリーン維持
- integration selfhost が FAIL=0 を維持
- 追加した nested loop fixture が PASSJoinIR lowering が使われたことをログ/タグで確認可能)
- 実測: `./target/release/hakorune --backend vm apps/tests/phase1883_nested_minimal.hako` → RC=9
---
@ -181,6 +186,10 @@ JoinIR merge は「JoinIR の param ValueId は SSA 命令で定義されない
- **Phase 188.3**: depth=2 の最小形を “確実に通す” + PoC fixture を smoke 固定
- **Phase 188.4+**: write-backouter carrier reconnectionと “再帰 lowering の一般化depthを増やしても壊れない” を docs-first で設計してから実装
### Post-completion (refactoring window)
実装完了後のリファクタ(意味論不変)を挟む場合は、`P2-REFACTORING-INSTRUCTIONS.md` を入口にする。
### Planned cleanup (after Phase 188.3)
Pattern6 を通す過程で露出しやすい “暗黙ルール” を SSOT 化して、今後の nested/generalization を楽にする: