refactor(joinir): Phase 286 P1-P3 - Boundary contract context enrichment

- P1: Add alloc_join_param()/alloc_join_local() API to JoinValueSpace
  - Prevents future API misuse (thin wrappers with explicit "JoinIR" context)
  - Updated docs with footnote-style number references

- P2: Enrich error context with host_fn for better diagnostics
  - Added context: &str parameter to verify_boundary_contract_at_creation()
  - Error format now shows: [merge_joinir_mir_blocks host=<fn> ...]

- P3: Add join-side info to error context (continuation count + boundary summary)
  - Uses boundary.continuation_func_ids.len() for join=
  - Adds [conts=X exits=Y conds=Z] suffix with fixed key names
  - Enables faster debugging with log-searchable format

Error format: [merge_joinir_mir_blocks host=X join=Y [conts=A exits=B conds=C]]

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-25 02:15:40 +09:00
parent 48048425e5
commit 843d094598
8 changed files with 530 additions and 74 deletions

View File

@ -0,0 +1,212 @@
# JoinIR Plan/Frag SSOT Documentation
**Status**: Active (2025-12-25)
**Phase**: Phase 286 P0 (docs-only)
**Purpose**: Single Source of Truth for Plan/Frag responsibilities, prohibitions, and freeze points
---
## 目標
JoinIR line を Plan/Frag に吸収する前提で、**責務・禁止事項・凍結点freeze points**をSSOTとして1ページに固定する。
「2本コンパイラ根治VM/LLVM」の合流点が明文化されていること。
---
## 1. Scope / Non-goals
### 対象Scope
- JoinIR linePattern1-9における Plan → Frag → MIR merge の限定されたパイプライン
- Plan と Frag の境界における責務分離
- VM/LLVM 両コンパイラでの共通契約
### 対象外Non-goals
- MIR実行・VM/LLVM固有の最適化
- JoinIR外のloweringAST → MIR 全体ではなく、限定されたパイプラインのみ)
---
## 2. 用語Terms
| 用語 | 定義 |
|------|------|
| **JoinIR line** | (Pattern detection/Plan) → (Frag+Boundary) → (MIR merge) の限定されたパイプライン |
| **Plan** | パターン抽出・正規化フェーズDomainPlan → CorePlan |
| **Frag** | フラグメント生成・エミットフェーズJoinModule + JoinFragmentMeta |
| **Boundary** | JoinInlineBoundaryhost↔JoinIR 変数マッピング) |
| **ExitKind** | 継続の種類Return/Break/Continue/Kont |
| **Freeze point** | 以降変更が禁止される確定ポイント |
| **SSOT** | Single Source of Truth唯一の情報源 |
**注記**: JoinIR line は AST → MIR 全体ではなく、上記の限定された範囲を指すJoinIR外のloweringは含まない
---
## 3. 責務Responsibilities
### Planが決めること
Plan 段階では以下を決定する:
- **パターン識別**: どのパターンPattern1-9
- **制御フロー構造**: ループ/if/try/scan の構造
- **キャリア変数**: ループ間で保持される変数
- **継続構造**: k_exit / k_continue の境界
- **ValueId 割り当て**: JoinIRローカルのValueIdhost ValueId と衝突しない領域)
### Planが決めないこと
Plan 段階では以下を決定しないFrag側の責務
- **ホスト変数**: host inputs の ValueId はFrag側で決定
- **最終的な命令列**: Copy命令の注入はFrag側
- **PHI構造**: header/exit PHIの最終構造はFrag側
### Fragが保持すること
Frag 段階では以下を保持する:
- **JoinInlineBoundary**: host↔JoinIR の全マッピング情報
- **JoinFragmentMeta**: expr_result / exit_meta / continuation_funcs
- **terminator SSOT**: emit_frag() を唯一のterminator生成ポイント
### Fragが保持しないこと
Frag 段階では以下を保持しないPlan側の責務
- **パターン知識**: Pattern1-9 の違いを知らないCorePlanのみ処理
- **AST情報**: AST構造には直接アクセスしない
---
## 4. 禁止事項Prohibitions - 最重要)
| 禁止事項 | 理由 |
|----------|------|
| **Planでの実行** | Planは純粋なデータ構造 |
| **Planでの名前解決** | 名前解決はFrag側 |
| **Planでの最適化** | Planは構造のみ記述 |
| **Planでのルール実装** | ルールはFrag側で実装 |
| **Fragでのパターン識別** | パターン知識はPlan側 |
| **明示的でないValueIdマッピング** | 全てBoundary経由 |
| ** terminator の多様な生成ポイント** | emit_frag() SSOT |
| **freeze point 以降の変更** | 不変条件違反 |
### 診断専用の扱い
開発中のデバッグ出力・トレースログは、debugタグ付き既定OFFでのみ許可。
### 実装注記ValueId領域
現状の実装では JoinIR-local ValueId として 100-999 領域を使用しているが、これは実装詳細であり変更されうる。SSOT としての原則は「host ValueId と衝突しない領域を使う」である。
---
## 5. 凍結点Freeze Points
| Stage | 凍結されるもの | 以降禁止される操作 |
|-------|----------------|-------------------|
| **PlanFreeze** (V1-V9) | CorePlanの構造 | Plan構造の変更 |
| **BoundaryFreeze** | JoinInlineBoundaryの全フィールド | Boundaryマッピングの変更 |
| **ValueIdAllocate** | JoinIRローカルValueIdhostと非衝突領域 | 領域の再割り当て |
| **MergeComplete** | MIR block構造 | CFGの変更 |
---
## 6. 不変条件Invariants / Fail-Fast
### Plan段階の不変条件
- **V1**: 条件のValueIdは有効pre-generated
- **V2**: Exitの妥当性Returnは関数内、Break/Continueはループ内
- **V3**: Seqは非空
- **V4**: Ifのthen_plansは非空
- **V5**: ループはキャリアを1つ以上持つ
- **V6**: Frag entryはheader_bbを指す
- **V7**: block_effectsにheader_bbが含まれる
### Boundary段階の不変条件
- **B1**: join_inputsのValueIdはhost ValueIdと衝突しない領域^1
- JoinIR lowering では `alloc_join_param()` を使用すること(再発防止)
- **B2**: exit_bindingsは対応するexit PHIを持つ
- **C1**: 同じjoin_valueは同じhost_valueにマッピング
- **C2**: condition bindingのjoin_valueはhost ValueIdと衝突しない領域^1
- ConditionEnv 経由で割り当てられる
---
^1) 現状の実装では 100-999 の範囲を使用しているが、これは実装詳細であり将来の実装では変わりうる。
### Merge段階の不変条件debug_assertions
- **M1**: Header PHI dstは再定義されない
- **M2**: Exit PHI inputsは全て定義されている
- **M3**: terminator targetsは全て存在する
- **M4**: ValueIdは領域を守っている
### 破ったらどこで落とすか
- **Contract違反**: `contract_checks.rs` で早期return
- **Debug assert**: `debug_assert!` / `unreachable!` で即座に落とす
- **検証漏れ**: CIで `cfg(debug_assertions)` テストを実行
---
## 7. 2本コンパイラ根治の合流点
### 共通パスShared Path
```
AST → JoinIR Plan → JoinIR Frag → MIR Merge
```
共通パスでは以下が共有される:
- **Pattern Detection**: 同じパターン認識アルゴリズム
- **Boundary Construction**: 同じ JoinInlineBoundary 構造
- **MIR Merge**: 同じ merge_joinir_mir_blocks() ロジック
### 分岐点Divergence Point
```
MIR → [VM: MirInterpreter]
→ [LLVM: llvmlite/inkwell]
```
### 差分が許される場所
- **Method ID injection**: LLVM側のみ
- **Experimental features**: NYASH_JOINIR_VM_BRIDGE vs NYASH_JOINIR_LLVM_EXPERIMENT
- **Execution engines**: MirInterpreter vs llvmlite
### 差分が許されない場所
- **JoinIR structure**: 同じ構造でなければならない
- **PHI nodes**: 同じPHI構造でなければならない
- **ValueId mappings**: 同じマッピングでなければならない
---
## 8. デバッグ導線
詳細は CLAUDE.md へのリンク(重複させない):
- **NYASH_CLI_VERBOSE=1**: 一般的な詳細ログ
- **HAKO_JOINIR_DEBUG=1**: JoinIR ルーティング・ブロック割り当て
- **NYASH_TRACE_VARMAP=1**: variable_map トレースPHI接続デバッグ
- **JoinIR architecture**: docs/development/current/main/joinir-architecture-overview.md
---
## 関連ドキュメント
- **JoinIR Architecture Overview**: [joinir-architecture-overview.md](./joinir-architecture-overview.md) - 全体アーキテクチャのSSOT
- **JoinIR Design Map**: [joinir-design-map.md](./joinir-design-map.md) - 実装導線の地図
- **Phase 286 README**: [../phases/phase-286/README.md](../phases/phase-286/README.md) - フェーズ進捗
---
## 変更ログ
- **2025-12-25**: Phase 286 P0 - 初版作成docs-only

View File

@ -36,22 +36,61 @@ Phase 286 では JoinIR line を “第2の lowerer” として放置せず、*
## Scope提案
### P0docs-only
### P0docs-only✅ COMPLETE (2025-12-25)
- 「JoinIR line をどの粒度で吸収するか」を SSOT として決める
- 例: JoinIR は DomainPlan 生成の補助へ降格 / JoinIR→MIR merge を段階撤去
- “禁止事項” を明文化pattern 側への散布、二重 SSOT の再発)
**完了内容**:
- **SSOT ドキュメント作成**: `docs/development/current/main/design/joinir-plan-frag-ssot.md` を作成
- **8章構成で固定**:
1. Scope / Non-goals - 対象範囲の明確化
2. 用語Terms - JoinIR line, Plan, Frag, Boundary, ExitKind, Freeze point, SSOT の定義
3. 責務Responsibilities - Planが決めること・決めないこと / Fragが保持すること・保持しないこと
4. 禁止事項Prohibitions - Planでの実行・名前解決・最適化・ルール実装の禁止 等
5. 凍結点Freeze Points - PlanFreeze / BoundaryFreeze / ValueIdAllocate / MergeComplete
6. 不変条件Invariants / Fail-Fast - Plan段階(V1-V7) / Boundary段階(B1-B2, C1-C2) / Merge段階(M1-M4)
7. 2本コンパイラ根治の合流点 - 共通パス・分岐点・差分許容場所/非許容場所
8. デバッグ導線 - NYASH_CLI_VERBOSE, HAKO_JOINIR_DEBUG, NYASH_TRACE_VARMAP 等
### P1investigation
**重要な設計決定**:
- JoinIR line を AST → MIR 全体ではなく、「(Pattern detection/Plan) → (Frag+Boundary) → (MIR merge) の限定されたパイプライン」として定義
- 禁止事項の「例外なし」表現を削除し、「診断専用の扱いdebugタグ付き・既定OFF」という運用ルールに変更
- ValueId 100-999 固定範囲を「host ValueId と衝突しない領域」という原則に変更(具体数値は実装詳細として注記)
- JoinIR line が持っている「本当は SSOT に寄せたい責務」を棚卸し
- return/break/continue の扱い
- exit phi / boundary の責務
- optimizer/type propagation の入り口
**成果物**:
- `docs/development/current/main/design/joinir-plan-frag-ssot.md` (新規)
- コード変更なしdocs-only
### P1 (contract_checks 導入 + 実バグ修正) ✅ COMPLETE (2025-12-25)
**完了内容**:
- **contract_checks.rs に検証関数追加**: `verify_boundary_contract_at_creation()`
- B1検証: join_inputs が Param 領域にあること
- C2検証: condition_bindings が Param 領域にあること
- **merge/mod.rs に検証呼び出し追加**: merge開始時にFail-Fast検証
- **実バグ3件修正**: Pattern2/4/5 で `alloc_local()` を誤って使っていた箇所を `alloc_param()` に修正
**成果物**:
- `src/mir/builder/control_flow/joinir/merge/contract_checks.rs` (変更)
- `src/mir/builder/control_flow/joinir/merge/mod.rs` (変更)
- `src/mir/join_ir/lowering/loop_with_break_minimal.rs` (変更)
- `src/mir/join_ir/lowering/loop_with_continue_minimal.rs` (変更)
- `src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs` (変更)
**発見された問題**:
- 各 pattern の lowering で関数パラメータに `alloc_local()` を使っていた(本来は `alloc_param()`
- これにより join_inputs に Local ValueId (1000+) が混入し、検証エラーになっていた
**改善の示唆Post-P1 Polish 実施済み)**:
- API名の曖昧さが誤用を招いていたため、`alloc_join_param()` / `alloc_join_local()` の導入が検討されている
- エラーメッセージの「原因特定」強化として context パラメータの追加が検討されている
**Post-P1 Polish 追加** (2025-12-25):
- **新API追加**: `JoinValueSpace::alloc_join_param()` / `alloc_join_local()` (薄いラッパー)
- **エラーメッセージ改善**: `verify_boundary_contract_at_creation()``context: &str` パラメータ追加
- **docs反映**: SSOTドキュメントに脚注形式で数値記載、新API使用の明記
### P2PoC
- 代表 1 パターン(例: Pattern4JoinIR 生成 → CorePlan/Frag に変換する PoC
- 代表 1 パターン(例: Pattern4"JoinIR 生成 → CorePlan/Frag" に変換する PoC
- 目的: merge を通さずに `emit_frag()` 経由で終端が生成できることの証明
## AcceptanceP0