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>
This commit is contained in:
@ -21,6 +21,10 @@ P1 のゴールは:
|
||||
- `return` を含む loop-body が “別パターンへ静かに流れる” 状態をなくす
|
||||
- SSOT 経路で `ExitKind::Return` に落ちるようにする
|
||||
|
||||
補足(設計意図):
|
||||
- Phase 284 は “return だけ” を特別扱いするのではなく、**Exit の語彙(ExitKind)を SSOT 化**するフェーズでもある。
|
||||
- `return` を「条件付き Jump の一種」として扱えるようにしておくと、将来の `break/continue` / `throw` も同じ導線に乗る。
|
||||
|
||||
## 実装タスク(推奨順)
|
||||
|
||||
### Step 1: 現状の `return` ハンドリングを棚卸し(read-only)
|
||||
@ -42,9 +46,28 @@ P1 のゴールは:
|
||||
- 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 つに決める):
|
||||
- A) loop lowering(Frag 構築)段で Return edge を first-class で追加
|
||||
- B) JoinIR conversion の merge 段で Return を ExitKind に正規化
|
||||
- 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 禁止)
|
||||
@ -76,4 +99,3 @@ smoke:
|
||||
- `return` を含む loop fixture が VM/LLVM で同一動作
|
||||
- pattern 側に “return の特例 if” が増えていない(root fix のみ)
|
||||
- `Ok(None)` / `Err` の境界が崩れていない(silent fallback なし)
|
||||
|
||||
|
||||
@ -39,6 +39,40 @@ Phase 284 の完了条件は「`return` を含むケースが close-but-unsuppor
|
||||
- `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)
|
||||
@ -56,3 +90,14 @@ Phase 284 の完了条件は「`return` を含むケースが close-but-unsuppor
|
||||
- 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 終端として生成される)
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
# Phase 285 P0(docs-only): Box lifecycle / weakref / finalization / GC SSOT
|
||||
|
||||
目的: “実装が仕様” になっている Box の寿命・弱参照・最終化を、docs と smoke の SSOT に固定する。
|
||||
|
||||
## 1. このP0でやること(コード変更なし)
|
||||
|
||||
1) 仕様SSOTを 1 ファイルにまとめる
|
||||
- `docs/development/current/main/phases/phase-285/README.md` を入口SSOTとして育てる。
|
||||
|
||||
2) 用語と境界を固定する
|
||||
- strong/weak/roots/finalizer/collection の定義
|
||||
- weakref の API(upgrade/生存判定)
|
||||
- finalizer の禁止事項(再入・例外・順序)
|
||||
|
||||
3) LLVM harness の扱いを明文化する
|
||||
- 未対応なら “未対応” を仕様として書く(差分を隠さない)。
|
||||
|
||||
## 2. README に必ず書く事項(チェックリスト)
|
||||
|
||||
- [ ] “roots” は何か(stack/local/global/handle/plugin 等)
|
||||
- [ ] strong/weak の意味(upgrade の成否条件)
|
||||
- [ ] finalizer はあるか/いつ発火するか/何が禁止か
|
||||
- [ ] GC/解放のトリガ(自動/手動/閾値/テスト用)
|
||||
- [ ] VM と LLVM harness の差分(未対応の場合の方針)
|
||||
|
||||
## 3. 次(P1/P2)への導線(箇条書きでOK)
|
||||
|
||||
- P1(investigation): 棚卸し対象のファイル一覧と観測ポイント
|
||||
- P2(smoke): fixture の仕様(stdout/exit)と LLVM 側の扱い(PASS/SKIP)
|
||||
|
||||
## 4. P1 調査チェックリスト(提案)
|
||||
|
||||
### Rust VM(SSOT)
|
||||
|
||||
- `src/value.rs`
|
||||
- `NyashValue::WeakBox` の生成箇所(weak をどう作るか)
|
||||
- `upgrade()` 失敗時の観測方法(文字列化/判定API)
|
||||
- unit test: `test_weak_reference_drop` の仕様(何を固定しているか)
|
||||
- `src/finalization.rs`
|
||||
- finalizer の存在(あれば: 登録、呼び出しタイミング、順序)
|
||||
- 禁止事項(再入/例外/I/O/alloc)をどこでガードするか
|
||||
- `src/box_trait.rs` / `src/scope_tracker.rs`
|
||||
- Box の所有モデル(Arc/Weakの境界、roots の形成点)
|
||||
|
||||
### LLVM harness(差分を SSOT 化)
|
||||
|
||||
- `src/llvm_py/`
|
||||
- weakref/finalizer の相当機能があるか(まず “無いなら無い” を明文化)
|
||||
- 未対応の場合は smoke を SKIP にし、理由をログで固定する方針
|
||||
71
docs/development/current/main/phases/phase-285/README.md
Normal file
71
docs/development/current/main/phases/phase-285/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Phase 285: Box lifecycle / weakref / finalization / GC SSOT
|
||||
|
||||
Status: Planned (design-first)
|
||||
|
||||
## Goal
|
||||
|
||||
Box の生存期間(強参照/弱参照/解放/最終化/GC)を SSOT として固定し、移行期間でも意味論が割れない状態にする。
|
||||
|
||||
## Why now
|
||||
|
||||
- JoinIR/Plan/compose の収束が進むほど、実行時の “値の寿命” の揺れが目立つ。
|
||||
- weakref/finalization は「実装が仕様」になりやすく、後から直すコストが最大級。
|
||||
- LLVM harness 側は未対応の可能性が高く、差分を “仕様として明文化” しないと再現/調査が難しい。
|
||||
|
||||
## SSOT References (current code)
|
||||
|
||||
- weakref の値表現: `src/value.rs`(`NyashValue::WeakBox`)
|
||||
- finalization: `src/finalization.rs`
|
||||
- Box trait: `src/box_trait.rs`(`SharedNyashBox = Arc<dyn NyashBox>`)
|
||||
- Scope tracking: `src/scope_tracker.rs`(Box の登録/スコープ)
|
||||
|
||||
## Snapshot(今わかっていること)
|
||||
|
||||
- weakref は `Weak<Mutex<dyn NyashBox>>` で保持される(`NyashValue::WeakBox`)
|
||||
- `WeakBox` の `to_string()` は `upgrade()` を試み、`WeakRef(null)` 表示になりうる(観測可能)
|
||||
- `src/value.rs` に weakref の drop 挙動を固定する unit test がある(`test_weak_reference_drop`)
|
||||
|
||||
## Responsibility Map(どこが仕様を決めるか)
|
||||
|
||||
- **SSOT(意味)**: Rust VM 実装(`src/value.rs`, `src/finalization.rs` 周辺)
|
||||
- **SSOT(観測)**: fixture/smoke(Phase 285 P2 で作る)
|
||||
- **LLVM harness**: まずは “差分を仕様として明文化” が優先(未対応なら SKIP を SSOT 化する)
|
||||
|
||||
## 用語(P0で固定する)
|
||||
|
||||
- **Strong reference**: 所有参照(`Arc` 等で Box を保持)
|
||||
- **Weak reference**: 非所有参照(`Weak` / `upgrade()` が失敗しうる)
|
||||
- **Upgrade**: weak → strong の昇格(成功/失敗が意味論)
|
||||
- **Roots**: 解放/GC から保護される参照集合(stack/local/global/handle/plugin)
|
||||
- **Finalizer**: 解放に伴う最終化処理(もし存在するなら)
|
||||
|
||||
## Questions to Answer (P0/P1)
|
||||
|
||||
- weakref の “生存判定” は何で観測できるか(`toString` / `is_alive` / `upgrade` API など)
|
||||
- finalizer は存在するか / いつ発火するか(drop 時?GC 時?明示 API?)
|
||||
- finalizer 内での禁止事項(再入、例外、I/O、allocation)をどうするか
|
||||
- LLVM harness の扱い(現状未対応なら “未対応として SSOT 化”)
|
||||
|
||||
## Scope (proposed)
|
||||
|
||||
### P0(docs-only)
|
||||
|
||||
- 用語の固定(strong/weak/roots/finalizer/collection)
|
||||
- 仕様の固定(weakref の upgrade 成否、finalizer の発火条件、禁止事項)
|
||||
- “LLVM harness の扱い” を明文化(未対応なら未対応として SSOT に書く)
|
||||
|
||||
### P1(investigation)
|
||||
|
||||
- Rust VM の現状実装の棚卸し(どこで roots が形成され、どこで解放/最終化されるか)
|
||||
- LLVM harness の現状調査(弱参照/GC が無い場合は差分として記録)
|
||||
|
||||
### P2(smoke)
|
||||
|
||||
- weakref の最小 fixture/smoke を作り、挙動を固定する
|
||||
- VM: stdout/exit code で固定
|
||||
- LLVM: 未対応なら “スキップ理由” を smoke で明示
|
||||
|
||||
## Non-goals
|
||||
|
||||
- GC アルゴリズム刷新(RC→tracing 等の設計変更)
|
||||
- LLVM harness に同等機能を “一気に” 実装(差分の記録→段階導入を優先)
|
||||
@ -0,0 +1,25 @@
|
||||
# Phase 286 P0(docs-only): JoinIR Line Absorption SSOT
|
||||
|
||||
目的: 「JoinIR line が第2の lowerer として残っている」状態を、設計で終わらせる(収束点の SSOT を固定する)。
|
||||
|
||||
## このP0でやること(コード変更なし)
|
||||
|
||||
1) 収束点(1本化ポイント)を SSOT として決める
|
||||
2) 禁止事項(散布・二重SSOT)を文章で固定する
|
||||
3) Phase 284(Return)/ Phase 285(Box lifecycle)と整合する責務マップを作る
|
||||
|
||||
## README に必ず入れる事項(チェックリスト)
|
||||
|
||||
- [ ] 「Plan line / JoinIR line」の現状と、なぜ二重化が危険か(迷子の原因)
|
||||
- [ ] 収束後の SSOT フロー(`Extractor → PlanFreeze → Lowerer → Frag/emit_frag`)
|
||||
- [ ] JoinIR の将来位置づけ(DomainPlan生成補助 / もしくは撤去までの段階)
|
||||
- [ ] 禁止事項(pattern側へ return/break/continue を散布しない、Ok(None)黙殺禁止)
|
||||
- [ ] 次フェーズ(P1 investigation / P2 PoC)の観測点と最小成功条件
|
||||
|
||||
## SSOTリンク
|
||||
|
||||
- Router(SSOT=extract / safety valve): `docs/development/current/main/phases/phase-282/README.md`
|
||||
- Frag/compose/emit SSOT: `docs/development/current/main/design/edgecfg-fragments.md`
|
||||
- JoinIR line 共通入口: `src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`
|
||||
- Plan line SSOT: `docs/development/current/main/phases/phase-273/README.md`
|
||||
|
||||
61
docs/development/current/main/phases/phase-286/README.md
Normal file
61
docs/development/current/main/phases/phase-286/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Phase 286: JoinIR Line Absorption(JoinIR→CorePlan/Frag 収束)
|
||||
|
||||
Status: Planned (design-first)
|
||||
|
||||
## Goal
|
||||
|
||||
移行期間に残っている「2本の lowering」を、構造で 1 本に収束させる。
|
||||
|
||||
- Plan line(Pattern6/7): `CorePlan → Frag(compose) → emit_frag()` が SSOT
|
||||
- JoinIR line(Pattern1–5,9): `JoinIR → bridge → merge` が SSOT
|
||||
|
||||
Phase 286 では JoinIR line を “第2の lowerer” として放置せず、**Plan/Frag SSOT へ吸収**する道筋を固定する。
|
||||
|
||||
## Why(なぜ今)
|
||||
|
||||
- `return` のような「大きな出口語彙」は、責務が分散すると実装場所が揺れて事故りやすい
|
||||
- 移行期間の弱点は「同じASTでも経路により意味論が割れる可能性がある」こと
|
||||
- pattern を溶かしていく思想の最後の壁が “JoinIR line の残存” になりやすい
|
||||
|
||||
## SSOT(Phase 286 で守る憲法)
|
||||
|
||||
- **SSOT=extract**(Phase 282): 検出は extract の成功でのみ決める。`pattern_kind` は O(1) safety valve のみ。
|
||||
- **CFG/terminator SSOT**(Phase 280/281): `Frag + compose::* + emit_frag()` が唯一の terminator 生成点。
|
||||
- **Fail-Fast**: close-but-unsupported を `Ok(None)` で黙殺しない(silent reroute 禁止)。
|
||||
|
||||
## Responsibility Map(どこを触るか)
|
||||
|
||||
- JoinIR line の共通入口(現状):
|
||||
- `src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`
|
||||
- `src/mir/join_ir_vm_bridge/bridge.rs`
|
||||
- `src/mir/builder/control_flow/joinir/merge/mod.rs`
|
||||
- Plan/Frag SSOT(収束先):
|
||||
- `src/mir/builder/control_flow/plan/*`
|
||||
- `src/mir/builder/control_flow/edgecfg/api/compose.rs`
|
||||
- `src/mir/builder/control_flow/edgecfg/api/emit.rs`
|
||||
|
||||
## Scope(提案)
|
||||
|
||||
### P0(docs-only)
|
||||
|
||||
- 「JoinIR line をどの粒度で吸収するか」を SSOT として決める
|
||||
- 例: JoinIR は DomainPlan 生成の補助へ降格 / JoinIR→MIR merge を段階撤去
|
||||
- “禁止事項” を明文化(pattern 側への散布、二重 SSOT の再発)
|
||||
|
||||
### P1(investigation)
|
||||
|
||||
- JoinIR line が持っている「本当は SSOT に寄せたい責務」を棚卸し
|
||||
- return/break/continue の扱い
|
||||
- exit phi / boundary の責務
|
||||
- optimizer/type propagation の入り口
|
||||
|
||||
### P2(PoC)
|
||||
|
||||
- 代表 1 パターン(例: Pattern4)を “JoinIR 生成 → CorePlan/Frag” に変換する PoC
|
||||
- 目的: merge を通さずに `emit_frag()` 経由で終端が生成できることの証明
|
||||
|
||||
## Acceptance(P0)
|
||||
|
||||
- 2本の lowering が “設計として” どこで 1 本に収束するかが明文化されている
|
||||
- Phase 284(Return)/ Phase 285(GC)と矛盾しない
|
||||
|
||||
Reference in New Issue
Block a user