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.rs` - `src/mir/builder/control_flow/joinir/patterns/pattern7_split_scan.rs` - `src/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 は “両方一気に” ではなく、以下の順で段階適用する。 1. Pattern6 を `Frag + emit_frag()` に切り替え(wiring を SSOT 化) 2. Pattern7 を `Frag + emit_frag()` に切り替え(副作用 push を含む) 3. 旧 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` ## 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)` で不適用(既定挙動不変) - 旧 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)` - after: - P0 では `return -1` を既存 AST lowering に任せてもよい(after を current_block にする) ### 受け入れ - `cargo test -p nyash-rust --lib --release` - `HAKORUNE_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 - 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番号列挙を中心にしない) ## 旧 JoinIR 経路の撤去条件(SSOT) 旧 `JoinIRConversionPipeline` 系の経路を削るのは、以下を満たした後に行う。 1. Pattern6/7 の fixture/smoke が Frag 経路で PASS 2. `tools/smokes/v2/run.sh --profile quick` が悪化しない 3. router から該当 pattern の “旧経路” が消せる(最小差分で削除可能) ## テスト手順(固定) 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/phase254_p0_index_of_vm.sh` 4. `HAKORUNE_BIN=./target/release/hakorune bash tools/smokes/v2/profiles/integration/apps/phase256_p0_split_vm.sh`