## 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>
7.9 KiB
7.9 KiB
Status: Active
Date: 2025-12-22
Scope: Pattern6/7 を Frag + emit_frag() へ段階吸収(pattern列挙の増殖を止める)
Related:
- Design SSOT:
docs/development/current/main/design/edgecfg-fragments.md - Phase 269(Pattern8 Frag):
docs/development/current/main/phases/phase-269/README.md - Phase 270(Pattern9 bridge):
docs/development/current/main/phases/phase-270/README.md
Phase 272(P0): Pattern6/7 を Frag+emit_frag へ吸収(段階適用)
ステータス
- P0.1(Pattern6): ✅ 完了(Frag+emit_frag 経路へ移行)
- P0.2(Pattern7): ✅ 完了(Frag+emit_frag 経路へ移行)
目的
- Pattern6/7(scan系)の CFG 構築を “pattern番号ごとの推測分岐” から外し、**EdgeCFG Frag 合成(ExitKind/wires/branches)**に収束させる。
- terminator emission を SSOT(
emit_frag())へ集約し、block の successors/preds 同期漏れを構造で防ぐ。
スコープ境界
✅ 触る
src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rssrc/mir/builder/control_flow/joinir/patterns/pattern7_split_scan.rssrc/mir/builder/emission/(Pattern8 と同じ “薄い入口” の追加)
❌ 触らない
- merge/EdgeCFG plumbing(Phase 260-268 の SSOT は維持)
- cf_loop の非JoinIR経路追加(JoinIR-only hard-freeze 維持)
- by-name ハードコード(Box名/Pattern名文字列での分岐増殖など)
入口SSOT(fixture/smoke)
Pattern6(index_of)
- fixture:
apps/tests/phase254_p0_index_of_min.hako(exit=1) - smoke (VM):
tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh
Pattern7(split)
- fixture:
apps/tests/phase256_p0_split_min.hako(exit=3) - smoke (VM):
tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh
方針(P0)
P0 は “両方一気に” ではなく、以下の順で段階適用する。
- Pattern6 を
Frag + emit_frag()に切り替え(wiring を SSOT 化) - Pattern7 を
Frag + emit_frag()に切り替え(副作用 push を含む) - 旧 JoinIR 経路の撤去条件が満たせた時点で削除(本READMEに明記)
実装ガイド(共通)
- PHI は block 先頭(after existing phis)へ挿入し、入力を
[(pred_bb, val)]の形で固定する:crate::mir::ssot::cf_common::insert_phi_at_head_spanned
- terminator emission は
crate::mir::builder::control_flow::edgecfg::api::emit_fragに集約する。 - Pattern8 の構造(参考):
- emission 入口:
src/mir/builder/emission/loop_predicate_scan.rs
- emission 入口:
P0.1: Pattern6(index_of)— Frag 化
狙い
- loop 骨格(header/body/step/after + early return)を Frag に落とし、Jump/Branch/Return を
emit_frag()に集約する。
実装結果(✅ 完了)
- emission 入口を新設し、Pattern6 の terminator emission を
emit_frag()(SSOT)へ集約- 新規:
src/mir/builder/emission/loop_scan_with_init.rs - 更新:
src/mir/builder/emission/mod.rs
- 新規:
- Pattern6 の JoinIRConversionPipeline 経路を撤去し、Frag 経路へ切り替え
- 更新:
src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs
- 更新:
- P0 スコープ:
- forward scan(
step=1)のみ適用 - reverse/dynamic needle 等は
Ok(None)で不適用(既定挙動不変)
- forward scan(
- 旧 DCE 対策(exit PHI 用の post-loop guard)を撤去(Frag が Return を直接 emit するため)
最小 CFG 形(forward scan)
- blocks:
header,body,step,after,ret_found - header:
i_current = phi [i_init, preheader], [i_next, step_bb]cond_loop = (i_current < len)- branch: true→body, false→after
- body:
ch = s.substring(i_current, i_current+1)cond_match = (ch == needle)- branch: true→ret_found, false→step
- step:
i_next = i_current + 1- jump: →header
- ret_found:
- wire:
Return(i_current)
- wire:
- after:
- P0 では
return -1を既存 AST lowering に任せてもよい(after を current_block にする)
- P0 では
受け入れ
cargo test -p nyash-rust --lib --releaseHAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.sh
追加の検証(推奨)
NYASH_VM_DUMP_MIR=1 ./target/release/hakorune --backend vm apps/tests/phase254_p0_index_of_min.hakoで PHI/terminator を確認(任意)
P0.2: Pattern7(split)— Frag 化
狙い
- Pattern7 の terminator 配線(if/loop の遷移)を Frag に集約し、副作用(
result.push)を含む形でも CFG を壊さない。
注意点(Fail-Fast)
- carriers が複数(
i,start)なので、header の PHI を 2 本(以上)で SSA を閉じる必要がある。 result.pushは副作用なので、block 配置(評価順)を壊さない(P0は固定形のみ受理)。
実装結果(✅ 完了)
- Pattern7 の JoinIRConversionPipeline 経路を撤去し、Frag+emit_frag 経路へ切り替え
- 更新:
src/mir/builder/control_flow/joinir/patterns/pattern7_split_scan.rs
- 更新:
- emission 入口を新設し、terminator emission を
emit_frag()(SSOT)へ集約- 新規:
src/mir/builder/emission/loop_split_scan.rs - 更新:
src/mir/builder/emission/mod.rs
- 新規:
- CFG 形(P0):
- blocks:
header,body,then,else,step,after(+ 入口 preheader) - header: PHI(
i_current,start_current) + loop condition - body: delimiter match check
- then:
result.push(segment)+start_next_then計算(dominance 安全) - else:
i_next_else = i_current + 1 - step: PHI(
i_next,start_next) + jump header
- blocks:
- Compare は
CompareOp::Le(i <= limit)を使用(固定形)
リファクタ結果(共通SSOT)
Phase 272 P0.2 完了後、Pattern6/7/8 の重複を以下へ収束した(仕様不変):
- PHI 挿入の薄いラッパ:
src/mir/builder/emission/phi.rs - variable_map の fail-fast 取得:
src/mir/builder/variable_context.rs(require(name, ctx)) - can_lower の戦略メモ:
src/mir/builder/control_flow/joinir/patterns/router.rs(CanLowerStrategy)
受け入れ
HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh- PASS(exit=3)
- MIR 形(PHI/Branch/Jump/BoxCall(push))を目視確認できること(任意)
Next(planned): Phase 273(design-first)— Pattern を Plan Extractor に降格して裾広がりを止める
Phase 272(P0)で “terminator SSOT(emit_frag)へ寄せる” を完了したら、次は上流の収束(compiler flow の一本化)を行う。
- 相談メモ(外部レビュー用):
docs/development/current/main/investigations/phase-272-frag-plan-architecture-consult.md - ねらい:
- Pattern = Plan 抽出(pure) に降格(builder を触らない)
- Plan =
seq/if/loop/exit/effect/letの固定語彙(増殖しない) - PlanLowerer が block/value/phi を作る唯一の箱(emit_frag は SSOT のまま)
- 受け入れ(最小):
- extractor が
next_block_id/next_value_id/insert_phi_*を呼ばない(純関数) - Plan→Frag→emit_frag の本線が 1 本になる(pattern番号列挙を中心にしない)
- extractor が
旧 JoinIR 経路の撤去条件(SSOT)
旧 JoinIRConversionPipeline 系の経路を削るのは、以下を満たした後に行う。
- Pattern6/7 の fixture/smoke が Frag 経路で PASS
tools/smokes/v2/run.sh --profile quickが悪化しない- router から該当 pattern の “旧経路” が消せる(最小差分で削除可能)
テスト手順(固定)
cargo build --releasecargo test -p nyash-rust --lib --releaseHAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase254_p0_index_of_vm.shHAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh