feat(joinir): Phase 259 P0 complete - Pattern8 final fixes + docs (pre-block-params migration)

Phase 259 P0: Pattern8 (BoolPredicateScan) 完全完了
is_integer/1 を Pattern8 で受理し、VM/LLVM EXE 両方で動作確認完了。
次の大工事(block-parameterized CFG への移行)前のマイルストーンとして記録。

## Key Fixes Applied

1. **skipped_entry_redirects** (instruction_rewriter.rs)
   - k_exit のスキップ時、entry block 参照を exit_block_id へリダイレクト
   - BasicBlockId not found エラーを根治

2. **loop_var_name** (pattern8_scan_bool_predicate.rs)
   - merge_entry_block 選択に使用(`Some(parts.loop_var.clone())`)
   - 未設定時の誤った entry block 選択を修正

3. **loop_invariants** (pattern8_scan_bool_predicate.rs)
   - PHI-free 不変量パラメータ(`[(me, me_host), (s, s_host)]`)
   - loop_var_name 設定時、BoundaryInjector が join_inputs Copy を全スキップするため必要
   - Pattern6 と同じ設計(header PHI で不変量を保持)

4. **expr_result** (pattern8_scan_bool_predicate.rs)
   - k_exit からの返り値を明示設定(`Some(join_exit_value)`)
   - Pattern7 style(推測ではなく明示)

5. **Smoke test scripts**
   - set +e パターンで exit code 7 をキャプチャ
   - LLVM EXE スクリプトにコメント追加(tools/build_llvm.sh 経由の明記)

## Contract Documentation

- join-explicit-cfg-construction.md に Pattern8 契約の具体例を追加
  - "pattern増でも推測増にしない" の実例として記録
  - loop_var_name / loop_invariants / expr_result / jump_args_layout の契約を明示
- 20-Decisions.md に正規化(Semantic/Plumbing)の分離方針を追記
- DOCS_LAYOUT.md に重要ドキュメントへの参照を追加

## Test Results

-  VM smoke test: `[PASS] phase259_p0_is_integer_vm` (exit 7)
-  LLVM EXE: tools/build_llvm.sh 経由で exit 7 確認
-  --verify: PASS

## Next FAIL (Phase 260+)

- Function: `Main.main/0` in `apps/examples/json_lint/main.hako`
- Error: `[cf_loop/pattern2] Failed to extract break condition from loop body`
- Pattern: Nested loop(外側 loop + 内側 loop with break)

🚀 次の大工事: block-parameterized CFG への移行を開始します。

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 03:21:22 +09:00
parent a767f0f3a9
commit 4496b6243d
9 changed files with 253 additions and 19 deletions

View File

