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

12 KiB
Raw Blame History

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 を順次処理する際、
  • stmtIf ノードの場合に 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):

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::Ifexpression 経路にだけ流していた
  • 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() 経由で処理

重要なポイント

  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 ダンプ確認

./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: isum の 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 に変換される
  • sumi の 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