Extended PatternPipelineContext and CarrierUpdateInfo for Pattern 3 AST-based generalization. Changes: 1. PatternPipelineContext: - Added loop_condition: Option<ASTNode> - Added loop_body: Option<Vec<ASTNode>> - Added loop_update_summary: Option<LoopUpdateSummary> - Updated build_pattern_context() for Pattern 3 2. CarrierUpdateInfo: - Added then_expr: Option<ASTNode> - Added else_expr: Option<ASTNode> - Updated analyze_loop_updates() with None defaults Status: Phase 213-2 Steps 2-2 & 2-3 complete Next: Create Pattern3IfAnalyzer to extract if statement and populate update summary
427 lines
12 KiB
Markdown
427 lines
12 KiB
Markdown
# Phase 212.5: ループ内 if → MIR 変換バグ修正(緊急ミニフェーズ)
|
||
|
||
**Phase**: 212.5
|
||
**Date**: 2025-12-09
|
||
**Status**: 🔧 In Progress
|
||
**Prerequisite**: Phase 212 完了(制約発見)
|
||
|
||
---
|
||
|
||
## 🎯 Phase 212.5 の目的
|
||
|
||
Phase 212 で発見した「ループ内 if/else が MIR に変換されない」問題を修正する。
|
||
|
||
**戦略**:
|
||
- JoinIR に触らない(AST→MIR Builder だけを修正)
|
||
- 既存の If lowering 箱を再利用
|
||
- 最小限の変更で根治
|
||
|
||
---
|
||
|
||
## Task 212.5-1: 現状の AST / MIR を確認 ✅
|
||
|
||
### テストファイル
|
||
|
||
**`apps/tests/phase212_if_sum_min.hako`**:
|
||
|
||
```hako
|
||
static box IfSumTest {
|
||
sum_def_count(defs) {
|
||
local sum = 0
|
||
local i = 0
|
||
local len = 3
|
||
|
||
loop(i < len) {
|
||
// ← この if がループ本体に含まれるはず
|
||
if i > 0 {
|
||
sum = sum + 1 // ← 条件付き更新
|
||
}
|
||
i = i + 1
|
||
}
|
||
return sum
|
||
}
|
||
|
||
main() {
|
||
local result = IfSumTest.sum_def_count(0)
|
||
return result
|
||
}
|
||
}
|
||
```
|
||
|
||
### 期待される AST 構造
|
||
|
||
ループ本体の AST ノードには以下が含まれるはず:
|
||
|
||
```
|
||
Loop {
|
||
condition: BinaryOp(Lt, i, len),
|
||
body: Block [
|
||
// ← If ノードがここにあるはず
|
||
If {
|
||
condition: BinaryOp(Gt, i, 0),
|
||
then_block: Block [
|
||
Assignment(sum, BinOp(Add, sum, 1))
|
||
],
|
||
else_block: None
|
||
},
|
||
Assignment(i, BinOp(Add, i, 1))
|
||
]
|
||
}
|
||
```
|
||
|
||
### 実際の MIR 出力(Before)
|
||
|
||
```mir
|
||
define i64 @IfSumTest.sum_def_count/1(? %0) effects(read) {
|
||
bb1:
|
||
%2 = const 0 ; sum 初期化
|
||
%4 = const 0 ; i 初期化
|
||
br label bb3
|
||
|
||
bb2:
|
||
ret %2 ; return sum
|
||
|
||
bb3:
|
||
%7 = phi [%4, bb1], [%16, bb6] ; ← i の PHI のみ
|
||
br label bb4
|
||
|
||
bb4:
|
||
%12 = const 3
|
||
%13 = icmp Lt %7, %12
|
||
%14 = Not %13
|
||
br %14, label bb5, label bb6
|
||
|
||
bb5:
|
||
br label bb2
|
||
|
||
bb6:
|
||
; ← ここに if 由来の Compare / Branch が無い!
|
||
extern_call env.console.log(%7) [effects: pure|io]
|
||
%15 = const 1
|
||
%16 = %7 Add %15
|
||
%7 = copy %16
|
||
br label bb3
|
||
}
|
||
```
|
||
|
||
### 問題点の詳細
|
||
|
||
**欠落している MIR 命令**:
|
||
|
||
bb6 ループ本体ブロックには以下があるべき:
|
||
|
||
```mir
|
||
bb6:
|
||
; ← if i > 0 の条件チェック
|
||
%const_0 = const 0
|
||
%cond = icmp Gt %7, %const_0
|
||
br %cond, label bb_then, label bb_else
|
||
|
||
bb_then:
|
||
; sum = sum + 1
|
||
%sum_phi = phi [%2, bb3], [%sum_updated, bb_else] ; ← sum の PHI
|
||
%const_1 = const 1
|
||
%sum_updated = %sum_phi Add %const_1
|
||
br label bb_merge
|
||
|
||
bb_else:
|
||
br label bb_merge
|
||
|
||
bb_merge:
|
||
%sum_final = phi [%sum_updated, bb_then], [%sum_phi, bb_else]
|
||
; i = i + 1
|
||
%15 = const 1
|
||
%16 = %7 Add %15
|
||
br label bb3
|
||
```
|
||
|
||
**実際には**:
|
||
- if 由来の `Compare` / `Branch` が一切無い
|
||
- `sum` 変数に関する処理(PHI・加算)が完全に消失
|
||
|
||
### 仮説: どの層が壊しているか
|
||
|
||
#### ✅ Parser 層は OK
|
||
|
||
理由:
|
||
- Phase 212 で print を if 内に追加しても同じ結果
|
||
- Parser が if ノードを落としているなら、syntax error になるはず
|
||
- → **Parser は正しく AST を生成している可能性が高い**
|
||
|
||
#### ❌ LoopForm / control_flow builder が怪しい
|
||
|
||
**仮説 1**: ループ本体の AST ノードが **フラット化** されている
|
||
- `loop { stmt1; stmt2; }` の各 stmt を順次処理する際、
|
||
- `stmt` が `If` ノードの場合に **match していない** 可能性
|
||
|
||
**仮説 2**: ループ本体の `build_block()` が If を **スキップ** している
|
||
- `build_block()` が Statement を処理する際、
|
||
- `Statement::Expr(If)` を **式として評価** せずに無視している可能性
|
||
|
||
**仮説 3**: If が **Dead Code Elimination (DCE)** で消えている
|
||
- `sum` の値が return で使われているから DCE で消えないはず
|
||
- でも念のため確認が必要
|
||
|
||
---
|
||
|
||
## Task 212.5-2: MIR Builder の責務位置を特定 ✅
|
||
|
||
### 確認したファイル
|
||
|
||
1. ✅ **`src/mir/builder/stmts.rs`** - Statement 処理
|
||
2. ✅ **`src/mir/builder/exprs.rs`** - Expression 処理
|
||
3. ✅ **`src/mir/builder/control_flow/mod.rs`** - cf_if(), cf_loop()
|
||
|
||
### 問題の根本原因を特定
|
||
|
||
#### 🚨 **発見した問題**
|
||
|
||
**`build_statement()` (stmts.rs:215-222)**:
|
||
|
||
```rust
|
||
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
|
||
self.current_span = node.span();
|
||
match node {
|
||
// 将来ここに While / ForRange / Match / Using など statement 専用分岐を追加する。
|
||
other => self.build_expression(other), // ← すべて expression として処理
|
||
}
|
||
}
|
||
```
|
||
|
||
**問題点**:
|
||
- `ASTNode::If` のケースが **match に存在しない**
|
||
- すべての Statement が `other =>` で `build_expression()` に投げられる
|
||
- **If が式として評価される** → 値が使われない場合に最適化で消える可能性
|
||
|
||
#### If の処理フロー(現状)
|
||
|
||
```
|
||
build_statement(ASTNode::If)
|
||
↓
|
||
match { other => build_expression(other) } ← If ケースなし
|
||
↓
|
||
build_expression(ASTNode::If)
|
||
↓
|
||
match { ASTNode::If { ... } => self.cf_if(...) } ← ここで処理
|
||
↓
|
||
cf_if(condition, then_branch, else_branch)
|
||
↓
|
||
lower_if_form(...) ← JoinIR ベースの PHI 生成
|
||
```
|
||
|
||
#### 新しい仮説
|
||
|
||
**仮説 1**: If が式として評価され、**値が使われない**ため DCE で消える
|
||
- ループ内の `if i > 0 { sum = sum + 1 }` は Statement として書かれている
|
||
- でも `build_statement()` は `build_expression()` に投げる
|
||
- `build_expression()` は ValueId を返すが、ループ本体では **その値を使わない**
|
||
- → 最適化 (DCE) で If ブロック全体が消える?
|
||
|
||
**仮説 2**: ループ本体の AST が JoinIR 経路で **フラット化** されている
|
||
- `cf_loop()` → `try_cf_loop_joinir()` の経路で
|
||
- ループ本体の AST ノードが別の形式に変換される際に If が消失
|
||
|
||
**仮説 3**: `lower_if_form()` がループ内 if を **スキップ** している
|
||
- `lower_if_form()` が「ループ外の if のみ対応」の可能性
|
||
- ループ内 if は別の処理が必要だが、その処理が未実装
|
||
|
||
### 次の調査対象
|
||
|
||
1. **DCE (Dead Code Elimination)** の動作確認
|
||
- If 式の戻り値が使われない場合に DCE で消えるか?
|
||
|
||
2. **`try_cf_loop_joinir()` の実装確認**
|
||
- ループ本体の AST がどう処理されているか
|
||
- If ノードが JoinIR 変換時に保持されているか
|
||
|
||
3. **`lower_if_form()` の実装確認**
|
||
- ループ内 if でも正しく動作するか
|
||
- ループコンテキストでの制約があるか
|
||
|
||
---
|
||
|
||
## Task 212.5-3: 小さな箱として if-lowering を足す 🔧
|
||
|
||
### 根本原因の確定
|
||
|
||
**問題**:
|
||
- `build_statement()` が `ASTNode::If` を **expression 経路にだけ流していた**
|
||
- Statement としての If(副作用のみが欲しい)が expression として評価される
|
||
- → 値が使われないと最適化で消える
|
||
|
||
**対応方針**:
|
||
- **Option A** を採用: `build_statement()` に statement 用の If ケースを追加
|
||
- 既存の If lowering 箱 (`cf_if` / `lower_if_form`) を呼ぶだけ
|
||
|
||
### 設計方針
|
||
|
||
**原則**:
|
||
- 新規巨大箱は作らない
|
||
- 既存の If lowering 箱を再利用
|
||
- Statement と Expression の If を明確に分離
|
||
|
||
### 実装戦略(Option A)
|
||
|
||
#### 修正箇所: `src/mir/builder/stmts.rs`
|
||
|
||
**Before**:
|
||
```rust
|
||
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
|
||
self.current_span = node.span();
|
||
match node {
|
||
// TODO: While / ForRange / Match / Using …
|
||
other => self.build_expression(other), // ← If も expression 扱い
|
||
}
|
||
}
|
||
```
|
||
|
||
**After**:
|
||
```rust
|
||
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
|
||
self.current_span = node.span();
|
||
match node {
|
||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||
// Statement としての If - 既存 If lowering を呼ぶ
|
||
self.build_if_statement(*condition, then_body, else_body)?;
|
||
// Statement なので値は使わない(Void を返す)
|
||
Ok(crate::mir::builder::emission::constant::emit_void(self))
|
||
}
|
||
// 将来: While / ForRange / Match / Using など
|
||
other => self.build_expression(other),
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 新規関数: `build_if_statement()`
|
||
|
||
既存の If lowering を薄くラップする小さい箱:
|
||
|
||
```rust
|
||
/// Statement としての If 処理(副作用のみ)
|
||
///
|
||
/// ループ内 if や top-level statement if はここを通る。
|
||
/// Expression としての if(値を使う場合)は build_expression 経由。
|
||
pub(super) fn build_if_statement(
|
||
&mut self,
|
||
condition: ASTNode,
|
||
then_body: Vec<ASTNode>,
|
||
else_body: Option<Vec<ASTNode>>,
|
||
) -> Result<(), String> {
|
||
use crate::ast::Span;
|
||
|
||
// then_body と else_body を ASTNode::Program に変換
|
||
let then_node = ASTNode::Program {
|
||
statements: then_body,
|
||
span: Span::unknown(),
|
||
};
|
||
let else_node = else_body.map(|b| ASTNode::Program {
|
||
statements: b,
|
||
span: Span::unknown(),
|
||
});
|
||
|
||
// 既存の If lowering を呼ぶ(cf_if は lower_if_form を呼ぶ)
|
||
self.cf_if(condition, then_node, else_node)?;
|
||
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
### Expression vs Statement の分離
|
||
|
||
**Expression としての If** (既存のまま):
|
||
```hako
|
||
local x = if cond { 1 } else { 2 } // ← 値を使う
|
||
```
|
||
→ `build_expression()` 経由で処理
|
||
|
||
**Statement としての If** (今回追加):
|
||
```hako
|
||
if i > 0 { sum = sum + 1 } // ← 副作用のみ
|
||
```
|
||
→ `build_statement()` 経由で処理
|
||
|
||
### 重要なポイント
|
||
|
||
1. **JoinIR 側には触らない**
|
||
- 今は素の MIR だけ直す
|
||
- JoinIR Pattern3 (IfPHI) は Phase 212.5 完了後に使う
|
||
|
||
2. **既存 If lowering を再利用**
|
||
- `cf_if()` → `lower_if_form()` の既存パスをそのまま使う
|
||
- **ループ内 if も top-level if と同じ構造**(特別扱いしない)
|
||
|
||
3. **1 箇所だけで修正**
|
||
- `build_statement()` に If ケースを追加するだけ
|
||
- 複数箇所で同じことをしない(DRY 原則)
|
||
|
||
---
|
||
|
||
## Task 212.5-4: phase212_if_sum_min.hako で再検証 🧪
|
||
|
||
### 検証手順
|
||
|
||
#### Step 1: 素の MIR ダンプ確認
|
||
|
||
```bash
|
||
./target/release/hakorune --dump-mir apps/tests/phase212_if_sum_min.hako 2>&1 | grep -A 50 "sum_def_count"
|
||
```
|
||
|
||
**期待される MIR**:
|
||
|
||
- ループ body 内に:
|
||
- ✅ `Compare` 命令: `%cond = icmp Gt %i, 0`
|
||
- ✅ `Branch` 命令: `br %cond, label bb_then, label bb_else`
|
||
- ✅ then ブロック: `sum = sum + 1` 相当の BinOp
|
||
- ✅ PHI 命令: `sum` の merge PHI
|
||
|
||
#### Step 2: JoinIR 経由の E2E テスト
|
||
|
||
```bash
|
||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase212_if_sum_min.hako
|
||
```
|
||
|
||
**期待される結果**:
|
||
|
||
- RC: **2** (i=1, i=2 で sum が increment されるため)
|
||
- Pattern 3 (IfPHI) または Pattern 1 + IfPHI が選ばれる
|
||
- Carrier: `i` と `sum` の 2 つ
|
||
|
||
---
|
||
|
||
## Task 212.5-5: ドキュメント & CURRENT_TASK の更新 📝
|
||
|
||
### Before/After まとめ
|
||
|
||
**Before** (Phase 212 時点):
|
||
- ループ内 if が MIR に現れない
|
||
- `sum` 変数が carrier として認識されない
|
||
- RC: 0 (期待: 2)
|
||
|
||
**After** (Phase 212.5 完了後):
|
||
- ループ内 if が正常に MIR に変換される
|
||
- `sum` と `i` の 2-carrier が正常動作
|
||
- RC: 2 (正常)
|
||
|
||
### CURRENT_TASK.md 更新内容
|
||
|
||
```markdown
|
||
- [x] **Phase 212.5: ループ内 if の AST→MIR 修正** ✅ (完了: 2025-12-09)
|
||
- **目的**: Phase 212 で発見した「ループ内 if が MIR に変換されない」問題を修正
|
||
- **修正箇所**: [ファイル名・関数名]
|
||
- **修正内容**: [具体的な変更内容]
|
||
- **検証結果**: phase212_if_sum_min.hako で RC=2 を確認
|
||
- **Phase 212 BLOCKED 解消**: ループ内 if の根本問題を解決
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Phase 212.5 の進捗
|
||
|
||
- [x] Task 212.5-1: 現状確認・設計メモ作成 ✅
|
||
- [ ] Task 212.5-2: MIR Builder 責務位置特定
|
||
- [ ] Task 212.5-3: if-lowering 追加
|
||
- [ ] Task 212.5-4: 再検証
|
||
- [ ] Task 212.5-5: ドキュメント更新
|
||
|
||
**次のステップ**: Task 212.5-2(ファイル読み込み・責務特定)
|