@ -27,6 +27,45 @@ Related:
- `jump_args` が IR の外側メタ扱いだと、DCE/最適化が “use” を見落としやすい
- spans が並行 Vec だと、パスが 1 箇所でも取りこぼすと SPAN MISMATCH になる
## 方針の核Phase 259+
“正規化normalized” を 2 つに分けて SSOT を縮退させる:
1. **Semantic Normalization意味SSOT**
- terminator 語彙を固定し、意味の揺れを禁止する
- 例: `cond 付き Jump` を **正規形から禁止**し、`Branch` に落とす
2. **Plumbing Normalization配線SSOT**
- edge-args / CFG successor / spans など「壊れやすい配線」を IR 構造に閉じ込める
- 目標: “忘れると壊れるメタ” を減らし、変換を写像に縮退させる
これにより、パターン追加が「意味SSOTに従う局所変更」になり、merge/optimizer 側の推測や補正が増殖しにくくなる。
### 具体例: Pattern8 契約Phase 259 P0
Pattern8BoolPredicateScanの実装で明示した契約要素"pattern増でも推測増にしない"の実例):
- **`loop_var_name`**: merge_entry_block 選択に使用(`Some(parts.loop_var.clone())`
- 未設定だと誤った entry block が選ばれる
- **`loop_invariants`**: PHI-free 不変量パラメータ(`[(me, me_host), (s, s_host)]`
- `loop_var_name` 設定時、BoundaryInjector が ALL join_inputs Copy をスキップするため必要
- 不変量は header PHI で持つPattern6 と同じ設計)
- **`expr_result`**: k_exit からの返り値を明示(`Some(join_exit_value)`
- Pattern7 style推測ではなく明示設定
- **`jump_args_layout`**: ExprResultPlusCarrierscarriers=0
- Pattern8 は carriers なし、expr_result のみ
- **`exit_bindings`**: Empty
- carriers なしなので binding も不要
これらを「boundary builder で明示」することで、merge 側の推測を完全に排除。
## 最小の箱Box構成小さく強く
- `NormalizeBox`意味SSOT: Structured → Normalized、terminator 語彙の固定、Fail-Fast verify
- `AbiBox`(役割/順序SSOT: `JoinAbi`sig/roles/special/aliasで暗黙 ABI を封印し、pack/unpack を一箇所に集約
- `EdgeArgsPlumbingBox`配線SSOT: edge-args を terminator operand に寄せる段階導入、CFG/spans の同期点を一本化
増やす基準: 同じ不変条件を 2 箇所以上で守り始めたら箱を追加し、参照点を 1 箇所に縮退させる。
## 移行戦略(段階導入 / Strangler
原則:
@ -38,7 +77,7 @@ Related:
狙い: 推測をなくし、順序/役割の SSOT を 1 箇所へ寄せる。
- boundary に `jump_args_layout` のような **layout SSOT** を持たせ、collector/rewriter が推測しない
- `JoinInst::Jump` を terminator 語彙として正規化cond 付きは `Branch` へ寄せる)
- terminator 語彙を固定し、`cond 付き Jump` `Branch` へ寄せる(正規形から禁止
- continuation の識別は **ID SSOT**String は debug/serialize のみに縮退)
受け入れ:
@ -49,7 +88,11 @@ Related:
狙い: `jump_args` を “意味データ” として IR に埋め込み、DCE/CFG が自然に追える形へ収束する。
- `jump_args` を BasicBlock メタから terminator operand へ寄せる(段階導入: 互換フィールド併存→移行)
- `jump_args` を BasicBlock メタから terminator operand へ寄せる(段階導入: 互換フィールド併存→移行→削除
- “参照 API” は Branch を含むので **複数 edge を前提**にする(単発 `edge_args()` は曖昧になりやすい)
- 例: `block.out_edges()` / `block.edge_args_to(target)`
- terminator operand 側は `Vec<ValueId>` だけでなく **意味layout**も同梱する
- 例: `EdgeArgs { layout: JumpArgsLayout, values: Vec<ValueId> }`
- spans は `Vec<Spanned<_>>`API で不変条件を守る)
受け入れ:
@ -66,3 +109,15 @@ Related:
- 新パターン/新機能は「新しい Contract で記述できる場合のみ」追加する
- Contract の導入中は “機能追加より SSOT 固め” を優先する(泥沼デバッグの再発防止)
## API の作り方(迷子防止)
Strangler 期間は “読む側だけ寄せる” と取りこぼしが起きやすい。読む側/書く側を両方とも API に閉じ込める。
- **読む側(参照点の一本化)**
- `out_edges()` のように edge を列挙できる API を SSOT にする(`Jump`/`Branch` を同じ形で扱える)
- 旧メタ(`jump_args`)は API 内部でのみ参照し、外部は見ない
- **書く側terminator 更新の一本化)**
- `set_terminator(...)` のような入口に寄せ、successors/preds の同期漏れを構造で潰す
- **verifyFail-Fast**
- terminator から計算した successors と、キャッシュ `block.successors` の一致をチェックして “同期漏れ” を即死させる