Files
hakorune/docs/development/current/main/phases/phase-269
tomoaki 757193891f feat(llvm/phi): Phase 277 P1 - fail-fast validation for PHI strict mode
## Summary
Implemented fail-fast validation for PHI ordering and value resolution in strict mode.

## Changes

### P1-1: Strict mode for "PHI after terminator"
- File: `src/llvm_py/phi_wiring/wiring.py::ensure_phi`
- Behavior: `NYASH_LLVM_PHI_STRICT=1` → RuntimeError if PHI created after terminator
- Default: Warning only (no regression)

### P1-2: Strict mode for "fallback 0"
- File: `src/llvm_py/phi_wiring/wiring.py::wire_incomings`
- Behavior: Strict mode forbids silent fallback to 0 (2 locations)
  - Location 1: Unresolvable incoming value
  - Location 2: Type coercion failure
- Error messages point to next debug file: `llvm_builder.py::_value_at_end_i64`

### P1-3: Connect verify_phi_ordering() to execution path
- File: `src/llvm_py/builders/function_lower.py`
- Behavior: Verify PHI ordering after all instructions emitted
- Debug mode: Shows " All N blocks have correct PHI ordering"
- Strict mode: Raises RuntimeError with block list if violations found

## Testing
 Test 1: strict=OFF - passes without errors
 Test 2: strict=ON - passes without errors (no violations in test fixtures)
 Test 3: debug mode - verify_phi_ordering() connected and running

## Scope
- LLVM harness (Python) changes only
- No new environment variables (uses existing 3 from Phase 277 P2)
- No JoinIR/Rust changes (root fix is Phase 279)
- Default behavior unchanged (strict mode opt-in)

## Next Steps
- Phase 278: Remove deprecated env var support
- Phase 279: Root fix - unify "2本のコンパイラ" pipelines

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 14:48:37 +09:00
..

Phase 269: Pattern8 への Frag 適用P0=test-only → P1=実装)

Status: 完了P1 Date: 2025-12-22

サブフェーズ状況

  • P1Pattern8 EdgeCFG lowering: SSA の i PHI を含めて閉じた)
  • P1.1call_method return type: 署名SSOTで型注釈
  • P1.2static box の this/me → static call 正規化): runtime receiver を禁止して SSOT 化)

目的

Pattern8BoolPredicateScanを EdgeCFG FragmentFrag + emit_fragで実装し、pattern番号の列挙を “exit配線” に収束させる。

  • P0: test-only stub + 最小 fixture/smoke で “入口” を固定DONE
  • P1: 実装MIR CFG層で Frag を組み、emit_frag で terminator を SSOT 化DONE

実装範囲(重要:スコープ境界)

触るP1 スコープ)

  • src/mir/builder/control_flow/joinir/patterns/pattern8_scan_bool_predicate.rs
  • src/mir/builder/emission/loop_predicate_scan.rs薄い入口Frag 構築 + emit_frag
  • fixture/smokeapps/tests/phase269_p0_pattern8_frag_min.hako, tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh

触らないP1 スコープ外)

  • cf_loopJoinIR-only hard-freeze
  • merge/EdgeCFG plumbingPhase 260-268 の SSOT は維持)
  • Pattern6/7/9Phase 269 は Pattern8 に集中)

実装戦略Phase 268 パターン踏襲)

アーキテクチャ図

pattern8_scan_bool_predicate.rs (Pattern8 層)
  ↓ P1: emission 入口に委譲
emission/loop_predicate_scan.rs::emit_bool_predicate_scan_edgecfg()
  ↓ 内部で使用break/continue 無しなので手配線)
Fragbranches+wires+ emit_frag()
  ↓ 最終的に呼び出し
set_branch_with_edge_args() / set_jump_with_edge_args() (Phase 260 SSOT)

P1 実装のポイント(重要)

1) Return を exits に置かない

  • emit_frag() が emit するのは brancheswires のみ(exits は “上位に伝搬する未配線”)
  • したがって early-exit の return falsewiresExitKind::Return として入れる(target=None を許可)

2) return true は loop 後 AST に任せる

  • Frag 経路は “loop の制御” と “early-exit” のみ担当し、関数全体の return は既存 AST lowering に任せる
  • P1 の最小は「失敗で return false / それ以外は after に落ちる」

3) SSA: ループ変数 i の PHI が必須

  • header の条件評価は毎周回評価されるが、SSA 的には i の “現在値” を header に合流させる必要がある
  • 最小の形header に挿入、先頭に置く):
    • i_current = phi [i_init, preheader_bb], [i_next, step_bb]
    • header/body/step は i_current を参照
    • step で i_next = i_current + 1 を作り、backedge の入力にする

