Files
hakorune/docs/development/current/main/investigations/phase-256-joinir-contract-questions.md

260 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 256: JoinIR Contract Questions (for ChatGPT Pro)
目的: Phase 256 の詰まりJump/continuation/params/jump_args の暗黙契約)を、設計として固めるための相談メモ。
---
## Q1. SSOT をどこに置くべき?
JoinIR の「意味論 SSOT」をどこに置くべきか。
- A) Structured JoinIR を SSOT として維持し、bridge/merge が意味解釈する
- B) Normalized JoinIR を SSOT とし、Structured→Normalized の正規化箱を必須化する
判断材料として、現在の層の境界と責務:
- Pattern lowererStructured JoinIR 生成)
- `join_ir_vm_bridge`JoinIR→MIR 変換)
- mergeMIR inline + PHI/ExitLine wiring
---
## Q2. `JoinInst::Jump` の正規形(不変条件)
現状の詰まりは `Jump` が層を跨ぐときに「tail call 等価」になったり「Return 化」になったりして、continuation が失われる点にある。
相談したい:
- `JoinInst::Jump { cont, args, cond }` を SSOT 的にどう定義するべきか?
- cond 付き Jump は JoinIR 語彙として残すべきか?それとも IfMerge に寄せるべきか?
最小コードJoinIR 命令):
```rust
// src/mir/join_ir/mod.rs
pub enum JoinInst {
// ...
Jump {
cont: JoinContId,
args: Vec<VarId>,
cond: Option<VarId>,
},
Call {
func: JoinFuncId,
args: Vec<VarId>,
dst: Option<VarId>,
k_next: Option<JoinContId>,
},
Ret { value: Option<VarId> },
// ...
}
```
---
## Q3. boundary/params/jump_args の順序契約をどこで固定する?
Phase 256 では、次の対応関係が暗黙で、崩れると SSA undef / PHI wiring fail-fast になりやすい。
- `JoinInlineBoundary.join_inputs``JoinModule.entry.params`
- `exit_bindings` ↔ ExitLine の carrier PHI reconnect
- `jump_args`tail call args metadata↔ ExitLine の latch incoming 復元
最小コードboundary:
```rust
// src/mir/join_ir/lowering/inline_boundary.rs
pub struct JoinInlineBoundary {
pub join_inputs: Vec<ValueId>,
pub host_inputs: Vec<ValueId>,
pub loop_invariants: Vec<(String, ValueId)>,
pub exit_bindings: Vec<LoopExitBinding>,
pub expr_result: Option<ValueId>,
pub loop_var_name: Option<String>,
pub continuation_func_ids: std::collections::BTreeSet<String>,
// ...
}
```
契約の fail-fast は現在ここで行っている:
```rust
// src/mir/builder/control_flow/joinir/merge/contract_checks.rs
pub(in crate::mir::builder::control_flow::joinir) fn run_all_pipeline_checks(
join_module: &crate::mir::join_ir::JoinModule,
boundary: &JoinInlineBoundary,
) -> Result<(), String> {
verify_boundary_entry_params(join_module, boundary)?;
// ...
Ok(())
}
```
相談したい:
- この順序契約は「boundary」「normalizer」「bridge」「merge」のどの層が SSOT になるべきか?
- fail-fast の責務をどこに置くべきか(今は conversion_pipeline 直前)?
---
## Q4. continuation の識別: `JoinFuncId` vs `String`(関数名)
Phase 256 P1.7 で「continuation 関数が merge で見つからず SSA undef」になった。
原因は bridge 側と merge 側の “関数名 SSOT” 不一致。
現状の暫定 SSOT は `canonical_names`:
```rust
// src/mir/join_ir/lowering/canonical_names.rs
pub const K_EXIT: &str = "k_exit";
pub const K_EXIT_LEGACY: &str = "join_func_2";
pub const LOOP_STEP: &str = "loop_step";
pub const MAIN: &str = "main";
```
相談したい:
- continuation を `JoinFuncId` で保持し、bridge で 1 回だけ名前解決するべきか?
- それとも `String` を SSOT にして “MirModule key” と一致させ続けるべきか?
- 併存するなら、変換境界(片方→片方)をどこに置くべきか?
---
## Q5. 正規化 shadow`join_func_N`)との共存戦略
normalized_shadow 側は `join_func_2` のような命名を使う箇所がある。
この legacy をいつ・どう統一するべきか(または統一しないなら境界をどう明文化するか)。
---
## Q6. `jump_args` は MIR のどの層の SSOT か?
観測:
- ExitLine/merge 側は `BasicBlock.jump_args` を「exit/carry 値の SSOT」として参照する。
- bridge 側で tail call / Jump を生成するときに `jump_args` を落とし忘れると、ExitLine が fallback 経路へ入りやすく、
SSA/dominance の破綻につながる。
最小コードMIR basic block:
```rust
// src/mir/mod.rs
pub struct BasicBlock {
pub instructions: Vec<MirInstruction>,
pub instruction_spans: Vec<Span>,
pub terminator: Option<MirInstruction>,
pub jump_args: Option<Vec<ValueId>>,
// ...
}
```
相談したい:
- `jump_args` は terminatorJump/Branch/Return/Callに埋め込むべきか、それとも BasicBlock の外部メタのままでよいか?
- `jump_args` の契約順序・長さ・expr_result の有無・invariants の混在)をどこで固定するべきか?
---
## Q7. Optimizer/DCE の不変条件spans 同期と jump_args
観測:
- DCE が `jump_args` だけで使われる値を “unused” とみなすと、Copy/Const が消えて merge が壊れる。
- DCE が `instructions` だけを削って `instruction_spans` を同期しないと、SPAN MISMATCH が発生しデバッグが困難になる。
相談したい:
- `jump_args` は “use” として扱うのが SSOT として正しいか?
- spans 同期は「各パスの責務」か、それとも `BasicBlock` の API例: spanned filterに閉じ込めるべきか
---
## 観測された失敗例(短く)
- SSA undef関数名不一致で continuation が merge 対象にならず到達不能/未定義が露出)
- 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) **案1JoinIR 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 slotjump_args[0])” を機械的に仮定すると、offset がずれて ExitLine の配線が崩れる。
- 対策として `ExitArgsCollector` には “slot があるかどうか” を推測させず、呼び出し側が `expect_expr_result_slot`
を明示して渡すのが安全Fail-Fast + 構造)。