## Summary Completed Phase 284 P1: Enable return statements in Pattern4/5 loops via JoinInst::Ret infrastructure (100% pre-existing, no new infrastructure needed). **Critical Bug Fix**: Block ID remap priority - Fixed: local_block_map must take precedence over skipped_entry_redirects - Root cause: Function-local block IDs can collide with global remap entries (example: loop_step:bb4 vs k_exit:bb4 after merge allocation) - Impact: Conditional Jump else branches were incorrectly redirected to exit - Solution: Check local_block_map FIRST, then skipped_entry_redirects ## Implementation ### New Files - `src/mir/join_ir/lowering/return_collector.rs` - Return detection SSOT (top-level only, P1 scope) - `apps/tests/phase284_p1_return_in_loop_min.hako` - Test fixture (exit code 7) - Smoke test scripts (VM/LLVM) ### Modified Files - `loop_with_continue_minimal.rs`: Return condition check + Jump generation - `pattern4_with_continue.rs`: K_RETURN registration in continuation_funcs - `canonical_names.rs`: K_RETURN constant - `instruction_rewriter.rs`: Fixed Branch remap priority (P1 fix) - `terminator.rs`: Fixed Jump/Branch remap priority (P1 fix) - `conversion_pipeline.rs`: Return normalization support ## Testing ✅ VM: exit=7 PASS ✅ LLVM: exit=7 PASS ✅ Baseline: 46 PASS, 1 FAIL (pre-existing emit issue) ✅ Zero regression ## Design Notes - JoinInst::Ret infrastructure was 100% complete before P1 - Bridge automatically converts JoinInst::Ret → MIR Return terminator - Pattern4/5 now properly merge k_return as non-skippable continuation - Correct semantics: true condition → return, false → continue loop ## Next Phase (P2+) - Refactor: Block remap SSOT (block_remapper.rs) - Refactor: Return jump emitter extraction - Scope: Nested if/loop returns, multiple returns - Design: Standardize early exit pattern (return/break/continue as Jump with cond) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
5.3 KiB
5.3 KiB
Phase 284 P1(code): Return as ExitKind SSOT(実装)
目的: return を pattern 固有の特例にせず、ExitKind::Return と compose::* / emit_frag() へ収束させる。
前提SSOT(P0):
docs/development/current/main/phases/phase-284/README.md- Phase 282 の境界ルール(SSOT=extract / close-but-unsupported=Err):
docs/development/current/main/phases/phase-282/README.md
実装方針(最小)
1) 返り値の運搬(ExitKind::Return + args)
return <expr>はExitKind::Returnの edgeとして表現する。- Return edge が持つ値は
EdgeArgsで運ぶ(Return terminator の operand)。 - terminator は
emit_frag()が生成する(pattern/box が直に Return 命令を生やさない)。
2) 「移行期間の穴」を消す
現状は Pattern4/5 などが return を Err(close-but-unsupported) にしている。
P1 のゴールは:
returnを含む loop-body が “別パターンへ静かに流れる” 状態をなくす- SSOT 経路で
ExitKind::Returnに落ちるようにする
補足(設計意図):
- Phase 284 は “return だけ” を特別扱いするのではなく、Exit の語彙(ExitKind)を SSOT 化するフェーズでもある。
returnを「条件付き Jump の一種」として扱えるようにしておくと、将来のbreak/continue/throwも同じ導線に乗る。
実装タスク(推奨順)
Step 1: 現状の return ハンドリングを棚卸し(read-only)
-
joinir patterns extractors:
src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rssrc/mir/builder/control_flow/joinir/patterns/extractors/pattern5.rsreturnを Err にしている箇所(close-but-unsupported の根拠)を列挙する
-
control-flow lowering:
emit_frag()が Return edge をどう生成しているか確認する(target=None の Return wire/exit)compose::cleanup()の Return wiring が想定どおりか確認する
成果物: docs/development/current/main/phases/phase-284/P1-NOTES.md(短い箇条書きでOK)
Step 2: return を ExitKind に落とす “単一入口” を作る(root fix)
狙い:
- loop body のどの位置でも
returnが現れたらExitKind::Returnで外へ出せること - これを 1 箇所に寄せる(pattern 側に増やさない)
重要: Phase 284 で一番の迷子ポイントは「どこに寄せるか」なので、先に経路を固定する。
A) Plan line と JoinIR line を混同しない(必須)
- Plan line(Pattern6/7):
src/mir/builder/control_flow/plan/normalizer.rsが Frag 構築 SSOT。compose::cleanup()/emit_frag()へ寄せるのが正しい。 - JoinIR line(Pattern1–5,9):
src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rsが共通入口。 Pattern4/5 の root fix をplan/normalizer.rsに寄せても効かない(経路が違う)。
B) root fix の候補(JoinIR line を対象にする)
実装候補(どれか 1 つに決める):
- B1) JoinIR lowerer 側に “Return collector” を 1 箇所だけ追加し、Pattern4/5 はそれを呼ぶだけにする
- 方針: まずは fixture で使う形だけを対応(例: top-level if の then に return)
- 未対応形は Err(silent fallback 禁止)
- B2) JoinIR→MIR 変換/merge の共通入口へ正規化を寄せる(
conversion_pipeline.rs/merge/mod.rs)- ただし、JoinModule に return 相当が無いと後段で作れないので、ここは “最後に出る地点” にしないこと
- 実体としては B1 が先に必要になるケースが多い
重要:
conversion_pipeline.rsは JoinModule 構築の後で走るため、「return の配線を作る」責務をそこへ押し込むと設計が歪みやすい。 P1 の最小は「JoinIR lowering の入口(JoinInst を作る地点)で return を ExitKind に落とす」こと。
要件:
- Fail-Fast: “表現できない return” は Err(silent fallback 禁止)
- 既定挙動は変えない(return を含む既存 fixture があれば、その期待値は明示して更新)
Step 3: extractor の return ポリシーを更新(穴を埋める)
P1 で Return SSOT が通るようになったら、以下を更新する:
- Pattern4/5 の extractor で
returnを Err にしない(close-but-unsupported ではなくなるため) - ただし “return があるせいでパターン形状が曖昧になる” 場合は Err を維持(Fail-Fast)
Step 4: fixture + smoke(VM/LLVM)で SSOT を固定
最小 fixture の要件:
returnが loop の then/else どちらかに現れる- exit code が安定(stdout 抑制の LLVM でも確認できる)
例(案):
apps/tests/phase284_p1_return_in_loop_min.hako- loop 内で条件により
return 7/continue等 - 最終 exit code を 7 に固定
- loop 内で条件により
smoke:
- VM: stdout/exit code を検証
- LLVM: exit code + harness の
Result: <code>を検証(stdout が出ない想定)
受け入れ基準
returnを含む loop fixture が VM/LLVM で同一動作- pattern 側に “return の特例 if” が増えていない(root fix のみ)
Ok(None)/Errの境界が崩れていない(silent fallback なし)