完了確認P1

  • Pattern8 Frag lower が header に PHI を挿入し、i_currentCompare/substring/step の参照に使用する
  • integration smoke:
    • tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh PASS
    • tools/smokes/v2/profiles/integration/apps/phase259_p0_is_integer_vm.sh PASS回帰なし

P1.2DONE: static box の this/me を static call に正規化runtime receiver 禁止)

目的SSOT

static box 内の this.method(...) / me.method(...)runtime receiverNewBox / 文字列 receiverにしない。 compile-time に current_static_box.method/arity の canonical key を構築し、static call へ正規化する。

SSOT / 禁止(再掲)

  • SSOT:
    • comp_ctx.current_static_boxbox 名の唯一の出どころ)
    • BoxName.method/aritycanonical key: call_method 署名注釈と共用)
  • 禁止:
    • emit_string("StringUtils") などの文字列レシーバによる by-name 的回避
    • static box の this/me を NewBox で runtime object 化(退行の原因)

実装(責務分離)

  • src/mir/builder/calls/build.rs
    • MethodCall の共通入口で This/Me receiver を最優先で検出し、static call に正規化する
    • box 名は comp_ctx.current_static_box のみから取り出す(ハードコード禁止)
  • src/mir/builder/stmts.rs
    • static/instance の文脈エラーを Fail-Fast で明確化(誤誘導のメッセージ整理)
  • src/mir/builder/control_flow/joinir/patterns/pattern8_scan_bool_predicate.rs
    • 現状の安全策: static box 文脈の loop は Pattern8 対象外にし、汎用 loweringPattern1 等)へ戻す
    • 目的: receiver 正規化を “1箇所” に収束させ、Pattern8 が runtime receiver を作る経路を封じる
    • 撤去条件: Pattern8 が「正規化後の MethodCallstatic call key」前提で安全に動くことを fixture/smoke で確認できたら、この除外を削除する

検証fixture/smoke

  • apps/tests/phase269_p1_2_this_method_in_loop_min.hako
  • tools/smokes/v2/profiles/integration/apps/phase269_p1_2_this_method_in_loop_vm.sh
  • 受け入れ条件:
    • MIR dump に const "StringUtils" が receiver として出ない
    • call_method StringUtils.is_digit/1(または同等の static callになる

テスト手順(固定)

  1. cargo build --release
  2. cargo test -p nyash-rust --lib --release
  3. HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase259_p0_is_integer_vm.sh
  4. HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh
  5. HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase269_p1_2_this_method_in_loop_vm.sh
  6. ./tools/smokes/v2/run.sh --profile quick45/46 を維持)

P0historical

P0 の test-only 記録は履歴として残す(入口の固定に寄与)。

検出基準Phase 259 P0 固定形式)

  1. Loop condition: i < s.length()
  2. Loop body has if statement:
    • Condition: not this.method(...) (UnaryOp::Not + MethodCall)
    • Then branch: return false (early exit)
  3. Loop body has step: i = i + 1
  4. Post-loop: return true

既存実装の処理フロー

// 1. Extract pattern parts
let parts = extract_bool_predicate_scan_parts(condition, body)?;

// 2. Get host ValueIds
let s_host = variable_map[haystack];
let i_host = variable_map[loop_var];
let me_host = build_me_expression()?;

// 3. Create JoinModule
let join_module = lower_scan_bool_predicate_minimal(...);

// 4. Build boundary
let boundary = JoinInlineBoundaryBuilder::new()
    .with_inputs(join_inputs, host_inputs)
    .with_loop_invariants(loop_invariants)
    .with_exit_bindings(exit_bindings)
    .with_carrier_info(carrier_info)
    .with_loop_var_name(Some(loop_var))
    .with_expr_result(Some(join_exit_value))
    .build();

// 5. Execute JoinIRConversionPipeline
let result = JoinIRConversionPipeline::execute(self, join_module, Some(&boundary), ...)?;

P0 Frag 版 lowerer 設計

目的

  • JoinModule から MIR terminator を生成する部分を Frag 化
  • 既存の JoinModule 生成・boundary 構築は維持
  • emit_frag() による terminator 生成を導入

実装方針

