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>
12 KiB
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:
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)
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 ループ本体ブロックには以下があるべき:
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 の責務位置を特定 ✅
確認したファイル
- ✅
src/mir/builder/stmts.rs- Statement 処理 - ✅
src/mir/builder/exprs.rs- Expression 処理 - ✅
src/mir/builder/control_flow/mod.rs- cf_if(), cf_loop()
問題の根本原因を特定
🚨 発見した問題
build_statement() (stmts.rs:215-222):
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 は別の処理が必要だが、その処理が未実装
次の調査対象
-
DCE (Dead Code Elimination) の動作確認
- If 式の戻り値が使われない場合に DCE で消えるか?
-
try_cf_loop_joinir()の実装確認- ループ本体の AST がどう処理されているか
- If ノードが JoinIR 変換時に保持されているか
-
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:
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:
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 を薄くラップする小さい箱:
/// 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 (既存のまま):
local x = if cond { 1 } else { 2 } // ← 値を使う
→ build_expression() 経由で処理
Statement としての If (今回追加):
if i > 0 { sum = sum + 1 } // ← 副作用のみ
→ build_statement() 経由で処理
重要なポイント
-
JoinIR 側には触らない
- 今は素の MIR だけ直す
- JoinIR Pattern3 (IfPHI) は Phase 212.5 完了後に使う
-
既存 If lowering を再利用
cf_if()→lower_if_form()の既存パスをそのまま使う- ループ内 if も top-level if と同じ構造(特別扱いしない)
-
1 箇所だけで修正
build_statement()に If ケースを追加するだけ- 複数箇所で同じことをしない(DRY 原則)
Task 212.5-4: phase212_if_sum_min.hako で再検証 🧪
検証手順
Step 1: 素の MIR ダンプ確認
./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 テスト
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 更新内容
- [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 の進捗
- 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)