refactor(joinir): make jump_args layout explicit (Phase 256)
This commit is contained in:
@ -48,7 +48,7 @@
|
||||
## 2025-12-19:Phase 256(StringUtils.split/2 可変 step ループ)🔜
|
||||
|
||||
- Phase 256 README: `docs/development/current/main/phases/phase-256/README.md`
|
||||
- Current first FAIL: `StringUtils.split/2`(MIR verification: “Value defined multiple times” + SSA undef)
|
||||
- Current first FAIL: `json_lint_vm`(Pattern2 break cond: `this.is_whitespace(...)` needs `current_static_box_name`)
|
||||
- 状況:
|
||||
- `MirInstruction::Select` の導入は完了、Pattern6(index_of)は PASS 維持。
|
||||
- `ValueId(57)` undefined は根治(原因は `const_1` 未初期化)。
|
||||
@ -56,6 +56,7 @@
|
||||
- P1.8で ExitLine/jump_args の余剰許容と関数名マッピングを整流。
|
||||
- P1.9で `JoinInst::Jump` を tail call として bridge に落とし、`jump_args` を SSOT として保持。
|
||||
- P1.10で DCE が `jump_args` を used 扱いし、`instruction_spans` を同期(SPAN MISMATCH 根治)。
|
||||
- P1.11で ExitArgsCollector の expr_result slot 判定を明確化し、split が `--verify` / integration smoke まで PASS。
|
||||
- P1.5-DBG: boundary entry params の契約チェックを追加(VM実行前 fail-fast)。
|
||||
- P1.6: 契約チェックの薄い集約 `run_all_pipeline_checks()` を導入(pipeline の責務を縮退)。
|
||||
|
||||
|
||||
@ -10,6 +10,12 @@
|
||||
- Call/MethodCall は effects + typing の論点が増えるため、pure とは分離して Phase 141+ で段階投入する。
|
||||
- out-of-scope は `Ok(None)` で既存経路へフォールバックし、既定挙動不変を維持する(strict は “close-but-unsupported” のみ fail-fast)。
|
||||
|
||||
2025‑12‑20
|
||||
- Phase 256 の詰まり(Jump/continuation/params/jump_args)を「暗黙 ABI の分裂」と捉え、契約を `JoinIR ABI/Contract` として明文化していく(SSOT を 1 箇所へ集約)。
|
||||
- continuation の識別は ID を SSOT(String は debug/serialize 用)とし、`join_func_N` の legacy は alias で隔離する。
|
||||
- `jump_args` は意味論の SSOT なので、最終的には MIR terminator operand に統合して DCE/CFG から自然に追える形へ収束させる(Phase 256 を緑に戻した後に段階導入)。
|
||||
- 上記の収束先(north star)を “Join-Explicit CFG Construction” と命名し、段階移行(案1→案2→必要なら案3)で進める。
|
||||
|
||||
2025‑09‑08
|
||||
- ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。
|
||||
- Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
## 現役の設計図(入口)
|
||||
|
||||
- JoinIR の地図(navigation SSOT): `docs/development/current/main/design/joinir-design-map.md`
|
||||
- Join-Explicit CFG Construction(north star): `docs/development/current/main/design/join-explicit-cfg-construction.md`
|
||||
- Loop Canonicalizer(設計 SSOT): `docs/development/current/main/design/loop-canonicalizer.md`
|
||||
- ControlTree / StepTree(構造SSOT): `docs/development/current/main/design/control-tree.md`
|
||||
- Normalized ExprLowerer(式の一般化 SSOT): `docs/development/current/main/design/normalized-expr-lowering.md`
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
# Join-Explicit CFG Construction
|
||||
|
||||
Status: SSOT(design goal)
|
||||
Scope: JoinIR→MIR の「暗黙 ABI」を消し、Join を第一級に扱う CFG へ収束させる北極星(north star)。
|
||||
Related:
|
||||
- Navigation SSOT: `docs/development/current/main/design/joinir-design-map.md`
|
||||
- Investigation (Phase 256): `docs/development/current/main/investigations/phase-256-joinir-contract-questions.md`
|
||||
- Decisions: `docs/development/current/main/20-Decisions.md`
|
||||
|
||||
## Goal(最終形)
|
||||
|
||||
“Join-Explicit CFG Construction” を目指す。
|
||||
|
||||
- `Jump/continuation/params/edge-args` を **第一級(explicit)**として扱う
|
||||
- JoinIR↔MIR 間の **暗黙 ABI(順序/長さ/名前/役割)** をなくす(SSOT を 1 箇所に封印)
|
||||
- 変換は「意味の解釈」ではなく「写像(mapping)」に縮退する
|
||||
|
||||
## Non-Goals(いまはやらない)
|
||||
|
||||
- JoinIR を即座に削除する(まずは ABI/Contract で SSOT を固める)
|
||||
- PHI を全面廃止して block params に置換する一括リファクタ(段階導入)
|
||||
|
||||
## 現状の問題(Phase 256 で露出した型)
|
||||
|
||||
- `jump_args` / `exit_bindings` / `entry.params` / `boundary.join_inputs` が “だいたい同じ順序” を前提にしており、ズレると SSA/dominance が破綻する
|
||||
- `expr_result` と LoopState carrier が同一 ValueId になり得るが、legacy “expr_result slot” 推測で offset がずれて誤配線になる
|
||||
- `jump_args` が IR の外側メタ扱いだと、DCE/最適化が “use” を見落としやすい
|
||||
- spans が並行 Vec だと、パスが 1 箇所でも取りこぼすと SPAN MISMATCH になる
|
||||
|
||||
## 移行戦略(段階導入 / Strangler)
|
||||
|
||||
原則:
|
||||
- **移行を先に固定**し、機能追加は「新契約に乗るものだけ」併走する(旧経路に新機能を足さない)
|
||||
- 既定挙動を変えない(必要なら dev-only の診断ガードで観測)
|
||||
|
||||
### Stage 1(短期): JoinIR を “ABI/Contract 付き Normalized SSOT” にする
|
||||
|
||||
狙い: 推測をなくし、順序/役割の SSOT を 1 箇所へ寄せる。
|
||||
|
||||
- boundary に `jump_args_layout` のような **layout SSOT** を持たせ、collector/rewriter が推測しない
|
||||
- `JoinInst::Jump` を terminator 語彙として正規化(cond 付きは `Branch` へ寄せる)
|
||||
- continuation の識別は **ID SSOT**(String は debug/serialize のみに縮退)
|
||||
|
||||
受け入れ:
|
||||
- `--verify` が PASS(SSA/dominance/PHI/ExitLine の契約違反が消える)
|
||||
- 直撃回帰テスト(`expr_result == carrier` 等)が固定される
|
||||
|
||||
### Stage 2(中期): MIR を “edge-args を terminator operand に持つ CFG” に寄せる
|
||||
|
||||
狙い: `jump_args` を “意味データ” として IR に埋め込み、DCE/CFG が自然に追える形へ収束する。
|
||||
|
||||
- `jump_args` を BasicBlock 外メタから terminator operand へ寄せる(段階導入: 互換フィールド併存→移行)
|
||||
- spans は `Vec<Spanned<_>>` へ(API で不変条件を守る)
|
||||
|
||||
受け入れ:
|
||||
- `jump_args` 由来の use が最適化で消えない(テストで固定)
|
||||
- SPAN MISMATCH が構造的に起きない
|
||||
|
||||
### Stage 3(長期): JoinIR と MIR の境界を薄くし、必要なら JoinIR を builder へ降格
|
||||
|
||||
狙い: “bridge/merge が意味解釈する余地” を最小化し、一本の CFG 語彙に収束させる。
|
||||
|
||||
- JoinIR を SSOT IR として残すか、builder DSL として降格するかは、この段階で再判断する
|
||||
|
||||
## 実務ルール(Phase 中の運用)
|
||||
|
||||
- 新パターン/新機能は「新しい Contract で記述できる場合のみ」追加する
|
||||
- Contract の導入中は “機能追加より SSOT 固め” を優先する(泥沼デバッグの再発防止)
|
||||
@ -85,6 +85,15 @@ flowchart LR
|
||||
|
||||
---
|
||||
|
||||
## North Star: Join-Explicit CFG Construction
|
||||
|
||||
JoinIR/MIR 間に生える “暗黙 ABI(順序/長さ/名前/役割)” を減らし、Join を第一級として扱う CFG へ収束させる。
|
||||
|
||||
SSOT(設計目標):
|
||||
- `docs/development/current/main/design/join-explicit-cfg-construction.md`
|
||||
|
||||
---
|
||||
|
||||
## “箱”の責務マップ(担当境界)
|
||||
|
||||
| 領域 | 役割(何を決めるか) | 主な入口/箱(SSOT寄り) | 主な出力 | Fail-Fast(典型) |
|
||||
|
||||
@ -167,3 +167,93 @@ pub struct BasicBlock {
|
||||
- ExitLine: `jump_args` の長さ契約ミスマッチ(carriers と invariants の混在)
|
||||
- `JoinInst::Jump` が bridge で “Return 化” され、continuation の意味が落ちる疑い
|
||||
- DCE が `jump_args` 由来の use を落とし、Copy が消えて SSA/dominance が崩れる
|
||||
|
||||
---
|
||||
|
||||
## ChatGPT Pro からの設計回答(要約)
|
||||
|
||||
診断(短く):
|
||||
- JoinIR/MIR 間に「暗黙 ABI(呼び出し規約)」が生えており、SSOT が分裂している
|
||||
- `Jump/cont/params/jump_args` が層を跨ぐたびに意味が揺れて、SSA/dominance/DCE が壊れやすい
|
||||
|
||||
命名:
|
||||
- この収束先(north star)を **Join-Explicit CFG Construction** と呼ぶ
|
||||
|
||||
提案の大枠(3案):
|
||||
|
||||
### 案1: JoinIR ABI / Contract モジュール(推奨)
|
||||
|
||||
狙い:
|
||||
- いま暗黙のまま散っている契約(順序/長さ/名前/役割)を “ABI オブジェクト” として 1 箇所に封印する
|
||||
- `Vec` の順序契約に魂を預けない(pack/unpack を ABI 経由にする)
|
||||
|
||||
最小イメージ(雰囲気):
|
||||
|
||||
```rust
|
||||
pub struct JoinAbi {
|
||||
pub cont_sigs: Vec<ContSig>, // continuation signature SSOT
|
||||
pub special: SpecialConts, // main/loop_step/k_exit
|
||||
pub legacy_alias: AliasTable, // join_func_2 -> k_exit 等
|
||||
}
|
||||
|
||||
pub struct ContSig {
|
||||
pub params: Vec<Param>,
|
||||
}
|
||||
|
||||
pub enum ParamRole {
|
||||
Carrier,
|
||||
Invariant,
|
||||
Result,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Jump の正規形(Normalized JoinIR):
|
||||
- `Jump` は cond を持たず、必ず終端
|
||||
- cond 付きは `Branch { then_cont+args, else_cont+args }` に寄せる
|
||||
|
||||
### 案2: MIR を “ブロック引数 SSA” に昇格(強力)
|
||||
|
||||
狙い:
|
||||
- JoinIR の cont/args と MIR の block/jump_args を同型化し、bridge/merge の解釈余地を消す
|
||||
|
||||
即効の 2 点(最優先):
|
||||
- `jump_args` を BasicBlock 外メタではなく terminator に埋め込む(use-def / DCE が自然に追える)
|
||||
- spans を並行 Vec から `Vec<Spanned<_>>` へ(SPAN MISMATCH を構造で防ぐ)
|
||||
|
||||
### 案3: JoinIR をやめて CPS CFG 一本化(最終収束案)
|
||||
|
||||
狙い:
|
||||
- SSOT を 1 個にする(JoinIR/MIR の “橋” を消す)
|
||||
- ただし移行は重いので、案2 の延長として収束させるのが現実的
|
||||
|
||||
---
|
||||
|
||||
## 推奨(この repo の制約込み)
|
||||
|
||||
Phase 256 の “今日の詰まり” に効く順で:
|
||||
|
||||
1) **案1(JoinIR ABI/Contract)を設計 SSOT として採用**
|
||||
- まず “順序契約を殺す” のが最大のデバッグ短縮になる
|
||||
2) **案2 のうち即効ポイントを段階導入**
|
||||
- `jump_args` の SSOT は “terminator operand” に移す(大工事なので Phase 256 を緑に戻した後に着手が安全)
|
||||
- spans の `Spanned` 化も同様に段階導入(いまは pass 側で不変条件をテストで固定)
|
||||
|
||||
ここまでで、JoinIR を増やさずに「暗黙 ABI を明文化」できる。
|
||||
|
||||
---
|
||||
|
||||
## 次に設計として決めたいこと(Decision 候補)
|
||||
|
||||
- `JoinInst::Jump` の SSOT は “tail call 等価” ではなく、Normalized JoinIR の terminator 語彙として固定する
|
||||
- continuation の識別は **ID SSOT**(String は debug/serialize 用に限る)
|
||||
- `jump_args` を SSOT にするなら、最終的に MIR terminator に埋め込む(DCE/CFG の整合性が自然になる)
|
||||
|
||||
---
|
||||
|
||||
## Phase 256 実装から得た追加の教訓(SSOT)
|
||||
|
||||
- `expr_result` と LoopState carrier が同一 ValueId になるケースが現実に起きる(例: ループ式の返り値が `result`)。
|
||||
このとき “legacy expr_result slot(jump_args[0])” を機械的に仮定すると、offset がずれて ExitLine の配線が崩れる。
|
||||
- 対策として `ExitArgsCollector` には “slot があるかどうか” を推測させず、呼び出し側が `expect_expr_result_slot`
|
||||
を明示して渡すのが安全(Fail-Fast + 構造)。
|
||||
|
||||
@ -5,16 +5,19 @@ Scope: Loop pattern recognition for split/tokenization operations
|
||||
Related:
|
||||
- Phase 255 完了(loop_invariants 導入、Pattern 6 完成)
|
||||
- Phase 254 完了(Pattern 6 index_of 実装)
|
||||
- North star(設計目標): `docs/development/current/main/design/join-explicit-cfg-construction.md`
|
||||
|
||||
## Current Status (SSOT)
|
||||
|
||||
- Current first FAIL: `StringUtils.split/2`(MIR verification: “Value defined multiple times” + SSA undef)
|
||||
- Pattern6 は PASS 維持
|
||||
- Current first FAIL: `json_lint_vm`(Pattern2 break cond: `this.is_whitespace(...)` needs `current_static_box_name`)
|
||||
- `StringUtils.split/2` は VM `--verify` / smoke まで PASS
|
||||
- Pattern6(index_of)は PASS 維持
|
||||
- 直近の完了:
|
||||
- P1.10: DCE が `jump_args` 参照を保持し、`instruction_spans` と同期するよう修正(回帰テスト追加)
|
||||
- P1.7: SSA undef(`%49/%67`)根治(continuation 関数名の SSOT 不一致)
|
||||
- P1.6: pipeline contract checks を `run_all_pipeline_checks()` に集約
|
||||
- 次の作業: P1.11(merge 側の ExitLine/PHI/dominance を `--verify` で緑に戻す)
|
||||
- 次の作業: Pattern2 の static box context を break condition lowering に渡す(次フェーズ)
|
||||
- 設計メモ(ChatGPT Pro 相談まとめ): `docs/development/current/main/investigations/phase-256-joinir-contract-questions.md`
|
||||
|
||||
---
|
||||
|
||||
@ -414,6 +417,44 @@ Option A(Pattern 7 新設)を推奨。
|
||||
- `instruction_spans` と `instructions` の同期不変条件を維持(SPAN MISMATCH 根治)
|
||||
- 回帰テストを追加(`test_dce_keeps_jump_args_values`, `test_dce_syncs_instruction_spans`)
|
||||
|
||||
---
|
||||
|
||||
## 進捗(P1.11)
|
||||
|
||||
### P1.11: ExitArgsCollector の expr_result slot 判定を明確化(完了)
|
||||
|
||||
症状(split の誤動作):
|
||||
- runtime 側で `start` と `result` が入れ替わり、ExitLine の配線が崩れる
|
||||
- 結果として VM 実行で型エラー(例: `ArrayBox <= 5`)に到達
|
||||
|
||||
根本原因(SSOT):
|
||||
- ExitArgsCollector が `jump_args[0]` を “expr_result slot” とみなす条件が粗く、
|
||||
`expr_result` が exit_bindings の LoopState carrier と同一 ValueId(例: `result`)のケースでも offset=1 側に寄っていた
|
||||
|
||||
修正(方針):
|
||||
- `expect_expr_result_slot` を明示的に渡し、かつ
|
||||
`expr_result` が exit_bindings の LoopState carriers に含まれる場合は `expect_expr_result_slot=false` とする
|
||||
- `exit_args_collector.rs` 側は `expect_expr_result_slot` の意味に合わせて整理し、
|
||||
「末尾の invariants は無視」の仕様は維持する
|
||||
|
||||
受け入れ(確認):
|
||||
- `./target/release/hakorune --backend vm --verify apps/tests/phase256_p0_split_min.hako` PASS
|
||||
- `./target/release/hakorune --backend vm apps/tests/phase256_p0_split_min.hako` が期待 RC(`3`)で終了
|
||||
|
||||
---
|
||||
|
||||
## 進捗(P1.12)
|
||||
|
||||
### P1.12: integration smoke scripts の終了コード取得を安定化(完了)
|
||||
|
||||
変更(要旨):
|
||||
- `set -e` のまま “対象コマンドだけ” を `set +e` でラップし、非0終了でも exit code を取得できるようにする
|
||||
- `HAKORUNE_BIN` の既定値を追加し、手動実行の再現性を上げる
|
||||
|
||||
受け入れ(確認):
|
||||
- `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh` PASS(exit=3)
|
||||
- `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh` PASS(exit=1)
|
||||
|
||||
### リファクタリング方針(P1.6候補 / 先送り推奨)
|
||||
|
||||
現時点(split がまだ FAIL)では、箱化のための箱化で複雑さが増えやすいので、以下を推奨する:
|
||||
|
||||
Reference in New Issue
Block a user