#[cfg(test)]
pub(crate) fn lower_pattern8_frag(
    builder: &mut MirBuilder,
    join_module: JoinModule,
    boundary: &JoinInlineBoundary,
    debug: bool,
) -> Result<Option<ValueId>, String> {
    // 1. JoinModule から必要な情報を取得
    //    - loop_step function (loop body)
    //    - k_exit function (return true)

    // 2. Loop body Frag 構築
    //    - loop_step の処理を Frag で表現
    //    - early return (return false) は Return exit として扱う

    // 3. compose::loop_() で合成
    //    - loop_id, header, after, body_frag

    // 4. emit_frag() で MIR terminator に変換

    // 5. result 返却expr_result
    Ok(boundary.expr_result)
}

Unit Test 設計

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pattern8_frag_lowering() {
        // 1. Create minimal JoinModule
        // 2. Create boundary
        // 3. Call lower_pattern8_frag()
        // 4. Verify MIR terminator generation
        //    - Branch terminator exists
        //    - Jump terminator exists
        //    - Return terminator exists (early exit)
    }
}

最小 fixture 設計

phase269_p0_pattern8_frag_min.hako

Phase 259 の is_integer_min.hako の縮小版:

static box StringUtils {
    is_digit(ch) {
        return ch == "0" or ch == "1"
    }

    is_integer(s) {
        if s.length() == 0 {
            return false
        }

        local i = 0
        loop(i < s.length()) {
            if not this.is_digit(s.substring(i, i + 1)) {
                return false
            }
            i = i + 1
        }
        return true
    }
}

static box Main {
    main() {
        return StringUtils.is_integer("01") ? 7 : 1
    }
}

Smoke Test 設計

tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh:

#!/bin/bash
set -e
cd "$(dirname "$0")/../../../../../.."
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
set +e
$HAKORUNE_BIN apps/tests/phase269_p0_pattern8_frag_min.hako > /tmp/phase269_out.txt 2>&1
EXIT_CODE=$?
set -e
if [ $EXIT_CODE -eq 7 ]; then
    echo "[PASS] phase269_p0_pattern8_frag_vm"
    exit 0
else
    echo "[FAIL] phase269_p0_pattern8_frag_vm: expected exit 7, got $EXIT_CODE"
    cat /tmp/phase269_out.txt
    exit 1
fi

重要な設計判断

なぜ test-only か

  1. 非破壊的: 既存 Pattern8 実装を壊さない
  2. 段階的: Frag 版の動作を先に確認してから統合
  3. デバッグ容易: 問題切り分けが簡単Frag 版 vs 既存実装)
  4. 拡張性: Pattern6/7 にも同じパターンを適用可能

なぜ最小 fixture か

  1. 高速検証: 小さいコードで問題を早期発見
  2. デバッグ容易: MIR dump が読みやすい
  3. 回帰テスト: quick smoke に含めやすい

P0 での制約

  • VM のみLLVM は P1 以降)
  • Pattern8 のみPattern6/7 は P1 以降)
  • test-only既存実装置換は P1 以降)

次フェーズへの橋渡し

Phase 269 P1+: 既存 Pattern8 を Frag 版に置換

  • cf_loop_pattern8_bool_predicate_impl() から lower_pattern8_frag() を呼び出す
  • JoinIRConversionPipeline を廃止Frag 版に一本化)
  • Pattern6/7 にも適用

Phase 270+: Pattern 分岐削減

  • Pattern 番号による分岐を削減
  • compose API による統一的なループ処理

関連ドキュメント

  • Phase 268: docs/development/current/main/phases/phase-268/README.md
  • 設計図: docs/development/current/main/design/edgecfg-fragments.md
  • JoinIR アーキテクチャ: docs/development/current/main/joinir-architecture-overview.md
  • 現在のタスク: docs/development/current/main/10-Now.md

受け入れ基準P0

  • cargo build --release 成功
  • cargo test --lib --release で全テスト PASS
  • Pattern8 Frag 版 lowerer の unit test PASS
  • apps/tests/phase269_p0_pattern8_frag_min.hako 作成
  • tools/smokes/v2/profiles/integration/apps/phase269_p0_pattern8_frag_vm.sh 作成
  • smoke test PASSexit code 7
  • tools/smokes/v2/run.sh --profile quick で 45/46 PASS 維持
  • MIR dump で Branch/Jump/Return terminator 正常生成確認
  • ドキュメント更新完了:
    • phases/phase-269/README.md 新規作成(このファイル)
    • 10-Now.md 追記
    • 30-Backlog.md 更新

まとめ

Phase 269 P0 の核心:

  • Pattern8 を Frag 化test-only、既存と並走
  • compose::loop_() + emit_frag() を使用
  • 最小 fixture + smoke test で動作確認
  • quick smoke 45/46 を悪化させない

次のステップ: P1 で既存 Pattern8 を Frag 版に置換 → Pattern6/7 にも適用