**Phase 34-6 実装完了**: MethodCall 構造を JoinIR に追加し、本物の substring
呼び出しを通すことに成功。
## 主要変更
### 1. MethodCall 構造追加 (34-6.1)
- `src/mir/join_ir/mod.rs`: JoinInst::MethodCall バリアント (+8 lines)
- 構造: `{ dst, receiver, method, args }`
- 設計原則: JoinIR は構造のみ、意味論は MIR レベル
### 2. extract_value 更新 (34-6.2)
- `src/mir/join_ir/frontend/ast_lowerer.rs`: Method 処理本物化 (+37 lines)
- receiver/args を extract_value で再帰処理
- ダミー Const(0) 削除 → 本物の MethodCall 生成
- cond 処理修正: ValueId(0) ハードコード → extract_value で取得
### 3. JoinIR→MIR 変換実装 (34-6.3)
- `src/mir/join_ir_vm_bridge.rs`: MethodCall → BoxCall 変換 (+12 lines)
- `src/mir/join_ir/json.rs`: MethodCall JSON シリアライゼーション (+16 lines)
- `src/mir/join_ir_runner.rs`: MethodCall 未対応エラー (+7 lines)
### 4. テスト更新 (34-6.4)
- `docs/.../fixtures/json_shape_read_value.program.json`: 本物の substring 構造
- `src/tests/joinir_frontend_if_select.rs`: run_joinir_via_vm 使用
- テスト成功: v="hello", at=3 → "hel" ✅
## 成果
- ✅ テスト全通過(1 passed; 0 failed)
- ✅ 設計原則確立: JoinIR = 構造 SSOT、意味論 = MIR レベル
- ✅ Phase 33-10 原則との整合性: Method でも同じ原則適用
**ドキュメント更新**: CURRENT_TASK.md + TASKS.md(Phase 34-6 完了記録)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
Phase 33-10: Local Pattern の実MIR構造分析レポート
作成日: 2025-11-27 分析者: Claude Code 目的: If lowering における「local pattern」の実MIR構造を分析し、Phase 33-10の実装方針を決定する
1. Executive Summary
1.1 重要な結論
PHI命令を持つMIRは、JoinIR変換の対象外とすべき(Option A推奨)
理由:
- PHI命令は既にSSAの標準的な値合流表現 - これ以上の変換は不要
- JoinIR Select/IfMergeの役割は "PHI生成" - 既存PHIを変換するのは逆行
- try_match_local_pattern()の想定は誤り - 実MIRとの構造差分が根本的
1.2 実装への影響
Phase 33-10の方針:
- ❌ try_match_local_pattern()の修正は不要
- ✅ local patternはPhase 33-10の対象外として扱う
- ✅ JoinIR loweringは「PHI命令がない場合のみ」実施
- ✅ 既存のif_phi.rsによるPHI生成は維持(これが正常動作)
2. 構造比較詳細
2.1 ユニットテスト想定(誤り)
try_match_local_pattern()が期待していた構造:
bb0: br cond, bb1, bb2
bb1 (then):
Copy { dst: x, src: 100 } ← 単一の代入命令
Jump bb3
bb2 (else):
Copy { dst: x, src: 200 } ← 単一の代入命令
Jump bb3
bb3 (merge):
[空 - 命令なし] ← 重要: 命令がない!
Return x
検証コード(if_select.rs:262-263):
if !merge_block.instructions.is_empty() {
return None; // ← merge blockが空でなければ失敗
}
2.2 実MIR構造(正しい)
実際のコンパイラ出力 (/tmp/test_phi_local.hako):
define i64 @main() {
bb0:
1: %3: Integer = const 1
1: %4: Integer = copy %3
1: %5: Integer = copy %4
1: br %5, label bb3, label bb4
bb3 (then):
1: %8: Integer = const 100 ← Const命令(Copy不使用)
1: br label bb5
bb4 (else):
1: %11: Integer = const 200 ← Const命令(Copy不使用)
1: br label bb5
bb5 (merge):
1: %12 = phi [%8, bb3], [%11, bb4] ← PHI命令が存在!
1: ret %12
}
構造差分:
| 要素 | ユニットテスト想定 | 実MIR | 差分の意味 |
|---|---|---|---|
| Then/Else命令 | Copy { dst: x, src: 100 } |
Const { dst: %8, value: 100 } |
Copyではなく直接Const |
| Merge命令 | 空(なし) | Phi { dst: %12, inputs: [...] } |
PHI命令が存在 |
| Return値 | Return x(then/elseで書き込んだ変数) |
Return %12(PHI結果の新しいValueId) |
異なるValueId |
2.3 Phase 33-9.2の修正との比較
Phase 33-9.2で修正したSimple pattern:
// 修正内容: Const/Copyを許容
fn is_side_effect_free(&self, instructions: &[MirInstruction]) -> bool {
instructions.iter().all(|inst| {
matches!(inst, MirInstruction::Const { .. } | MirInstruction::Copy { .. })
})
}
Simple patternの実MIR:
bb3 (then):
%8: Integer = const 100
ret %8 ← Return命令(merge blockなし)
bb4 (else):
%11: Integer = const 200
ret %11 ← Return命令(merge blockなし)
重要な違い:
- Simple: merge blockなし → return直接
- Local: merge blockあり → PHI命令で合流
3. PHI命令の妥当性検証
3.1 SSA形式の正しさ
実MIRのPHI構造は完全に正しい:
-
定義(Definition):
- SSA形式では、複数の前任ブロックから値が流入する場合、PHI命令で合流させる
- これが唯一の正規的な表現
-
実MIRの構造:
bb5 (merge): %12 = phi [%8, bb3], [%11, bb4]- bb3から%8が流入
- bb4から%11が流入
- bb5では%12として合流
- 完璧なSSA形式
-
検証:
- ✅ 各ValueIdは一度だけ定義される(Single Assignment)
- ✅ 前任ブロックごとの値が明確(Static Single)
- ✅ PHI配置は支配フロンティアに従っている
3.2 PHI命令の配置
標準的なPHI配置規則:
- PHI命令はmerge blockの先頭に配置される
- Return/Jump等の終端命令の前に配置される
実MIRの配置:
bb5:
1: %12 = phi [%8, bb3], [%11, bb4] ← 先頭に配置
1: ret %12 ← 終端命令
✅ 完全に標準的な配置
4. JoinIR変換の必要性分析
4.1 JoinIR Select/IfMergeの役割
設計文書より (if_joinir_design.md):
/// Phase 33.1: 単純な値選択
Select {
dst: VarId,
cond: VarId,
then_val: VarId,
else_val: VarId,
}
Select命令の意味論:
- 条件に応じて値を選択する
- MIR reverse loweringでPHI命令を生成する(重要!)
4.2 PHI → Select変換の問題点
仮にPHI → Selectを実装した場合:
MIR (入力):
bb5: %12 = phi [%8, bb3], [%11, bb4]
↓ lowering (MIR → JoinIR)
JoinIR:
Select { dst: %12, cond: ???, then_val: %8, else_val: %11 }
↓ reverse lowering (JoinIR → MIR)
MIR (出力):
bb5: %12 = phi [%8, bb3], [%11, bb4] ← 元に戻る
問題:
- 情報損失: PHI → Select時に条件式が不明(%5はbb0で消費済み)
- 無意味な往復: MIR → JoinIR → MIR で同じPHIに戻る
- 責務の逆転: JoinIRの役割は「PHI生成」であり、「PHI変換」ではない
4.3 JoinIR変換が有意義なケース
唯一有意義なのは「PHI命令がまだ生成されていない場合」:
高レベルAST:
if cond { x = 100 } else { x = 200 }; return x
↓ (従来の方式: if_phi.rs経由)
MIR:
bb5: %12 = phi [%8, bb3], [%11, bb4]
ret %12
↓ (新しい方式: JoinIR経由)
JoinIR:
Select { dst: %12, cond: %5, then_val: %8, else_val: %11 }
↓ reverse lowering
MIR:
bb5: %12 = phi [%8, bb3], [%11, bb4]
ret %12
結果: 最終MIRは同じだが、生成経路が異なる
価値:
- ✅ if_phi.rsの削除が可能(コード削減)
- ✅ JoinIR側で型検証が可能
- ❌ 既にPHIがある場合は意味なし
5. Phase 33の設計意図確認
5.1 設計文書の記述
if_joinir_design.md:11-27より:
1. If/Else の「値としての if」(`if ... then ... else ...` の PHI)を JoinIR 側で表現できるようにする。
- ループ PHI と同じく、「PHI を関数引数や join 関数の引数に押し出す」設計に寄せる。
- MIR Builder 側の if_phi.rs / conservative.rs / phi_invariants.rs に依存しない JoinIR lowering を用意する。
重要な前提:
- 「MIR Builder側のif_phi.rsに依存しない」 = if_phi.rsを使わずにPHIを生成する
- PHI生成の新しい経路としてJoinIRを使う
5.2 if_phi.rsの役割
現状のif_phi.rs:
- ASTから直接PHI命令を生成(MIR Builder層)
- if/else構造を解析してPHI配置を決定
- 316行のロジック
JoinIR経由の場合:
- ASTからJoinIR Select/IfMergeを生成
- JoinIR → MIR reverse lowering時にPHI命令を生成
- if_phi.rsは削除可能
5.3 Phase 33のターゲット
Phase 33-10の実装対象:
入力: AST(まだPHI生成されていない)
出力: MIR(PHI命令を含む)
経路選択:
Route A(従来): AST → if_phi.rs → MIR(PHI)
Route B(新規): AST → JoinIR lowering → JoinIR Select → reverse lowering → MIR(PHI)
Phase 33-10が扱うべきでないケース:
入力: MIR(既にPHI命令が存在)
出力: ???(何に変換する意味がある?)
6. Phase 33-10実装方針
6.1 推奨方針: Option A(PHI命令を変換対象外とする)
実装内容:
// src/mir/join_ir/lowering/if_select.rs
fn try_match_local_pattern(
&self,
func: &MirFunction,
branch: &IfBranch,
then_block: &crate::mir::BasicBlock,
else_block: &crate::mir::BasicBlock,
) -> Option<IfPattern> {
// Phase 33-10: PHI命令が既に存在する場合は対象外
// (JoinIRの役割はPHI生成であり、既存PHI変換ではない)
// merge blockの取得
let merge_block_id = match then_block.terminator.as_ref()? {
MirInstruction::Jump { target } => *target,
_ => return None,
};
let merge_block = func.blocks.get(&merge_block_id)?;
// ❌ PHI命令がある場合は変換対象外(重要!)
for inst in &merge_block.instructions {
if matches!(inst, MirInstruction::Phi { .. }) {
return None; // 既にPHIがあるので、JoinIR変換不要
}
}
// ✅ PHI命令がない場合のみ、local patternとして処理
// (この場合は現在の実装で正しく動作する)
// ... 既存のロジック継続 ...
}
効果:
- ✅ PHI命令がある = 既にif_phi.rsで処理済み → JoinIR変換不要
- ✅ PHI命令がない = JoinIR経由で処理すべき → 現在の実装が機能
- ✅ 責務の明確化: JoinIRは「PHI生成器」として機能
6.2 Option B(PHI → Select/IfMerge変換)の問題点
実装した場合の問題:
-
条件式の復元困難:
bb0: br %5, label bb3, label bb4 ← %5はここで消費 ... bb5: %12 = phi [%8, bb3], [%11, bb4] ← %5にアクセスできない -
無意味な往復変換:
- MIR(PHI) → JoinIR(Select) → MIR(PHI)
- 何も変わらない
-
Phase 33の意図に反する:
- 目的: if_phi.rsを削除して、JoinIR経由でPHI生成
- 実際: 既にあるPHIを変換(意味なし)
6.3 Option C(新しいパターン定義)の不要性
「PHI-based pattern」を定義する必要はない:
理由:
- PHIがある = 既にMIR Builder(if_phi.rs)で処理済み
- JoinIRの役割は「まだPHIがない場合にPHI生成する」こと
- 既存PHIを再処理するパターンは設計意図に反する
7. Simple PatternとLocal Patternの真の違い
7.1 Simple Pattern(正しく動作)
構造:
bb0: br cond, bb3, bb4
bb3 (then):
%8 = const 100
ret %8 ← 直接return(merge blockなし)
bb4 (else):
%11 = const 200
ret %11 ← 直接return(merge blockなし)
特徴:
- ❌ merge blockが存在しない
- ❌ PHI命令が不要(各ブロックで直接return)
- ✅ JoinIR Selectで表現可能
7.2 Local Pattern(既にPHI生成済み)
構造:
bb0: br cond, bb3, bb4
bb3 (then):
%8 = const 100
br bb5 ← mergeへジャンプ
bb4 (else):
%11 = const 200
br bb5 ← mergeへジャンプ
bb5 (merge):
%12 = phi [%8, bb3], [%11, bb4] ← PHI命令(既に生成済み)
ret %12
特徴:
- ✅ merge blockが存在
- ✅ PHI命令が既に存在(if_phi.rsで生成済み)
- ❌ JoinIR変換の必要なし(既にSSA形式完成)
7.3 真のLocal Pattern(JoinIR変換すべきケース)
仮想的な構造(実際には生成されない):
bb0: br cond, bb3, bb4
bb3 (then):
Store { ptr: &x, value: 100 } ← PHIではなくStore
br bb5
bb4 (else):
Store { ptr: &x, value: 200 } ← PHIではなくStore
br bb5
bb5 (merge):
[空 - 命令なし] ← PHIなし
%v = Load { ptr: &x } ← 値の読み込み
ret %v
現実:
- このような構造はMIR Builderが生成しない
- Nyashは常にSSA形式 → 必ずPHI命令を使用
- したがって、try_match_local_pattern()の想定自体が実装されないケースを前提にしている
8. Phase 33全体への影響
8.1 Simple Pattern(Phase 33-9.2)
現状: ✅ 完全動作
- Const/Copy許容で100%成功
- PHI命令なし → JoinIR変換が有意義
8.2 Local Pattern(Phase 33-10)
結論: ⚠️ Phase 33-10の対象外
- PHI命令あり → 既にif_phi.rsで処理完了
- JoinIR変換は不要(無意味な往復)
推奨:
- try_match_local_pattern()を削除するか、
- PHI命令チェックを追加して早期return
8.3 IfMerge Pattern
Phase 33-6の設計との整合性:
IfMerge設計(if_joinir_design.md:748-823):
JoinInst::IfMerge {
cond: ValueId,
merges: Vec<MergePair>, // 複数のPHI相当
}
重要: IfMergeも「PHI生成」が目的
- 入力: AST(複数変数代入)
- 出力: MIR(複数PHI命令)
- 既存PHI → IfMerge変換ではない
9. 最終推奨事項
9.1 Phase 33-10の実装方針
推奨: Option A(PHI命令を対象外とする)
// Phase 33-10: 実装修正
fn try_match_local_pattern(...) -> Option<IfPattern> {
// Step 1: merge blockの取得
let merge_block_id = ...;
let merge_block = func.blocks.get(&merge_block_id)?;
// Step 2: PHI命令チェック(追加)
if merge_block.instructions.iter().any(|inst| {
matches!(inst, MirInstruction::Phi { .. })
}) {
// PHI命令がある = if_phi.rsで処理済み → JoinIR変換不要
return None;
}
// Step 3: 既存のロジック継続
// (PHI命令がない場合のみ到達)
// ...
}
9.2 Phase 33-10の完了判定
完了条件:
- ✅ try_match_local_pattern()にPHIチェック追加
- ✅ 既存ユニットテスト維持(PHI命令なしケース)
- ✅ 実用MIR(PHI命令あり)は正しくフォールバック
- ✅ ドキュメント更新(本レポート)
非目標:
- ❌ PHI → Select変換実装(不要)
- ❌ local patternの実用MIR対応(既にif_phi.rsで処理済み)
9.3 Phase 33全体の戦略修正
修正前の理解:
- Simple/Local/IfMerge の3パターンをJoinIRで実装 → if_phi.rs削除
修正後の理解:
- Simple: PHI不要 → JoinIR変換有意義 ✅
- Local: PHI既存 → JoinIR変換不要(if_phi.rs保持) ⚠️
- IfMerge: 複数PHI → AST→JoinIR経路でのみ有意義 ✅
結論:
- if_phi.rsの完全削除はPhase 34以降に延期
- Phase 33は「PHI生成前の経路にJoinIR挿入」に集中
- 既存PHIの再処理は無意味 → 実装しない
10. 参考資料
10.1 実MIR出力(完全版)
; MIR Module: main
; Source: /tmp/test_phi_local.hako
define i64 @main() {
bb0:
1: %3: Integer = const 1
1: %4: Integer = copy %3
1: %5: Integer = copy %4
1: br %5, label bb3, label bb4
bb3:
1: %8: Integer = const 100
1: br label bb5
bb4:
1: %11: Integer = const 200
1: br label bb5
bb5:
1: %12 = phi [%8, bb3], [%11, bb4]
1: ret %12
}
10.2 Simple Pattern MIR(Phase 33-9.2)
define i64 @IfSelectTest.test/1(i64 %0) {
bb2:
br %3, label bb3, label bb4
bb3:
%4: Integer = const 10
ret %4
bb4:
%6: Integer = const 20
ret %6
}
10.3 関連ファイル
src/mir/join_ir/lowering/if_select.rs:201-273- try_match_local_pattern()実装docs/private/roadmap2/phases/phase-33-joinir-if-phi-cleanup/if_joinir_design.md- 設計文書src/mir/builder/if_phi.rs- 現行PHI生成ロジック(316行)
11. ChatGPT実装者への提言
11.1 実装不要の理由
結論: Phase 33-10で local pattern のMIR変換実装は不要
理由:
- 実MIRは既に正しいPHI命令を含む
- JoinIRの役割は「PHI生成」であり「PHI変換」ではない
- 既存のif_phi.rsが正しく動作している
- 無意味な往復変換を避けるべき
11.2 実装すべき内容
唯一実装すべきこと: PHI命令チェックの追加
// Phase 33-10: 5行の追加のみ
if merge_block.instructions.iter().any(|inst| {
matches!(inst, MirInstruction::Phi { .. })
}) {
return None; // PHI命令がある場合は対象外
}
効果:
- ✅ 責務の明確化
- ✅ 無駄な処理の防止
- ✅ 設計意図との整合性
11.3 Phase 33-10完了判定
完了条件(簡略化):
- ✅ PHIチェック追加(5行)
- ✅ 既存テスト維持
- ✅ ドキュメント更新
作業時間: 30分以内
Phase 33-10完了判定: PHIチェック追加のみで完了とする 次のフェーズ: Phase 33-11(IfMerge実装、またはPhase 34へ移行)