279 lines
9.5 KiB
Markdown
279 lines
9.5 KiB
Markdown
|
|
# JoinIR — 関数正規化 IR(関数と継続だけで制御を表現する層)
|
|||
|
|
|
|||
|
|
目的
|
|||
|
|
- Hakorune の制御構造(`if` / `loop` / `break` / `continue` / `return`)を、**関数呼び出し+継続(continuation)だけに正規化する IR 層**として設計する。
|
|||
|
|
- これにより:
|
|||
|
|
- φ ノード = 関数の引数
|
|||
|
|
- merge ブロック = join 関数
|
|||
|
|
- ループ = 再帰関数(loop_step)+ exit 継続(k_exit)
|
|||
|
|
- break / continue = 適切な関数呼び出し
|
|||
|
|
という形に落とし込み、LoopForm v2 / Exit PHI / BodyLocal / ExitLiveness の負担を**構造的に軽くする**。
|
|||
|
|
|
|||
|
|
前提・方針
|
|||
|
|
- LoopForm / ControlForm は「構造の前段」として残す(削除しない)。
|
|||
|
|
- JoinIR は「PHI/SSA の実装負担を肩代わりする層」として導入し、ヘッダ/exit φ や BodyLocal の扱いを **関数の引数と継続** に吸収していく。
|
|||
|
|
- PHI 専用の箱(HeaderPhiBuilder / ExitPhiBuilder / BodyLocalPhiBuilder など)は、最終的には JoinIR 降ろしの補助に縮退させることを目標にする。
|
|||
|
|
|
|||
|
|
位置づけ
|
|||
|
|
- 変換パイプラインにおける位置:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
AST → MIR(+LoopForm v2) → JoinIR → VM / LLVM
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- AST / MIR では従来どおり Nyash 構文(if / loop 等)を扱い、
|
|||
|
|
JoinIR 以降では **関数と値(Box+Primitive)だけ**を見る。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. JoinIR のコアアイデア
|
|||
|
|
|
|||
|
|
### 1-1. 「ループ = 関数を何回も呼ぶこと」
|
|||
|
|
|
|||
|
|
通常の while/loop は:
|
|||
|
|
|
|||
|
|
```hako
|
|||
|
|
loop(i < n) {
|
|||
|
|
if i >= n { break }
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
return i
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
JoinIR 的に見ると:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
fn main(k_exit) {
|
|||
|
|
loop_step(0, k_exit)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn loop_step(i, k_exit) {
|
|||
|
|
if i >= n {
|
|||
|
|
// break
|
|||
|
|
k_exit(i)
|
|||
|
|
} else {
|
|||
|
|
// continue
|
|||
|
|
loop_step(i + 1, k_exit)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- ループ本体 = `loop_step(i, k_exit)` という関数。
|
|||
|
|
- `i` は LoopCarried(carrier)変数 → 関数の引数で表現。
|
|||
|
|
- break = exit 継続 `k_exit` の呼び出し。
|
|||
|
|
- continue = `loop_step` をもう一度呼ぶこと。
|
|||
|
|
|
|||
|
|
### 1-2. 「if の merge = join 関数」
|
|||
|
|
|
|||
|
|
ソース:
|
|||
|
|
|
|||
|
|
```hako
|
|||
|
|
if cond {
|
|||
|
|
x = 1
|
|||
|
|
} else {
|
|||
|
|
x = 2
|
|||
|
|
}
|
|||
|
|
print(x)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
JoinIR 的には:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
fn main(k_exit) {
|
|||
|
|
if cond {
|
|||
|
|
then_branch(k_exit)
|
|||
|
|
} else {
|
|||
|
|
else_branch(k_exit)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn then_branch(k_exit) {
|
|||
|
|
x = 1
|
|||
|
|
join_after_if(x, k_exit)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn else_branch(k_exit) {
|
|||
|
|
x = 2
|
|||
|
|
join_after_if(x, k_exit)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn join_after_if(x, k_exit) {
|
|||
|
|
print(x)
|
|||
|
|
k_exit(0)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- φ ノード = `join_after_if` の引数 `x`。
|
|||
|
|
- merge ブロック = `join_after_if` 関数。
|
|||
|
|
- 「どのブランチから来たか」の情報は、**関数呼び出し**に吸収される。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. JoinIR の型イメージ
|
|||
|
|
|
|||
|
|
※ Phase 26-H 時点では「設計のみ」を置いておき、実装は最小ケースから。
|
|||
|
|
|
|||
|
|
### 2-1. 関数と継続
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
/// JoinIR 関数ID(MIR 関数とは別 ID でもよい)
|
|||
|
|
struct JoinFuncId(u32);
|
|||
|
|
|
|||
|
|
/// 継続(join / ループ step / exit continuation)を識別するID
|
|||
|
|
struct JoinContId(u32);
|
|||
|
|
|
|||
|
|
/// JoinIR 関数
|
|||
|
|
struct JoinFunction {
|
|||
|
|
id: JoinFuncId,
|
|||
|
|
params: Vec<VarId>, // 引数(φ に相当)
|
|||
|
|
body: Vec<JoinInst>, // 命令列
|
|||
|
|
exit_cont: Option<JoinContId>, // 呼び出し元に返す継続(ルートは None)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2-2. 命令セット(最小)
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
enum JoinInst {
|
|||
|
|
/// 通常の関数呼び出し: f(args..., k_next)
|
|||
|
|
Call {
|
|||
|
|
func: JoinFuncId,
|
|||
|
|
args: Vec<VarId>,
|
|||
|
|
k_next: JoinContId,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/// 継続呼び出し(join / exit 継続など)
|
|||
|
|
Jump {
|
|||
|
|
cont: JoinContId,
|
|||
|
|
args: Vec<VarId>,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/// ルート関数 or 上位への戻り
|
|||
|
|
Ret {
|
|||
|
|
value: Option<VarId>,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/// それ以外の演算は、現行 MIR の算術/比較/boxcall を再利用
|
|||
|
|
Compute(MirLikeInst),
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2-3. 継続の分類
|
|||
|
|
|
|||
|
|
- join 継続:
|
|||
|
|
- if の merge や、ループ body 終了後の「次の場所」を表す。
|
|||
|
|
- loop_step:
|
|||
|
|
- ループ 1ステップ分の処理。
|
|||
|
|
- exit 継続:
|
|||
|
|
- ループを抜けた後の処理(`k_exit`)。
|
|||
|
|
- これらはすべて「関数 or 継続 ID」と「引数」で表現される。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. MIR/LoopForm から JoinIR への変換ルール(v0 草案)
|
|||
|
|
|
|||
|
|
ここでは Phase 26-H で扱う最小のルールだけを書く。
|
|||
|
|
|
|||
|
|
### 3-1. ループ(LoopForm v2)→ loop_step + k_exit
|
|||
|
|
|
|||
|
|
前提: LoopForm v2 が提供する情報
|
|||
|
|
- preheader / header / body / latch / exit / continue_merge / break ブロック集合。
|
|||
|
|
- LoopVarClassBox による分類(Pinned / Carrier / BodyLocalExit / BodyLocalInternal)。
|
|||
|
|
|
|||
|
|
変換方針(概略):
|
|||
|
|
|
|||
|
|
1. LoopCarried 変数の集合 `C = {v1, v2, ...}` を求める。
|
|||
|
|
2. 「ループを抜けた後で使う変数」の集合 `E` を ExitLiveness から得る(長期的には MirScanExitLiveness)。
|
|||
|
|
3. JoinIR 上で:
|
|||
|
|
- `fn loop_step(C, k_exit)` を新設。
|
|||
|
|
- ループ前の値から `loop_step(C0, k_exit0)` を呼び出す。
|
|||
|
|
- body 内の `break` は `k_exit(E)` に変換。
|
|||
|
|
- body 内の `continue` は `loop_step(C_next, k_exit)` に変換。
|
|||
|
|
|
|||
|
|
今の LoopForm / PhiBuilderBox がやっている仕事(header φ / exit φ 生成)は、
|
|||
|
|
最終的には「`C` と `E` を決める補助」に寄せていくイメージになる。
|
|||
|
|
|
|||
|
|
### 3-2. if → join_after_if
|
|||
|
|
|
|||
|
|
前提:
|
|||
|
|
- control_flow / if_form.rs で IfForm(cond / then / else / merge)が取れる。
|
|||
|
|
|
|||
|
|
変換方針:
|
|||
|
|
|
|||
|
|
1. merge ブロックで φ が必要な変数集合 `M = {x1, x2, ...}` を求める。
|
|||
|
|
2. JoinIR 上で:
|
|||
|
|
- `fn join_after_if(M, k_exit)` を新設。
|
|||
|
|
- then ブランチの末尾に `join_after_if(x1_then, x2_then, ..., k_exit)` を挿入。
|
|||
|
|
- else ブランチの末尾に `join_after_if(x1_else, x2_else, ..., k_exit)` を挿入。
|
|||
|
|
3. merge ブロックの本体は `join_after_if` に移す。
|
|||
|
|
|
|||
|
|
これにより、φ ノードは `join_after_if` の引数に吸収される。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. フェーズ計画(Phase 26-H 〜 27.x の流れ)
|
|||
|
|
|
|||
|
|
### Phase 26-H(現在フェーズ)— 設計+ミニ実験
|
|||
|
|
|
|||
|
|
- スコープ:
|
|||
|
|
- JoinIR の設計ドキュメント(このファイル+ phase-26-H/README)。
|
|||
|
|
- `src/mir/join_ir.rs` に型だけ入れる(変換ロジックは最小)。
|
|||
|
|
- 1 ケース限定の変換実験:
|
|||
|
|
- 例: `apps/tests/joinir_min_loop.hako` → MIR → JoinIR → JoinIR のダンプを Rust テストで確認。
|
|||
|
|
- ゴール:
|
|||
|
|
- 「関数正規化 IR(JoinIR)が箱理論/LoopForm v2 と矛盾しない」ことを確認する。
|
|||
|
|
- 大規模リプレースに進む価値があるか、感触を掴む。
|
|||
|
|
|
|||
|
|
### Phase 27.x(仮)— 段階的な採用案(まだ構想段階)
|
|||
|
|
|
|||
|
|
この段階はまだ「構想メモ」として置いておく。
|
|||
|
|
|
|||
|
|
- 27.1: LoopForm v2 → JoinIR の変換を部分的に実装
|
|||
|
|
- まずは「break なしの loop + if」だけを対象にする。
|
|||
|
|
- Exit φ / header φ の一部を JoinIR の引数に置き換える。
|
|||
|
|
- 27.2: break / continue / BodyLocalExit を JoinIR で扱う
|
|||
|
|
- BodyLocalPhiBuilder / LoopExitLiveness の「決定」を JoinIR 引数設計に寄せる。
|
|||
|
|
- ExitLiveness は JoinIR 関数の use/def から算出可能かを検証する。
|
|||
|
|
- 27.3: SSA/PHI との役割調整
|
|||
|
|
- LoopForm v2 / PhiBuilderBox は「JoinIR 生成補助」の位置付けに縮退。
|
|||
|
|
- MirVerifier は JoinIR ベースのチェック(又は MIR/JoinIR 並列)に切り替え検討。
|
|||
|
|
|
|||
|
|
※これら 27.x フェーズは、26-F / 26-G で現行ラインの根治が一段落してから進める前提。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 他ドキュメントとの関係
|
|||
|
|
|
|||
|
|
- `docs/development/roadmap/phases/phase-26-H/README.md`
|
|||
|
|
- Phase 26-H のスコープ(設計+ミニ実験)、他フェーズ(25.1 / 26-F / 26-G)との関係を定義。
|
|||
|
|
- `docs/development/architecture/loops/loopform_ssot.md`
|
|||
|
|
- LoopForm v2 / Exit PHI / 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)の設計ノート。
|
|||
|
|
- 将来、JoinIR を導入するときは「LoopForm v2 → JoinIR 変換の前段」としてこの SSOT を活かす。
|
|||
|
|
|
|||
|
|
このファイルは、JoinIR を「全部関数な言語」のコアとして扱うための設計メモだよ。
|
|||
|
|
実際の導入は小さな実験(Phase 26-H)から始めて、問題なければ 27.x 以降で段階的に進める想定にしておく。
|
|||
|
|
|
|||
|
|
## 6. φ と merge の対応表(抜粋)
|
|||
|
|
|
|||
|
|
| 構文/概念 | JoinIR での表現 | φ/合流の扱い |
|
|||
|
|
|-----------|-----------------|--------------|
|
|||
|
|
| if/merge | join 関数呼び出し | join 関数の引数が φ 相当 |
|
|||
|
|
| loop | step 再帰 + k_exit 継続 | LoopCarried/Exit の値を引数で渡す |
|
|||
|
|
| break | k_exit 呼び出し | φ 不要(引数で値を渡す) |
|
|||
|
|
| continue | step 呼び出し | φ 不要(引数で値を渡す) |
|
|||
|
|
| return | 継続または ret | そのまま値を返す |
|
|||
|
|
|
|||
|
|
## 7. 26-H で増やす実装箱 / 概念ラベル
|
|||
|
|
|
|||
|
|
- 実装として増やす(このフェーズで手を動かす)
|
|||
|
|
- `join_ir.rs`: `JoinFunction/JoinBlock/JoinInst` の最小定義とダンプ
|
|||
|
|
- LoopForm→JoinIR のミニ変換関数(1 ケース限定で OK)
|
|||
|
|
- 実験トグル(例: `NYASH_JOINIR_EXPERIMENT=1`)で JoinIR をダンプするフック
|
|||
|
|
|
|||
|
|
- 概念ラベルのみ(27.x 以降で拡張を検討)
|
|||
|
|
- MirQueryBox のような MIR ビュー層(reads/writes/succs を trait 化)
|
|||
|
|
- LoopFnLoweringBox / JoinIRBox の分割(必要になったら分ける)
|
|||
|
|
- JoinIR 上での最適化や VM/LLVM 統合
|
|||
|
|
|
|||
|
|
このフェーズでは「箱を増やしすぎない」ことを意識し、型定義+最小変換+ダンプ確認だけに留める。
|