Files
hakorune/docs/development/current/main/phases/phase-284/README.md
tomoaki 661bbe1ab7 feat(phase284): P1 Complete - Return in Loop with Block Remap Fix
## 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>
2025-12-23 14:21:27 +09:00

5.3 KiB
Raw Blame History

Phase 284: Return as ExitKind SSOTpatternに散らさない

Status: Planned (design-first)

Goal

return を “pattern 個別の特例” として増やさず、ExitKind::Returncompose::* / 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.rsemit_frag()
  • Router SSOTSSOT=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 exprExitKind::Return として表現する。
  • 返り値ValueIdEdgeArgs で運ぶReturn edge が value を持つ)。
  • Return は 必ず emit 側で terminator になるpattern 側で命令を直に生成しない)。

2) Detect の境界Ok(None) / Err

  • Ok(None): 一致しない(次の extractor へ)
  • Err(...): 一致したが未対応close-but-unsupportedFail-Fast

Phase 284 の完了条件は「return を含むケースが close-but-unsupported ではなく SSOT 経路で処理される」状態に寄せること。

3) 実装の集約点(どこに寄せるか)

  • return の lowering は ExitKind + compose + emit_frag に集約する。
  • pattern の extractor は “認識” のみSSOT=extractreturn の解釈ロジックを増やさない。

補足: Phase 284 は “return だけのため” ではない。ここで固定するのは Exit 正規化ExitKind の語彙化)で、 return/break/continue/(将来の unwind) を同じ土台に載せるのが狙い。 「Jump/Branch の配線で exit を表現できる」状態ができると、return はその一例として自然に入る。

Responsibility Map迷子防止

このフェーズで一番起きやすい事故は「return をどこで処理するべきか分からず、pattern 側へ散布してしまう」こと。 そこで、どの経路で lower されるかを前提に責務を固定する。

A) Plan linePattern6/7

  • 入口: src/mir/builder/control_flow/joinir/patterns/router.rsroute=plan
  • SSOT:
    • src/mir/builder/control_flow/plan/normalizer.rsFrag 構築: branches/wires/exits
    • src/mir/builder/control_flow/edgecfg/api/compose.rs(合成 SSOT
    • src/mir/builder/control_flow/edgecfg/api/emit.rsemit_frag() terminator SSOT
  • ここでは return を **Return edgeExitKind::Return**として組み立てるのが自然。

B) JoinIR linePattern15,9

  • 入口: src/mir/builder/control_flow/joinir/patterns/router.rsroute=joinir
  • SSOT:
    • JoinIR 生成pattern 固有の JoinIR lowerer
    • src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rsJoinIR→MIR→merge の唯一入口)
    • src/mir/builder/control_flow/joinir/merge/mod.rsReturn 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

P0docs-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 の SSOTExitKind/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 終端として生成される)