## 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>
Phase 284: Return as ExitKind SSOT(patternに散らさない)
Status: Planned (design-first)
Goal
return を “pattern 個別の特例” として増やさず、ExitKind::Return と compose::* / emit_frag() に収束させる。
移行期間中の検出穴(Ok(None) による黙殺)を消し、Fail-Fast を構造で担保する。
SSOT References
- Frag/ExitKind 設計:
docs/development/current/main/design/edgecfg-fragments.md - Composition API:
src/mir/builder/control_flow/edgecfg/api/compose.rs - Terminator emission:
src/mir/builder/control_flow/edgecfg/api/emit.rs(emit_frag()) - Router SSOT(SSOT=extract / safety valve):
docs/development/current/main/phases/phase-282/README.md
Problem(移行期間の弱さ)
- Pattern 単位で
returnを “未対応” にすると、検出戦略(Ok(None)/Err)次第で 静かに別経路へ落ちる。 - その結果、同じソースでも「どの lowering が
returnを解釈したか」が曖昧になり、SSOT が割れる。
Core SSOT(決めること)
1) 返り値の意味(ExitKind)
return exprはExitKind::Returnとして表現する。- 返り値(ValueId)は
EdgeArgsで運ぶ(Return edge が value を持つ)。 - Return は 必ず emit 側で terminator になる(pattern 側で命令を直に生成しない)。
2) Detect の境界(Ok(None) / Err)
Ok(None): 一致しない(次の extractor へ)Err(...): 一致したが未対応(close-but-unsupported)→ Fail-Fast
Phase 284 の完了条件は「return を含むケースが close-but-unsupported ではなく SSOT 経路で処理される」状態に寄せること。
3) 実装の集約点(どこに寄せるか)
returnの lowering は ExitKind + compose + emit_frag に集約する。- pattern の extractor は “認識” のみ(SSOT=extract)。
returnの解釈ロジックを増やさない。
補足: Phase 284 は “return だけのため” ではない。ここで固定するのは Exit 正規化(ExitKind の語彙化)で、
return/break/continue/(将来の unwind) を同じ土台に載せるのが狙い。
「Jump/Branch の配線で exit を表現できる」状態ができると、return はその一例として自然に入る。
Responsibility Map(迷子防止)
このフェーズで一番起きやすい事故は「return をどこで処理するべきか分からず、pattern 側へ散布してしまう」こと。
そこで、どの経路で lower されるかを前提に責務を固定する。
A) Plan line(Pattern6/7)
- 入口:
src/mir/builder/control_flow/joinir/patterns/router.rs(route=plan) - SSOT:
src/mir/builder/control_flow/plan/normalizer.rs(Frag 構築: branches/wires/exits)src/mir/builder/control_flow/edgecfg/api/compose.rs(合成 SSOT)src/mir/builder/control_flow/edgecfg/api/emit.rs(emit_frag()terminator SSOT)
- ここでは
returnを **Return edge(ExitKind::Return)**として組み立てるのが自然。
B) JoinIR line(Pattern1–5,9)
- 入口:
src/mir/builder/control_flow/joinir/patterns/router.rs(route=joinir) - SSOT:
- JoinIR 生成(pattern 固有の JoinIR lowerer)
src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs(JoinIR→MIR→merge の唯一入口)src/mir/builder/control_flow/joinir/merge/mod.rs(Return merge / exit block SSOT)
- 注意:
src/mir/builder/control_flow/plan/normalizer.rsは Plan line 専用なので、 Pattern4/5 の return 問題の root fix をここへ寄せても効かない。
禁止事項(Phase 284 の憲法)
- ❌ Pattern4/5 の
lower()へ「return を特別扱いする if」を散布しない(SSOTが割れる) - ❌ Extractor が
returnを見つけた時にOk(None)で黙殺しない(silent reroute 禁止) - ✅
returnの “対応/非対応” は 共通入口の Fail-Fastで固定する(P1 で実装)
Scope
P0(docs-only)
returnを ExitKind として扱う SSOT を文章で固定する(本ファイル + 参照先リンク)。- 移行期間のルール(Ok(None)/Err の境界、黙殺禁止)を Phase 282 と整合させる。
P1+(code)
returnを含む loop body を、JoinIR/Plan のどちらの経路でも 同じ ExitKind::Return に落とす。- VM/LLVM の両方で、
returnを含む fixture を smoke 化して SSOT を固定する。
Acceptance
- P0:
returnの SSOT(ExitKind/compose/emit)と detect 境界が明文化されている - P1+:
returnを含む loop fixture が VM/LLVM で同一結果になり、smoke で固定されている
P1 の実装方針(design-first 注記)
P1 の root fix は「PlanNormalizer へ寄せる」ではなく、JoinIR line の共通入口へ寄せる:
- 入口候補:
src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs(最終的な “ここで統一したい”)- もしくは JoinIR lowerer 側に “Return collector” を 1 箇所だけ作り、Pattern4/5 はそれを呼ぶだけにする
どちらにしても、目的は同じ:
- pattern 側へロジックを増やさず(散布しない)
ExitKind::Returnへ収束させる(MIR では Return 終端として生成される)