Files
hakorune/docs/development/architecture/loops/loopform_ssot.md
nyash-codex 3d5979c78e refactor(joinir): Phase 27.9 - Modular separation of join_ir.rs into directory structure
Phase 27.9 で join_ir.rs (~1,336行) を以下のモジュール構造に分離:

## 新規ディレクトリ構造:
```
src/mir/join_ir/
├── mod.rs                           # 型定義・共通ユーティリティ (~330行)
└── lowering/
    ├── mod.rs                       # lowering インターフェース
    ├── min_loop.rs                  # lower_min_loop_to_joinir (~140行)
    ├── skip_ws.rs                   # skip_ws lowering 3関数 (~390行)
    └── funcscanner_trim.rs          # trim lowering (~480行)
```

## 技術的変更:
- **型定義統一**: JoinFuncId, JoinInst, JoinModule 等を mod.rs に集約
- **lowering 分離**: 3つの lowering 関数を個別モジュールに移動
- **後方互換性**: pub use で lowering 関数を re-export(既存コード影響なし)
- **削除**: src/mir/join_ir.rs (旧単一ファイル)

## テスト結果:
- **385 passed** (+1 from 384)
- **9 failed** (-1 from 10)
- **ビルド成功**: 0 errors, 18 warnings (変化なし)

## 効果:
- **保守性向上**: 1,336行 → 4ファイル(各300-500行)で可読性向上
- **モジュール境界明確化**: 型定義 vs lowering 実装の責務分離
- **将来の拡張容易**: 新 lowering 関数追加が簡単に

Phase 27.8 で実装した MIR 自動解析 lowering の基盤整備完了。

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 16:49:49 +09:00

10 KiB
Raw Blame History

LoopForm SSOT単一起点設計ート

目的

  • ループのPHI整形・前処理preheader Copy、header PHI seed、latch/continue 合流)を単一モジュールに集約してドリフトを防ぐ。
  • ビルダーDirect MIRとブリッジJSON v0 → MIRの双方で同一の規約と検証を通す。

SSOTSingle Source of Truth

  • 中心: src/mir/phi_core/loop_phi.rs
    • 型: IncompletePhi, VarSnapshot
    • API: prepare_loop_variables_with, seal_incomplete_phis_with, build_exit_phis_with
    • デバッグ: phi_core::common::debug_verify_phi_inputs(到達検証・重複検出)
  • Direct MIR既にSSOT使用
    • src/mir/loop_builder.rsLoopPhiOps を実装し、prepare/seal/exit を phi_core へ委譲。
    • 形状: preheader → header(φ) → body → latch → header|exitLoopForm準拠
  • JSON v0 Bridge段階移行→完了済みの範囲
    • header PHIseed/完成・exit PHI を LoopPhiOps アダプタ経由で SSOT API に委譲。
    • break/continue スナップショットは threadlocal stack で収集し、seal/build_exit に渡す。
    • 代表 parity カナリアoptinで Direct と Bridge の一致を検証。

規約(不変条件)

  • header の PHI 入力は「preheader 経由の定義済み値」と「latch/continue からの値」だけ。
  • preheader で Copy を先行挿入し、PHI 入力は Copy の出力を参照するUse-Before-Def回避
  • 1 predecessor なら直接 bindPHI省略、2つ以上で PHI を生成。
  • 検証は FailFast ではなく開発時 WARNdebug_assert)だが、将来 Core 側で整形に移管予定。

