Files
hakorune/docs/development/current/main/phase212-5-loop-if-mir-bug.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

429 lines
12 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 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ファイル読み込み・責務特定
Status: Active
Scope: loop-if MIR バグ調査JoinIR v2