4箱構成Phase 26-F 系の整理)

  • LoopVarClassBoxloop_var_classifier.rs
    • 変数のスコープ分類専用箱だよ。Pinned / Carrier / BodyLocalExit / BodyLocalInternal を決めるだけで、PHI 発行はしない。
  • LoopExitLivenessBoxloop_exit_liveness.rs
    • ループ exit 直後で「実際に使われる可能性がある変数」を集める箱だよ。Phase 26-F 時点では live_at_exit は保守的近似で、将来 get_block_instructions() などを使った MIR スキャンに差し替える予定。
    • ExitLivenessProvider を実装していて、ExitPhiBuilder は Box を受け取る形にしたので、Legacy既定と MirScan 版の差し替えがそのまま出来る。
    • 環境変数 NYASH_EXIT_LIVE_ENABLE=1 で将来の実装を段階的に有効化、NYASH_EXIT_LIVENESS_TRACE=1 でトレースを出せるようにしてある。
  • BodyLocalPhiBuilderbody_local_phi_builder.rs
    • 上の 2つの箱の結果を統合する決定箱だよ。
      • class.needs_exit_phi() が truePinned / Carrier / BodyLocalExitのものは従来どおり exit PHI 候補。
      • それ以外でも、BodyLocalInternal かつ live_at_exit に含まれ、かつ is_available_in_all で全 pred 定義が確認できるものだけを安全側で救済候補にできるようにしてある(この救済ロジック自体は NYASH_EXIT_LIVE_ENABLE でガード)。
  • PhiInvariantsBoxphi_invariants.rs
    • 最後に「全 pred で定義されているか」「不正な incoming が無いか」を Fail-Fast でチェックする箱だよ。ここで落ちる場合は LoopForm/BodyLocal 側の構造バグとみなしている。

今後の移行

  • Bridge 側に LoopPhiOps 実装を追加し、prepare/seal/exit を直接呼ぶ。
  • ループ形状の生成をユーティリティ化builder/bridge 双方から共通呼び出し)。
  • ExitLivenessProvider は 26-G 以降で MIR 命令列スキャン版に差し替える予定(現状の MirScanExitLiveness は header/exit_snapshots の union 近似)。
  • Header/Exit φ については、Phase 26-H / 27.x で導入した JoinIR関数正規化IR側の LoopHeaderShape / LoopExitShapeloop_step / k_exit 引数に最終的に吸収し、Rust 側では LoopVarClass / LoopExitLiveness / BodyLocalPhi / PhiInvariants による前処理+検証だけを残す方針。

LoopForm v2 ケース表

Case loop 条件形 exit preds の構成 想定される PHI 入力の形 対応テスト 対応 .hako
A loop(i < n) header / body header fallthrough + break loop_conditional_reassign_exit_phi_header_and_break -
B loop(1 == 1) body のみ break のみ loop_constant_true_exit_phi_dominates apps/tests/minimal_ssa_skip_ws.hako
C loop(1 == 1) + body-local body のみ(一部の経路のみ定義) break のみBodyLocalInternal は除外) loop_body_local_exit_phi_body_only -
D loop(i < n) + continue header / body / continue_merge header + break + continue_merge loop_continue_merge_header_exit -

ケース説明

Case A: header+break標準パターン

  • 条件: loop(i < n) のような動的条件
  • 特徴: header→exit と body→exit の両方が CFG 上存在
  • exit PHI: header fallthrough + break 経路の両方を含む
  • 検証: loop_conditional_reassign_exit_phi_header_and_break

Case B: constant-true+break-onlyheader 除外パターン)

  • 条件: loop(1 == 1) のような定数 true
  • 特徴: exit pred は break 経路のみ(header→exit は無い)
  • exit PHI: break 経路のみheader は CFG predecessor でないため除外)
  • 検証: loop_constant_true_exit_phi_dominates + minimal_ssa_skip_ws.hako

Case C: body-local変数BodyLocalInternal 除外パターン)

  • 条件: body 内で宣言された変数が一部の exit 経路でのみ定義される
  • 特徴: 変数が全 exit predecessors で定義されていない
  • exit PHI: BodyLocalInternal 変数は除外PHI 生成しない)
  • 検証: loop_body_local_exit_phi_body_only

Case D: continue+breakcontinue_merge パターン)

  • 条件: continue 文を含むループ
  • 特徴: continue_merge → header → exit の経路あり
  • exit PHI: header + break + continue_merge の 3 系統
  • 検証: loop_continue_merge_header_exit

実装ファイル

ファイル 役割
src/mir/loop_builder.rs ループ構造生成・[LoopForm] コメント付き
src/mir/phi_core/loop_snapshot_merge.rs Case A/B 分岐ロジック
src/mir/phi_core/exit_phi_builder.rs Exit PHI 生成・Phantom block 除外
src/tests/mir_loopform_conditional_reassign.rs 4 ケース全てのテスト([LoopForm-Test] タグ付き)

Phase 26-H 以降: PHI/Loop 箱 → JoinIR 移行対応表

JoinIR関数正規化IRの導入により、φ ードが関数引数に、merge ブロックが join 関数に変換される。各箱の将来的な扱いを整理する。

現在の箱 ファイル Phase 将来的な扱い 理由
HeaderPhiBuilder header_phi_builder.rs 26-C 🔄 JoinIR に吸収 header φ → loop_step 関数の引数に変換
ExitPhiBuilder exit_phi_builder.rs 26-D 🔄 JoinIR に吸収 exit φ → k_exit 継続の引数に変換
LoopSnapshotManager loop_snapshot_manager.rs 26-C 🔄 JoinIR に吸収 スナップショット → 関数呼び出し時の引数に統合
LoopSnapshotMerge loop_snapshot_merge.rs 26-C 🔄 JoinIR に吸収 スナップショット合流 → 関数呼び出しで自然に表現
PhiInputCollector phi_input_collector.rs 26-B 🔄 JoinIR に吸収 φ 入力収集 → 関数引数決定ロジックに統合
IfBodyLocalMerge if_body_local_merge.rs 26-F-2 🔄 JoinIR に吸収 if merge φ → join 関数の引数に変換
PhiBuilderBox phi_builder_box.rs 26-E 削除候補 統一箱は JoinIR 生成で不要に
IfPhi if_phi.rs Phase 1 削除候補 if φ → join 関数に完全統合
LoopPhi loop_phi.rs Phase 1 削除候補 loop φ → 関数引数に完全統合
LoopFormBuilder loopform_builder.rs Phase 1 削除候補 JoinIR 生成ロジックに置き換え
LoopVarClassifier loop_var_classifier.rs Option C LoopForm 前段として残す 変数分類は MIR → JoinIR 変換の前処理として必要
LoopExitLiveness loop_exit_liveness.rs 26-F-4 LoopForm 前段として残す exit 後使用変数は k_exit 引数決定に必要
BodyLocalPhiBuilder body_local_phi_builder.rs 26-B LoopForm 前段として残す 前処理段階での統合決定ロジック
LocalScopeInspector local_scope_inspector.rs Option C LoopForm 前段として残す スコープ分析は前処理として必要
PhiInvariants phi_invariants.rs 26-F-3 検証用として残す JoinIR 変換前後の検証に有用
Common common.rs Phase 1 ユーティリティとして残す 共通ヘルパー関数群
Conservative conservative.rs Phase 1 フォールバックとして残す 保守的 PHI 生成(レガシー互換)

記号説明

  • 🔄 JoinIR に吸収: φ ノード → 関数引数変換により不要になる
  • 削除候補: JoinIR 生成ロジックで完全に置き換えられる
  • 残す: MIR → JoinIR 変換の前処理/検証として必要

移行戦略Phase 27 以降)

  1. Phase 27: 一般化 MIR → JoinIR 変換実装
    • lower_min_loop_to_joinir の一般化
    • LoopVarClassifier / LoopExitLiveness を前処理として利用
  2. Phase 28: JoinIR 実行器実装VM / LLVM
    • 関数呼び出し/継続ベースの実行
  3. Phase 29: レガシー PHI 箱の段階的削除
    • HeaderPhiBuilder → JoinIR 引数生成に置き換え
    • ExitPhiBuilder → JoinIR 継続引数に置き換え
    • PhiBuilderBox / IfPhi / LoopPhi の完全削除

関連

  • src/mir/loop_builder.rs
  • src/runner/json_v0_bridge/lowering/loop_.rs
  • src/mir/phi_core/common.rs
  • src/mir/phi_core/loop_snapshot_merge.rs
  • src/mir/phi_core/exit_phi_builder.rs
  • src/tests/mir_loopform_conditional_reassign.rs
  • Phase 26-H: src/mir/join_ir.rs - JoinIR 型定義・自動変換実装